File Index
Files and directories
Sign in
FILE

debugfs.c

File preview with syntax highlighting.

linux-6.19.14/drivers/net/wireless/intel/iwlwifi/mld/debugfs.c C
   1// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
   2/*
   3 * Copyright (C) 2024-2025 Intel Corporation
   4 */
   5
   6#include "mld.h"
   7#include "debugfs.h"
   8#include "iwl-io.h"
   9#include "hcmd.h"
  10#include "iface.h"
  11#include "sta.h"
  12#include "tlc.h"
  13#include "power.h"
  14#include "notif.h"
  15#include "ap.h"
  16#include "iwl-utils.h"
  17#include "scan.h"
  18#ifdef CONFIG_THERMAL
  19#include "thermal.h"
  20#endif
  21
  22#include "fw/api/rs.h"
  23#include "fw/api/dhc.h"
  24#include "fw/api/rfi.h"
  25#include "fw/dhc-utils.h"
  26#include <linux/dmi.h>
  27
  28#define MLD_DEBUGFS_READ_FILE_OPS(name, bufsz)				\
  29	_MLD_DEBUGFS_READ_FILE_OPS(name, bufsz, struct iwl_mld)
  30
  31#define MLD_DEBUGFS_ADD_FILE_ALIAS(alias, name, parent, mode)		\
  32	debugfs_create_file(alias, mode, parent, mld,			\
  33			    &iwl_dbgfs_##name##_ops)
  34#define MLD_DEBUGFS_ADD_FILE(name, parent, mode)			\
  35	MLD_DEBUGFS_ADD_FILE_ALIAS(#name, name, parent, mode)
  36
  37static bool iwl_mld_dbgfs_fw_cmd_disabled(struct iwl_mld *mld)
  38{
  39#ifdef CONFIG_PM_SLEEP
  40	return !mld->fw_status.running || mld->fw_status.in_d3;
  41#else
  42	return !mld->fw_status.running;
  43#endif /* CONFIG_PM_SLEEP */
  44}
  45
  46static ssize_t iwl_dbgfs_fw_dbg_clear_write(struct iwl_mld *mld,
  47					    char *buf, size_t count)
  48{
  49	/* If the firmware is not running, silently succeed since there is
  50	 * no data to clear.
  51	 */
  52	if (iwl_mld_dbgfs_fw_cmd_disabled(mld))
  53		return 0;
  54
  55	iwl_fw_dbg_clear_monitor_buf(&mld->fwrt);
  56
  57	return count;
  58}
  59
  60static ssize_t iwl_dbgfs_fw_nmi_write(struct iwl_mld *mld, char *buf,
  61				      size_t count)
  62{
  63	if (iwl_mld_dbgfs_fw_cmd_disabled(mld))
  64		return -EIO;
  65
  66	IWL_ERR(mld, "Triggering an NMI from debugfs\n");
  67
  68	if (count == 6 && !strcmp(buf, "nolog\n"))
  69		mld->fw_status.do_not_dump_once = true;
  70
  71	iwl_force_nmi(mld->trans);
  72
  73	return count;
  74}
  75
  76static ssize_t iwl_dbgfs_fw_restart_write(struct iwl_mld *mld, char *buf,
  77					  size_t count)
  78{
  79	int __maybe_unused ret;
  80
  81	if (!iwlwifi_mod_params.fw_restart)
  82		return -EPERM;
  83
  84	if (iwl_mld_dbgfs_fw_cmd_disabled(mld))
  85		return -EIO;
  86
  87	if (count == 6 && !strcmp(buf, "nolog\n")) {
  88		mld->fw_status.do_not_dump_once = true;
  89		mld->trans->suppress_cmd_error_once = true;
  90	}
  91
  92	/* take the return value to make compiler happy - it will
  93	 * fail anyway
  94	 */
  95	ret = iwl_mld_send_cmd_empty(mld, WIDE_ID(LONG_GROUP, REPLY_ERROR));
  96
  97	return count;
  98}
  99
 100static ssize_t iwl_dbgfs_send_echo_cmd_write(struct iwl_mld *mld, char *buf,
 101					     size_t count)
 102{
 103	if (iwl_mld_dbgfs_fw_cmd_disabled(mld))
 104		return -EIO;
 105
 106	return iwl_mld_send_cmd_empty(mld, ECHO_CMD) ?: count;
 107}
 108
 109struct iwl_mld_sniffer_apply {
 110	struct iwl_mld *mld;
 111	const u8 *bssid;
 112	u16 aid;
 113};
 114
 115static bool iwl_mld_sniffer_apply(struct iwl_notif_wait_data *notif_data,
 116				  struct iwl_rx_packet *pkt, void *data)
 117{
 118	struct iwl_mld_sniffer_apply *apply = data;
 119
 120	apply->mld->monitor.cur_aid = cpu_to_le16(apply->aid);
 121	memcpy(apply->mld->monitor.cur_bssid, apply->bssid,
 122	       sizeof(apply->mld->monitor.cur_bssid));
 123
 124	return true;
 125}
 126
 127static ssize_t
 128iwl_dbgfs_he_sniffer_params_write(struct iwl_mld *mld, char *buf,
 129				  size_t count)
 130{
 131	struct iwl_notification_wait wait;
 132	struct iwl_he_monitor_cmd he_mon_cmd = {};
 133	struct iwl_mld_sniffer_apply apply = {
 134		.mld = mld,
 135	};
 136	u16 wait_cmds[] = {
 137		WIDE_ID(DATA_PATH_GROUP, HE_AIR_SNIFFER_CONFIG_CMD),
 138	};
 139	u32 aid;
 140	int ret;
 141
 142	if (iwl_mld_dbgfs_fw_cmd_disabled(mld))
 143		return -EIO;
 144
 145	if (!mld->monitor.on)
 146		return -ENODEV;
 147
 148	ret = sscanf(buf, "%x %2hhx:%2hhx:%2hhx:%2hhx:%2hhx:%2hhx", &aid,
 149		     &he_mon_cmd.bssid[0], &he_mon_cmd.bssid[1],
 150		     &he_mon_cmd.bssid[2], &he_mon_cmd.bssid[3],
 151		     &he_mon_cmd.bssid[4], &he_mon_cmd.bssid[5]);
 152	if (ret != 7)
 153		return -EINVAL;
 154
 155	he_mon_cmd.aid = cpu_to_le16(aid);
 156
 157	apply.aid = aid;
 158	apply.bssid = (void *)he_mon_cmd.bssid;
 159
 160	/* Use the notification waiter to get our function triggered
 161	 * in sequence with other RX. This ensures that frames we get
 162	 * on the RX queue _before_ the new configuration is applied
 163	 * still have mld->cur_aid pointing to the old AID, and that
 164	 * frames on the RX queue _after_ the firmware processed the
 165	 * new configuration (and sent the response, synchronously)
 166	 * get mld->cur_aid correctly set to the new AID.
 167	 */
 168	iwl_init_notification_wait(&mld->notif_wait, &wait,
 169				   wait_cmds, ARRAY_SIZE(wait_cmds),
 170				   iwl_mld_sniffer_apply, &apply);
 171
 172	ret = iwl_mld_send_cmd_pdu(mld,
 173				   WIDE_ID(DATA_PATH_GROUP,
 174					   HE_AIR_SNIFFER_CONFIG_CMD),
 175				   &he_mon_cmd);
 176
 177	/* no need to really wait, we already did anyway */
 178	iwl_remove_notification(&mld->notif_wait, &wait);
 179
 180	return ret ?: count;
 181}
 182
 183static ssize_t
 184iwl_dbgfs_he_sniffer_params_read(struct iwl_mld *mld, char *buf, size_t count)
 185{
 186	return scnprintf(buf, count,
 187			 "%d %02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx\n",
 188			 le16_to_cpu(mld->monitor.cur_aid),
 189			 mld->monitor.cur_bssid[0], mld->monitor.cur_bssid[1],
 190			 mld->monitor.cur_bssid[2], mld->monitor.cur_bssid[3],
 191			 mld->monitor.cur_bssid[4], mld->monitor.cur_bssid[5]);
 192}
 193
 194/* The size computation is as follows:
 195 * each number needs at most 3 characters, number of rows is the size of
 196 * the table; So, need 5 chars for the "freq: " part and each tuple afterwards
 197 * needs 6 characters for numbers and 5 for the punctuation around. 32 bytes
 198 * for feature support message.
 199 */
 200#define IWL_RFI_DDR_BUF_SIZE (IWL_RFI_DDR_LUT_INSTALLED_SIZE *\
 201				(5 + IWL_RFI_DDR_LUT_ENTRY_CHANNELS_NUM *\
 202					(6 + 5)) + 32)
 203#define IWL_RFI_DLVR_BUF_SIZE (IWL_RFI_DLVR_LUT_INSTALLED_SIZE *\
 204				(5 + IWL_RFI_DLVR_LUT_ENTRY_CHANNELS_NUM *\
 205					(6 + 5)) + 32)
 206#define IWL_RFI_DESENSE_BUF_SIZE IWL_RFI_DDR_BUF_SIZE
 207
 208/* Extra 32 for "DDR and DLVR table" message */
 209#define IWL_RFI_BUF_SIZE (IWL_RFI_DDR_BUF_SIZE + IWL_RFI_DLVR_BUF_SIZE +\
 210				IWL_RFI_DESENSE_BUF_SIZE + 32)
 211
 212static size_t iwl_mld_dump_tas_resp(struct iwl_dhc_tas_status_resp *resp,
 213				    size_t count, u8 *buf)
 214{
 215	const char * const tas_dis_reason[TAS_DISABLED_REASON_MAX] = {
 216		[TAS_DISABLED_DUE_TO_BIOS] =
 217			"Due To BIOS",
 218		[TAS_DISABLED_DUE_TO_SAR_6DBM] =
 219			"Due To SAR Limit Less Than 6 dBm",
 220		[TAS_DISABLED_REASON_INVALID] =
 221			"N/A",
 222		[TAS_DISABLED_DUE_TO_TABLE_SOURCE_INVALID] =
 223			"Due to table source invalid"
 224	};
 225	const char * const tas_current_status[TAS_DYNA_STATUS_MAX] = {
 226		[TAS_DYNA_INACTIVE] = "INACTIVE",
 227		[TAS_DYNA_INACTIVE_MVM_MODE] =
 228			"inactive due to mvm mode",
 229		[TAS_DYNA_INACTIVE_TRIGGER_MODE] =
 230			"inactive due to trigger mode",
 231		[TAS_DYNA_INACTIVE_BLOCK_LISTED] =
 232			"inactive due to block listed",
 233		[TAS_DYNA_INACTIVE_UHB_NON_US] =
 234			"inactive due to uhb non US",
 235		[TAS_DYNA_ACTIVE] = "ACTIVE",
 236	};
 237	ssize_t pos = 0;
 238
 239	if (resp->header.version != 1) {
 240		pos += scnprintf(buf + pos, count - pos,
 241				 "Unsupported TAS response version:%d",
 242				 resp->header.version);
 243		return pos;
 244	}
 245
 246	pos += scnprintf(buf + pos, count - pos, "TAS Report\n");
 247	switch (resp->tas_config_info.table_source) {
 248	case BIOS_SOURCE_NONE:
 249		pos += scnprintf(buf + pos, count - pos,
 250				 "BIOS SOURCE NONE ");
 251		break;
 252	case BIOS_SOURCE_ACPI:
 253		pos += scnprintf(buf + pos, count - pos,
 254				 "BIOS SOURCE ACPI ");
 255		break;
 256	case BIOS_SOURCE_UEFI:
 257		pos += scnprintf(buf + pos, count - pos,
 258				 "BIOS SOURCE UEFI ");
 259		break;
 260	default:
 261		pos += scnprintf(buf + pos, count - pos,
 262				 "BIOS SOURCE UNKNOWN (%d) ",
 263				 resp->tas_config_info.table_source);
 264		break;
 265	}
 266
 267	pos += scnprintf(buf + pos, count - pos,
 268			 "revision is: %d data is: 0x%08x\n",
 269			 resp->tas_config_info.table_revision,
 270			 resp->tas_config_info.value);
 271	pos += scnprintf(buf + pos, count - pos, "Current MCC: 0x%x\n",
 272			 le16_to_cpu(resp->curr_mcc));
 273
 274	pos += scnprintf(buf + pos, count - pos, "Block list entries:");
 275	for (int i = 0; i < ARRAY_SIZE(resp->mcc_block_list); i++)
 276		pos += scnprintf(buf + pos, count - pos, " 0x%x",
 277				 le16_to_cpu(resp->mcc_block_list[i]));
 278
 279	pos += scnprintf(buf + pos, count - pos,
 280			 "\nDo TAS Support Dual Radio?: %s\n",
 281			 hweight8(resp->valid_radio_mask) > 1 ?
 282			 "TRUE" : "FALSE");
 283
 284	for (int i = 0; i < ARRAY_SIZE(resp->tas_status_radio); i++) {
 285		int tmp;
 286		unsigned long dynamic_status;
 287
 288		if (!(resp->valid_radio_mask & BIT(i)))
 289			continue;
 290
 291		pos += scnprintf(buf + pos, count - pos,
 292				 "TAS report for radio:%d\n", i + 1);
 293		pos += scnprintf(buf + pos, count - pos,
 294				 "Static status: %sabled\n",
 295				 resp->tas_status_radio[i].static_status ?
 296				 "En" : "Dis");
 297		if (!resp->tas_status_radio[i].static_status) {
 298			u8 static_disable_reason =
 299				resp->tas_status_radio[i].static_disable_reason;
 300
 301			pos += scnprintf(buf + pos, count - pos,
 302					 "\tStatic Disabled Reason: ");
 303			if (static_disable_reason >= TAS_DISABLED_REASON_MAX) {
 304				pos += scnprintf(buf + pos, count - pos,
 305						 "unsupported value (%d)\n",
 306						 static_disable_reason);
 307				continue;
 308			}
 309
 310			pos += scnprintf(buf + pos, count - pos,
 311					 "%s (%d)\n",
 312					 tas_dis_reason[static_disable_reason],
 313					 static_disable_reason);
 314			continue;
 315		}
 316
 317		pos += scnprintf(buf + pos, count - pos, "\tANT A %s and ",
 318				 (resp->tas_status_radio[i].dynamic_status_ant_a
 319				  & BIT(TAS_DYNA_ACTIVE)) ? "ON" : "OFF");
 320
 321		pos += scnprintf(buf + pos, count - pos, "ANT B %s for ",
 322				 (resp->tas_status_radio[i].dynamic_status_ant_b
 323				  & BIT(TAS_DYNA_ACTIVE)) ? "ON" : "OFF");
 324
 325		switch (resp->tas_status_radio[i].band) {
 326		case PHY_BAND_5:
 327			pos += scnprintf(buf + pos, count - pos, "HB\n");
 328			break;
 329		case PHY_BAND_24:
 330			pos += scnprintf(buf + pos, count - pos, "LB\n");
 331			break;
 332		case PHY_BAND_6:
 333			pos += scnprintf(buf + pos, count - pos, "UHB\n");
 334			break;
 335		default:
 336			pos += scnprintf(buf + pos, count - pos,
 337					 "Unsupported band (%d)\n",
 338					 resp->tas_status_radio[i].band);
 339			break;
 340		}
 341
 342		pos += scnprintf(buf + pos, count - pos,
 343				 "Is near disconnection?: %s\n",
 344				 resp->tas_status_radio[i].near_disconnection ?
 345				 "True" : "False");
 346
 347		pos += scnprintf(buf + pos, count - pos,
 348				 "Dynamic status antenna A:\n");
 349		dynamic_status = resp->tas_status_radio[i].dynamic_status_ant_a;
 350		for_each_set_bit(tmp, &dynamic_status, TAS_DYNA_STATUS_MAX) {
 351			pos += scnprintf(buf + pos, count - pos, "\t%s (%d)\n",
 352					 tas_current_status[tmp], tmp);
 353		}
 354		pos += scnprintf(buf + pos, count - pos,
 355				 "\nDynamic status antenna B:\n");
 356		dynamic_status = resp->tas_status_radio[i].dynamic_status_ant_b;
 357		for_each_set_bit(tmp, &dynamic_status, TAS_DYNA_STATUS_MAX) {
 358			pos += scnprintf(buf + pos, count - pos, "\t%s (%d)\n",
 359					 tas_current_status[tmp], tmp);
 360		}
 361
 362		tmp = le16_to_cpu(resp->tas_status_radio[i].max_reg_pwr_limit_ant_a);
 363		pos += scnprintf(buf + pos, count - pos,
 364				 "Max antenna A regulatory pwr limit (dBm): %d.%03d\n",
 365				 tmp / 8, 125 * (tmp % 8));
 366		tmp = le16_to_cpu(resp->tas_status_radio[i].max_reg_pwr_limit_ant_b);
 367		pos += scnprintf(buf + pos, count - pos,
 368				 "Max antenna B regulatory pwr limit (dBm): %d.%03d\n",
 369				 tmp / 8, 125 * (tmp % 8));
 370
 371		tmp = le16_to_cpu(resp->tas_status_radio[i].sar_limit_ant_a);
 372		pos += scnprintf(buf + pos, count - pos,
 373				 "Antenna A SAR limit (dBm): %d.%03d\n",
 374				 tmp / 8, 125 * (tmp % 8));
 375		tmp = le16_to_cpu(resp->tas_status_radio[i].sar_limit_ant_b);
 376		pos += scnprintf(buf + pos, count - pos,
 377				 "Antenna B SAR limit (dBm): %d.%03d\n",
 378				 tmp / 8, 125 * (tmp % 8));
 379	}
 380
 381	return pos;
 382}
 383
 384static ssize_t iwl_dbgfs_tas_get_status_read(struct iwl_mld *mld, char *buf,
 385					     size_t count)
 386{
 387	struct iwl_dhc_cmd cmd = {
 388		.index_and_mask = cpu_to_le32(DHC_TABLE_TOOLS |
 389					      DHC_TARGET_UMAC |
 390					      DHC_TOOLS_UMAC_GET_TAS_STATUS),
 391	};
 392	struct iwl_host_cmd hcmd = {
 393		.id = WIDE_ID(LEGACY_GROUP, DEBUG_HOST_COMMAND),
 394		.flags = CMD_WANT_SKB,
 395		.len[0] = sizeof(cmd),
 396		.data[0] = &cmd,
 397	};
 398	struct iwl_dhc_tas_status_resp *resp = NULL;
 399	u32 resp_len = 0;
 400	ssize_t pos = 0;
 401	u32 status;
 402	int ret;
 403
 404	if (iwl_mld_dbgfs_fw_cmd_disabled(mld))
 405		return -EIO;
 406
 407	ret = iwl_mld_send_cmd(mld, &hcmd);
 408	if (ret)
 409		return ret;
 410
 411	pos += scnprintf(buf + pos, count - pos, "\nOEM name: %s\n",
 412			 dmi_get_system_info(DMI_SYS_VENDOR) ?: "<unknown>");
 413	pos += scnprintf(buf + pos, count - pos,
 414			 "\tVendor In Approved List: %s\n",
 415			 iwl_is_tas_approved() ? "YES" : "NO");
 416
 417	status = iwl_dhc_resp_status(mld->fwrt.fw, hcmd.resp_pkt);
 418	if (status != 1) {
 419		pos += scnprintf(buf + pos, count - pos,
 420				 "response status is not success: %d\n",
 421				 status);
 422		goto out;
 423	}
 424
 425	resp = iwl_dhc_resp_data(mld->fwrt.fw, hcmd.resp_pkt, &resp_len);
 426	if (IS_ERR(resp) || resp_len != sizeof(*resp)) {
 427		pos += scnprintf(buf + pos, count - pos,
 428			"Invalid size for TAS response (%u instead of %zd)\n",
 429			resp_len, sizeof(*resp));
 430		goto out;
 431	}
 432
 433	pos += iwl_mld_dump_tas_resp(resp, count - pos, buf + pos);
 434
 435out:
 436	iwl_free_resp(&hcmd);
 437	return pos;
 438}
 439
 440WIPHY_DEBUGFS_WRITE_FILE_OPS_MLD(fw_nmi, 10);
 441WIPHY_DEBUGFS_WRITE_FILE_OPS_MLD(fw_restart, 10);
 442WIPHY_DEBUGFS_READ_WRITE_FILE_OPS_MLD(he_sniffer_params, 32);
 443WIPHY_DEBUGFS_WRITE_FILE_OPS_MLD(fw_dbg_clear, 10);
 444WIPHY_DEBUGFS_WRITE_FILE_OPS_MLD(send_echo_cmd, 8);
 445WIPHY_DEBUGFS_READ_FILE_OPS_MLD(tas_get_status, 2048);
 446
 447static ssize_t iwl_dbgfs_wifi_6e_enable_read(struct iwl_mld *mld,
 448					     size_t count, u8 *buf)
 449{
 450	int err;
 451	u32 value;
 452
 453	err = iwl_bios_get_dsm(&mld->fwrt, DSM_FUNC_ENABLE_6E, &value);
 454	if (err)
 455		return err;
 456
 457	return scnprintf(buf, count, "0x%08x\n", value);
 458}
 459
 460MLD_DEBUGFS_READ_FILE_OPS(wifi_6e_enable, 64);
 461
 462static ssize_t iwl_dbgfs_inject_packet_write(struct iwl_mld *mld,
 463					     char *buf, size_t count)
 464{
 465	struct iwl_op_mode *opmode = container_of((void *)mld,
 466						  struct iwl_op_mode,
 467						  op_mode_specific);
 468	struct iwl_rx_cmd_buffer rxb = {};
 469	struct iwl_rx_packet *pkt;
 470	int n_bytes = count / 2;
 471	int ret = -EINVAL;
 472
 473	if (iwl_mld_dbgfs_fw_cmd_disabled(mld))
 474		return -EIO;
 475
 476	rxb._page = alloc_pages(GFP_KERNEL, 0);
 477	if (!rxb._page)
 478		return -ENOMEM;
 479	pkt = rxb_addr(&rxb);
 480
 481	ret = hex2bin(page_address(rxb._page), buf, n_bytes);
 482	if (ret)
 483		goto out;
 484
 485	/* avoid invalid memory access and malformed packet */
 486	if (n_bytes < sizeof(*pkt) ||
 487	    n_bytes != sizeof(*pkt) + iwl_rx_packet_payload_len(pkt))
 488		goto out;
 489
 490	local_bh_disable();
 491	iwl_mld_rx(opmode, NULL, &rxb);
 492	local_bh_enable();
 493	ret = 0;
 494
 495out:
 496	iwl_free_rxb(&rxb);
 497
 498	return ret ?: count;
 499}
 500
 501WIPHY_DEBUGFS_WRITE_FILE_OPS_MLD(inject_packet, 512);
 502
 503#ifdef CONFIG_THERMAL
 504
 505static ssize_t iwl_dbgfs_stop_ctdp_write(struct iwl_mld *mld,
 506					 char *buf, size_t count)
 507{
 508	if (iwl_mld_dbgfs_fw_cmd_disabled(mld))
 509		return -EIO;
 510
 511	return iwl_mld_config_ctdp(mld, mld->cooling_dev.cur_state,
 512				   CTDP_CMD_OPERATION_STOP) ? : count;
 513}
 514
 515WIPHY_DEBUGFS_WRITE_FILE_OPS_MLD(stop_ctdp, 8);
 516
 517static ssize_t iwl_dbgfs_start_ctdp_write(struct iwl_mld *mld,
 518					  char *buf, size_t count)
 519{
 520	if (iwl_mld_dbgfs_fw_cmd_disabled(mld))
 521		return -EIO;
 522
 523	return iwl_mld_config_ctdp(mld, mld->cooling_dev.cur_state,
 524				   CTDP_CMD_OPERATION_START) ? : count;
 525}
 526
 527WIPHY_DEBUGFS_WRITE_FILE_OPS_MLD(start_ctdp, 8);
 528
 529#endif /* CONFIG_THERMAL */
 530
 531void
 532iwl_mld_add_debugfs_files(struct iwl_mld *mld, struct dentry *debugfs_dir)
 533{
 534	/* Add debugfs files here */
 535
 536	MLD_DEBUGFS_ADD_FILE(fw_nmi, debugfs_dir, 0200);
 537	MLD_DEBUGFS_ADD_FILE(fw_restart, debugfs_dir, 0200);
 538	MLD_DEBUGFS_ADD_FILE(wifi_6e_enable, debugfs_dir, 0400);
 539	MLD_DEBUGFS_ADD_FILE(he_sniffer_params, debugfs_dir, 0600);
 540	MLD_DEBUGFS_ADD_FILE(fw_dbg_clear, debugfs_dir, 0200);
 541	MLD_DEBUGFS_ADD_FILE(send_echo_cmd, debugfs_dir, 0200);
 542	MLD_DEBUGFS_ADD_FILE(tas_get_status, debugfs_dir, 0400);
 543#ifdef CONFIG_THERMAL
 544	MLD_DEBUGFS_ADD_FILE(start_ctdp, debugfs_dir, 0200);
 545	MLD_DEBUGFS_ADD_FILE(stop_ctdp, debugfs_dir, 0200);
 546#endif
 547	MLD_DEBUGFS_ADD_FILE(inject_packet, debugfs_dir, 0200);
 548
 549#ifdef CONFIG_PM_SLEEP
 550	debugfs_create_u32("max_sleep", 0600, debugfs_dir,
 551			   &mld->debug_max_sleep);
 552#endif
 553
 554	debugfs_create_bool("rx_ts_ptp", 0600, debugfs_dir,
 555			    &mld->monitor.ptp_time);
 556
 557	/* Create a symlink with mac80211. It will be removed when mac80211
 558	 * exits (before the opmode exits which removes the target.)
 559	 */
 560	if (!IS_ERR(debugfs_dir)) {
 561		char buf[100];
 562
 563		snprintf(buf, 100, "../../%pd2", debugfs_dir->d_parent);
 564		debugfs_create_symlink("iwlwifi", mld->wiphy->debugfsdir,
 565				       buf);
 566	}
 567}
 568
 569#define VIF_DEBUGFS_WRITE_FILE_OPS(name, bufsz)			\
 570	WIPHY_DEBUGFS_WRITE_FILE_OPS(vif_##name, bufsz, vif)
 571
 572#define VIF_DEBUGFS_READ_WRITE_FILE_OPS(name, bufsz)			    \
 573	IEEE80211_WIPHY_DEBUGFS_READ_WRITE_FILE_OPS(vif_##name, bufsz, vif) \
 574
 575#define VIF_DEBUGFS_ADD_FILE_ALIAS(alias, name, parent, mode)	\
 576	debugfs_create_file(alias, mode, parent, vif,		\
 577			    &iwl_dbgfs_vif_##name##_ops)
 578#define VIF_DEBUGFS_ADD_FILE(name, parent, mode)		\
 579	VIF_DEBUGFS_ADD_FILE_ALIAS(#name, name, parent, mode)
 580
 581static ssize_t iwl_dbgfs_vif_bf_params_write(struct iwl_mld *mld, char *buf,
 582					     size_t count, void *data)
 583{
 584	struct ieee80211_vif *vif = data;
 585	struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
 586	int link_id = vif->active_links ? __ffs(vif->active_links) : 0;
 587	struct ieee80211_bss_conf *link_conf;
 588	int val;
 589
 590	if (!strncmp("bf_enable_beacon_filter=", buf, 24)) {
 591		if (sscanf(buf + 24, "%d", &val) != 1)
 592			return -EINVAL;
 593	} else {
 594		return -EINVAL;
 595	}
 596
 597	if (val != 0 && val != 1)
 598		return -EINVAL;
 599
 600	link_conf = link_conf_dereference_protected(vif, link_id);
 601	if (WARN_ON(!link_conf))
 602		return -ENODEV;
 603
 604	if (iwl_mld_dbgfs_fw_cmd_disabled(mld))
 605		return -EIO;
 606
 607	mld_vif->disable_bf = !val;
 608
 609	if (val)
 610		return iwl_mld_enable_beacon_filter(mld, link_conf,
 611						    false) ?: count;
 612	else
 613		return iwl_mld_disable_beacon_filter(mld, vif) ?: count;
 614}
 615
 616static ssize_t iwl_dbgfs_vif_pm_params_write(struct iwl_mld *mld,
 617					     char *buf,
 618					     size_t count, void *data)
 619{
 620	struct ieee80211_vif *vif = data;
 621	struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
 622	int val;
 623
 624	if (!strncmp("use_ps_poll=", buf, 12)) {
 625		if (sscanf(buf + 12, "%d", &val) != 1)
 626			return -EINVAL;
 627	} else {
 628		return -EINVAL;
 629	}
 630
 631	if (iwl_mld_dbgfs_fw_cmd_disabled(mld))
 632		return -EIO;
 633
 634	mld_vif->use_ps_poll = val;
 635
 636	return iwl_mld_update_mac_power(mld, vif, false) ?: count;
 637}
 638
 639static ssize_t iwl_dbgfs_vif_low_latency_write(struct iwl_mld *mld,
 640					       char *buf, size_t count,
 641					       void *data)
 642{
 643	struct ieee80211_vif *vif = data;
 644	u8 value;
 645	int ret;
 646
 647	ret = kstrtou8(buf, 0, &value);
 648	if (ret)
 649		return ret;
 650
 651	if (value > 1)
 652		return -EINVAL;
 653
 654	iwl_mld_vif_update_low_latency(mld, vif, value, LOW_LATENCY_DEBUGFS);
 655
 656	return count;
 657}
 658
 659static ssize_t iwl_dbgfs_vif_low_latency_read(struct ieee80211_vif *vif,
 660					      size_t count, char *buf)
 661{
 662	struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
 663	char format[] = "traffic=%d\ndbgfs=%d\nvif_type=%d\nactual=%d\n";
 664	u8 ll_causes;
 665
 666	if (WARN_ON(count < sizeof(format)))
 667		return -EINVAL;
 668
 669	ll_causes = READ_ONCE(mld_vif->low_latency_causes);
 670
 671	/* all values in format are boolean so the size of format is enough
 672	 * for holding the result string
 673	 */
 674	return scnprintf(buf, count, format,
 675			 !!(ll_causes & LOW_LATENCY_TRAFFIC),
 676			 !!(ll_causes & LOW_LATENCY_DEBUGFS),
 677			 !!(ll_causes & LOW_LATENCY_VIF_TYPE),
 678			 !!(ll_causes));
 679}
 680
 681VIF_DEBUGFS_WRITE_FILE_OPS(pm_params, 32);
 682VIF_DEBUGFS_WRITE_FILE_OPS(bf_params, 32);
 683VIF_DEBUGFS_READ_WRITE_FILE_OPS(low_latency, 45);
 684
 685static int
 686_iwl_dbgfs_inject_beacon_ie(struct iwl_mld *mld, struct ieee80211_vif *vif,
 687			    char *bin, ssize_t len,
 688			    bool restore)
 689{
 690	struct iwl_mld_vif *mld_vif;
 691	struct iwl_mld_link *mld_link;
 692	struct iwl_mac_beacon_cmd beacon_cmd = {};
 693	int n_bytes = len / 2;
 694
 695	/* Element len should be represented by u8 */
 696	if (n_bytes >= U8_MAX)
 697		return -EINVAL;
 698
 699	if (iwl_mld_dbgfs_fw_cmd_disabled(mld))
 700		return -EIO;
 701
 702	if (!vif)
 703		return -EINVAL;
 704
 705	mld_vif = iwl_mld_vif_from_mac80211(vif);
 706	mld_vif->beacon_inject_active = true;
 707	mld->hw->extra_beacon_tailroom = n_bytes;
 708
 709	for_each_mld_vif_valid_link(mld_vif, mld_link) {
 710		u32 offset;
 711		struct ieee80211_tx_info *info;
 712		struct ieee80211_bss_conf *link_conf =
 713			link_conf_dereference_protected(vif, link_id);
 714		struct ieee80211_chanctx_conf *ctx =
 715			wiphy_dereference(mld->wiphy, link_conf->chanctx_conf);
 716		struct sk_buff *beacon =
 717			ieee80211_beacon_get_template(mld->hw, vif,
 718						      NULL, link_id);
 719
 720		if (!beacon)
 721			return -EINVAL;
 722
 723		if (!restore && (WARN_ON(!n_bytes || !bin) ||
 724				 hex2bin(skb_put_zero(beacon, n_bytes),
 725					 bin, n_bytes))) {
 726			dev_kfree_skb(beacon);
 727			return -EINVAL;
 728		}
 729
 730		info = IEEE80211_SKB_CB(beacon);
 731
 732		beacon_cmd.flags =
 733			cpu_to_le16(iwl_mld_get_rate_flags(mld, info, vif,
 734							   link_conf,
 735							   ctx->def.chan->band));
 736		beacon_cmd.byte_cnt = cpu_to_le16((u16)beacon->len);
 737		beacon_cmd.link_id =
 738			cpu_to_le32(mld_link->fw_id);
 739
 740		iwl_mld_set_tim_idx(mld, &beacon_cmd.tim_idx,
 741				    beacon->data, beacon->len);
 742
 743		offset = iwl_find_ie_offset(beacon->data,
 744					    WLAN_EID_S1G_TWT,
 745					    beacon->len);
 746
 747		beacon_cmd.btwt_offset = cpu_to_le32(offset);
 748
 749		iwl_mld_send_beacon_template_cmd(mld, beacon, &beacon_cmd);
 750		dev_kfree_skb(beacon);
 751	}
 752
 753	if (restore)
 754		mld_vif->beacon_inject_active = false;
 755
 756	return 0;
 757}
 758
 759static ssize_t
 760iwl_dbgfs_vif_inject_beacon_ie_write(struct iwl_mld *mld,
 761				     char *buf, size_t count,
 762				     void *data)
 763{
 764	struct ieee80211_vif *vif = data;
 765	int ret = _iwl_dbgfs_inject_beacon_ie(mld, vif, buf,
 766					      count, false);
 767
 768	mld->hw->extra_beacon_tailroom = 0;
 769	return ret ?: count;
 770}
 771
 772VIF_DEBUGFS_WRITE_FILE_OPS(inject_beacon_ie, 512);
 773
 774static ssize_t
 775iwl_dbgfs_vif_inject_beacon_ie_restore_write(struct iwl_mld *mld,
 776					     char *buf,
 777					     size_t count,
 778					     void *data)
 779{
 780	struct ieee80211_vif *vif = data;
 781	int ret = _iwl_dbgfs_inject_beacon_ie(mld, vif, NULL,
 782					      0, true);
 783
 784	mld->hw->extra_beacon_tailroom = 0;
 785	return ret ?: count;
 786}
 787
 788VIF_DEBUGFS_WRITE_FILE_OPS(inject_beacon_ie_restore, 512);
 789
 790static ssize_t
 791iwl_dbgfs_vif_twt_setup_write(struct iwl_mld *mld, char *buf, size_t count,
 792			      void *data)
 793{
 794	struct iwl_host_cmd hcmd = {
 795		.id = WIDE_ID(IWL_ALWAYS_LONG_GROUP, DEBUG_HOST_COMMAND),
 796	};
 797	struct ieee80211_vif *vif = data;
 798	struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
 799	struct iwl_dhc_cmd *cmd __free(kfree) = NULL;
 800	struct iwl_dhc_twt_operation *dhc_twt_cmd;
 801	u64 target_wake_time;
 802	u32 twt_operation, interval_exp, interval_mantissa, min_wake_duration;
 803	u8 trigger, flow_type, flow_id, protection, tenth_param;
 804	u8 twt_request = 1, broadcast = 0;
 805	int ret;
 806
 807	if (iwl_mld_dbgfs_fw_cmd_disabled(mld))
 808		return -EIO;
 809
 810	ret = sscanf(buf, "%u %llu %u %u %u %hhu %hhu %hhu %hhu %hhu",
 811		     &twt_operation, &target_wake_time, &interval_exp,
 812		     &interval_mantissa, &min_wake_duration, &trigger,
 813		     &flow_type, &flow_id, &protection, &tenth_param);
 814
 815	/* the new twt_request parameter is optional for station */
 816	if ((ret != 9 && ret != 10) ||
 817	    (ret == 10 && vif->type != NL80211_IFTYPE_STATION &&
 818	     tenth_param == 1))
 819		return -EINVAL;
 820
 821	/* The 10th parameter:
 822	 * In STA mode - the TWT type (broadcast or individual)
 823	 * In AP mode - the role (0 responder, 2 unsolicited)
 824	 */
 825	if (ret == 10) {
 826		if (vif->type == NL80211_IFTYPE_STATION)
 827			broadcast = tenth_param;
 828		else
 829			twt_request = tenth_param;
 830	}
 831
 832	cmd = kzalloc(sizeof(*cmd) + sizeof(*dhc_twt_cmd), GFP_KERNEL);
 833	if (!cmd)
 834		return -ENOMEM;
 835
 836	dhc_twt_cmd = (void *)cmd->data;
 837	dhc_twt_cmd->mac_id = cpu_to_le32(mld_vif->fw_id);
 838	dhc_twt_cmd->twt_operation = cpu_to_le32(twt_operation);
 839	dhc_twt_cmd->target_wake_time = cpu_to_le64(target_wake_time);
 840	dhc_twt_cmd->interval_exp = cpu_to_le32(interval_exp);
 841	dhc_twt_cmd->interval_mantissa = cpu_to_le32(interval_mantissa);
 842	dhc_twt_cmd->min_wake_duration = cpu_to_le32(min_wake_duration);
 843	dhc_twt_cmd->trigger = trigger;
 844	dhc_twt_cmd->flow_type = flow_type;
 845	dhc_twt_cmd->flow_id = flow_id;
 846	dhc_twt_cmd->protection = protection;
 847	dhc_twt_cmd->twt_request = twt_request;
 848	dhc_twt_cmd->negotiation_type = broadcast ? 3 : 0;
 849
 850	cmd->length = cpu_to_le32(sizeof(*dhc_twt_cmd) >> 2);
 851	cmd->index_and_mask =
 852		cpu_to_le32(DHC_TABLE_INTEGRATION | DHC_TARGET_UMAC |
 853			    DHC_INT_UMAC_TWT_OPERATION);
 854
 855	hcmd.len[0] = sizeof(*cmd) + sizeof(*dhc_twt_cmd);
 856	hcmd.data[0] = cmd;
 857
 858	ret = iwl_mld_send_cmd(mld, &hcmd);
 859
 860	return ret ?: count;
 861}
 862
 863VIF_DEBUGFS_WRITE_FILE_OPS(twt_setup, 256);
 864
 865static ssize_t
 866iwl_dbgfs_vif_twt_operation_write(struct iwl_mld *mld, char *buf, size_t count,
 867				  void *data)
 868{
 869	struct ieee80211_vif *vif = data;
 870	struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
 871	struct iwl_twt_operation_cmd twt_cmd = {};
 872	int link_id = vif->active_links ? __ffs(vif->active_links) : 0;
 873	struct iwl_mld_link *mld_link = iwl_mld_link_dereference_check(mld_vif,
 874								       link_id);
 875	int ret;
 876
 877	if (WARN_ON(!mld_link))
 878		return -ENODEV;
 879
 880	if (iwl_mld_dbgfs_fw_cmd_disabled(mld))
 881		return -EIO;
 882
 883	if (hweight16(vif->active_links) > 1)
 884		return -EOPNOTSUPP;
 885
 886	ret = sscanf(buf,
 887		     "%u %llu %u %u %u %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu",
 888		     &twt_cmd.twt_operation, &twt_cmd.target_wake_time,
 889		     &twt_cmd.interval_exponent, &twt_cmd.interval_mantissa,
 890		     &twt_cmd.minimum_wake_duration, &twt_cmd.trigger,
 891		     &twt_cmd.flow_type, &twt_cmd.flow_id,
 892		     &twt_cmd.twt_protection, &twt_cmd.ndp_paging_indicator,
 893		     &twt_cmd.responder_pm_mode, &twt_cmd.negotiation_type,
 894		     &twt_cmd.twt_request, &twt_cmd.implicit,
 895		     &twt_cmd.twt_group_assignment, &twt_cmd.twt_channel,
 896		     &twt_cmd.restricted_info_present, &twt_cmd.dl_bitmap_valid,
 897		     &twt_cmd.ul_bitmap_valid, &twt_cmd.dl_tid_bitmap,
 898		     &twt_cmd.ul_tid_bitmap);
 899
 900	if (ret != 21)
 901		return -EINVAL;
 902
 903	twt_cmd.link_id = cpu_to_le32(mld_link->fw_id);
 904
 905	ret = iwl_mld_send_cmd_pdu(mld,
 906				   WIDE_ID(MAC_CONF_GROUP, TWT_OPERATION_CMD),
 907				   &twt_cmd);
 908	return ret ?: count;
 909}
 910
 911VIF_DEBUGFS_WRITE_FILE_OPS(twt_operation, 256);
 912
 913static ssize_t iwl_dbgfs_vif_int_mlo_scan_write(struct iwl_mld *mld, char *buf,
 914						size_t count, void *data)
 915{
 916	struct ieee80211_vif *vif = data;
 917	u32 action;
 918	int ret;
 919
 920	if (!vif->cfg.assoc || !ieee80211_vif_is_mld(vif))
 921		return -EINVAL;
 922
 923	if (kstrtou32(buf, 0, &action))
 924		return -EINVAL;
 925
 926	if (action == 0) {
 927		ret = iwl_mld_scan_stop(mld, IWL_MLD_SCAN_INT_MLO, false);
 928	} else if (action == 1) {
 929		iwl_mld_int_mlo_scan(mld, vif);
 930		ret = 0;
 931	} else {
 932		ret = -EINVAL;
 933	}
 934
 935	return ret ?: count;
 936}
 937
 938VIF_DEBUGFS_WRITE_FILE_OPS(int_mlo_scan, 32);
 939
 940void iwl_mld_add_vif_debugfs(struct ieee80211_hw *hw,
 941			     struct ieee80211_vif *vif)
 942{
 943	struct dentry *mld_vif_dbgfs =
 944		debugfs_create_dir("iwlmld", vif->debugfs_dir);
 945	struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
 946	struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw);
 947	char target[3 * 3 + 11 + (NL80211_WIPHY_NAME_MAXLEN + 1) +
 948		    (7 + IFNAMSIZ + 1) + 6 + 1];
 949	char name[7 + IFNAMSIZ + 1];
 950
 951	/* Create symlink for convenience pointing to interface specific
 952	 * debugfs entries for the driver. For example, under
 953	 * /sys/kernel/debug/iwlwifi/0000\:02\:00.0/iwlmld/
 954	 * find
 955	 * netdev:wlan0 -> ../../../ieee80211/phy0/netdev:wlan0/iwlmld/
 956	 */
 957	snprintf(name, sizeof(name), "%pd", vif->debugfs_dir);
 958	snprintf(target, sizeof(target), "../../../%pd3/iwlmld",
 959		 vif->debugfs_dir);
 960	if (!mld_vif->dbgfs_slink)
 961		mld_vif->dbgfs_slink =
 962			debugfs_create_symlink(name, mld->debugfs_dir, target);
 963
 964	if (iwlmld_mod_params.power_scheme != IWL_POWER_SCHEME_CAM &&
 965	    vif->type == NL80211_IFTYPE_STATION) {
 966		VIF_DEBUGFS_ADD_FILE(pm_params, mld_vif_dbgfs, 0200);
 967		VIF_DEBUGFS_ADD_FILE(bf_params, mld_vif_dbgfs, 0200);
 968	}
 969
 970	if (vif->type == NL80211_IFTYPE_AP) {
 971		VIF_DEBUGFS_ADD_FILE(inject_beacon_ie, mld_vif_dbgfs, 0200);
 972		VIF_DEBUGFS_ADD_FILE(inject_beacon_ie_restore,
 973				     mld_vif_dbgfs, 0200);
 974	}
 975
 976	VIF_DEBUGFS_ADD_FILE(low_latency, mld_vif_dbgfs, 0600);
 977	VIF_DEBUGFS_ADD_FILE(twt_setup, mld_vif_dbgfs, 0200);
 978	VIF_DEBUGFS_ADD_FILE(twt_operation, mld_vif_dbgfs, 0200);
 979	VIF_DEBUGFS_ADD_FILE(int_mlo_scan, mld_vif_dbgfs, 0200);
 980}
 981#define LINK_DEBUGFS_WRITE_FILE_OPS(name, bufsz)			\
 982	WIPHY_DEBUGFS_WRITE_FILE_OPS(link_##name, bufsz, bss_conf)
 983
 984#define LINK_DEBUGFS_ADD_FILE_ALIAS(alias, name, parent, mode)		\
 985	debugfs_create_file(alias, mode, parent, link_conf,		\
 986			    &iwl_dbgfs_link_##name##_ops)
 987#define LINK_DEBUGFS_ADD_FILE(name, parent, mode)			\
 988	LINK_DEBUGFS_ADD_FILE_ALIAS(#name, name, parent, mode)
 989
 990void iwl_mld_add_link_debugfs(struct ieee80211_hw *hw,
 991			      struct ieee80211_vif *vif,
 992			      struct ieee80211_bss_conf *link_conf,
 993			      struct dentry *dir)
 994{
 995	struct dentry *mld_link_dir;
 996
 997	mld_link_dir = debugfs_lookup("iwlmld", dir);
 998
 999	/* For non-MLO vifs, the dir of deflink is the same as the vif's one.
1000	 * so if iwlmld dir already exists, this means that this is deflink.
1001	 * If not, this is a per-link dir of a MLO vif, add in it the iwlmld
1002	 * dir.
1003	 */
1004	if (!mld_link_dir) {
1005		mld_link_dir = debugfs_create_dir("iwlmld", dir);
1006	} else {
1007		/* Release the reference from debugfs_lookup */
1008		dput(mld_link_dir);
1009	}
1010}
1011
1012static ssize_t _iwl_dbgfs_fixed_rate_write(struct iwl_mld *mld, char *buf,
1013					   size_t count, void *data, bool v3)
1014{
1015	struct ieee80211_link_sta *link_sta = data;
1016	struct iwl_mld_link_sta *mld_link_sta;
1017	u32 rate;
1018	u32 partial = false;
1019	char pretty_rate[100];
1020	int ret;
1021	u8 fw_sta_id;
1022
1023	mld_link_sta = iwl_mld_link_sta_from_mac80211(link_sta);
1024	if (WARN_ON(!mld_link_sta))
1025		return -EINVAL;
1026
1027	fw_sta_id = mld_link_sta->fw_id;
1028
1029	if (sscanf(buf, "%i %i", &rate, &partial) == 0)
1030		return -EINVAL;
1031
1032	if (iwl_mld_dbgfs_fw_cmd_disabled(mld))
1033		return -EIO;
1034
1035	/* input is in FW format (v2 or v3) so convert to v3 */
1036	rate = iwl_v3_rate_from_v2_v3(cpu_to_le32(rate), v3);
1037	rate = le32_to_cpu(iwl_v3_rate_to_v2_v3(rate, mld->fw_rates_ver_3));
1038
1039	ret = iwl_mld_send_tlc_dhc(mld, fw_sta_id,
1040				   partial ? IWL_TLC_DEBUG_PARTIAL_FIXED_RATE :
1041					     IWL_TLC_DEBUG_FIXED_RATE,
1042				   rate);
1043
1044	rs_pretty_print_rate(pretty_rate, sizeof(pretty_rate), rate);
1045
1046	IWL_DEBUG_RATE(mld, "sta_id %d rate %s partial: %d, ret:%d\n",
1047		       fw_sta_id, pretty_rate, partial, ret);
1048
1049	return ret ? : count;
1050}
1051
1052static ssize_t iwl_dbgfs_fixed_rate_write(struct iwl_mld *mld, char *buf,
1053					  size_t count, void *data)
1054{
1055	return _iwl_dbgfs_fixed_rate_write(mld, buf, count, data, false);
1056}
1057
1058static ssize_t iwl_dbgfs_fixed_rate_v3_write(struct iwl_mld *mld, char *buf,
1059					     size_t count, void *data)
1060{
1061	return _iwl_dbgfs_fixed_rate_write(mld, buf, count, data, true);
1062}
1063
1064static ssize_t iwl_dbgfs_tlc_dhc_write(struct iwl_mld *mld, char *buf,
1065				       size_t count, void *data)
1066{
1067	struct ieee80211_link_sta *link_sta = data;
1068	struct iwl_mld_link_sta *mld_link_sta;
1069	u32 type, value;
1070	int ret;
1071	u8 fw_sta_id;
1072
1073	mld_link_sta = iwl_mld_link_sta_from_mac80211(link_sta);
1074	if (WARN_ON(!mld_link_sta))
1075		return -EINVAL;
1076
1077	fw_sta_id = mld_link_sta->fw_id;
1078
1079	if (sscanf(buf, "%i %i", &type, &value) != 2) {
1080		IWL_DEBUG_RATE(mld, "usage <type> <value>\n");
1081		return -EINVAL;
1082	}
1083
1084	if (iwl_mld_dbgfs_fw_cmd_disabled(mld))
1085		return -EIO;
1086
1087	ret = iwl_mld_send_tlc_dhc(mld, fw_sta_id, type, value);
1088
1089	return ret ? : count;
1090}
1091
1092#define LINK_STA_DEBUGFS_ADD_FILE_ALIAS(alias, name, parent, mode)	\
1093	debugfs_create_file(alias, mode, parent, link_sta,		\
1094			    &iwl_dbgfs_##name##_ops)
1095#define LINK_STA_DEBUGFS_ADD_FILE(name, parent, mode)			\
1096	LINK_STA_DEBUGFS_ADD_FILE_ALIAS(#name, name, parent, mode)
1097
1098#define LINK_STA_WIPHY_DEBUGFS_WRITE_OPS(name, bufsz)			\
1099	WIPHY_DEBUGFS_WRITE_FILE_OPS(name, bufsz, link_sta)
1100
1101LINK_STA_WIPHY_DEBUGFS_WRITE_OPS(tlc_dhc, 64);
1102LINK_STA_WIPHY_DEBUGFS_WRITE_OPS(fixed_rate, 64);
1103LINK_STA_WIPHY_DEBUGFS_WRITE_OPS(fixed_rate_v3, 64);
1104
1105void iwl_mld_add_link_sta_debugfs(struct ieee80211_hw *hw,
1106				  struct ieee80211_vif *vif,
1107				  struct ieee80211_link_sta *link_sta,
1108				  struct dentry *dir)
1109{
1110	LINK_STA_DEBUGFS_ADD_FILE(fixed_rate, dir, 0200);
1111	LINK_STA_DEBUGFS_ADD_FILE(fixed_rate_v3, dir, 0200);
1112	LINK_STA_DEBUGFS_ADD_FILE(tlc_dhc, dir, 0200);
1113}
~/ Read-only file preview.