xref: /linux/drivers/net/wireless/intel/iwlwifi/mld/debugfs.c (revision 54be64fdf3ba6dbad2f5c48e466e1db43ad74bca)
1d1e879ecSMiri Korenblit // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
2d1e879ecSMiri Korenblit /*
3d1e879ecSMiri Korenblit  * Copyright (C) 2024-2025 Intel Corporation
4d1e879ecSMiri Korenblit  */
5d1e879ecSMiri Korenblit 
6d1e879ecSMiri Korenblit #include "mld.h"
7d1e879ecSMiri Korenblit #include "debugfs.h"
8d1e879ecSMiri Korenblit #include "iwl-io.h"
9d1e879ecSMiri Korenblit #include "hcmd.h"
10d1e879ecSMiri Korenblit #include "iface.h"
11d1e879ecSMiri Korenblit #include "sta.h"
12d1e879ecSMiri Korenblit #include "tlc.h"
13d1e879ecSMiri Korenblit #include "power.h"
14d1e879ecSMiri Korenblit #include "notif.h"
15d1e879ecSMiri Korenblit #include "ap.h"
16d1e879ecSMiri Korenblit #include "iwl-utils.h"
17d1e879ecSMiri Korenblit #ifdef CONFIG_THERMAL
18d1e879ecSMiri Korenblit #include "thermal.h"
19d1e879ecSMiri Korenblit #endif
20d1e879ecSMiri Korenblit 
21d1e879ecSMiri Korenblit #include "fw/api/rs.h"
22d1e879ecSMiri Korenblit #include "fw/api/dhc.h"
23d1e879ecSMiri Korenblit #include "fw/api/rfi.h"
24d1e879ecSMiri Korenblit 
25d1e879ecSMiri Korenblit #define MLD_DEBUGFS_READ_FILE_OPS(name, bufsz)				\
26d1e879ecSMiri Korenblit 	_MLD_DEBUGFS_READ_FILE_OPS(name, bufsz, struct iwl_mld)
27d1e879ecSMiri Korenblit 
28d1e879ecSMiri Korenblit #define MLD_DEBUGFS_ADD_FILE_ALIAS(alias, name, parent, mode)		\
29d1e879ecSMiri Korenblit 	debugfs_create_file(alias, mode, parent, mld,			\
30d1e879ecSMiri Korenblit 			    &iwl_dbgfs_##name##_ops)
31d1e879ecSMiri Korenblit #define MLD_DEBUGFS_ADD_FILE(name, parent, mode)			\
32d1e879ecSMiri Korenblit 	MLD_DEBUGFS_ADD_FILE_ALIAS(#name, name, parent, mode)
33d1e879ecSMiri Korenblit 
34d1e879ecSMiri Korenblit static bool iwl_mld_dbgfs_fw_cmd_disabled(struct iwl_mld *mld)
35d1e879ecSMiri Korenblit {
36d1e879ecSMiri Korenblit #ifdef CONFIG_PM_SLEEP
37d1e879ecSMiri Korenblit 	return !mld->fw_status.running || mld->fw_status.in_d3;
38d1e879ecSMiri Korenblit #else
39d1e879ecSMiri Korenblit 	return !mld->fw_status.running;
40d1e879ecSMiri Korenblit #endif /* CONFIG_PM_SLEEP */
41d1e879ecSMiri Korenblit }
42d1e879ecSMiri Korenblit 
43d1e879ecSMiri Korenblit static ssize_t iwl_dbgfs_fw_dbg_clear_write(struct iwl_mld *mld,
44d1e879ecSMiri Korenblit 					    char *buf, size_t count)
45d1e879ecSMiri Korenblit {
46d1e879ecSMiri Korenblit 	/* If the firmware is not running, silently succeed since there is
47d1e879ecSMiri Korenblit 	 * no data to clear.
48d1e879ecSMiri Korenblit 	 */
49d1e879ecSMiri Korenblit 	if (iwl_mld_dbgfs_fw_cmd_disabled(mld))
50d1e879ecSMiri Korenblit 		return 0;
51d1e879ecSMiri Korenblit 
52d1e879ecSMiri Korenblit 	iwl_fw_dbg_clear_monitor_buf(&mld->fwrt);
53d1e879ecSMiri Korenblit 
54d1e879ecSMiri Korenblit 	return count;
55d1e879ecSMiri Korenblit }
56d1e879ecSMiri Korenblit 
57d1e879ecSMiri Korenblit static ssize_t iwl_dbgfs_fw_nmi_write(struct iwl_mld *mld, char *buf,
58d1e879ecSMiri Korenblit 				      size_t count)
59d1e879ecSMiri Korenblit {
60d1e879ecSMiri Korenblit 	if (iwl_mld_dbgfs_fw_cmd_disabled(mld))
61d1e879ecSMiri Korenblit 		return -EIO;
62d1e879ecSMiri Korenblit 
63d1e879ecSMiri Korenblit 	IWL_ERR(mld, "Triggering an NMI from debugfs\n");
64d1e879ecSMiri Korenblit 
65d1e879ecSMiri Korenblit 	if (count == 6 && !strcmp(buf, "nolog\n"))
66d1e879ecSMiri Korenblit 		mld->fw_status.do_not_dump_once = true;
67d1e879ecSMiri Korenblit 
68d1e879ecSMiri Korenblit 	iwl_force_nmi(mld->trans);
69d1e879ecSMiri Korenblit 
70d1e879ecSMiri Korenblit 	return count;
71d1e879ecSMiri Korenblit }
72d1e879ecSMiri Korenblit 
73d1e879ecSMiri Korenblit static ssize_t iwl_dbgfs_fw_restart_write(struct iwl_mld *mld, char *buf,
74d1e879ecSMiri Korenblit 					  size_t count)
75d1e879ecSMiri Korenblit {
76d1e879ecSMiri Korenblit 	int __maybe_unused ret;
77d1e879ecSMiri Korenblit 
78d1e879ecSMiri Korenblit 	if (!iwlwifi_mod_params.fw_restart)
79d1e879ecSMiri Korenblit 		return -EPERM;
80d1e879ecSMiri Korenblit 
81d1e879ecSMiri Korenblit 	if (iwl_mld_dbgfs_fw_cmd_disabled(mld))
82d1e879ecSMiri Korenblit 		return -EIO;
83d1e879ecSMiri Korenblit 
84d1e879ecSMiri Korenblit 	if (count == 6 && !strcmp(buf, "nolog\n")) {
85d1e879ecSMiri Korenblit 		mld->fw_status.do_not_dump_once = true;
86d1e879ecSMiri Korenblit 		set_bit(STATUS_SUPPRESS_CMD_ERROR_ONCE, &mld->trans->status);
87d1e879ecSMiri Korenblit 	}
88d1e879ecSMiri Korenblit 
89d1e879ecSMiri Korenblit 	/* take the return value to make compiler happy - it will
90d1e879ecSMiri Korenblit 	 * fail anyway
91d1e879ecSMiri Korenblit 	 */
92d1e879ecSMiri Korenblit 	ret = iwl_mld_send_cmd_empty(mld, WIDE_ID(LONG_GROUP, REPLY_ERROR));
93d1e879ecSMiri Korenblit 
94d1e879ecSMiri Korenblit 	return count;
95d1e879ecSMiri Korenblit }
96d1e879ecSMiri Korenblit 
97d1e879ecSMiri Korenblit static ssize_t iwl_dbgfs_send_echo_cmd_write(struct iwl_mld *mld, char *buf,
98d1e879ecSMiri Korenblit 					     size_t count)
99d1e879ecSMiri Korenblit {
100d1e879ecSMiri Korenblit 	if (iwl_mld_dbgfs_fw_cmd_disabled(mld))
101d1e879ecSMiri Korenblit 		return -EIO;
102d1e879ecSMiri Korenblit 
103d1e879ecSMiri Korenblit 	return iwl_mld_send_cmd_empty(mld, ECHO_CMD) ?: count;
104d1e879ecSMiri Korenblit }
105d1e879ecSMiri Korenblit 
106d1e879ecSMiri Korenblit struct iwl_mld_sniffer_apply {
107d1e879ecSMiri Korenblit 	struct iwl_mld *mld;
108d1e879ecSMiri Korenblit 	const u8 *bssid;
109d1e879ecSMiri Korenblit 	u16 aid;
110d1e879ecSMiri Korenblit };
111d1e879ecSMiri Korenblit 
112d1e879ecSMiri Korenblit static bool iwl_mld_sniffer_apply(struct iwl_notif_wait_data *notif_data,
113d1e879ecSMiri Korenblit 				  struct iwl_rx_packet *pkt, void *data)
114d1e879ecSMiri Korenblit {
115d1e879ecSMiri Korenblit 	struct iwl_mld_sniffer_apply *apply = data;
116d1e879ecSMiri Korenblit 
117d1e879ecSMiri Korenblit 	apply->mld->monitor.cur_aid = cpu_to_le16(apply->aid);
118d1e879ecSMiri Korenblit 	memcpy(apply->mld->monitor.cur_bssid, apply->bssid,
119d1e879ecSMiri Korenblit 	       sizeof(apply->mld->monitor.cur_bssid));
120d1e879ecSMiri Korenblit 
121d1e879ecSMiri Korenblit 	return true;
122d1e879ecSMiri Korenblit }
123d1e879ecSMiri Korenblit 
124d1e879ecSMiri Korenblit static ssize_t
125d1e879ecSMiri Korenblit iwl_dbgfs_he_sniffer_params_write(struct iwl_mld *mld, char *buf,
126d1e879ecSMiri Korenblit 				  size_t count)
127d1e879ecSMiri Korenblit {
128d1e879ecSMiri Korenblit 	struct iwl_notification_wait wait;
129d1e879ecSMiri Korenblit 	struct iwl_he_monitor_cmd he_mon_cmd = {};
130d1e879ecSMiri Korenblit 	struct iwl_mld_sniffer_apply apply = {
131d1e879ecSMiri Korenblit 		.mld = mld,
132d1e879ecSMiri Korenblit 	};
133d1e879ecSMiri Korenblit 	u16 wait_cmds[] = {
134d1e879ecSMiri Korenblit 		WIDE_ID(DATA_PATH_GROUP, HE_AIR_SNIFFER_CONFIG_CMD),
135d1e879ecSMiri Korenblit 	};
136d1e879ecSMiri Korenblit 	u32 aid;
137d1e879ecSMiri Korenblit 	int ret;
138d1e879ecSMiri Korenblit 
139d1e879ecSMiri Korenblit 	if (iwl_mld_dbgfs_fw_cmd_disabled(mld))
140d1e879ecSMiri Korenblit 		return -EIO;
141d1e879ecSMiri Korenblit 
142d1e879ecSMiri Korenblit 	if (!mld->monitor.on)
143d1e879ecSMiri Korenblit 		return -ENODEV;
144d1e879ecSMiri Korenblit 
145d1e879ecSMiri Korenblit 	ret = sscanf(buf, "%x %2hhx:%2hhx:%2hhx:%2hhx:%2hhx:%2hhx", &aid,
146d1e879ecSMiri Korenblit 		     &he_mon_cmd.bssid[0], &he_mon_cmd.bssid[1],
147d1e879ecSMiri Korenblit 		     &he_mon_cmd.bssid[2], &he_mon_cmd.bssid[3],
148d1e879ecSMiri Korenblit 		     &he_mon_cmd.bssid[4], &he_mon_cmd.bssid[5]);
149d1e879ecSMiri Korenblit 	if (ret != 7)
150d1e879ecSMiri Korenblit 		return -EINVAL;
151d1e879ecSMiri Korenblit 
152d1e879ecSMiri Korenblit 	he_mon_cmd.aid = cpu_to_le16(aid);
153d1e879ecSMiri Korenblit 
154d1e879ecSMiri Korenblit 	apply.aid = aid;
155d1e879ecSMiri Korenblit 	apply.bssid = (void *)he_mon_cmd.bssid;
156d1e879ecSMiri Korenblit 
157d1e879ecSMiri Korenblit 	/* Use the notification waiter to get our function triggered
158d1e879ecSMiri Korenblit 	 * in sequence with other RX. This ensures that frames we get
159d1e879ecSMiri Korenblit 	 * on the RX queue _before_ the new configuration is applied
160d1e879ecSMiri Korenblit 	 * still have mld->cur_aid pointing to the old AID, and that
161d1e879ecSMiri Korenblit 	 * frames on the RX queue _after_ the firmware processed the
162d1e879ecSMiri Korenblit 	 * new configuration (and sent the response, synchronously)
163d1e879ecSMiri Korenblit 	 * get mld->cur_aid correctly set to the new AID.
164d1e879ecSMiri Korenblit 	 */
165d1e879ecSMiri Korenblit 	iwl_init_notification_wait(&mld->notif_wait, &wait,
166d1e879ecSMiri Korenblit 				   wait_cmds, ARRAY_SIZE(wait_cmds),
167d1e879ecSMiri Korenblit 				   iwl_mld_sniffer_apply, &apply);
168d1e879ecSMiri Korenblit 
169d1e879ecSMiri Korenblit 	ret = iwl_mld_send_cmd_pdu(mld,
170d1e879ecSMiri Korenblit 				   WIDE_ID(DATA_PATH_GROUP,
171d1e879ecSMiri Korenblit 					   HE_AIR_SNIFFER_CONFIG_CMD),
172d1e879ecSMiri Korenblit 				   &he_mon_cmd);
173d1e879ecSMiri Korenblit 
174d1e879ecSMiri Korenblit 	/* no need to really wait, we already did anyway */
175d1e879ecSMiri Korenblit 	iwl_remove_notification(&mld->notif_wait, &wait);
176d1e879ecSMiri Korenblit 
177d1e879ecSMiri Korenblit 	return ret ?: count;
178d1e879ecSMiri Korenblit }
179d1e879ecSMiri Korenblit 
180d1e879ecSMiri Korenblit static ssize_t
181d1e879ecSMiri Korenblit iwl_dbgfs_he_sniffer_params_read(struct iwl_mld *mld, size_t count,
182d1e879ecSMiri Korenblit 				 char *buf)
183d1e879ecSMiri Korenblit {
184d1e879ecSMiri Korenblit 	return scnprintf(buf, count,
185d1e879ecSMiri Korenblit 			 "%d %02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx\n",
186d1e879ecSMiri Korenblit 			 le16_to_cpu(mld->monitor.cur_aid),
187d1e879ecSMiri Korenblit 			 mld->monitor.cur_bssid[0], mld->monitor.cur_bssid[1],
188d1e879ecSMiri Korenblit 			 mld->monitor.cur_bssid[2], mld->monitor.cur_bssid[3],
189d1e879ecSMiri Korenblit 			 mld->monitor.cur_bssid[4], mld->monitor.cur_bssid[5]);
190d1e879ecSMiri Korenblit }
191d1e879ecSMiri Korenblit 
192d1e879ecSMiri Korenblit /* The size computation is as follows:
193d1e879ecSMiri Korenblit  * each number needs at most 3 characters, number of rows is the size of
194d1e879ecSMiri Korenblit  * the table; So, need 5 chars for the "freq: " part and each tuple afterwards
195d1e879ecSMiri Korenblit  * needs 6 characters for numbers and 5 for the punctuation around. 32 bytes
196d1e879ecSMiri Korenblit  * for feature support message.
197d1e879ecSMiri Korenblit  */
198d1e879ecSMiri Korenblit #define IWL_RFI_DDR_BUF_SIZE (IWL_RFI_DDR_LUT_INSTALLED_SIZE *\
199d1e879ecSMiri Korenblit 				(5 + IWL_RFI_DDR_LUT_ENTRY_CHANNELS_NUM *\
200d1e879ecSMiri Korenblit 					(6 + 5)) + 32)
201d1e879ecSMiri Korenblit #define IWL_RFI_DLVR_BUF_SIZE (IWL_RFI_DLVR_LUT_INSTALLED_SIZE *\
202d1e879ecSMiri Korenblit 				(5 + IWL_RFI_DLVR_LUT_ENTRY_CHANNELS_NUM *\
203d1e879ecSMiri Korenblit 					(6 + 5)) + 32)
204d1e879ecSMiri Korenblit #define IWL_RFI_DESENSE_BUF_SIZE IWL_RFI_DDR_BUF_SIZE
205d1e879ecSMiri Korenblit 
206d1e879ecSMiri Korenblit /* Extra 32 for "DDR and DLVR table" message */
207d1e879ecSMiri Korenblit #define IWL_RFI_BUF_SIZE (IWL_RFI_DDR_BUF_SIZE + IWL_RFI_DLVR_BUF_SIZE +\
208d1e879ecSMiri Korenblit 				IWL_RFI_DESENSE_BUF_SIZE + 32)
209d1e879ecSMiri Korenblit 
210d1e879ecSMiri Korenblit WIPHY_DEBUGFS_WRITE_FILE_OPS_MLD(fw_nmi, 10);
211d1e879ecSMiri Korenblit WIPHY_DEBUGFS_WRITE_FILE_OPS_MLD(fw_restart, 10);
212d1e879ecSMiri Korenblit WIPHY_DEBUGFS_READ_WRITE_FILE_OPS_MLD(he_sniffer_params, 32);
213d1e879ecSMiri Korenblit WIPHY_DEBUGFS_WRITE_FILE_OPS_MLD(fw_dbg_clear, 10);
214d1e879ecSMiri Korenblit WIPHY_DEBUGFS_WRITE_FILE_OPS_MLD(send_echo_cmd, 8);
215d1e879ecSMiri Korenblit 
216d1e879ecSMiri Korenblit static ssize_t iwl_dbgfs_wifi_6e_enable_read(struct iwl_mld *mld,
217d1e879ecSMiri Korenblit 					     size_t count, u8 *buf)
218d1e879ecSMiri Korenblit {
219d1e879ecSMiri Korenblit 	int err;
220d1e879ecSMiri Korenblit 	u32 value;
221d1e879ecSMiri Korenblit 
222d1e879ecSMiri Korenblit 	err = iwl_bios_get_dsm(&mld->fwrt, DSM_FUNC_ENABLE_6E, &value);
223d1e879ecSMiri Korenblit 	if (err)
224d1e879ecSMiri Korenblit 		return err;
225d1e879ecSMiri Korenblit 
226d1e879ecSMiri Korenblit 	return scnprintf(buf, count, "0x%08x\n", value);
227d1e879ecSMiri Korenblit }
228d1e879ecSMiri Korenblit 
229d1e879ecSMiri Korenblit MLD_DEBUGFS_READ_FILE_OPS(wifi_6e_enable, 64);
230d1e879ecSMiri Korenblit 
231d1e879ecSMiri Korenblit static ssize_t iwl_dbgfs_inject_packet_write(struct iwl_mld *mld,
232d1e879ecSMiri Korenblit 					     char *buf, size_t count)
233d1e879ecSMiri Korenblit {
234d1e879ecSMiri Korenblit 	struct iwl_op_mode *opmode = container_of((void *)mld,
235d1e879ecSMiri Korenblit 						  struct iwl_op_mode,
236d1e879ecSMiri Korenblit 						  op_mode_specific);
237d1e879ecSMiri Korenblit 	struct iwl_rx_cmd_buffer rxb = {};
238d1e879ecSMiri Korenblit 	struct iwl_rx_packet *pkt;
239d1e879ecSMiri Korenblit 	int n_bytes = count / 2;
240d1e879ecSMiri Korenblit 	int ret = -EINVAL;
241d1e879ecSMiri Korenblit 
242d1e879ecSMiri Korenblit 	if (iwl_mld_dbgfs_fw_cmd_disabled(mld))
243d1e879ecSMiri Korenblit 		return -EIO;
244d1e879ecSMiri Korenblit 
245d1e879ecSMiri Korenblit 	rxb._page = alloc_pages(GFP_KERNEL, 0);
246d1e879ecSMiri Korenblit 	if (!rxb._page)
247d1e879ecSMiri Korenblit 		return -ENOMEM;
248d1e879ecSMiri Korenblit 	pkt = rxb_addr(&rxb);
249d1e879ecSMiri Korenblit 
250d1e879ecSMiri Korenblit 	ret = hex2bin(page_address(rxb._page), buf, n_bytes);
251d1e879ecSMiri Korenblit 	if (ret)
252d1e879ecSMiri Korenblit 		goto out;
253d1e879ecSMiri Korenblit 
254d1e879ecSMiri Korenblit 	/* avoid invalid memory access and malformed packet */
255d1e879ecSMiri Korenblit 	if (n_bytes < sizeof(*pkt) ||
256d1e879ecSMiri Korenblit 	    n_bytes != sizeof(*pkt) + iwl_rx_packet_payload_len(pkt))
257d1e879ecSMiri Korenblit 		goto out;
258d1e879ecSMiri Korenblit 
259d1e879ecSMiri Korenblit 	local_bh_disable();
260d1e879ecSMiri Korenblit 	iwl_mld_rx(opmode, NULL, &rxb);
261d1e879ecSMiri Korenblit 	local_bh_enable();
262d1e879ecSMiri Korenblit 	ret = 0;
263d1e879ecSMiri Korenblit 
264d1e879ecSMiri Korenblit out:
265d1e879ecSMiri Korenblit 	iwl_free_rxb(&rxb);
266d1e879ecSMiri Korenblit 
267d1e879ecSMiri Korenblit 	return ret ?: count;
268d1e879ecSMiri Korenblit }
269d1e879ecSMiri Korenblit 
270d1e879ecSMiri Korenblit WIPHY_DEBUGFS_WRITE_FILE_OPS_MLD(inject_packet, 512);
271d1e879ecSMiri Korenblit 
272d1e879ecSMiri Korenblit #ifdef CONFIG_THERMAL
273d1e879ecSMiri Korenblit 
274d1e879ecSMiri Korenblit static ssize_t iwl_dbgfs_stop_ctdp_write(struct iwl_mld *mld,
275d1e879ecSMiri Korenblit 					 char *buf, size_t count)
276d1e879ecSMiri Korenblit {
277d1e879ecSMiri Korenblit 	if (iwl_mld_dbgfs_fw_cmd_disabled(mld))
278d1e879ecSMiri Korenblit 		return -EIO;
279d1e879ecSMiri Korenblit 
280d1e879ecSMiri Korenblit 	return iwl_mld_config_ctdp(mld, mld->cooling_dev.cur_state,
281d1e879ecSMiri Korenblit 				   CTDP_CMD_OPERATION_STOP) ? : count;
282d1e879ecSMiri Korenblit }
283d1e879ecSMiri Korenblit 
284d1e879ecSMiri Korenblit WIPHY_DEBUGFS_WRITE_FILE_OPS_MLD(stop_ctdp, 8);
285d1e879ecSMiri Korenblit 
286d1e879ecSMiri Korenblit static ssize_t iwl_dbgfs_start_ctdp_write(struct iwl_mld *mld,
287d1e879ecSMiri Korenblit 					  char *buf, size_t count)
288d1e879ecSMiri Korenblit {
289d1e879ecSMiri Korenblit 	if (iwl_mld_dbgfs_fw_cmd_disabled(mld))
290d1e879ecSMiri Korenblit 		return -EIO;
291d1e879ecSMiri Korenblit 
292d1e879ecSMiri Korenblit 	return iwl_mld_config_ctdp(mld, mld->cooling_dev.cur_state,
293d1e879ecSMiri Korenblit 				   CTDP_CMD_OPERATION_START) ? : count;
294d1e879ecSMiri Korenblit }
295d1e879ecSMiri Korenblit 
296d1e879ecSMiri Korenblit WIPHY_DEBUGFS_WRITE_FILE_OPS_MLD(start_ctdp, 8);
297d1e879ecSMiri Korenblit 
298d1e879ecSMiri Korenblit #endif /* CONFIG_THERMAL */
299d1e879ecSMiri Korenblit 
300d1e879ecSMiri Korenblit void
301d1e879ecSMiri Korenblit iwl_mld_add_debugfs_files(struct iwl_mld *mld, struct dentry *debugfs_dir)
302d1e879ecSMiri Korenblit {
303d1e879ecSMiri Korenblit 	/* Add debugfs files here */
304d1e879ecSMiri Korenblit 
305d1e879ecSMiri Korenblit 	MLD_DEBUGFS_ADD_FILE(fw_nmi, debugfs_dir, 0200);
306d1e879ecSMiri Korenblit 	MLD_DEBUGFS_ADD_FILE(fw_restart, debugfs_dir, 0200);
307d1e879ecSMiri Korenblit 	MLD_DEBUGFS_ADD_FILE(wifi_6e_enable, debugfs_dir, 0400);
308d1e879ecSMiri Korenblit 	MLD_DEBUGFS_ADD_FILE(he_sniffer_params, debugfs_dir, 0600);
309d1e879ecSMiri Korenblit 	MLD_DEBUGFS_ADD_FILE(fw_dbg_clear, debugfs_dir, 0200);
310d1e879ecSMiri Korenblit 	MLD_DEBUGFS_ADD_FILE(send_echo_cmd, debugfs_dir, 0200);
311d1e879ecSMiri Korenblit #ifdef CONFIG_THERMAL
312d1e879ecSMiri Korenblit 	MLD_DEBUGFS_ADD_FILE(start_ctdp, debugfs_dir, 0200);
313d1e879ecSMiri Korenblit 	MLD_DEBUGFS_ADD_FILE(stop_ctdp, debugfs_dir, 0200);
314d1e879ecSMiri Korenblit #endif
315d1e879ecSMiri Korenblit 	MLD_DEBUGFS_ADD_FILE(inject_packet, debugfs_dir, 0200);
316d1e879ecSMiri Korenblit 
317d1e879ecSMiri Korenblit 	/* Create a symlink with mac80211. It will be removed when mac80211
318d1e879ecSMiri Korenblit 	 * exits (before the opmode exits which removes the target.)
319d1e879ecSMiri Korenblit 	 */
320d1e879ecSMiri Korenblit 	if (!IS_ERR(debugfs_dir)) {
321d1e879ecSMiri Korenblit 		char buf[100];
322d1e879ecSMiri Korenblit 
323d1e879ecSMiri Korenblit 		snprintf(buf, 100, "../../%pd2", debugfs_dir->d_parent);
324d1e879ecSMiri Korenblit 		debugfs_create_symlink("iwlwifi", mld->wiphy->debugfsdir,
325d1e879ecSMiri Korenblit 				       buf);
326d1e879ecSMiri Korenblit 	}
327d1e879ecSMiri Korenblit }
328d1e879ecSMiri Korenblit 
329d1e879ecSMiri Korenblit #define VIF_DEBUGFS_WRITE_FILE_OPS(name, bufsz)			\
330d1e879ecSMiri Korenblit 	WIPHY_DEBUGFS_WRITE_FILE_OPS(vif_##name, bufsz, vif)
331d1e879ecSMiri Korenblit 
332d1e879ecSMiri Korenblit #define VIF_DEBUGFS_READ_WRITE_FILE_OPS(name, bufsz)			    \
333d1e879ecSMiri Korenblit 	IEEE80211_WIPHY_DEBUGFS_READ_WRITE_FILE_OPS(vif_##name, bufsz, vif) \
334d1e879ecSMiri Korenblit 
335d1e879ecSMiri Korenblit #define VIF_DEBUGFS_ADD_FILE_ALIAS(alias, name, parent, mode)	\
336d1e879ecSMiri Korenblit 	debugfs_create_file(alias, mode, parent, vif,		\
337d1e879ecSMiri Korenblit 			    &iwl_dbgfs_vif_##name##_ops)
338d1e879ecSMiri Korenblit #define VIF_DEBUGFS_ADD_FILE(name, parent, mode)		\
339d1e879ecSMiri Korenblit 	VIF_DEBUGFS_ADD_FILE_ALIAS(#name, name, parent, mode)
340d1e879ecSMiri Korenblit 
341d1e879ecSMiri Korenblit static ssize_t iwl_dbgfs_vif_bf_params_write(struct iwl_mld *mld, char *buf,
342d1e879ecSMiri Korenblit 					     size_t count, void *data)
343d1e879ecSMiri Korenblit {
344d1e879ecSMiri Korenblit 	struct ieee80211_vif *vif = data;
345d1e879ecSMiri Korenblit 	struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
346d1e879ecSMiri Korenblit 	int link_id = vif->active_links ? __ffs(vif->active_links) : 0;
347d1e879ecSMiri Korenblit 	struct ieee80211_bss_conf *link_conf;
348d1e879ecSMiri Korenblit 	int val;
349d1e879ecSMiri Korenblit 
350d1e879ecSMiri Korenblit 	if (!strncmp("bf_enable_beacon_filter=", buf, 24)) {
351d1e879ecSMiri Korenblit 		if (sscanf(buf + 24, "%d", &val) != 1)
352d1e879ecSMiri Korenblit 			return -EINVAL;
353d1e879ecSMiri Korenblit 	} else {
354d1e879ecSMiri Korenblit 		return -EINVAL;
355d1e879ecSMiri Korenblit 	}
356d1e879ecSMiri Korenblit 
357d1e879ecSMiri Korenblit 	if (val != 0 && val != 1)
358d1e879ecSMiri Korenblit 		return -EINVAL;
359d1e879ecSMiri Korenblit 
360d1e879ecSMiri Korenblit 	link_conf = link_conf_dereference_protected(vif, link_id);
361d1e879ecSMiri Korenblit 	if (WARN_ON(!link_conf))
362d1e879ecSMiri Korenblit 		return -ENODEV;
363d1e879ecSMiri Korenblit 
364d1e879ecSMiri Korenblit 	if (iwl_mld_dbgfs_fw_cmd_disabled(mld))
365d1e879ecSMiri Korenblit 		return -EIO;
366d1e879ecSMiri Korenblit 
367d1e879ecSMiri Korenblit 	mld_vif->disable_bf = !val;
368d1e879ecSMiri Korenblit 
369d1e879ecSMiri Korenblit 	if (val)
370d1e879ecSMiri Korenblit 		return iwl_mld_enable_beacon_filter(mld, link_conf,
371d1e879ecSMiri Korenblit 						    false) ?: count;
372d1e879ecSMiri Korenblit 	else
373d1e879ecSMiri Korenblit 		return iwl_mld_disable_beacon_filter(mld, vif) ?: count;
374d1e879ecSMiri Korenblit }
375d1e879ecSMiri Korenblit 
376d1e879ecSMiri Korenblit static ssize_t iwl_dbgfs_vif_pm_params_write(struct iwl_mld *mld,
377d1e879ecSMiri Korenblit 					     char *buf,
378d1e879ecSMiri Korenblit 					     size_t count, void *data)
379d1e879ecSMiri Korenblit {
380d1e879ecSMiri Korenblit 	struct ieee80211_vif *vif = data;
381d1e879ecSMiri Korenblit 	struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
382d1e879ecSMiri Korenblit 	int val;
383d1e879ecSMiri Korenblit 
384d1e879ecSMiri Korenblit 	if (!strncmp("use_ps_poll=", buf, 12)) {
385d1e879ecSMiri Korenblit 		if (sscanf(buf + 12, "%d", &val) != 1)
386d1e879ecSMiri Korenblit 			return -EINVAL;
387d1e879ecSMiri Korenblit 	} else {
388d1e879ecSMiri Korenblit 		return -EINVAL;
389d1e879ecSMiri Korenblit 	}
390d1e879ecSMiri Korenblit 
391d1e879ecSMiri Korenblit 	if (iwl_mld_dbgfs_fw_cmd_disabled(mld))
392d1e879ecSMiri Korenblit 		return -EIO;
393d1e879ecSMiri Korenblit 
394d1e879ecSMiri Korenblit 	mld_vif->use_ps_poll = val;
395d1e879ecSMiri Korenblit 
396d1e879ecSMiri Korenblit 	return iwl_mld_update_mac_power(mld, vif, false) ?: count;
397d1e879ecSMiri Korenblit }
398d1e879ecSMiri Korenblit 
399d1e879ecSMiri Korenblit static ssize_t iwl_dbgfs_vif_low_latency_write(struct iwl_mld *mld,
400d1e879ecSMiri Korenblit 					       char *buf, size_t count,
401d1e879ecSMiri Korenblit 					       void *data)
402d1e879ecSMiri Korenblit {
403d1e879ecSMiri Korenblit 	struct ieee80211_vif *vif = data;
404d1e879ecSMiri Korenblit 	u8 value;
405d1e879ecSMiri Korenblit 	int ret;
406d1e879ecSMiri Korenblit 
407d1e879ecSMiri Korenblit 	ret = kstrtou8(buf, 0, &value);
408d1e879ecSMiri Korenblit 	if (ret)
409d1e879ecSMiri Korenblit 		return ret;
410d1e879ecSMiri Korenblit 
411d1e879ecSMiri Korenblit 	if (value > 1)
412d1e879ecSMiri Korenblit 		return -EINVAL;
413d1e879ecSMiri Korenblit 
414d1e879ecSMiri Korenblit 	iwl_mld_vif_update_low_latency(mld, vif, value, LOW_LATENCY_DEBUGFS);
415d1e879ecSMiri Korenblit 
416d1e879ecSMiri Korenblit 	return count;
417d1e879ecSMiri Korenblit }
418d1e879ecSMiri Korenblit 
419d1e879ecSMiri Korenblit static ssize_t iwl_dbgfs_vif_low_latency_read(struct ieee80211_vif *vif,
420d1e879ecSMiri Korenblit 					      size_t count, char *buf)
421d1e879ecSMiri Korenblit {
422d1e879ecSMiri Korenblit 	struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
423d1e879ecSMiri Korenblit 	char format[] = "traffic=%d\ndbgfs=%d\nvif_type=%d\nactual=%d\n";
424d1e879ecSMiri Korenblit 	u8 ll_causes;
425d1e879ecSMiri Korenblit 
426d1e879ecSMiri Korenblit 	if (WARN_ON(count < sizeof(format)))
427d1e879ecSMiri Korenblit 		return -EINVAL;
428d1e879ecSMiri Korenblit 
429d1e879ecSMiri Korenblit 	ll_causes = READ_ONCE(mld_vif->low_latency_causes);
430d1e879ecSMiri Korenblit 
431d1e879ecSMiri Korenblit 	/* all values in format are boolean so the size of format is enough
432d1e879ecSMiri Korenblit 	 * for holding the result string
433d1e879ecSMiri Korenblit 	 */
434d1e879ecSMiri Korenblit 	return scnprintf(buf, count, format,
435d1e879ecSMiri Korenblit 			 !!(ll_causes & LOW_LATENCY_TRAFFIC),
436d1e879ecSMiri Korenblit 			 !!(ll_causes & LOW_LATENCY_DEBUGFS),
437d1e879ecSMiri Korenblit 			 !!(ll_causes & LOW_LATENCY_VIF_TYPE),
438d1e879ecSMiri Korenblit 			 !!(ll_causes));
439d1e879ecSMiri Korenblit }
440d1e879ecSMiri Korenblit 
441d1e879ecSMiri Korenblit VIF_DEBUGFS_WRITE_FILE_OPS(pm_params, 32);
442d1e879ecSMiri Korenblit VIF_DEBUGFS_WRITE_FILE_OPS(bf_params, 32);
443d1e879ecSMiri Korenblit VIF_DEBUGFS_READ_WRITE_FILE_OPS(low_latency, 45);
444d1e879ecSMiri Korenblit 
445d1e879ecSMiri Korenblit static int
446d1e879ecSMiri Korenblit _iwl_dbgfs_inject_beacon_ie(struct iwl_mld *mld, struct ieee80211_vif *vif,
447d1e879ecSMiri Korenblit 			    char *bin, ssize_t len,
448d1e879ecSMiri Korenblit 			    bool restore)
449d1e879ecSMiri Korenblit {
450d1e879ecSMiri Korenblit 	struct iwl_mld_vif *mld_vif;
451d1e879ecSMiri Korenblit 	struct iwl_mld_link *mld_link;
452d1e879ecSMiri Korenblit 	struct iwl_mac_beacon_cmd beacon_cmd = {};
453d1e879ecSMiri Korenblit 	int n_bytes = len / 2;
454d1e879ecSMiri Korenblit 
455d1e879ecSMiri Korenblit 	/* Element len should be represented by u8 */
456d1e879ecSMiri Korenblit 	if (n_bytes >= U8_MAX)
457d1e879ecSMiri Korenblit 		return -EINVAL;
458d1e879ecSMiri Korenblit 
459d1e879ecSMiri Korenblit 	if (iwl_mld_dbgfs_fw_cmd_disabled(mld))
460d1e879ecSMiri Korenblit 		return -EIO;
461d1e879ecSMiri Korenblit 
462d1e879ecSMiri Korenblit 	if (!vif)
463d1e879ecSMiri Korenblit 		return -EINVAL;
464d1e879ecSMiri Korenblit 
465d1e879ecSMiri Korenblit 	mld_vif = iwl_mld_vif_from_mac80211(vif);
466d1e879ecSMiri Korenblit 	mld_vif->beacon_inject_active = true;
467d1e879ecSMiri Korenblit 	mld->hw->extra_beacon_tailroom = n_bytes;
468d1e879ecSMiri Korenblit 
469d1e879ecSMiri Korenblit 	for_each_mld_vif_valid_link(mld_vif, mld_link) {
470d1e879ecSMiri Korenblit 		u32 offset;
471d1e879ecSMiri Korenblit 		struct ieee80211_tx_info *info;
472d1e879ecSMiri Korenblit 		struct ieee80211_bss_conf *link_conf =
473d1e879ecSMiri Korenblit 			link_conf_dereference_protected(vif, link_id);
474d1e879ecSMiri Korenblit 		struct ieee80211_chanctx_conf *ctx =
475d1e879ecSMiri Korenblit 			wiphy_dereference(mld->wiphy, link_conf->chanctx_conf);
476d1e879ecSMiri Korenblit 		struct sk_buff *beacon =
477d1e879ecSMiri Korenblit 			ieee80211_beacon_get_template(mld->hw, vif,
478d1e879ecSMiri Korenblit 						      NULL, link_id);
479d1e879ecSMiri Korenblit 
480d1e879ecSMiri Korenblit 		if (!beacon)
481d1e879ecSMiri Korenblit 			return -EINVAL;
482d1e879ecSMiri Korenblit 
483d1e879ecSMiri Korenblit 		if (!restore && (WARN_ON(!n_bytes || !bin) ||
484d1e879ecSMiri Korenblit 				 hex2bin(skb_put_zero(beacon, n_bytes),
485d1e879ecSMiri Korenblit 					 bin, n_bytes))) {
486d1e879ecSMiri Korenblit 			dev_kfree_skb(beacon);
487d1e879ecSMiri Korenblit 			return -EINVAL;
488d1e879ecSMiri Korenblit 		}
489d1e879ecSMiri Korenblit 
490d1e879ecSMiri Korenblit 		info = IEEE80211_SKB_CB(beacon);
491d1e879ecSMiri Korenblit 
492d1e879ecSMiri Korenblit 		beacon_cmd.flags =
493d1e879ecSMiri Korenblit 			cpu_to_le16(iwl_mld_get_rate_flags(mld, info, vif,
494d1e879ecSMiri Korenblit 							   link_conf,
495d1e879ecSMiri Korenblit 							   ctx->def.chan->band));
496d1e879ecSMiri Korenblit 		beacon_cmd.byte_cnt = cpu_to_le16((u16)beacon->len);
497d1e879ecSMiri Korenblit 		beacon_cmd.link_id =
498d1e879ecSMiri Korenblit 			cpu_to_le32(mld_link->fw_id);
499d1e879ecSMiri Korenblit 
500d1e879ecSMiri Korenblit 		iwl_mld_set_tim_idx(mld, &beacon_cmd.tim_idx,
501d1e879ecSMiri Korenblit 				    beacon->data, beacon->len);
502d1e879ecSMiri Korenblit 
503d1e879ecSMiri Korenblit 		offset = iwl_find_ie_offset(beacon->data,
504d1e879ecSMiri Korenblit 					    WLAN_EID_S1G_TWT,
505d1e879ecSMiri Korenblit 					    beacon->len);
506d1e879ecSMiri Korenblit 
507d1e879ecSMiri Korenblit 		beacon_cmd.btwt_offset = cpu_to_le32(offset);
508d1e879ecSMiri Korenblit 
509d1e879ecSMiri Korenblit 		iwl_mld_send_beacon_template_cmd(mld, beacon, &beacon_cmd);
510d1e879ecSMiri Korenblit 		dev_kfree_skb(beacon);
511d1e879ecSMiri Korenblit 	}
512d1e879ecSMiri Korenblit 
513d1e879ecSMiri Korenblit 	if (restore)
514d1e879ecSMiri Korenblit 		mld_vif->beacon_inject_active = false;
515d1e879ecSMiri Korenblit 
516d1e879ecSMiri Korenblit 	return 0;
517d1e879ecSMiri Korenblit }
518d1e879ecSMiri Korenblit 
519d1e879ecSMiri Korenblit static ssize_t
520d1e879ecSMiri Korenblit iwl_dbgfs_vif_inject_beacon_ie_write(struct iwl_mld *mld,
521d1e879ecSMiri Korenblit 				     char *buf, size_t count,
522d1e879ecSMiri Korenblit 				     void *data)
523d1e879ecSMiri Korenblit {
524d1e879ecSMiri Korenblit 	struct ieee80211_vif *vif = data;
525d1e879ecSMiri Korenblit 	int ret = _iwl_dbgfs_inject_beacon_ie(mld, vif, buf,
526d1e879ecSMiri Korenblit 					      count, false);
527d1e879ecSMiri Korenblit 
528d1e879ecSMiri Korenblit 	mld->hw->extra_beacon_tailroom = 0;
529d1e879ecSMiri Korenblit 	return ret ?: count;
530d1e879ecSMiri Korenblit }
531d1e879ecSMiri Korenblit 
532d1e879ecSMiri Korenblit VIF_DEBUGFS_WRITE_FILE_OPS(inject_beacon_ie, 512);
533d1e879ecSMiri Korenblit 
534d1e879ecSMiri Korenblit static ssize_t
535d1e879ecSMiri Korenblit iwl_dbgfs_vif_inject_beacon_ie_restore_write(struct iwl_mld *mld,
536d1e879ecSMiri Korenblit 					     char *buf,
537d1e879ecSMiri Korenblit 					     size_t count,
538d1e879ecSMiri Korenblit 					     void *data)
539d1e879ecSMiri Korenblit {
540d1e879ecSMiri Korenblit 	struct ieee80211_vif *vif = data;
541d1e879ecSMiri Korenblit 	int ret = _iwl_dbgfs_inject_beacon_ie(mld, vif, NULL,
542d1e879ecSMiri Korenblit 					      0, true);
543d1e879ecSMiri Korenblit 
544d1e879ecSMiri Korenblit 	mld->hw->extra_beacon_tailroom = 0;
545d1e879ecSMiri Korenblit 	return ret ?: count;
546d1e879ecSMiri Korenblit }
547d1e879ecSMiri Korenblit 
548d1e879ecSMiri Korenblit VIF_DEBUGFS_WRITE_FILE_OPS(inject_beacon_ie_restore, 512);
549d1e879ecSMiri Korenblit 
550d1e879ecSMiri Korenblit static ssize_t
551d1e879ecSMiri Korenblit iwl_dbgfs_vif_twt_setup_write(struct iwl_mld *mld, char *buf, size_t count,
552d1e879ecSMiri Korenblit 			      void *data)
553d1e879ecSMiri Korenblit {
554d1e879ecSMiri Korenblit 	struct iwl_host_cmd hcmd = {
555d1e879ecSMiri Korenblit 		.id = WIDE_ID(IWL_ALWAYS_LONG_GROUP, DEBUG_HOST_COMMAND),
556d1e879ecSMiri Korenblit 	};
557d1e879ecSMiri Korenblit 	struct ieee80211_vif *vif = data;
558d1e879ecSMiri Korenblit 	struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
559*54be64fdSDan Carpenter 	struct iwl_dhc_cmd *cmd __free(kfree) = NULL;
560d1e879ecSMiri Korenblit 	struct iwl_dhc_twt_operation *dhc_twt_cmd;
561d1e879ecSMiri Korenblit 	u64 target_wake_time;
562d1e879ecSMiri Korenblit 	u32 twt_operation, interval_exp, interval_mantissa, min_wake_duration;
563d1e879ecSMiri Korenblit 	u8 trigger, flow_type, flow_id, protection, tenth_param;
564d1e879ecSMiri Korenblit 	u8 twt_request = 1, broadcast = 0;
565d1e879ecSMiri Korenblit 	int ret;
566d1e879ecSMiri Korenblit 
567d1e879ecSMiri Korenblit 	if (iwl_mld_dbgfs_fw_cmd_disabled(mld))
568d1e879ecSMiri Korenblit 		return -EIO;
569d1e879ecSMiri Korenblit 
570d1e879ecSMiri Korenblit 	ret = sscanf(buf, "%u %llu %u %u %u %hhu %hhu %hhu %hhu %hhu",
571d1e879ecSMiri Korenblit 		     &twt_operation, &target_wake_time, &interval_exp,
572d1e879ecSMiri Korenblit 		     &interval_mantissa, &min_wake_duration, &trigger,
573d1e879ecSMiri Korenblit 		     &flow_type, &flow_id, &protection, &tenth_param);
574d1e879ecSMiri Korenblit 
575d1e879ecSMiri Korenblit 	/* the new twt_request parameter is optional for station */
576d1e879ecSMiri Korenblit 	if ((ret != 9 && ret != 10) ||
577d1e879ecSMiri Korenblit 	    (ret == 10 && vif->type != NL80211_IFTYPE_STATION &&
578d1e879ecSMiri Korenblit 	     tenth_param == 1))
579d1e879ecSMiri Korenblit 		return -EINVAL;
580d1e879ecSMiri Korenblit 
581d1e879ecSMiri Korenblit 	/* The 10th parameter:
582d1e879ecSMiri Korenblit 	 * In STA mode - the TWT type (broadcast or individual)
583d1e879ecSMiri Korenblit 	 * In AP mode - the role (0 responder, 2 unsolicited)
584d1e879ecSMiri Korenblit 	 */
585d1e879ecSMiri Korenblit 	if (ret == 10) {
586d1e879ecSMiri Korenblit 		if (vif->type == NL80211_IFTYPE_STATION)
587d1e879ecSMiri Korenblit 			broadcast = tenth_param;
588d1e879ecSMiri Korenblit 		else
589d1e879ecSMiri Korenblit 			twt_request = tenth_param;
590d1e879ecSMiri Korenblit 	}
591d1e879ecSMiri Korenblit 
592d1e879ecSMiri Korenblit 	cmd = kzalloc(sizeof(*cmd) + sizeof(*dhc_twt_cmd), GFP_KERNEL);
593d1e879ecSMiri Korenblit 	if (!cmd)
594d1e879ecSMiri Korenblit 		return -ENOMEM;
595d1e879ecSMiri Korenblit 
596d1e879ecSMiri Korenblit 	dhc_twt_cmd = (void *)cmd->data;
597d1e879ecSMiri Korenblit 	dhc_twt_cmd->mac_id = cpu_to_le32(mld_vif->fw_id);
598d1e879ecSMiri Korenblit 	dhc_twt_cmd->twt_operation = cpu_to_le32(twt_operation);
599d1e879ecSMiri Korenblit 	dhc_twt_cmd->target_wake_time = cpu_to_le64(target_wake_time);
600d1e879ecSMiri Korenblit 	dhc_twt_cmd->interval_exp = cpu_to_le32(interval_exp);
601d1e879ecSMiri Korenblit 	dhc_twt_cmd->interval_mantissa = cpu_to_le32(interval_mantissa);
602d1e879ecSMiri Korenblit 	dhc_twt_cmd->min_wake_duration = cpu_to_le32(min_wake_duration);
603d1e879ecSMiri Korenblit 	dhc_twt_cmd->trigger = trigger;
604d1e879ecSMiri Korenblit 	dhc_twt_cmd->flow_type = flow_type;
605d1e879ecSMiri Korenblit 	dhc_twt_cmd->flow_id = flow_id;
606d1e879ecSMiri Korenblit 	dhc_twt_cmd->protection = protection;
607d1e879ecSMiri Korenblit 	dhc_twt_cmd->twt_request = twt_request;
608d1e879ecSMiri Korenblit 	dhc_twt_cmd->negotiation_type = broadcast ? 3 : 0;
609d1e879ecSMiri Korenblit 
610d1e879ecSMiri Korenblit 	cmd->length = cpu_to_le32(sizeof(*dhc_twt_cmd) >> 2);
611d1e879ecSMiri Korenblit 	cmd->index_and_mask =
612d1e879ecSMiri Korenblit 		cpu_to_le32(DHC_TABLE_INTEGRATION | DHC_TARGET_UMAC |
613d1e879ecSMiri Korenblit 			    DHC_INT_UMAC_TWT_OPERATION);
614d1e879ecSMiri Korenblit 
615d1e879ecSMiri Korenblit 	hcmd.len[0] = sizeof(*cmd) + sizeof(*dhc_twt_cmd);
616d1e879ecSMiri Korenblit 	hcmd.data[0] = cmd;
617d1e879ecSMiri Korenblit 
618d1e879ecSMiri Korenblit 	ret = iwl_mld_send_cmd(mld, &hcmd);
619d1e879ecSMiri Korenblit 
620d1e879ecSMiri Korenblit 	return ret ?: count;
621d1e879ecSMiri Korenblit }
622d1e879ecSMiri Korenblit 
623d1e879ecSMiri Korenblit VIF_DEBUGFS_WRITE_FILE_OPS(twt_setup, 256);
624d1e879ecSMiri Korenblit 
625d1e879ecSMiri Korenblit static ssize_t
626d1e879ecSMiri Korenblit iwl_dbgfs_vif_twt_operation_write(struct iwl_mld *mld, char *buf, size_t count,
627d1e879ecSMiri Korenblit 				  void *data)
628d1e879ecSMiri Korenblit {
629d1e879ecSMiri Korenblit 	struct ieee80211_vif *vif = data;
630d1e879ecSMiri Korenblit 	struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
631d1e879ecSMiri Korenblit 	struct iwl_twt_operation_cmd twt_cmd = {};
632d1e879ecSMiri Korenblit 	int link_id = vif->active_links ? __ffs(vif->active_links) : 0;
633d1e879ecSMiri Korenblit 	struct iwl_mld_link *mld_link = iwl_mld_link_dereference_check(mld_vif,
634d1e879ecSMiri Korenblit 								       link_id);
635d1e879ecSMiri Korenblit 	int ret;
636d1e879ecSMiri Korenblit 
637d1e879ecSMiri Korenblit 	if (WARN_ON(!mld_link))
638d1e879ecSMiri Korenblit 		return -ENODEV;
639d1e879ecSMiri Korenblit 
640d1e879ecSMiri Korenblit 	if (iwl_mld_dbgfs_fw_cmd_disabled(mld))
641d1e879ecSMiri Korenblit 		return -EIO;
642d1e879ecSMiri Korenblit 
643d1e879ecSMiri Korenblit 	if (hweight16(vif->active_links) > 1)
644d1e879ecSMiri Korenblit 		return -EOPNOTSUPP;
645d1e879ecSMiri Korenblit 
646d1e879ecSMiri Korenblit 	ret = sscanf(buf,
647d1e879ecSMiri Korenblit 		     "%u %llu %u %u %u %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu %hhu",
648d1e879ecSMiri Korenblit 		     &twt_cmd.twt_operation, &twt_cmd.target_wake_time,
649d1e879ecSMiri Korenblit 		     &twt_cmd.interval_exponent, &twt_cmd.interval_mantissa,
650d1e879ecSMiri Korenblit 		     &twt_cmd.minimum_wake_duration, &twt_cmd.trigger,
651d1e879ecSMiri Korenblit 		     &twt_cmd.flow_type, &twt_cmd.flow_id,
652d1e879ecSMiri Korenblit 		     &twt_cmd.twt_protection, &twt_cmd.ndp_paging_indicator,
653d1e879ecSMiri Korenblit 		     &twt_cmd.responder_pm_mode, &twt_cmd.negotiation_type,
654d1e879ecSMiri Korenblit 		     &twt_cmd.twt_request, &twt_cmd.implicit,
655d1e879ecSMiri Korenblit 		     &twt_cmd.twt_group_assignment, &twt_cmd.twt_channel,
656d1e879ecSMiri Korenblit 		     &twt_cmd.restricted_info_present, &twt_cmd.dl_bitmap_valid,
657d1e879ecSMiri Korenblit 		     &twt_cmd.ul_bitmap_valid, &twt_cmd.dl_tid_bitmap,
658d1e879ecSMiri Korenblit 		     &twt_cmd.ul_tid_bitmap);
659d1e879ecSMiri Korenblit 
660d1e879ecSMiri Korenblit 	if (ret != 21)
661d1e879ecSMiri Korenblit 		return -EINVAL;
662d1e879ecSMiri Korenblit 
663d1e879ecSMiri Korenblit 	twt_cmd.link_id = cpu_to_le32(mld_link->fw_id);
664d1e879ecSMiri Korenblit 
665d1e879ecSMiri Korenblit 	ret = iwl_mld_send_cmd_pdu(mld,
666d1e879ecSMiri Korenblit 				   WIDE_ID(MAC_CONF_GROUP, TWT_OPERATION_CMD),
667d1e879ecSMiri Korenblit 				   &twt_cmd);
668d1e879ecSMiri Korenblit 	return ret ?: count;
669d1e879ecSMiri Korenblit }
670d1e879ecSMiri Korenblit 
671d1e879ecSMiri Korenblit VIF_DEBUGFS_WRITE_FILE_OPS(twt_operation, 256);
672d1e879ecSMiri Korenblit 
673d1e879ecSMiri Korenblit void iwl_mld_add_vif_debugfs(struct ieee80211_hw *hw,
674d1e879ecSMiri Korenblit 			     struct ieee80211_vif *vif)
675d1e879ecSMiri Korenblit {
676d1e879ecSMiri Korenblit 	struct dentry *mld_vif_dbgfs =
677d1e879ecSMiri Korenblit 		debugfs_create_dir("iwlmld", vif->debugfs_dir);
678d1e879ecSMiri Korenblit 	struct iwl_mld_vif *mld_vif = iwl_mld_vif_from_mac80211(vif);
679d1e879ecSMiri Korenblit 	struct iwl_mld *mld = IWL_MAC80211_GET_MLD(hw);
680d1e879ecSMiri Korenblit 	char target[3 * 3 + 11 + (NL80211_WIPHY_NAME_MAXLEN + 1) +
681d1e879ecSMiri Korenblit 		    (7 + IFNAMSIZ + 1) + 6 + 1];
682d1e879ecSMiri Korenblit 	char name[7 + IFNAMSIZ + 1];
683d1e879ecSMiri Korenblit 
684d1e879ecSMiri Korenblit 	/* Create symlink for convenience pointing to interface specific
685d1e879ecSMiri Korenblit 	 * debugfs entries for the driver. For example, under
686d1e879ecSMiri Korenblit 	 * /sys/kernel/debug/iwlwifi/0000\:02\:00.0/iwlmld/
687d1e879ecSMiri Korenblit 	 * find
688d1e879ecSMiri Korenblit 	 * netdev:wlan0 -> ../../../ieee80211/phy0/netdev:wlan0/iwlmld/
689d1e879ecSMiri Korenblit 	 */
690d1e879ecSMiri Korenblit 	snprintf(name, sizeof(name), "%pd", vif->debugfs_dir);
691d1e879ecSMiri Korenblit 	snprintf(target, sizeof(target), "../../../%pd3/iwlmld",
692d1e879ecSMiri Korenblit 		 vif->debugfs_dir);
693d1e879ecSMiri Korenblit 	mld_vif->dbgfs_slink =
694d1e879ecSMiri Korenblit 		debugfs_create_symlink(name, mld->debugfs_dir, target);
695d1e879ecSMiri Korenblit 
696d1e879ecSMiri Korenblit 	if (iwlmld_mod_params.power_scheme != IWL_POWER_SCHEME_CAM &&
697d1e879ecSMiri Korenblit 	    vif->type == NL80211_IFTYPE_STATION) {
698d1e879ecSMiri Korenblit 		VIF_DEBUGFS_ADD_FILE(pm_params, mld_vif_dbgfs, 0200);
699d1e879ecSMiri Korenblit 		VIF_DEBUGFS_ADD_FILE(bf_params, mld_vif_dbgfs, 0200);
700d1e879ecSMiri Korenblit 	}
701d1e879ecSMiri Korenblit 
702d1e879ecSMiri Korenblit 	if (vif->type == NL80211_IFTYPE_AP) {
703d1e879ecSMiri Korenblit 		VIF_DEBUGFS_ADD_FILE(inject_beacon_ie, mld_vif_dbgfs, 0200);
704d1e879ecSMiri Korenblit 		VIF_DEBUGFS_ADD_FILE(inject_beacon_ie_restore,
705d1e879ecSMiri Korenblit 				     mld_vif_dbgfs, 0200);
706d1e879ecSMiri Korenblit 	}
707d1e879ecSMiri Korenblit 
708d1e879ecSMiri Korenblit 	VIF_DEBUGFS_ADD_FILE(low_latency, mld_vif_dbgfs, 0600);
709d1e879ecSMiri Korenblit 	VIF_DEBUGFS_ADD_FILE(twt_setup, mld_vif_dbgfs, 0200);
710d1e879ecSMiri Korenblit 	VIF_DEBUGFS_ADD_FILE(twt_operation, mld_vif_dbgfs, 0200);
711d1e879ecSMiri Korenblit }
712d1e879ecSMiri Korenblit 
713d1e879ecSMiri Korenblit #define LINK_DEBUGFS_WRITE_FILE_OPS(name, bufsz)			\
714d1e879ecSMiri Korenblit 	WIPHY_DEBUGFS_WRITE_FILE_OPS(link_##name, bufsz, bss_conf)
715d1e879ecSMiri Korenblit 
716d1e879ecSMiri Korenblit #define LINK_DEBUGFS_ADD_FILE_ALIAS(alias, name, parent, mode)		\
717d1e879ecSMiri Korenblit 	debugfs_create_file(alias, mode, parent, link_conf,		\
718d1e879ecSMiri Korenblit 			    &iwl_dbgfs_link_##name##_ops)
719d1e879ecSMiri Korenblit #define LINK_DEBUGFS_ADD_FILE(name, parent, mode)			\
720d1e879ecSMiri Korenblit 	LINK_DEBUGFS_ADD_FILE_ALIAS(#name, name, parent, mode)
721d1e879ecSMiri Korenblit 
722d1e879ecSMiri Korenblit void iwl_mld_add_link_debugfs(struct ieee80211_hw *hw,
723d1e879ecSMiri Korenblit 			      struct ieee80211_vif *vif,
724d1e879ecSMiri Korenblit 			      struct ieee80211_bss_conf *link_conf,
725d1e879ecSMiri Korenblit 			      struct dentry *dir)
726d1e879ecSMiri Korenblit {
727d1e879ecSMiri Korenblit 	struct dentry *mld_link_dir;
728d1e879ecSMiri Korenblit 
729d1e879ecSMiri Korenblit 	mld_link_dir = debugfs_lookup("iwlmld", dir);
730d1e879ecSMiri Korenblit 
731d1e879ecSMiri Korenblit 	/* For non-MLO vifs, the dir of deflink is the same as the vif's one.
732d1e879ecSMiri Korenblit 	 * so if iwlmld dir already exists, this means that this is deflink.
733d1e879ecSMiri Korenblit 	 * If not, this is a per-link dir of a MLO vif, add in it the iwlmld
734d1e879ecSMiri Korenblit 	 * dir.
735d1e879ecSMiri Korenblit 	 */
736d1e879ecSMiri Korenblit 	if (!mld_link_dir)
737d1e879ecSMiri Korenblit 		mld_link_dir = debugfs_create_dir("iwlmld", dir);
738d1e879ecSMiri Korenblit }
739d1e879ecSMiri Korenblit 
740d1e879ecSMiri Korenblit static ssize_t iwl_dbgfs_fixed_rate_write(struct iwl_mld *mld, char *buf,
741d1e879ecSMiri Korenblit 					  size_t count, void *data)
742d1e879ecSMiri Korenblit {
743d1e879ecSMiri Korenblit 	struct ieee80211_link_sta *link_sta = data;
744d1e879ecSMiri Korenblit 	struct iwl_mld_link_sta *mld_link_sta;
745d1e879ecSMiri Korenblit 	u32 rate;
746d1e879ecSMiri Korenblit 	u32 partial = false;
747d1e879ecSMiri Korenblit 	char pretty_rate[100];
748d1e879ecSMiri Korenblit 	int ret;
749d1e879ecSMiri Korenblit 	u8 fw_sta_id;
750d1e879ecSMiri Korenblit 
751d1e879ecSMiri Korenblit 	mld_link_sta = iwl_mld_link_sta_from_mac80211(link_sta);
752d1e879ecSMiri Korenblit 	if (WARN_ON(!mld_link_sta))
753d1e879ecSMiri Korenblit 		return -EINVAL;
754d1e879ecSMiri Korenblit 
755d1e879ecSMiri Korenblit 	fw_sta_id = mld_link_sta->fw_id;
756d1e879ecSMiri Korenblit 
757d1e879ecSMiri Korenblit 	if (sscanf(buf, "%i %i", &rate, &partial) == 0)
758d1e879ecSMiri Korenblit 		return -EINVAL;
759d1e879ecSMiri Korenblit 
760d1e879ecSMiri Korenblit 	if (iwl_mld_dbgfs_fw_cmd_disabled(mld))
761d1e879ecSMiri Korenblit 		return -EIO;
762d1e879ecSMiri Korenblit 
763d1e879ecSMiri Korenblit 	ret = iwl_mld_send_tlc_dhc(mld, fw_sta_id,
764d1e879ecSMiri Korenblit 				   partial ? IWL_TLC_DEBUG_PARTIAL_FIXED_RATE :
765d1e879ecSMiri Korenblit 					     IWL_TLC_DEBUG_FIXED_RATE,
766d1e879ecSMiri Korenblit 				   rate);
767d1e879ecSMiri Korenblit 
768d1e879ecSMiri Korenblit 	rs_pretty_print_rate(pretty_rate, sizeof(pretty_rate), rate);
769d1e879ecSMiri Korenblit 
770d1e879ecSMiri Korenblit 	IWL_DEBUG_RATE(mld, "sta_id %d rate %s partial: %d, ret:%d\n",
771d1e879ecSMiri Korenblit 		       fw_sta_id, pretty_rate, partial, ret);
772d1e879ecSMiri Korenblit 
773d1e879ecSMiri Korenblit 	return ret ? : count;
774d1e879ecSMiri Korenblit }
775d1e879ecSMiri Korenblit 
776d1e879ecSMiri Korenblit static ssize_t iwl_dbgfs_tlc_dhc_write(struct iwl_mld *mld, char *buf,
777d1e879ecSMiri Korenblit 				       size_t count, void *data)
778d1e879ecSMiri Korenblit {
779d1e879ecSMiri Korenblit 	struct ieee80211_link_sta *link_sta = data;
780d1e879ecSMiri Korenblit 	struct iwl_mld_link_sta *mld_link_sta;
781d1e879ecSMiri Korenblit 	u32 type, value;
782d1e879ecSMiri Korenblit 	int ret;
783d1e879ecSMiri Korenblit 	u8 fw_sta_id;
784d1e879ecSMiri Korenblit 
785d1e879ecSMiri Korenblit 	mld_link_sta = iwl_mld_link_sta_from_mac80211(link_sta);
786d1e879ecSMiri Korenblit 	if (WARN_ON(!mld_link_sta))
787d1e879ecSMiri Korenblit 		return -EINVAL;
788d1e879ecSMiri Korenblit 
789d1e879ecSMiri Korenblit 	fw_sta_id = mld_link_sta->fw_id;
790d1e879ecSMiri Korenblit 
791d1e879ecSMiri Korenblit 	if (sscanf(buf, "%i %i", &type, &value) != 2) {
792d1e879ecSMiri Korenblit 		IWL_DEBUG_RATE(mld, "usage <type> <value>\n");
793d1e879ecSMiri Korenblit 		return -EINVAL;
794d1e879ecSMiri Korenblit 	}
795d1e879ecSMiri Korenblit 
796d1e879ecSMiri Korenblit 	if (iwl_mld_dbgfs_fw_cmd_disabled(mld))
797d1e879ecSMiri Korenblit 		return -EIO;
798d1e879ecSMiri Korenblit 
799d1e879ecSMiri Korenblit 	ret = iwl_mld_send_tlc_dhc(mld, fw_sta_id, type, value);
800d1e879ecSMiri Korenblit 
801d1e879ecSMiri Korenblit 	return ret ? : count;
802d1e879ecSMiri Korenblit }
803d1e879ecSMiri Korenblit 
804d1e879ecSMiri Korenblit #define LINK_STA_DEBUGFS_ADD_FILE_ALIAS(alias, name, parent, mode)	\
805d1e879ecSMiri Korenblit 	debugfs_create_file(alias, mode, parent, link_sta,		\
806d1e879ecSMiri Korenblit 			    &iwl_dbgfs_##name##_ops)
807d1e879ecSMiri Korenblit #define LINK_STA_DEBUGFS_ADD_FILE(name, parent, mode)			\
808d1e879ecSMiri Korenblit 	LINK_STA_DEBUGFS_ADD_FILE_ALIAS(#name, name, parent, mode)
809d1e879ecSMiri Korenblit 
810d1e879ecSMiri Korenblit #define LINK_STA_WIPHY_DEBUGFS_WRITE_OPS(name, bufsz)			\
811d1e879ecSMiri Korenblit 	WIPHY_DEBUGFS_WRITE_FILE_OPS(name, bufsz, link_sta)
812d1e879ecSMiri Korenblit 
813d1e879ecSMiri Korenblit LINK_STA_WIPHY_DEBUGFS_WRITE_OPS(tlc_dhc, 64);
814d1e879ecSMiri Korenblit LINK_STA_WIPHY_DEBUGFS_WRITE_OPS(fixed_rate, 64);
815d1e879ecSMiri Korenblit 
816d1e879ecSMiri Korenblit void iwl_mld_add_link_sta_debugfs(struct ieee80211_hw *hw,
817d1e879ecSMiri Korenblit 				  struct ieee80211_vif *vif,
818d1e879ecSMiri Korenblit 				  struct ieee80211_link_sta *link_sta,
819d1e879ecSMiri Korenblit 				  struct dentry *dir)
820d1e879ecSMiri Korenblit {
821d1e879ecSMiri Korenblit 	LINK_STA_DEBUGFS_ADD_FILE(fixed_rate, dir, 0200);
822d1e879ecSMiri Korenblit 	LINK_STA_DEBUGFS_ADD_FILE(tlc_dhc, dir, 0200);
823d1e879ecSMiri Korenblit }
824