Commit | Line | Data |
---|---|---|
28c61a66 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
b7ffbd7e JB |
2 | /* |
3 | * mac80211 ethtool hooks for cfg80211 | |
4 | * | |
5 | * Copied from cfg.c - originally | |
6 | * Copyright 2006-2010 Johannes Berg <johannes@sipsolutions.net> | |
7 | * Copyright 2014 Intel Corporation (Author: Johannes Berg) | |
01ca280d | 8 | * Copyright (C) 2018, 2022-2023 Intel Corporation |
b7ffbd7e JB |
9 | */ |
10 | #include <linux/types.h> | |
11 | #include <net/cfg80211.h> | |
12 | #include "ieee80211_i.h" | |
13 | #include "sta_info.h" | |
14 | #include "driver-ops.h" | |
15 | ||
16 | static int ieee80211_set_ringparam(struct net_device *dev, | |
74624944 HC |
17 | struct ethtool_ringparam *rp, |
18 | struct kernel_ethtool_ringparam *kernel_rp, | |
19 | struct netlink_ext_ack *extack) | |
b7ffbd7e JB |
20 | { |
21 | struct ieee80211_local *local = wiphy_priv(dev->ieee80211_ptr->wiphy); | |
6b348f6e | 22 | int ret; |
b7ffbd7e JB |
23 | |
24 | if (rp->rx_mini_pending != 0 || rp->rx_jumbo_pending != 0) | |
25 | return -EINVAL; | |
26 | ||
6b348f6e JB |
27 | wiphy_lock(local->hw.wiphy); |
28 | ret = drv_set_ringparam(local, rp->tx_pending, rp->rx_pending); | |
29 | wiphy_unlock(local->hw.wiphy); | |
30 | ||
31 | return ret; | |
b7ffbd7e JB |
32 | } |
33 | ||
34 | static void ieee80211_get_ringparam(struct net_device *dev, | |
74624944 HC |
35 | struct ethtool_ringparam *rp, |
36 | struct kernel_ethtool_ringparam *kernel_rp, | |
37 | struct netlink_ext_ack *extack) | |
b7ffbd7e JB |
38 | { |
39 | struct ieee80211_local *local = wiphy_priv(dev->ieee80211_ptr->wiphy); | |
40 | ||
41 | memset(rp, 0, sizeof(*rp)); | |
42 | ||
6b348f6e | 43 | wiphy_lock(local->hw.wiphy); |
b7ffbd7e JB |
44 | drv_get_ringparam(local, &rp->tx_pending, &rp->tx_max_pending, |
45 | &rp->rx_pending, &rp->rx_max_pending); | |
6b348f6e | 46 | wiphy_unlock(local->hw.wiphy); |
b7ffbd7e JB |
47 | } |
48 | ||
49 | static const char ieee80211_gstrings_sta_stats[][ETH_GSTRING_LEN] = { | |
50 | "rx_packets", "rx_bytes", | |
51 | "rx_duplicates", "rx_fragments", "rx_dropped", | |
f83f1c12 | 52 | "tx_packets", "tx_bytes", |
b7ffbd7e | 53 | "tx_filtered", "tx_retry_failed", "tx_retries", |
976bd9ef | 54 | "sta_state", "txrate", "rxrate", "signal", |
b7ffbd7e JB |
55 | "channel", "noise", "ch_time", "ch_time_busy", |
56 | "ch_time_ext_busy", "ch_time_rx", "ch_time_tx" | |
57 | }; | |
58 | #define STA_STATS_LEN ARRAY_SIZE(ieee80211_gstrings_sta_stats) | |
59 | ||
60 | static int ieee80211_get_sset_count(struct net_device *dev, int sset) | |
61 | { | |
62 | struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); | |
63 | int rv = 0; | |
64 | ||
65 | if (sset == ETH_SS_STATS) | |
66 | rv += STA_STATS_LEN; | |
67 | ||
68 | rv += drv_get_et_sset_count(sdata, sset); | |
69 | ||
70 | if (rv == 0) | |
71 | return -EOPNOTSUPP; | |
72 | return rv; | |
73 | } | |
74 | ||
75 | static void ieee80211_get_stats(struct net_device *dev, | |
76 | struct ethtool_stats *stats, | |
77 | u64 *data) | |
78 | { | |
79 | struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); | |
80 | struct ieee80211_chanctx_conf *chanctx_conf; | |
81 | struct ieee80211_channel *channel; | |
82 | struct sta_info *sta; | |
83 | struct ieee80211_local *local = sdata->local; | |
73887fd9 | 84 | struct station_info sinfo; |
b7ffbd7e JB |
85 | struct survey_info survey; |
86 | int i, q; | |
87 | #define STA_STATS_SURVEY_LEN 7 | |
88 | ||
89 | memset(data, 0, sizeof(u64) * STA_STATS_LEN); | |
90 | ||
e5a9f8d0 JB |
91 | #define ADD_STA_STATS(sta) \ |
92 | do { \ | |
83888346 RL |
93 | data[i++] += sinfo.rx_packets; \ |
94 | data[i++] += sinfo.rx_bytes; \ | |
ce6893e9 JB |
95 | data[i++] += (sta)->rx_stats.num_duplicates; \ |
96 | data[i++] += (sta)->rx_stats.fragments; \ | |
83888346 | 97 | data[i++] += sinfo.rx_dropped_misc; \ |
e5a9f8d0 | 98 | \ |
73887fd9 JB |
99 | data[i++] += sinfo.tx_packets; \ |
100 | data[i++] += sinfo.tx_bytes; \ | |
ce6893e9 | 101 | data[i++] += (sta)->status_stats.filtered; \ |
83888346 RL |
102 | data[i++] += sinfo.tx_failed; \ |
103 | data[i++] += sinfo.tx_retries; \ | |
b7ffbd7e JB |
104 | } while (0) |
105 | ||
106 | /* For Managed stations, find the single station based on BSSID | |
107 | * and use that. For interface types, iterate through all available | |
108 | * stations and add stats for any station that is assigned to this | |
109 | * network device. | |
110 | */ | |
111 | ||
01ca280d | 112 | wiphy_lock(local->hw.wiphy); |
b7ffbd7e JB |
113 | |
114 | if (sdata->vif.type == NL80211_IFTYPE_STATION) { | |
bfd8403a | 115 | sta = sta_info_get_bss(sdata, sdata->deflink.u.mgd.bssid); |
b7ffbd7e JB |
116 | |
117 | if (!(sta && !WARN_ON(sta->sdata->dev != dev))) | |
118 | goto do_survey; | |
119 | ||
73887fd9 | 120 | memset(&sinfo, 0, sizeof(sinfo)); |
0fdf1493 | 121 | sta_set_sinfo(sta, &sinfo, false); |
b7ffbd7e JB |
122 | |
123 | i = 0; | |
ce6893e9 | 124 | ADD_STA_STATS(&sta->deflink); |
b7ffbd7e JB |
125 | |
126 | data[i++] = sta->sta_state; | |
127 | ||
128 | ||
a4217750 | 129 | if (sinfo.filled & BIT_ULL(NL80211_STA_INFO_TX_BITRATE)) |
57c6cb81 | 130 | data[i] = 100000ULL * |
73887fd9 | 131 | cfg80211_calculate_bitrate(&sinfo.txrate); |
b7ffbd7e | 132 | i++; |
a4217750 | 133 | if (sinfo.filled & BIT_ULL(NL80211_STA_INFO_RX_BITRATE)) |
57c6cb81 | 134 | data[i] = 100000ULL * |
73887fd9 | 135 | cfg80211_calculate_bitrate(&sinfo.rxrate); |
b7ffbd7e JB |
136 | i++; |
137 | ||
a4217750 | 138 | if (sinfo.filled & BIT_ULL(NL80211_STA_INFO_SIGNAL_AVG)) |
73887fd9 | 139 | data[i] = (u8)sinfo.signal_avg; |
b7ffbd7e JB |
140 | i++; |
141 | } else { | |
142 | list_for_each_entry(sta, &local->sta_list, list) { | |
143 | /* Make sure this station belongs to the proper dev */ | |
144 | if (sta->sdata->dev != dev) | |
145 | continue; | |
146 | ||
73887fd9 | 147 | memset(&sinfo, 0, sizeof(sinfo)); |
0fdf1493 | 148 | sta_set_sinfo(sta, &sinfo, false); |
b7ffbd7e | 149 | i = 0; |
ce6893e9 | 150 | ADD_STA_STATS(&sta->deflink); |
b7ffbd7e JB |
151 | } |
152 | } | |
153 | ||
154 | do_survey: | |
155 | i = STA_STATS_LEN - STA_STATS_SURVEY_LEN; | |
156 | /* Get survey stats for current channel */ | |
157 | survey.filled = 0; | |
158 | ||
159 | rcu_read_lock(); | |
d0a9123e | 160 | chanctx_conf = rcu_dereference(sdata->vif.bss_conf.chanctx_conf); |
b7ffbd7e JB |
161 | if (chanctx_conf) |
162 | channel = chanctx_conf->def.chan; | |
163 | else | |
164 | channel = NULL; | |
165 | rcu_read_unlock(); | |
166 | ||
167 | if (channel) { | |
168 | q = 0; | |
169 | do { | |
170 | survey.filled = 0; | |
171 | if (drv_get_survey(local, q, &survey) != 0) { | |
172 | survey.filled = 0; | |
173 | break; | |
174 | } | |
175 | q++; | |
176 | } while (channel != survey.channel); | |
177 | } | |
178 | ||
179 | if (survey.filled) | |
180 | data[i++] = survey.channel->center_freq; | |
181 | else | |
182 | data[i++] = 0; | |
183 | if (survey.filled & SURVEY_INFO_NOISE_DBM) | |
184 | data[i++] = (u8)survey.noise; | |
185 | else | |
186 | data[i++] = -1LL; | |
4ed20beb JB |
187 | if (survey.filled & SURVEY_INFO_TIME) |
188 | data[i++] = survey.time; | |
b7ffbd7e JB |
189 | else |
190 | data[i++] = -1LL; | |
4ed20beb JB |
191 | if (survey.filled & SURVEY_INFO_TIME_BUSY) |
192 | data[i++] = survey.time_busy; | |
b7ffbd7e JB |
193 | else |
194 | data[i++] = -1LL; | |
4ed20beb JB |
195 | if (survey.filled & SURVEY_INFO_TIME_EXT_BUSY) |
196 | data[i++] = survey.time_ext_busy; | |
b7ffbd7e JB |
197 | else |
198 | data[i++] = -1LL; | |
4ed20beb JB |
199 | if (survey.filled & SURVEY_INFO_TIME_RX) |
200 | data[i++] = survey.time_rx; | |
b7ffbd7e JB |
201 | else |
202 | data[i++] = -1LL; | |
4ed20beb JB |
203 | if (survey.filled & SURVEY_INFO_TIME_TX) |
204 | data[i++] = survey.time_tx; | |
b7ffbd7e JB |
205 | else |
206 | data[i++] = -1LL; | |
207 | ||
01ca280d JB |
208 | if (WARN_ON(i != STA_STATS_LEN)) { |
209 | wiphy_unlock(local->hw.wiphy); | |
b7ffbd7e | 210 | return; |
01ca280d | 211 | } |
b7ffbd7e JB |
212 | |
213 | drv_get_et_stats(sdata, stats, &(data[STA_STATS_LEN])); | |
01ca280d | 214 | wiphy_unlock(local->hw.wiphy); |
b7ffbd7e JB |
215 | } |
216 | ||
217 | static void ieee80211_get_strings(struct net_device *dev, u32 sset, u8 *data) | |
218 | { | |
219 | struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); | |
220 | int sz_sta_stats = 0; | |
221 | ||
222 | if (sset == ETH_SS_STATS) { | |
223 | sz_sta_stats = sizeof(ieee80211_gstrings_sta_stats); | |
224 | memcpy(data, ieee80211_gstrings_sta_stats, sz_sta_stats); | |
225 | } | |
226 | drv_get_et_strings(sdata, sset, &(data[sz_sta_stats])); | |
227 | } | |
228 | ||
229 | static int ieee80211_get_regs_len(struct net_device *dev) | |
230 | { | |
231 | return 0; | |
232 | } | |
233 | ||
234 | static void ieee80211_get_regs(struct net_device *dev, | |
235 | struct ethtool_regs *regs, | |
236 | void *data) | |
237 | { | |
238 | struct wireless_dev *wdev = dev->ieee80211_ptr; | |
239 | ||
240 | regs->version = wdev->wiphy->hw_version; | |
241 | regs->len = 0; | |
242 | } | |
243 | ||
244 | const struct ethtool_ops ieee80211_ethtool_ops = { | |
245 | .get_drvinfo = cfg80211_get_drvinfo, | |
246 | .get_regs_len = ieee80211_get_regs_len, | |
247 | .get_regs = ieee80211_get_regs, | |
248 | .get_link = ethtool_op_get_link, | |
249 | .get_ringparam = ieee80211_get_ringparam, | |
250 | .set_ringparam = ieee80211_set_ringparam, | |
251 | .get_strings = ieee80211_get_strings, | |
252 | .get_ethtool_stats = ieee80211_get_stats, | |
253 | .get_sset_count = ieee80211_get_sset_count, | |
254 | }; |