Path: blob/main/sys/contrib/dev/iwlwifi/mld/debugfs.c
48287 views
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause1/*2* Copyright (C) 2024-2025 Intel Corporation3*/45#include "mld.h"6#include "debugfs.h"7#include "iwl-io.h"8#include "hcmd.h"9#include "iface.h"10#include "sta.h"11#include "tlc.h"12#include "power.h"13#include "notif.h"14#include "ap.h"15#include "iwl-utils.h"16#include "scan.h"17#ifdef CONFIG_THERMAL18#include "thermal.h"19#endif2021#include "fw/api/rs.h"22#include "fw/api/dhc.h"23#include "fw/api/rfi.h"24#include "fw/dhc-utils.h"25#include <linux/dmi.h>2627#define MLD_DEBUGFS_READ_FILE_OPS(name, bufsz) \28_MLD_DEBUGFS_READ_FILE_OPS(name, bufsz, struct iwl_mld)2930#define MLD_DEBUGFS_ADD_FILE_ALIAS(alias, name, parent, mode) \31debugfs_create_file(alias, mode, parent, mld, \32&iwl_dbgfs_##name##_ops)33#define MLD_DEBUGFS_ADD_FILE(name, parent, mode) \34MLD_DEBUGFS_ADD_FILE_ALIAS(#name, name, parent, mode)3536static bool iwl_mld_dbgfs_fw_cmd_disabled(struct iwl_mld *mld)37{38#ifdef CONFIG_PM_SLEEP39return !mld->fw_status.running || mld->fw_status.in_d3;40#else41return !mld->fw_status.running;42#endif /* CONFIG_PM_SLEEP */43}4445static ssize_t iwl_dbgfs_fw_dbg_clear_write(struct iwl_mld *mld,46char *buf, size_t count)47{48/* If the firmware is not running, silently succeed since there is49* no data to clear.50*/51if (iwl_mld_dbgfs_fw_cmd_disabled(mld))52return 0;5354iwl_fw_dbg_clear_monitor_buf(&mld->fwrt);5556return count;57}5859static ssize_t iwl_dbgfs_fw_nmi_write(struct iwl_mld *mld, char *buf,60size_t count)61{62if (iwl_mld_dbgfs_fw_cmd_disabled(mld))63return -EIO;6465IWL_ERR(mld, "Triggering an NMI from debugfs\n");6667if (count == 6 && !strcmp(buf, "nolog\n"))68mld->fw_status.do_not_dump_once = true;6970iwl_force_nmi(mld->trans);7172return count;73}7475static ssize_t iwl_dbgfs_fw_restart_write(struct iwl_mld *mld, char *buf,76size_t count)77{78int __maybe_unused ret;7980if (!iwlwifi_mod_params.fw_restart)81return -EPERM;8283if (iwl_mld_dbgfs_fw_cmd_disabled(mld))84return -EIO;8586if (count == 6 && !strcmp(buf, "nolog\n")) {87mld->fw_status.do_not_dump_once = true;88iwl_trans_suppress_cmd_error_once(mld->trans);89}9091/* take the return value to make compiler happy - it will92* fail anyway93*/94ret = iwl_mld_send_cmd_empty(mld, WIDE_ID(LONG_GROUP, REPLY_ERROR));9596return count;97}9899static ssize_t iwl_dbgfs_send_echo_cmd_write(struct iwl_mld *mld, char *buf,100size_t count)101{102if (iwl_mld_dbgfs_fw_cmd_disabled(mld))103return -EIO;104105return iwl_mld_send_cmd_empty(mld, ECHO_CMD) ?: count;106}107108struct iwl_mld_sniffer_apply {109struct iwl_mld *mld;110const u8 *bssid;111u16 aid;112};113114static bool iwl_mld_sniffer_apply(struct iwl_notif_wait_data *notif_data,115struct iwl_rx_packet *pkt, void *data)116{117struct iwl_mld_sniffer_apply *apply = data;118119apply->mld->monitor.cur_aid = cpu_to_le16(apply->aid);120memcpy(apply->mld->monitor.cur_bssid, apply->bssid,121sizeof(apply->mld->monitor.cur_bssid));122123return true;124}125126static ssize_t127iwl_dbgfs_he_sniffer_params_write(struct iwl_mld *mld, char *buf,128size_t count)129{130struct iwl_notification_wait wait;131struct iwl_he_monitor_cmd he_mon_cmd = {};132struct iwl_mld_sniffer_apply apply = {133.mld = mld,134};135u16 wait_cmds[] = {136WIDE_ID(DATA_PATH_GROUP, HE_AIR_SNIFFER_CONFIG_CMD),137};138u32 aid;139int ret;140141if (iwl_mld_dbgfs_fw_cmd_disabled(mld))142return -EIO;143144if (!mld->monitor.on)145return -ENODEV;146147ret = sscanf(buf, "%x %2hhx:%2hhx:%2hhx:%2hhx:%2hhx:%2hhx", &aid,148&he_mon_cmd.bssid[0], &he_mon_cmd.bssid[1],149&he_mon_cmd.bssid[2], &he_mon_cmd.bssid[3],150&he_mon_cmd.bssid[4], &he_mon_cmd.bssid[5]);151if (ret != 7)152return -EINVAL;153154he_mon_cmd.aid = cpu_to_le16(aid);155156apply.aid = aid;157apply.bssid = (void *)he_mon_cmd.bssid;158159/* Use the notification waiter to get our function triggered160* in sequence with other RX. This ensures that frames we get161* on the RX queue _before_ the new configuration is applied162* still have mld->cur_aid pointing to the old AID, and that163* frames on the RX queue _after_ the firmware processed the164* new configuration (and sent the response, synchronously)165* get mld->cur_aid correctly set to the new AID.166*/167iwl_init_notification_wait(&mld->notif_wait, &wait,168wait_cmds, ARRAY_SIZE(wait_cmds),169iwl_mld_sniffer_apply, &apply);170171ret = iwl_mld_send_cmd_pdu(mld,172WIDE_ID(DATA_PATH_GROUP,173HE_AIR_SNIFFER_CONFIG_CMD),174&he_mon_cmd);175176/* no need to really wait, we already did anyway */177iwl_remove_notification(&mld->notif_wait, &wait);178179return ret ?: count;180}181182static ssize_t183iwl_dbgfs_he_sniffer_params_read(struct iwl_mld *mld, char *buf, size_t count)184{185return scnprintf(buf, count,186"%d %02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx\n",187le16_to_cpu(mld->monitor.cur_aid),188mld->monitor.cur_bssid[0], mld->monitor.cur_bssid[1],189mld->monitor.cur_bssid[2], mld->monitor.cur_bssid[3],190mld->monitor.cur_bssid[4], mld->monitor.cur_bssid[5]);191}192193/* The size computation is as follows:194* each number needs at most 3 characters, number of rows is the size of195* the table; So, need 5 chars for the "freq: " part and each tuple afterwards196* needs 6 characters for numbers and 5 for the punctuation around. 32 bytes197* for feature support message.198*/199#define IWL_RFI_DDR_BUF_SIZE (IWL_RFI_DDR_LUT_INSTALLED_SIZE *\200(5 + IWL_RFI_DDR_LUT_ENTRY_CHANNELS_NUM *\201(6 + 5)) + 32)202#define IWL_RFI_DLVR_BUF_SIZE (IWL_RFI_DLVR_LUT_INSTALLED_SIZE *\203(5 + IWL_RFI_DLVR_LUT_ENTRY_CHANNELS_NUM *\204(6 + 5)) + 32)205#define IWL_RFI_DESENSE_BUF_SIZE IWL_RFI_DDR_BUF_SIZE206207/* Extra 32 for "DDR and DLVR table" message */208#define IWL_RFI_BUF_SIZE (IWL_RFI_DDR_BUF_SIZE + IWL_RFI_DLVR_BUF_SIZE +\209IWL_RFI_DESENSE_BUF_SIZE + 32)210211static size_t iwl_mld_dump_tas_resp(struct iwl_dhc_tas_status_resp *resp,212size_t count, u8 *buf)213{214const char * const tas_dis_reason[TAS_DISABLED_REASON_MAX] = {215[TAS_DISABLED_DUE_TO_BIOS] =216"Due To BIOS",217[TAS_DISABLED_DUE_TO_SAR_6DBM] =218"Due To SAR Limit Less Than 6 dBm",219[TAS_DISABLED_REASON_INVALID] =220"N/A",221[TAS_DISABLED_DUE_TO_TABLE_SOURCE_INVALID] =222"Due to table source invalid"223};224const char * const tas_current_status[TAS_DYNA_STATUS_MAX] = {225[TAS_DYNA_INACTIVE] = "INACTIVE",226[TAS_DYNA_INACTIVE_MVM_MODE] =227"inactive due to mvm mode",228[TAS_DYNA_INACTIVE_TRIGGER_MODE] =229"inactive due to trigger mode",230[TAS_DYNA_INACTIVE_BLOCK_LISTED] =231"inactive due to block listed",232[TAS_DYNA_INACTIVE_UHB_NON_US] =233"inactive due to uhb non US",234[TAS_DYNA_ACTIVE] = "ACTIVE",235};236ssize_t pos = 0;237238if (resp->header.version != 1) {239pos += scnprintf(buf + pos, count - pos,240"Unsupported TAS response version:%d",241resp->header.version);242return pos;243}244245pos += scnprintf(buf + pos, count - pos, "TAS Report\n");246switch (resp->tas_config_info.table_source) {247case BIOS_SOURCE_NONE:248pos += scnprintf(buf + pos, count - pos,249"BIOS SOURCE NONE ");250break;251case BIOS_SOURCE_ACPI:252pos += scnprintf(buf + pos, count - pos,253"BIOS SOURCE ACPI ");254break;255case BIOS_SOURCE_UEFI:256pos += scnprintf(buf + pos, count - pos,257"BIOS SOURCE UEFI ");258break;259default:260pos += scnprintf(buf + pos, count - pos,261"BIOS SOURCE UNKNOWN (%d) ",262resp->tas_config_info.table_source);263break;264}265266pos += scnprintf(buf + pos, count - pos,267"revision is: %d data is: 0x%08x\n",268resp->tas_config_info.table_revision,269resp->tas_config_info.value);270pos += scnprintf(buf + pos, count - pos, "Current MCC: 0x%x\n",271le16_to_cpu(resp->curr_mcc));272273pos += scnprintf(buf + pos, count - pos, "Block list entries:");274for (int i = 0; i < ARRAY_SIZE(resp->mcc_block_list); i++)275pos += scnprintf(buf + pos, count - pos, " 0x%x",276le16_to_cpu(resp->mcc_block_list[i]));277278pos += scnprintf(buf + pos, count - pos,279"\nDo TAS Support Dual Radio?: %s\n",280hweight8(resp->valid_radio_mask) > 1 ?281"TRUE" : "FALSE");282283for (int i = 0; i < ARRAY_SIZE(resp->tas_status_radio); i++) {284int tmp;285unsigned long dynamic_status;286287if (!(resp->valid_radio_mask & BIT(i)))288continue;289290pos += scnprintf(buf + pos, count - pos,291"TAS report for radio:%d\n", i + 1);292pos += scnprintf(buf + pos, count - pos,293"Static status: %sabled\n",294resp->tas_status_radio[i].static_status ?295"En" : "Dis");296if (!resp->tas_status_radio[i].static_status) {297u8 static_disable_reason =298resp->tas_status_radio[i].static_disable_reason;299300pos += scnprintf(buf + pos, count - pos,301"\tStatic Disabled Reason: ");302if (static_disable_reason >= TAS_DISABLED_REASON_MAX) {303pos += scnprintf(buf + pos, count - pos,304"unsupported value (%d)\n",305static_disable_reason);306continue;307}308309pos += scnprintf(buf + pos, count - pos,310"%s (%d)\n",311tas_dis_reason[static_disable_reason],312static_disable_reason);313continue;314}315316pos += scnprintf(buf + pos, count - pos, "\tANT A %s and ",317(resp->tas_status_radio[i].dynamic_status_ant_a318& BIT(TAS_DYNA_ACTIVE)) ? "ON" : "OFF");319320pos += scnprintf(buf + pos, count - pos, "ANT B %s for ",321(resp->tas_status_radio[i].dynamic_status_ant_b322& BIT(TAS_DYNA_ACTIVE)) ? "ON" : "OFF");323324switch (resp->tas_status_radio[i].band) {325case PHY_BAND_5:326pos += scnprintf(buf + pos, count - pos, "HB\n");327break;328case PHY_BAND_24:329pos += scnprintf(buf + pos, count - pos, "LB\n");330break;331case PHY_BAND_6:332pos += scnprintf(buf + pos, count - pos, "UHB\n");333break;334default:335pos += scnprintf(buf + pos, count - pos,336"Unsupported band (%d)\n",337resp->tas_status_radio[i].band);338break;339}340341pos += scnprintf(buf + pos, count - pos,342"Is near disconnection?: %s\n",343resp->tas_status_radio[i].near_disconnection ?344"True" : "False");345346pos += scnprintf(buf + pos, count - pos,347"Dynamic status antenna A:\n");348dynamic_status = resp->tas_status_radio[i].dynamic_status_ant_a;349for_each_set_bit(tmp, &dynamic_status, TAS_DYNA_STATUS_MAX) {350pos += scnprintf(buf + pos, count - pos, "\t%s (%d)\n",351tas_current_status[tmp], tmp);352}353pos += scnprintf(buf + pos, count - pos,354"\nDynamic status antenna B:\n");355dynamic_status = resp->tas_status_radio[i].dynamic_status_ant_b;356for_each_set_bit(tmp, &dynamic_status, TAS_DYNA_STATUS_MAX) {357pos += scnprintf(buf + pos, count - pos, "\t%s (%d)\n",358tas_current_status[tmp], tmp);359}360361tmp = le16_to_cpu(resp->tas_status_radio[i].max_reg_pwr_limit_ant_a);362pos += scnprintf(buf + pos, count - pos,363"Max antenna A regulatory pwr limit (dBm): %d.%03d\n",364tmp / 8, 125 * (tmp % 8));365tmp = le16_to_cpu(resp->tas_status_radio[i].max_reg_pwr_limit_ant_b);366pos += scnprintf(buf + pos, count - pos,367"Max antenna B regulatory pwr limit (dBm): %d.%03d\n",368tmp / 8, 125 * (tmp % 8));369370tmp = le16_to_cpu(resp->tas_status_radio[i].sar_limit_ant_a);371pos += scnprintf(buf + pos, count - pos,372"Antenna A SAR limit (dBm): %d.%03d\n",373tmp / 8, 125 * (tmp % 8));374tmp = le16_to_cpu(resp->tas_status_radio[i].sar_limit_ant_b);375pos += scnprintf(buf + pos, count - pos,376"Antenna B SAR limit (dBm): %d.%03d\n",377tmp / 8, 125 * (tmp % 8));378}379380return pos;381}382383static ssize_t iwl_dbgfs_tas_get_status_read(struct iwl_mld *mld, char *buf,384size_t count)385{386struct iwl_dhc_cmd cmd = {387.index_and_mask = cpu_to_le32(DHC_TABLE_TOOLS |388DHC_TARGET_UMAC |389DHC_TOOLS_UMAC_GET_TAS_STATUS),390};391struct iwl_host_cmd hcmd = {392.id = WIDE_ID(LEGACY_GROUP, DEBUG_HOST_COMMAND),393.flags = CMD_WANT_SKB,394.len[0] = sizeof(cmd),395.data[0] = &cmd,396};397struct iwl_dhc_tas_status_resp *resp = NULL;398u32 resp_len = 0;399ssize_t pos = 0;400u32 status;401int ret;402403if (iwl_mld_dbgfs_fw_cmd_disabled(mld))404return -EIO;405406ret = iwl_mld_send_cmd(mld, &hcmd);407if (ret)408return ret;409410pos += scnprintf(buf + pos, count - pos, "\nOEM name: %s\n",411dmi_get_system_info(DMI_SYS_VENDOR) ?: "<unknown>");412pos += scnprintf(buf + pos, count - pos,413"\tVendor In Approved List: %s\n",414iwl_is_tas_approved() ? "YES" : "NO");415416status = iwl_dhc_resp_status(mld->fwrt.fw, hcmd.resp_pkt);417if (status != 1) {418pos += scnprintf(buf + pos, count - pos,419"response status is not success: %d\n",420status);421goto out;422}423424resp = iwl_dhc_resp_data(mld->fwrt.fw, hcmd.resp_pkt, &resp_len);425if (IS_ERR(resp) || resp_len != sizeof(*resp)) {426pos += scnprintf(buf + pos, count - pos,427"Invalid size for TAS response (%u instead of %zd)\n",428resp_len, sizeof(*resp));429goto out;430}431432pos += iwl_mld_dump_tas_resp(resp, count - pos, buf + pos);433434out:435iwl_free_resp(&hcmd);436return pos;437}438439WIPHY_DEBUGFS_WRITE_FILE_OPS_MLD(fw_nmi, 10);440WIPHY_DEBUGFS_WRITE_FILE_OPS_MLD(fw_restart, 10);441WIPHY_DEBUGFS_READ_WRITE_FILE_OPS_MLD(he_sniffer_params, 32);442WIPHY_DEBUGFS_WRITE_FILE_OPS_MLD(fw_dbg_clear, 10);443WIPHY_DEBUGFS_WRITE_FILE_OPS_MLD(send_echo_cmd, 8);444WIPHY_DEBUGFS_READ_FILE_OPS_MLD(tas_get_status, 2048);445446static ssize_t iwl_dbgfs_wifi_6e_enable_read(struct iwl_mld *mld,447size_t count, u8 *buf)448{449int err;450u32 value;451452err = iwl_bios_get_dsm(&mld->fwrt, DSM_FUNC_ENABLE_6E, &value);453if (err)454return err;455456return scnprintf(buf, count, "0x%08x\n", value);457}458459MLD_DEBUGFS_READ_FILE_OPS(wifi_6e_enable, 64);460461static ssize_t iwl_dbgfs_inject_packet_write(struct iwl_mld *mld,462char *buf, size_t count)463{464struct iwl_op_mode *opmode = container_of((void *)mld,465struct iwl_op_mode,466op_mode_specific);467struct iwl_rx_cmd_buffer rxb = {};468struct iwl_rx_packet *pkt;469int n_bytes = count / 2;470int ret = -EINVAL;471472if (iwl_mld_dbgfs_fw_cmd_disabled(mld))473return -EIO;474475rxb._page = alloc_pages(GFP_KERNEL, 0);476if (!rxb._page)477return -ENOMEM;478pkt = rxb_addr(&rxb);479480ret = hex2bin(page_address(rxb._page), buf, n_bytes);481if (ret)482goto out;483484/* avoid invalid memory access and malformed packet */485if (n_bytes < sizeof(*pkt) ||486n_bytes != sizeof(*pkt) + iwl_rx_packet_payload_len(pkt))487goto out;488489local_bh_disable();490iwl_mld_rx(opmode, NULL, &rxb);491local_bh_enable();492ret = 0;493494out:495iwl_free_rxb(&rxb);496497return ret ?: count;498}499500WIPHY_DEBUGFS_WRITE_FILE_OPS_MLD(inject_packet, 512);501502#ifdef CONFIG_THERMAL503504static ssize_t iwl_dbgfs_stop_ctdp_write(struct iwl_mld *mld,505char *buf, size_t count)506{507if (iwl_mld_dbgfs_fw_cmd_disabled(mld))508return -EIO;509510return iwl_mld_config_ctdp(mld, mld->cooling_dev.cur_state,511CTDP_CMD_OPERATION_STOP) ? : count;512}513514WIPHY_DEBUGFS_WRITE_FILE_OPS_MLD(stop_ctdp, 8);515516static ssize_t iwl_dbgfs_start_ctdp_write(struct iwl_mld *mld,517char *buf, size_t count)518{519if (iwl_mld_dbgfs_fw_cmd_disabled(mld))520return -EIO;521522return iwl_mld_config_ctdp(mld, mld->cooling_dev.cur_state,523CTDP_CMD_OPERATION_START) ? : count;524}525526WIPHY_DEBUGFS_WRITE_FILE_OPS_MLD(start_ctdp, 8);527528#endif /* CONFIG_THERMAL */529530void531iwl_mld_add_debugfs_files(struct iwl_mld *mld, struct dentry *debugfs_dir)532{533/* Add debugfs files here */534535MLD_DEBUGFS_ADD_FILE(fw_nmi, debugfs_dir, 0200);536MLD_DEBUGFS_ADD_FILE(fw_restart, debugfs_dir, 0200);537MLD_DEBUGFS_ADD_FILE(wifi_6e_enable, debugfs_dir, 0400);538MLD_DEBUGFS_ADD_FILE(he_sniffer_params, debugfs_dir, 0600);539MLD_DEBUGFS_ADD_FILE(fw_dbg_clear, debugfs_dir, 0200);540MLD_DEBUGFS_ADD_FILE(send_echo_cmd, debugfs_dir, 0200);541MLD_DEBUGFS_ADD_FILE(tas_get_status, debugfs_dir, 0400);542#ifdef CONFIG_THERMAL543MLD_DEBUGFS_ADD_FILE(start_ctdp, debugfs_dir, 0200);544MLD_DEBUGFS_ADD_FILE(stop_ctdp, debugfs_dir, 0200);545#endif546MLD_DEBUGFS_ADD_FILE(inject_packet, debugfs_dir, 0200);547548#ifdef CONFIG_PM_SLEEP549debugfs_create_u32("max_sleep", 0600, debugfs_dir,550&mld->debug_max_sleep);551#endif552553debugfs_create_bool("rx_ts_ptp", 0600, debugfs_dir,554&mld->monitor.ptp_time);555556/* Create a symlink with mac80211. It will be removed when mac80211557* exits (before the opmode exits which removes the target.)558*/559if (!IS_ERR(debugfs_dir)) {560char buf[100];561562snprintf(buf, 100, "../../%pd2", debugfs_dir->d_parent);563debugfs_create_symlink("iwlwifi", mld->wiphy->debugfsdir,564buf);565}566}567568#define VIF_DEBUGFS_WRITE_FILE_OPS(name, bufsz) \569WIPHY_DEBUGFS_WRITE_FILE_OPS(vif_##name, bufsz, vif)570571#define VIF_DEBUGFS_READ_WRITE_FILE_OPS(name, bufsz) \572IEEE80211_WIPHY_DEBUGFS_READ_WRITE_FILE_OPS(vif_##name, bufsz, vif) \573574#define VIF_DEBUGFS_ADD_FILE_ALIAS(alias, name, parent, mode) \575debugfs_create_file(alias, mode, parent, vif, \576&iwl_dbgfs_vif_##name##_ops)577#define VIF_DEBUGFS_ADD_FILE(name, parent, mode) \578VIF_DEBUGFS_ADD_FILE_ALIAS(#name, name, parent, mode)579580static ssize_t iwl_dbgfs_vif_bf_params_write(struct iwl_mld *mld, char *buf,581size_t count, void *data)582{583struct ieee80211_vif *vif = data;584struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);585int link_id = vif->active_links ? __ffs(vif->active_links) : 0;586struct ieee80211_bss_conf *link_conf;587int val;588589if (!strncmp("bf_enable_beacon_filter=", buf, 24)) {590if (sscanf(buf + 24, "%d", &val) != 1)591return -EINVAL;592} else {593return -EINVAL;594}595596if (val != 0 && val != 1)597return -EINVAL;598599link_conf = link_conf_dereference_protected(vif, link_id);600if (WARN_ON(!link_conf))601return -ENODEV;602603if (iwl_mld_dbgfs_fw_cmd_disabled(mld))604return -EIO;605606mld_vif->disable_bf = !val;607608if (val)609return iwl_mld_enable_beacon_filter(mld, link_conf,610false) ?: count;611else612return iwl_mld_disable_beacon_filter(mld, vif) ?: count;613}614615static ssize_t iwl_dbgfs_vif_pm_params_write(struct iwl_mld *mld,616char *buf,617size_t count, void *data)618{619struct ieee80211_vif *vif = data;620struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);621int val;622623if (!strncmp("use_ps_poll=", buf, 12)) {624if (sscanf(buf + 12, "%d", &val) != 1)625return -EINVAL;626} else {627return -EINVAL;628}629630if (iwl_mld_dbgfs_fw_cmd_disabled(mld))631return -EIO;632633mld_vif->use_ps_poll = val;634635return iwl_mld_update_mac_power(mld, vif, false) ?: count;636}637638static ssize_t iwl_dbgfs_vif_low_latency_write(struct iwl_mld *mld,639char *buf, size_t count,640void *data)641{642struct ieee80211_vif *vif = data;643u8 value;644int ret;645646ret = kstrtou8(buf, 0, &value);647if (ret)648return ret;649650if (value > 1)651return -EINVAL;652653iwl_mld_vif_update_low_latency(mld, vif, value, LOW_LATENCY_DEBUGFS);654655return count;656}657658static ssize_t iwl_dbgfs_vif_low_latency_read(struct ieee80211_vif *vif,659size_t count, char *buf)660{661struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);662char format[] = "traffic=%d\ndbgfs=%d\nvif_type=%d\nactual=%d\n";663u8 ll_causes;664665if (WARN_ON(count < sizeof(format)))666return -EINVAL;667668ll_causes = READ_ONCE(mld_vif->low_latency_causes);669670/* all values in format are boolean so the size of format is enough671* for holding the result string672*/673return scnprintf(buf, count, format,674!!(ll_causes & LOW_LATENCY_TRAFFIC),675!!(ll_causes & LOW_LATENCY_DEBUGFS),676!!(ll_causes & LOW_LATENCY_VIF_TYPE),677!!(ll_causes));678}679680VIF_DEBUGFS_WRITE_FILE_OPS(pm_params, 32);681VIF_DEBUGFS_WRITE_FILE_OPS(bf_params, 32);682VIF_DEBUGFS_READ_WRITE_FILE_OPS(low_latency, 45);683684static int685_iwl_dbgfs_inject_beacon_ie(struct iwl_mld *mld, struct ieee80211_vif *vif,686char *bin, ssize_t len,687bool restore)688{689struct iwl_mld_vif *mld_vif;690struct iwl_mld_link *mld_link;691struct iwl_mac_beacon_cmd beacon_cmd = {};692int n_bytes = len / 2;693694/* Element len should be represented by u8 */695if (n_bytes >= U8_MAX)696return -EINVAL;697698if (iwl_mld_dbgfs_fw_cmd_disabled(mld))699return -EIO;700701if (!vif)702return -EINVAL;703704mld_vif = iwl_mld_vif_from_mac80211(vif);705mld_vif->beacon_inject_active = true;706mld->hw->extra_beacon_tailroom = n_bytes;707708for_each_mld_vif_valid_link(mld_vif, mld_link) {709u32 offset;710struct ieee80211_tx_info *info;711struct ieee80211_bss_conf *link_conf =712link_conf_dereference_protected(vif, link_id);713struct ieee80211_chanctx_conf *ctx =714wiphy_dereference(mld->wiphy, link_conf->chanctx_conf);715struct sk_buff *beacon =716ieee80211_beacon_get_template(mld->hw, vif,717NULL, link_id);718719if (!beacon)720return -EINVAL;721722if (!restore && (WARN_ON(!n_bytes || !bin) ||723hex2bin(skb_put_zero(beacon, n_bytes),724bin, n_bytes))) {725dev_kfree_skb(beacon);726return -EINVAL;727}728729info = IEEE80211_SKB_CB(beacon);730731beacon_cmd.flags =732cpu_to_le16(iwl_mld_get_rate_flags(mld, info, vif,733link_conf,734ctx->def.chan->band));735beacon_cmd.byte_cnt = cpu_to_le16((u16)beacon->len);736beacon_cmd.link_id =737cpu_to_le32(mld_link->fw_id);738739iwl_mld_set_tim_idx(mld, &beacon_cmd.tim_idx,740beacon->data, beacon->len);741742offset = iwl_find_ie_offset(beacon->data,743WLAN_EID_S1G_TWT,744beacon->len);745746beacon_cmd.btwt_offset = cpu_to_le32(offset);747748iwl_mld_send_beacon_template_cmd(mld, beacon, &beacon_cmd);749dev_kfree_skb(beacon);750}751752if (restore)753mld_vif->beacon_inject_active = false;754755return 0;756}757758static ssize_t759iwl_dbgfs_vif_inject_beacon_ie_write(struct iwl_mld *mld,760char *buf, size_t count,761void *data)762{763struct ieee80211_vif *vif = data;764int ret = _iwl_dbgfs_inject_beacon_ie(mld, vif, buf,765count, false);766767mld->hw->extra_beacon_tailroom = 0;768return ret ?: count;769}770771VIF_DEBUGFS_WRITE_FILE_OPS(inject_beacon_ie, 512);772773static ssize_t774iwl_dbgfs_vif_inject_beacon_ie_restore_write(struct iwl_mld *mld,775char *buf,776size_t count,777void *data)778{779struct ieee80211_vif *vif = data;780int ret = _iwl_dbgfs_inject_beacon_ie(mld, vif, NULL,7810, true);782783mld->hw->extra_beacon_tailroom = 0;784return ret ?: count;785}786787VIF_DEBUGFS_WRITE_FILE_OPS(inject_beacon_ie_restore, 512);788789static ssize_t790iwl_dbgfs_vif_twt_setup_write(struct iwl_mld *mld, char *buf, size_t count,791void *data)792{793struct iwl_host_cmd hcmd = {794.id = WIDE_ID(IWL_ALWAYS_LONG_GROUP, DEBUG_HOST_COMMAND),795};796struct ieee80211_vif *vif = data;797struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);798struct iwl_dhc_cmd *cmd __free(kfree) = NULL;799struct iwl_dhc_twt_operation *dhc_twt_cmd;800u64 target_wake_time;801u32 twt_operation, interval_exp, interval_mantissa, min_wake_duration;802u8 trigger, flow_type, flow_id, protection, tenth_param;803u8 twt_request = 1, broadcast = 0;804int ret;805806if (iwl_mld_dbgfs_fw_cmd_disabled(mld))807return -EIO;808809ret = sscanf(buf, "%u %llu %u %u %u %hhu %hhu %hhu %hhu %hhu",810&twt_operation, &target_wake_time, &interval_exp,811&interval_mantissa, &min_wake_duration, &trigger,812&flow_type, &flow_id, &protection, &tenth_param);813814/* the new twt_request parameter is optional for station */815if ((ret != 9 && ret != 10) ||816(ret == 10 && vif->type != NL80211_IFTYPE_STATION &&817tenth_param == 1))818return -EINVAL;819820/* The 10th parameter:821* In STA mode - the TWT type (broadcast or individual)822* In AP mode - the role (0 responder, 2 unsolicited)823*/824if (ret == 10) {825if (vif->type == NL80211_IFTYPE_STATION)826broadcast = tenth_param;827else828twt_request = tenth_param;829}830831cmd = kzalloc(sizeof(*cmd) + sizeof(*dhc_twt_cmd), GFP_KERNEL);832if (!cmd)833return -ENOMEM;834835dhc_twt_cmd = (void *)cmd->data;836dhc_twt_cmd->mac_id = cpu_to_le32(mld_vif->fw_id);837dhc_twt_cmd->twt_operation = cpu_to_le32(twt_operation);838dhc_twt_cmd->target_wake_time = cpu_to_le64(target_wake_time);839dhc_twt_cmd->interval_exp = cpu_to_le32(interval_exp);840dhc_twt_cmd->interval_mantissa = cpu_to_le32(interval_mantissa);841dhc_twt_cmd->min_wake_duration = cpu_to_le32(min_wake_duration);842dhc_twt_cmd->trigger = trigger;843dhc_twt_cmd->flow_type = flow_type;844dhc_twt_cmd->flow_id = flow_id;845dhc_twt_cmd->protection = protection;846dhc_twt_cmd->twt_request = twt_request;847dhc_twt_cmd->negotiation_type = broadcast ? 3 : 0;848849cmd->length = cpu_to_le32(sizeof(*dhc_twt_cmd) >> 2);850cmd->index_and_mask =851cpu_to_le32(DHC_TABLE_INTEGRATION | DHC_TARGET_UMAC |852DHC_INT_UMAC_TWT_OPERATION);853854hcmd.len[0] = sizeof(*cmd) + sizeof(*dhc_twt_cmd);855hcmd.data[0] = cmd;856857ret = iwl_mld_send_cmd(mld, &hcmd);858859return ret ?: count;860}861862VIF_DEBUGFS_WRITE_FILE_OPS(twt_setup, 256);863864static ssize_t865iwl_dbgfs_vif_twt_operation_write(struct iwl_mld *mld, char *buf, size_t count,866void *data)867{868struct ieee80211_vif *vif = data;869struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);870struct iwl_twt_operation_cmd twt_cmd = {};871int link_id = vif->active_links ? __ffs(vif->active_links) : 0;872struct iwl_mld_link *mld_link = iwl_mld_link_dereference_check(mld_vif,873link_id);874int ret;875876if (WARN_ON(!mld_link))877return -ENODEV;878879if (iwl_mld_dbgfs_fw_cmd_disabled(mld))880return -EIO;881882if (hweight16(vif->active_links) > 1)883return -EOPNOTSUPP;884885ret = sscanf(buf,886"%u %llu %u %u %u %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu",887&twt_cmd.twt_operation, &twt_cmd.target_wake_time,888&twt_cmd.interval_exponent, &twt_cmd.interval_mantissa,889&twt_cmd.minimum_wake_duration, &twt_cmd.trigger,890&twt_cmd.flow_type, &twt_cmd.flow_id,891&twt_cmd.twt_protection, &twt_cmd.ndp_paging_indicator,892&twt_cmd.responder_pm_mode, &twt_cmd.negotiation_type,893&twt_cmd.twt_request, &twt_cmd.implicit,894&twt_cmd.twt_group_assignment, &twt_cmd.twt_channel,895&twt_cmd.restricted_info_present, &twt_cmd.dl_bitmap_valid,896&twt_cmd.ul_bitmap_valid, &twt_cmd.dl_tid_bitmap,897&twt_cmd.ul_tid_bitmap);898899if (ret != 21)900return -EINVAL;901902twt_cmd.link_id = cpu_to_le32(mld_link->fw_id);903904ret = iwl_mld_send_cmd_pdu(mld,905WIDE_ID(MAC_CONF_GROUP, TWT_OPERATION_CMD),906&twt_cmd);907return ret ?: count;908}909910VIF_DEBUGFS_WRITE_FILE_OPS(twt_operation, 256);911912static ssize_t iwl_dbgfs_vif_int_mlo_scan_write(struct iwl_mld *mld, char *buf,913size_t count, void *data)914{915struct ieee80211_vif *vif = data;916u32 action;917int ret;918919if (!vif->cfg.assoc || !ieee80211_vif_is_mld(vif))920return -EINVAL;921922if (kstrtou32(buf, 0, &action))923return -EINVAL;924925if (action == 0) {926ret = iwl_mld_scan_stop(mld, IWL_MLD_SCAN_INT_MLO, false);927} else if (action == 1) {928iwl_mld_int_mlo_scan(mld, vif);929ret = 0;930} else {931ret = -EINVAL;932}933934return ret ?: count;935}936937VIF_DEBUGFS_WRITE_FILE_OPS(int_mlo_scan, 32);938939void iwl_mld_add_vif_debugfs(struct ieee80211_hw *hw,940struct ieee80211_vif *vif)941{942struct dentry *mld_vif_dbgfs =943debugfs_create_dir("iwlmld", vif->debugfs_dir);944struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);945struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw);946char target[3 * 3 + 11 + (NL80211_WIPHY_NAME_MAXLEN + 1) +947(7 + IFNAMSIZ + 1) + 6 + 1];948char name[7 + IFNAMSIZ + 1];949950/* Create symlink for convenience pointing to interface specific951* debugfs entries for the driver. For example, under952* /sys/kernel/debug/iwlwifi/0000\:02\:00.0/iwlmld/953* find954* netdev:wlan0 -> ../../../ieee80211/phy0/netdev:wlan0/iwlmld/955*/956snprintf(name, sizeof(name), "%pd", vif->debugfs_dir);957snprintf(target, sizeof(target), "../../../%pd3/iwlmld",958vif->debugfs_dir);959if (!mld_vif->dbgfs_slink)960mld_vif->dbgfs_slink =961debugfs_create_symlink(name, mld->debugfs_dir, target);962963if (iwlmld_mod_params.power_scheme != IWL_POWER_SCHEME_CAM &&964vif->type == NL80211_IFTYPE_STATION) {965VIF_DEBUGFS_ADD_FILE(pm_params, mld_vif_dbgfs, 0200);966VIF_DEBUGFS_ADD_FILE(bf_params, mld_vif_dbgfs, 0200);967}968969if (vif->type == NL80211_IFTYPE_AP) {970VIF_DEBUGFS_ADD_FILE(inject_beacon_ie, mld_vif_dbgfs, 0200);971VIF_DEBUGFS_ADD_FILE(inject_beacon_ie_restore,972mld_vif_dbgfs, 0200);973}974975VIF_DEBUGFS_ADD_FILE(low_latency, mld_vif_dbgfs, 0600);976VIF_DEBUGFS_ADD_FILE(twt_setup, mld_vif_dbgfs, 0200);977VIF_DEBUGFS_ADD_FILE(twt_operation, mld_vif_dbgfs, 0200);978VIF_DEBUGFS_ADD_FILE(int_mlo_scan, mld_vif_dbgfs, 0200);979}980#define LINK_DEBUGFS_WRITE_FILE_OPS(name, bufsz) \981WIPHY_DEBUGFS_WRITE_FILE_OPS(link_##name, bufsz, bss_conf)982983#define LINK_DEBUGFS_ADD_FILE_ALIAS(alias, name, parent, mode) \984debugfs_create_file(alias, mode, parent, link_conf, \985&iwl_dbgfs_link_##name##_ops)986#define LINK_DEBUGFS_ADD_FILE(name, parent, mode) \987LINK_DEBUGFS_ADD_FILE_ALIAS(#name, name, parent, mode)988989void iwl_mld_add_link_debugfs(struct ieee80211_hw *hw,990struct ieee80211_vif *vif,991struct ieee80211_bss_conf *link_conf,992struct dentry *dir)993{994struct dentry *mld_link_dir;995996mld_link_dir = debugfs_lookup("iwlmld", dir);997998/* For non-MLO vifs, the dir of deflink is the same as the vif's one.999* so if iwlmld dir already exists, this means that this is deflink.1000* If not, this is a per-link dir of a MLO vif, add in it the iwlmld1001* dir.1002*/1003if (!mld_link_dir)1004mld_link_dir = debugfs_create_dir("iwlmld", dir);1005}10061007static ssize_t _iwl_dbgfs_fixed_rate_write(struct iwl_mld *mld, char *buf,1008size_t count, void *data, bool v3)1009{1010struct ieee80211_link_sta *link_sta = data;1011struct iwl_mld_link_sta *mld_link_sta;1012u32 rate;1013u32 partial = false;1014char pretty_rate[100];1015int ret;1016u8 fw_sta_id;10171018mld_link_sta = iwl_mld_link_sta_from_mac80211(link_sta);1019if (WARN_ON(!mld_link_sta))1020return -EINVAL;10211022fw_sta_id = mld_link_sta->fw_id;10231024if (sscanf(buf, "%i %i", &rate, &partial) == 0)1025return -EINVAL;10261027if (iwl_mld_dbgfs_fw_cmd_disabled(mld))1028return -EIO;10291030/* input is in FW format (v2 or v3) so convert to v3 */1031rate = iwl_v3_rate_from_v2_v3(cpu_to_le32(rate), v3);1032rate = le32_to_cpu(iwl_v3_rate_to_v2_v3(rate, mld->fw_rates_ver_3));10331034ret = iwl_mld_send_tlc_dhc(mld, fw_sta_id,1035partial ? IWL_TLC_DEBUG_PARTIAL_FIXED_RATE :1036IWL_TLC_DEBUG_FIXED_RATE,1037rate);10381039rs_pretty_print_rate(pretty_rate, sizeof(pretty_rate), rate);10401041IWL_DEBUG_RATE(mld, "sta_id %d rate %s partial: %d, ret:%d\n",1042fw_sta_id, pretty_rate, partial, ret);10431044return ret ? : count;1045}10461047static ssize_t iwl_dbgfs_fixed_rate_write(struct iwl_mld *mld, char *buf,1048size_t count, void *data)1049{1050return _iwl_dbgfs_fixed_rate_write(mld, buf, count, data, false);1051}10521053static ssize_t iwl_dbgfs_fixed_rate_v3_write(struct iwl_mld *mld, char *buf,1054size_t count, void *data)1055{1056return _iwl_dbgfs_fixed_rate_write(mld, buf, count, data, true);1057}10581059static ssize_t iwl_dbgfs_tlc_dhc_write(struct iwl_mld *mld, char *buf,1060size_t count, void *data)1061{1062struct ieee80211_link_sta *link_sta = data;1063struct iwl_mld_link_sta *mld_link_sta;1064u32 type, value;1065int ret;1066u8 fw_sta_id;10671068mld_link_sta = iwl_mld_link_sta_from_mac80211(link_sta);1069if (WARN_ON(!mld_link_sta))1070return -EINVAL;10711072fw_sta_id = mld_link_sta->fw_id;10731074if (sscanf(buf, "%i %i", &type, &value) != 2) {1075IWL_DEBUG_RATE(mld, "usage <type> <value>\n");1076return -EINVAL;1077}10781079if (iwl_mld_dbgfs_fw_cmd_disabled(mld))1080return -EIO;10811082ret = iwl_mld_send_tlc_dhc(mld, fw_sta_id, type, value);10831084return ret ? : count;1085}10861087#define LINK_STA_DEBUGFS_ADD_FILE_ALIAS(alias, name, parent, mode) \1088debugfs_create_file(alias, mode, parent, link_sta, \1089&iwl_dbgfs_##name##_ops)1090#define LINK_STA_DEBUGFS_ADD_FILE(name, parent, mode) \1091LINK_STA_DEBUGFS_ADD_FILE_ALIAS(#name, name, parent, mode)10921093#define LINK_STA_WIPHY_DEBUGFS_WRITE_OPS(name, bufsz) \1094WIPHY_DEBUGFS_WRITE_FILE_OPS(name, bufsz, link_sta)10951096LINK_STA_WIPHY_DEBUGFS_WRITE_OPS(tlc_dhc, 64);1097LINK_STA_WIPHY_DEBUGFS_WRITE_OPS(fixed_rate, 64);1098LINK_STA_WIPHY_DEBUGFS_WRITE_OPS(fixed_rate_v3, 64);10991100void iwl_mld_add_link_sta_debugfs(struct ieee80211_hw *hw,1101struct ieee80211_vif *vif,1102struct ieee80211_link_sta *link_sta,1103struct dentry *dir)1104{1105LINK_STA_DEBUGFS_ADD_FILE(fixed_rate, dir, 0200);1106LINK_STA_DEBUGFS_ADD_FILE(fixed_rate_v3, dir, 0200);1107LINK_STA_DEBUGFS_ADD_FILE(tlc_dhc, dir, 0200);1108}110911101111