11// SPDX-License-Identifier: BSD-3-Clause-Clear
22/*
33 * Copyright (c) 2018-2021 The Linux Foundation. All rights reserved.
4- * Copyright (c) 2021-2024 Qualcomm Innovation Center, Inc. All rights reserved.
4+ * Copyright (c) 2021-2025 Qualcomm Innovation Center, Inc. All rights reserved.
55 */
66
77#include "core.h"
8+ #include "debug.h"
89#include "debugfs.h"
910#include "debugfs_htt_stats.h"
1011
@@ -68,6 +69,199 @@ void ath12k_debugfs_soc_destroy(struct ath12k_base *ab)
6869 */
6970}
7071
72+ static void ath12k_fw_stats_vdevs_free (struct list_head * head )
73+ {
74+ struct ath12k_fw_stats_vdev * i , * tmp ;
75+
76+ list_for_each_entry_safe (i , tmp , head , list ) {
77+ list_del (& i -> list );
78+ kfree (i );
79+ }
80+ }
81+
82+ void ath12k_debugfs_fw_stats_reset (struct ath12k * ar )
83+ {
84+ spin_lock_bh (& ar -> data_lock );
85+ ar -> fw_stats .fw_stats_done = false;
86+ ath12k_fw_stats_vdevs_free (& ar -> fw_stats .vdevs );
87+ spin_unlock_bh (& ar -> data_lock );
88+ }
89+
90+ static int ath12k_debugfs_fw_stats_request (struct ath12k * ar ,
91+ struct ath12k_fw_stats_req_params * param )
92+ {
93+ struct ath12k_base * ab = ar -> ab ;
94+ unsigned long timeout , time_left ;
95+ int ret ;
96+
97+ lockdep_assert_wiphy (ath12k_ar_to_hw (ar )-> wiphy );
98+
99+ /* FW stats can get split when exceeding the stats data buffer limit.
100+ * In that case, since there is no end marking for the back-to-back
101+ * received 'update stats' event, we keep a 3 seconds timeout in case,
102+ * fw_stats_done is not marked yet
103+ */
104+ timeout = jiffies + msecs_to_jiffies (3 * 1000 );
105+
106+ ath12k_debugfs_fw_stats_reset (ar );
107+
108+ reinit_completion (& ar -> fw_stats_complete );
109+
110+ ret = ath12k_wmi_send_stats_request_cmd (ar , param -> stats_id ,
111+ param -> vdev_id , param -> pdev_id );
112+
113+ if (ret ) {
114+ ath12k_warn (ab , "could not request fw stats (%d)\n" ,
115+ ret );
116+ return ret ;
117+ }
118+
119+ time_left = wait_for_completion_timeout (& ar -> fw_stats_complete ,
120+ 1 * HZ );
121+ /* If the wait timed out, return -ETIMEDOUT */
122+ if (!time_left )
123+ return - ETIMEDOUT ;
124+
125+ /* Firmware sends WMI_UPDATE_STATS_EVENTID back-to-back
126+ * when stats data buffer limit is reached. fw_stats_complete
127+ * is completed once host receives first event from firmware, but
128+ * still end might not be marked in the TLV.
129+ * Below loop is to confirm that firmware completed sending all the event
130+ * and fw_stats_done is marked true when end is marked in the TLV
131+ */
132+ for (;;) {
133+ if (time_after (jiffies , timeout ))
134+ break ;
135+
136+ spin_lock_bh (& ar -> data_lock );
137+ if (ar -> fw_stats .fw_stats_done ) {
138+ spin_unlock_bh (& ar -> data_lock );
139+ break ;
140+ }
141+ spin_unlock_bh (& ar -> data_lock );
142+ }
143+ return 0 ;
144+ }
145+
146+ void
147+ ath12k_debugfs_fw_stats_process (struct ath12k * ar ,
148+ struct ath12k_fw_stats * stats )
149+ {
150+ struct ath12k_base * ab = ar -> ab ;
151+ struct ath12k_pdev * pdev ;
152+ bool is_end ;
153+ static unsigned int num_vdev ;
154+ size_t total_vdevs_started = 0 ;
155+ int i ;
156+
157+ if (stats -> stats_id == WMI_REQUEST_VDEV_STAT ) {
158+ if (list_empty (& stats -> vdevs )) {
159+ ath12k_warn (ab , "empty vdev stats" );
160+ return ;
161+ }
162+ /* FW sends all the active VDEV stats irrespective of PDEV,
163+ * hence limit until the count of all VDEVs started
164+ */
165+ rcu_read_lock ();
166+ for (i = 0 ; i < ab -> num_radios ; i ++ ) {
167+ pdev = rcu_dereference (ab -> pdevs_active [i ]);
168+ if (pdev && pdev -> ar )
169+ total_vdevs_started += pdev -> ar -> num_started_vdevs ;
170+ }
171+ rcu_read_unlock ();
172+
173+ is_end = ((++ num_vdev ) == total_vdevs_started );
174+
175+ list_splice_tail_init (& stats -> vdevs ,
176+ & ar -> fw_stats .vdevs );
177+
178+ if (is_end ) {
179+ ar -> fw_stats .fw_stats_done = true;
180+ num_vdev = 0 ;
181+ }
182+ return ;
183+ }
184+ }
185+
186+ static int ath12k_open_vdev_stats (struct inode * inode , struct file * file )
187+ {
188+ struct ath12k * ar = inode -> i_private ;
189+ struct ath12k_fw_stats_req_params param ;
190+ struct ath12k_hw * ah = ath12k_ar_to_ah (ar );
191+ int ret ;
192+
193+ guard (wiphy )(ath12k_ar_to_hw (ar )-> wiphy );
194+
195+ if (!ah )
196+ return - ENETDOWN ;
197+
198+ if (ah -> state != ATH12K_HW_STATE_ON )
199+ return - ENETDOWN ;
200+
201+ void * buf __free (kfree ) = kzalloc (ATH12K_FW_STATS_BUF_SIZE , GFP_ATOMIC );
202+ if (!buf )
203+ return - ENOMEM ;
204+
205+ param .pdev_id = ath12k_mac_get_target_pdev_id (ar );
206+ /* VDEV stats is always sent for all active VDEVs from FW */
207+ param .vdev_id = 0 ;
208+ param .stats_id = WMI_REQUEST_VDEV_STAT ;
209+
210+ ret = ath12k_debugfs_fw_stats_request (ar , & param );
211+ if (ret ) {
212+ ath12k_warn (ar -> ab , "failed to request fw vdev stats: %d\n" , ret );
213+ return ret ;
214+ }
215+
216+ ath12k_wmi_fw_stats_dump (ar , & ar -> fw_stats , param .stats_id ,
217+ buf );
218+
219+ file -> private_data = no_free_ptr (buf );
220+
221+ return 0 ;
222+ }
223+
224+ static int ath12k_release_vdev_stats (struct inode * inode , struct file * file )
225+ {
226+ kfree (file -> private_data );
227+
228+ return 0 ;
229+ }
230+
231+ static ssize_t ath12k_read_vdev_stats (struct file * file ,
232+ char __user * user_buf ,
233+ size_t count , loff_t * ppos )
234+ {
235+ const char * buf = file -> private_data ;
236+ size_t len = strlen (buf );
237+
238+ return simple_read_from_buffer (user_buf , count , ppos , buf , len );
239+ }
240+
241+ static const struct file_operations fops_vdev_stats = {
242+ .open = ath12k_open_vdev_stats ,
243+ .release = ath12k_release_vdev_stats ,
244+ .read = ath12k_read_vdev_stats ,
245+ .owner = THIS_MODULE ,
246+ .llseek = default_llseek ,
247+ };
248+
249+ static
250+ void ath12k_debugfs_fw_stats_register (struct ath12k * ar )
251+ {
252+ struct dentry * fwstats_dir = debugfs_create_dir ("fw_stats" ,
253+ ar -> debug .debugfs_pdev );
254+
255+ /* all stats debugfs files created are under "fw_stats" directory
256+ * created per PDEV
257+ */
258+ debugfs_create_file ("vdev_stats" , 0600 , fwstats_dir , ar ,
259+ & fops_vdev_stats );
260+
261+ INIT_LIST_HEAD (& ar -> fw_stats .vdevs );
262+ init_completion (& ar -> fw_stats_complete );
263+ }
264+
71265void ath12k_debugfs_register (struct ath12k * ar )
72266{
73267 struct ath12k_base * ab = ar -> ab ;
@@ -92,6 +286,7 @@ void ath12k_debugfs_register(struct ath12k *ar)
92286 }
93287
94288 ath12k_debugfs_htt_stats_register (ar );
289+ ath12k_debugfs_fw_stats_register (ar );
95290}
96291
97292void ath12k_debugfs_unregister (struct ath12k * ar )
0 commit comments