1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * DPAA2 Ethernet Switch ethtool support |
4 | * |
5 | * Copyright 2014-2016 Freescale Semiconductor Inc. |
6 | * Copyright 2017-2018 NXP |
7 | * |
8 | */ |
9 | |
10 | #include <linux/ethtool.h> |
11 | |
12 | #include "dpaa2-switch.h" |
13 | |
14 | static struct { |
15 | enum dpsw_counter id; |
16 | char name[ETH_GSTRING_LEN]; |
17 | } dpaa2_switch_ethtool_counters[] = { |
18 | {.id: DPSW_CNT_ING_FRAME, .name: "[hw] rx frames" }, |
19 | {DPSW_CNT_ING_BYTE, "[hw] rx bytes" }, |
20 | {DPSW_CNT_ING_FLTR_FRAME, "[hw] rx filtered frames" }, |
21 | {DPSW_CNT_ING_FRAME_DISCARD, "[hw] rx discarded frames" }, |
22 | {DPSW_CNT_ING_BCAST_FRAME, "[hw] rx bcast frames" }, |
23 | {DPSW_CNT_ING_BCAST_BYTES, "[hw] rx bcast bytes" }, |
24 | {DPSW_CNT_ING_MCAST_FRAME, "[hw] rx mcast frames" }, |
25 | {DPSW_CNT_ING_MCAST_BYTE, "[hw] rx mcast bytes" }, |
26 | {DPSW_CNT_EGR_FRAME, "[hw] tx frames" }, |
27 | {DPSW_CNT_EGR_BYTE, "[hw] tx bytes" }, |
28 | {DPSW_CNT_EGR_FRAME_DISCARD, "[hw] tx discarded frames" }, |
29 | {DPSW_CNT_ING_NO_BUFF_DISCARD, "[hw] rx nobuffer discards" }, |
30 | }; |
31 | |
32 | #define DPAA2_SWITCH_NUM_COUNTERS ARRAY_SIZE(dpaa2_switch_ethtool_counters) |
33 | |
34 | static void dpaa2_switch_get_drvinfo(struct net_device *netdev, |
35 | struct ethtool_drvinfo *drvinfo) |
36 | { |
37 | struct ethsw_port_priv *port_priv = netdev_priv(dev: netdev); |
38 | u16 version_major, version_minor; |
39 | int err; |
40 | |
41 | strscpy(drvinfo->driver, KBUILD_MODNAME, sizeof(drvinfo->driver)); |
42 | |
43 | err = dpsw_get_api_version(mc_io: port_priv->ethsw_data->mc_io, cmd_flags: 0, |
44 | major_ver: &version_major, |
45 | minor_ver: &version_minor); |
46 | if (err) |
47 | strscpy(drvinfo->fw_version, "N/A" , |
48 | sizeof(drvinfo->fw_version)); |
49 | else |
50 | snprintf(buf: drvinfo->fw_version, size: sizeof(drvinfo->fw_version), |
51 | fmt: "%u.%u" , version_major, version_minor); |
52 | |
53 | strscpy(drvinfo->bus_info, dev_name(netdev->dev.parent->parent), |
54 | sizeof(drvinfo->bus_info)); |
55 | } |
56 | |
57 | static int |
58 | dpaa2_switch_get_link_ksettings(struct net_device *netdev, |
59 | struct ethtool_link_ksettings *link_ksettings) |
60 | { |
61 | struct ethsw_port_priv *port_priv = netdev_priv(dev: netdev); |
62 | struct dpsw_link_state state = {0}; |
63 | int err; |
64 | |
65 | mutex_lock(&port_priv->mac_lock); |
66 | |
67 | if (dpaa2_switch_port_is_type_phy(port_priv)) { |
68 | err = phylink_ethtool_ksettings_get(port_priv->mac->phylink, |
69 | link_ksettings); |
70 | mutex_unlock(lock: &port_priv->mac_lock); |
71 | return err; |
72 | } |
73 | |
74 | mutex_unlock(lock: &port_priv->mac_lock); |
75 | |
76 | err = dpsw_if_get_link_state(mc_io: port_priv->ethsw_data->mc_io, cmd_flags: 0, |
77 | token: port_priv->ethsw_data->dpsw_handle, |
78 | if_id: port_priv->idx, |
79 | state: &state); |
80 | if (err) { |
81 | netdev_err(dev: netdev, format: "ERROR %d getting link state\n" , err); |
82 | goto out; |
83 | } |
84 | |
85 | /* At the moment, we have no way of interrogating the DPMAC |
86 | * from the DPSW side or there may not exist a DPMAC at all. |
87 | * Report only autoneg state, duplexity and speed. |
88 | */ |
89 | if (state.options & DPSW_LINK_OPT_AUTONEG) |
90 | link_ksettings->base.autoneg = AUTONEG_ENABLE; |
91 | if (!(state.options & DPSW_LINK_OPT_HALF_DUPLEX)) |
92 | link_ksettings->base.duplex = DUPLEX_FULL; |
93 | link_ksettings->base.speed = state.rate; |
94 | |
95 | out: |
96 | return err; |
97 | } |
98 | |
99 | static int |
100 | dpaa2_switch_set_link_ksettings(struct net_device *netdev, |
101 | const struct ethtool_link_ksettings *link_ksettings) |
102 | { |
103 | struct ethsw_port_priv *port_priv = netdev_priv(dev: netdev); |
104 | struct ethsw_core *ethsw = port_priv->ethsw_data; |
105 | struct dpsw_link_cfg cfg = {0}; |
106 | bool if_running; |
107 | int err = 0, ret; |
108 | |
109 | mutex_lock(&port_priv->mac_lock); |
110 | |
111 | if (dpaa2_switch_port_is_type_phy(port_priv)) { |
112 | err = phylink_ethtool_ksettings_set(port_priv->mac->phylink, |
113 | link_ksettings); |
114 | mutex_unlock(lock: &port_priv->mac_lock); |
115 | return err; |
116 | } |
117 | |
118 | mutex_unlock(lock: &port_priv->mac_lock); |
119 | |
120 | /* Interface needs to be down to change link settings */ |
121 | if_running = netif_running(dev: netdev); |
122 | if (if_running) { |
123 | err = dpsw_if_disable(mc_io: ethsw->mc_io, cmd_flags: 0, |
124 | token: ethsw->dpsw_handle, |
125 | if_id: port_priv->idx); |
126 | if (err) { |
127 | netdev_err(dev: netdev, format: "dpsw_if_disable err %d\n" , err); |
128 | return err; |
129 | } |
130 | } |
131 | |
132 | cfg.rate = link_ksettings->base.speed; |
133 | if (link_ksettings->base.autoneg == AUTONEG_ENABLE) |
134 | cfg.options |= DPSW_LINK_OPT_AUTONEG; |
135 | else |
136 | cfg.options &= ~DPSW_LINK_OPT_AUTONEG; |
137 | if (link_ksettings->base.duplex == DUPLEX_HALF) |
138 | cfg.options |= DPSW_LINK_OPT_HALF_DUPLEX; |
139 | else |
140 | cfg.options &= ~DPSW_LINK_OPT_HALF_DUPLEX; |
141 | |
142 | err = dpsw_if_set_link_cfg(mc_io: port_priv->ethsw_data->mc_io, cmd_flags: 0, |
143 | token: port_priv->ethsw_data->dpsw_handle, |
144 | if_id: port_priv->idx, |
145 | cfg: &cfg); |
146 | |
147 | if (if_running) { |
148 | ret = dpsw_if_enable(mc_io: ethsw->mc_io, cmd_flags: 0, |
149 | token: ethsw->dpsw_handle, |
150 | if_id: port_priv->idx); |
151 | if (ret) { |
152 | netdev_err(dev: netdev, format: "dpsw_if_enable err %d\n" , ret); |
153 | return ret; |
154 | } |
155 | } |
156 | return err; |
157 | } |
158 | |
159 | static int |
160 | dpaa2_switch_ethtool_get_sset_count(struct net_device *netdev, int sset) |
161 | { |
162 | switch (sset) { |
163 | case ETH_SS_STATS: |
164 | return DPAA2_SWITCH_NUM_COUNTERS + dpaa2_mac_get_sset_count(); |
165 | default: |
166 | return -EOPNOTSUPP; |
167 | } |
168 | } |
169 | |
170 | static void dpaa2_switch_ethtool_get_strings(struct net_device *netdev, |
171 | u32 stringset, u8 *data) |
172 | { |
173 | u8 *p = data; |
174 | int i; |
175 | |
176 | switch (stringset) { |
177 | case ETH_SS_STATS: |
178 | for (i = 0; i < DPAA2_SWITCH_NUM_COUNTERS; i++) { |
179 | memcpy(p, dpaa2_switch_ethtool_counters[i].name, |
180 | ETH_GSTRING_LEN); |
181 | p += ETH_GSTRING_LEN; |
182 | } |
183 | dpaa2_mac_get_strings(data: p); |
184 | break; |
185 | } |
186 | } |
187 | |
188 | static void dpaa2_switch_ethtool_get_stats(struct net_device *netdev, |
189 | struct ethtool_stats *stats, |
190 | u64 *data) |
191 | { |
192 | struct ethsw_port_priv *port_priv = netdev_priv(dev: netdev); |
193 | int i, err; |
194 | |
195 | for (i = 0; i < DPAA2_SWITCH_NUM_COUNTERS; i++) { |
196 | err = dpsw_if_get_counter(mc_io: port_priv->ethsw_data->mc_io, cmd_flags: 0, |
197 | token: port_priv->ethsw_data->dpsw_handle, |
198 | if_id: port_priv->idx, |
199 | type: dpaa2_switch_ethtool_counters[i].id, |
200 | counter: &data[i]); |
201 | if (err) |
202 | netdev_err(dev: netdev, format: "dpsw_if_get_counter[%s] err %d\n" , |
203 | dpaa2_switch_ethtool_counters[i].name, err); |
204 | } |
205 | |
206 | mutex_lock(&port_priv->mac_lock); |
207 | |
208 | if (dpaa2_switch_port_has_mac(port_priv)) |
209 | dpaa2_mac_get_ethtool_stats(mac: port_priv->mac, data: data + i); |
210 | |
211 | mutex_unlock(lock: &port_priv->mac_lock); |
212 | } |
213 | |
214 | const struct ethtool_ops dpaa2_switch_port_ethtool_ops = { |
215 | .get_drvinfo = dpaa2_switch_get_drvinfo, |
216 | .get_link = ethtool_op_get_link, |
217 | .get_link_ksettings = dpaa2_switch_get_link_ksettings, |
218 | .set_link_ksettings = dpaa2_switch_set_link_ksettings, |
219 | .get_strings = dpaa2_switch_ethtool_get_strings, |
220 | .get_ethtool_stats = dpaa2_switch_ethtool_get_stats, |
221 | .get_sset_count = dpaa2_switch_ethtool_get_sset_count, |
222 | }; |
223 | |