1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * FUJITSU Extended Socket Network Device driver |
4 | * Copyright (c) 2015 FUJITSU LIMITED |
5 | */ |
6 | |
7 | /* ethtool support for fjes */ |
8 | |
9 | #include <linux/vmalloc.h> |
10 | #include <linux/netdevice.h> |
11 | #include <linux/ethtool.h> |
12 | #include <linux/platform_device.h> |
13 | |
14 | #include "fjes.h" |
15 | |
16 | struct fjes_stats { |
17 | char stat_string[ETH_GSTRING_LEN]; |
18 | int sizeof_stat; |
19 | int stat_offset; |
20 | }; |
21 | |
22 | #define FJES_STAT(name, stat) { \ |
23 | .stat_string = name, \ |
24 | .sizeof_stat = sizeof_field(struct fjes_adapter, stat), \ |
25 | .stat_offset = offsetof(struct fjes_adapter, stat) \ |
26 | } |
27 | |
28 | static const struct fjes_stats fjes_gstrings_stats[] = { |
29 | FJES_STAT("rx_packets" , stats64.rx_packets), |
30 | FJES_STAT("tx_packets" , stats64.tx_packets), |
31 | FJES_STAT("rx_bytes" , stats64.rx_bytes), |
32 | FJES_STAT("tx_bytes" , stats64.rx_bytes), |
33 | FJES_STAT("rx_dropped" , stats64.rx_dropped), |
34 | FJES_STAT("tx_dropped" , stats64.tx_dropped), |
35 | }; |
36 | |
37 | #define FJES_EP_STATS_LEN 14 |
38 | #define FJES_STATS_LEN \ |
39 | (ARRAY_SIZE(fjes_gstrings_stats) + \ |
40 | ((&((struct fjes_adapter *)netdev_priv(netdev))->hw)->max_epid - 1) * \ |
41 | FJES_EP_STATS_LEN) |
42 | |
43 | static void fjes_get_ethtool_stats(struct net_device *netdev, |
44 | struct ethtool_stats *stats, u64 *data) |
45 | { |
46 | struct fjes_adapter *adapter = netdev_priv(dev: netdev); |
47 | struct fjes_hw *hw = &adapter->hw; |
48 | int epidx; |
49 | char *p; |
50 | int i; |
51 | |
52 | for (i = 0; i < ARRAY_SIZE(fjes_gstrings_stats); i++) { |
53 | p = (char *)adapter + fjes_gstrings_stats[i].stat_offset; |
54 | data[i] = (fjes_gstrings_stats[i].sizeof_stat == sizeof(u64)) |
55 | ? *(u64 *)p : *(u32 *)p; |
56 | } |
57 | for (epidx = 0; epidx < hw->max_epid; epidx++) { |
58 | if (epidx == hw->my_epid) |
59 | continue; |
60 | data[i++] = hw->ep_shm_info[epidx].ep_stats |
61 | .com_regist_buf_exec; |
62 | data[i++] = hw->ep_shm_info[epidx].ep_stats |
63 | .com_unregist_buf_exec; |
64 | data[i++] = hw->ep_shm_info[epidx].ep_stats.send_intr_rx; |
65 | data[i++] = hw->ep_shm_info[epidx].ep_stats.send_intr_unshare; |
66 | data[i++] = hw->ep_shm_info[epidx].ep_stats |
67 | .send_intr_zoneupdate; |
68 | data[i++] = hw->ep_shm_info[epidx].ep_stats.recv_intr_rx; |
69 | data[i++] = hw->ep_shm_info[epidx].ep_stats.recv_intr_unshare; |
70 | data[i++] = hw->ep_shm_info[epidx].ep_stats.recv_intr_stop; |
71 | data[i++] = hw->ep_shm_info[epidx].ep_stats |
72 | .recv_intr_zoneupdate; |
73 | data[i++] = hw->ep_shm_info[epidx].ep_stats.tx_buffer_full; |
74 | data[i++] = hw->ep_shm_info[epidx].ep_stats |
75 | .tx_dropped_not_shared; |
76 | data[i++] = hw->ep_shm_info[epidx].ep_stats |
77 | .tx_dropped_ver_mismatch; |
78 | data[i++] = hw->ep_shm_info[epidx].ep_stats |
79 | .tx_dropped_buf_size_mismatch; |
80 | data[i++] = hw->ep_shm_info[epidx].ep_stats |
81 | .tx_dropped_vlanid_mismatch; |
82 | } |
83 | } |
84 | |
85 | static void fjes_get_strings(struct net_device *netdev, |
86 | u32 stringset, u8 *data) |
87 | { |
88 | struct fjes_adapter *adapter = netdev_priv(dev: netdev); |
89 | struct fjes_hw *hw = &adapter->hw; |
90 | u8 *p = data; |
91 | int i; |
92 | |
93 | switch (stringset) { |
94 | case ETH_SS_STATS: |
95 | for (i = 0; i < ARRAY_SIZE(fjes_gstrings_stats); i++) { |
96 | memcpy(p, fjes_gstrings_stats[i].stat_string, |
97 | ETH_GSTRING_LEN); |
98 | p += ETH_GSTRING_LEN; |
99 | } |
100 | for (i = 0; i < hw->max_epid; i++) { |
101 | if (i == hw->my_epid) |
102 | continue; |
103 | sprintf(buf: p, fmt: "ep%u_com_regist_buf_exec" , i); |
104 | p += ETH_GSTRING_LEN; |
105 | sprintf(buf: p, fmt: "ep%u_com_unregist_buf_exec" , i); |
106 | p += ETH_GSTRING_LEN; |
107 | sprintf(buf: p, fmt: "ep%u_send_intr_rx" , i); |
108 | p += ETH_GSTRING_LEN; |
109 | sprintf(buf: p, fmt: "ep%u_send_intr_unshare" , i); |
110 | p += ETH_GSTRING_LEN; |
111 | sprintf(buf: p, fmt: "ep%u_send_intr_zoneupdate" , i); |
112 | p += ETH_GSTRING_LEN; |
113 | sprintf(buf: p, fmt: "ep%u_recv_intr_rx" , i); |
114 | p += ETH_GSTRING_LEN; |
115 | sprintf(buf: p, fmt: "ep%u_recv_intr_unshare" , i); |
116 | p += ETH_GSTRING_LEN; |
117 | sprintf(buf: p, fmt: "ep%u_recv_intr_stop" , i); |
118 | p += ETH_GSTRING_LEN; |
119 | sprintf(buf: p, fmt: "ep%u_recv_intr_zoneupdate" , i); |
120 | p += ETH_GSTRING_LEN; |
121 | sprintf(buf: p, fmt: "ep%u_tx_buffer_full" , i); |
122 | p += ETH_GSTRING_LEN; |
123 | sprintf(buf: p, fmt: "ep%u_tx_dropped_not_shared" , i); |
124 | p += ETH_GSTRING_LEN; |
125 | sprintf(buf: p, fmt: "ep%u_tx_dropped_ver_mismatch" , i); |
126 | p += ETH_GSTRING_LEN; |
127 | sprintf(buf: p, fmt: "ep%u_tx_dropped_buf_size_mismatch" , i); |
128 | p += ETH_GSTRING_LEN; |
129 | sprintf(buf: p, fmt: "ep%u_tx_dropped_vlanid_mismatch" , i); |
130 | p += ETH_GSTRING_LEN; |
131 | } |
132 | break; |
133 | } |
134 | } |
135 | |
136 | static int fjes_get_sset_count(struct net_device *netdev, int sset) |
137 | { |
138 | switch (sset) { |
139 | case ETH_SS_STATS: |
140 | return FJES_STATS_LEN; |
141 | default: |
142 | return -EOPNOTSUPP; |
143 | } |
144 | } |
145 | |
146 | static void fjes_get_drvinfo(struct net_device *netdev, |
147 | struct ethtool_drvinfo *drvinfo) |
148 | { |
149 | struct fjes_adapter *adapter = netdev_priv(dev: netdev); |
150 | struct platform_device *plat_dev; |
151 | |
152 | plat_dev = adapter->plat_dev; |
153 | |
154 | strscpy(drvinfo->driver, fjes_driver_name, sizeof(drvinfo->driver)); |
155 | strscpy(drvinfo->version, fjes_driver_version, |
156 | sizeof(drvinfo->version)); |
157 | |
158 | strscpy(drvinfo->fw_version, "none" , sizeof(drvinfo->fw_version)); |
159 | snprintf(buf: drvinfo->bus_info, size: sizeof(drvinfo->bus_info), |
160 | fmt: "platform:%s" , plat_dev->name); |
161 | } |
162 | |
163 | static int fjes_get_link_ksettings(struct net_device *netdev, |
164 | struct ethtool_link_ksettings *ecmd) |
165 | { |
166 | ethtool_link_ksettings_zero_link_mode(ecmd, supported); |
167 | ethtool_link_ksettings_zero_link_mode(ecmd, advertising); |
168 | ecmd->base.duplex = DUPLEX_FULL; |
169 | ecmd->base.autoneg = AUTONEG_DISABLE; |
170 | ecmd->base.port = PORT_NONE; |
171 | ecmd->base.speed = 20000; /* 20Gb/s */ |
172 | |
173 | return 0; |
174 | } |
175 | |
176 | static int fjes_get_regs_len(struct net_device *netdev) |
177 | { |
178 | #define FJES_REGS_LEN 37 |
179 | return FJES_REGS_LEN * sizeof(u32); |
180 | } |
181 | |
182 | static void fjes_get_regs(struct net_device *netdev, |
183 | struct ethtool_regs *regs, void *p) |
184 | { |
185 | struct fjes_adapter *adapter = netdev_priv(dev: netdev); |
186 | struct fjes_hw *hw = &adapter->hw; |
187 | u32 *regs_buff = p; |
188 | |
189 | memset(p, 0, FJES_REGS_LEN * sizeof(u32)); |
190 | |
191 | regs->version = 1; |
192 | |
193 | /* Information registers */ |
194 | regs_buff[0] = rd32(XSCT_OWNER_EPID); |
195 | regs_buff[1] = rd32(XSCT_MAX_EP); |
196 | |
197 | /* Device Control registers */ |
198 | regs_buff[4] = rd32(XSCT_DCTL); |
199 | |
200 | /* Command Control registers */ |
201 | regs_buff[8] = rd32(XSCT_CR); |
202 | regs_buff[9] = rd32(XSCT_CS); |
203 | regs_buff[10] = rd32(XSCT_SHSTSAL); |
204 | regs_buff[11] = rd32(XSCT_SHSTSAH); |
205 | |
206 | regs_buff[13] = rd32(XSCT_REQBL); |
207 | regs_buff[14] = rd32(XSCT_REQBAL); |
208 | regs_buff[15] = rd32(XSCT_REQBAH); |
209 | |
210 | regs_buff[17] = rd32(XSCT_RESPBL); |
211 | regs_buff[18] = rd32(XSCT_RESPBAL); |
212 | regs_buff[19] = rd32(XSCT_RESPBAH); |
213 | |
214 | /* Interrupt Control registers */ |
215 | regs_buff[32] = rd32(XSCT_IS); |
216 | regs_buff[33] = rd32(XSCT_IMS); |
217 | regs_buff[34] = rd32(XSCT_IMC); |
218 | regs_buff[35] = rd32(XSCT_IG); |
219 | regs_buff[36] = rd32(XSCT_ICTL); |
220 | } |
221 | |
222 | static int fjes_set_dump(struct net_device *netdev, struct ethtool_dump *dump) |
223 | { |
224 | struct fjes_adapter *adapter = netdev_priv(dev: netdev); |
225 | struct fjes_hw *hw = &adapter->hw; |
226 | int ret = 0; |
227 | |
228 | if (dump->flag) { |
229 | if (hw->debug_mode) |
230 | return -EPERM; |
231 | |
232 | hw->debug_mode = dump->flag; |
233 | |
234 | /* enable debug mode */ |
235 | mutex_lock(&hw->hw_info.lock); |
236 | ret = fjes_hw_start_debug(hw); |
237 | mutex_unlock(lock: &hw->hw_info.lock); |
238 | |
239 | if (ret) |
240 | hw->debug_mode = 0; |
241 | } else { |
242 | if (!hw->debug_mode) |
243 | return -EPERM; |
244 | |
245 | /* disable debug mode */ |
246 | mutex_lock(&hw->hw_info.lock); |
247 | ret = fjes_hw_stop_debug(hw); |
248 | mutex_unlock(lock: &hw->hw_info.lock); |
249 | } |
250 | |
251 | return ret; |
252 | } |
253 | |
254 | static int fjes_get_dump_flag(struct net_device *netdev, |
255 | struct ethtool_dump *dump) |
256 | { |
257 | struct fjes_adapter *adapter = netdev_priv(dev: netdev); |
258 | struct fjes_hw *hw = &adapter->hw; |
259 | |
260 | dump->len = hw->hw_info.trace_size; |
261 | dump->version = 1; |
262 | dump->flag = hw->debug_mode; |
263 | |
264 | return 0; |
265 | } |
266 | |
267 | static int fjes_get_dump_data(struct net_device *netdev, |
268 | struct ethtool_dump *dump, void *buf) |
269 | { |
270 | struct fjes_adapter *adapter = netdev_priv(dev: netdev); |
271 | struct fjes_hw *hw = &adapter->hw; |
272 | int ret = 0; |
273 | |
274 | if (hw->hw_info.trace) |
275 | memcpy(buf, hw->hw_info.trace, hw->hw_info.trace_size); |
276 | else |
277 | ret = -EPERM; |
278 | |
279 | return ret; |
280 | } |
281 | |
282 | static const struct ethtool_ops fjes_ethtool_ops = { |
283 | .get_drvinfo = fjes_get_drvinfo, |
284 | .get_ethtool_stats = fjes_get_ethtool_stats, |
285 | .get_strings = fjes_get_strings, |
286 | .get_sset_count = fjes_get_sset_count, |
287 | .get_regs = fjes_get_regs, |
288 | .get_regs_len = fjes_get_regs_len, |
289 | .set_dump = fjes_set_dump, |
290 | .get_dump_flag = fjes_get_dump_flag, |
291 | .get_dump_data = fjes_get_dump_data, |
292 | .get_link_ksettings = fjes_get_link_ksettings, |
293 | }; |
294 | |
295 | void fjes_set_ethtool_ops(struct net_device *netdev) |
296 | { |
297 | netdev->ethtool_ops = &fjes_ethtool_ops; |
298 | } |
299 | |