1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // Copyright (c) 2020 Facebook |
3 | |
4 | #include <linux/debugfs.h> |
5 | #include <linux/ethtool.h> |
6 | #include <linux/random.h> |
7 | |
8 | #include "netdevsim.h" |
9 | |
10 | static void |
11 | nsim_get_pause_stats(struct net_device *dev, |
12 | struct ethtool_pause_stats *pause_stats) |
13 | { |
14 | struct netdevsim *ns = netdev_priv(dev); |
15 | |
16 | if (ns->ethtool.pauseparam.report_stats_rx) |
17 | pause_stats->rx_pause_frames = 1; |
18 | if (ns->ethtool.pauseparam.report_stats_tx) |
19 | pause_stats->tx_pause_frames = 2; |
20 | } |
21 | |
22 | static void |
23 | nsim_get_pauseparam(struct net_device *dev, struct ethtool_pauseparam *pause) |
24 | { |
25 | struct netdevsim *ns = netdev_priv(dev); |
26 | |
27 | pause->autoneg = 0; /* We don't support ksettings, so can't pretend */ |
28 | pause->rx_pause = ns->ethtool.pauseparam.rx; |
29 | pause->tx_pause = ns->ethtool.pauseparam.tx; |
30 | } |
31 | |
32 | static int |
33 | nsim_set_pauseparam(struct net_device *dev, struct ethtool_pauseparam *pause) |
34 | { |
35 | struct netdevsim *ns = netdev_priv(dev); |
36 | |
37 | if (pause->autoneg) |
38 | return -EINVAL; |
39 | |
40 | ns->ethtool.pauseparam.rx = pause->rx_pause; |
41 | ns->ethtool.pauseparam.tx = pause->tx_pause; |
42 | return 0; |
43 | } |
44 | |
45 | static int nsim_get_coalesce(struct net_device *dev, |
46 | struct ethtool_coalesce *coal, |
47 | struct kernel_ethtool_coalesce *kernel_coal, |
48 | struct netlink_ext_ack *extack) |
49 | { |
50 | struct netdevsim *ns = netdev_priv(dev); |
51 | |
52 | memcpy(coal, &ns->ethtool.coalesce, sizeof(ns->ethtool.coalesce)); |
53 | return 0; |
54 | } |
55 | |
56 | static int nsim_set_coalesce(struct net_device *dev, |
57 | struct ethtool_coalesce *coal, |
58 | struct kernel_ethtool_coalesce *kernel_coal, |
59 | struct netlink_ext_ack *extack) |
60 | { |
61 | struct netdevsim *ns = netdev_priv(dev); |
62 | |
63 | memcpy(&ns->ethtool.coalesce, coal, sizeof(ns->ethtool.coalesce)); |
64 | return 0; |
65 | } |
66 | |
67 | static void nsim_get_ringparam(struct net_device *dev, |
68 | struct ethtool_ringparam *ring, |
69 | struct kernel_ethtool_ringparam *kernel_ring, |
70 | struct netlink_ext_ack *extack) |
71 | { |
72 | struct netdevsim *ns = netdev_priv(dev); |
73 | |
74 | memcpy(ring, &ns->ethtool.ring, sizeof(ns->ethtool.ring)); |
75 | } |
76 | |
77 | static int nsim_set_ringparam(struct net_device *dev, |
78 | struct ethtool_ringparam *ring, |
79 | struct kernel_ethtool_ringparam *kernel_ring, |
80 | struct netlink_ext_ack *extack) |
81 | { |
82 | struct netdevsim *ns = netdev_priv(dev); |
83 | |
84 | ns->ethtool.ring.rx_pending = ring->rx_pending; |
85 | ns->ethtool.ring.rx_jumbo_pending = ring->rx_jumbo_pending; |
86 | ns->ethtool.ring.rx_mini_pending = ring->rx_mini_pending; |
87 | ns->ethtool.ring.tx_pending = ring->tx_pending; |
88 | return 0; |
89 | } |
90 | |
91 | static void |
92 | nsim_get_channels(struct net_device *dev, struct ethtool_channels *ch) |
93 | { |
94 | struct netdevsim *ns = netdev_priv(dev); |
95 | |
96 | ch->max_combined = ns->nsim_bus_dev->num_queues; |
97 | ch->combined_count = ns->ethtool.channels; |
98 | } |
99 | |
100 | static int |
101 | nsim_set_channels(struct net_device *dev, struct ethtool_channels *ch) |
102 | { |
103 | struct netdevsim *ns = netdev_priv(dev); |
104 | int err; |
105 | |
106 | err = netif_set_real_num_queues(dev, txq: ch->combined_count, |
107 | rxq: ch->combined_count); |
108 | if (err) |
109 | return err; |
110 | |
111 | ns->ethtool.channels = ch->combined_count; |
112 | return 0; |
113 | } |
114 | |
115 | static int |
116 | nsim_get_fecparam(struct net_device *dev, struct ethtool_fecparam *fecparam) |
117 | { |
118 | struct netdevsim *ns = netdev_priv(dev); |
119 | |
120 | if (ns->ethtool.get_err) |
121 | return -ns->ethtool.get_err; |
122 | memcpy(fecparam, &ns->ethtool.fec, sizeof(ns->ethtool.fec)); |
123 | return 0; |
124 | } |
125 | |
126 | static int |
127 | nsim_set_fecparam(struct net_device *dev, struct ethtool_fecparam *fecparam) |
128 | { |
129 | struct netdevsim *ns = netdev_priv(dev); |
130 | u32 fec; |
131 | |
132 | if (ns->ethtool.set_err) |
133 | return -ns->ethtool.set_err; |
134 | memcpy(&ns->ethtool.fec, fecparam, sizeof(ns->ethtool.fec)); |
135 | fec = fecparam->fec; |
136 | if (fec == ETHTOOL_FEC_AUTO) |
137 | fec |= ETHTOOL_FEC_OFF; |
138 | fec |= ETHTOOL_FEC_NONE; |
139 | ns->ethtool.fec.active_fec = 1 << (fls(x: fec) - 1); |
140 | return 0; |
141 | } |
142 | |
143 | static int nsim_get_ts_info(struct net_device *dev, |
144 | struct ethtool_ts_info *info) |
145 | { |
146 | struct netdevsim *ns = netdev_priv(dev); |
147 | |
148 | info->phc_index = mock_phc_index(phc: ns->phc); |
149 | |
150 | return 0; |
151 | } |
152 | |
153 | static const struct ethtool_ops nsim_ethtool_ops = { |
154 | .supported_coalesce_params = ETHTOOL_COALESCE_ALL_PARAMS, |
155 | .get_pause_stats = nsim_get_pause_stats, |
156 | .get_pauseparam = nsim_get_pauseparam, |
157 | .set_pauseparam = nsim_set_pauseparam, |
158 | .set_coalesce = nsim_set_coalesce, |
159 | .get_coalesce = nsim_get_coalesce, |
160 | .get_ringparam = nsim_get_ringparam, |
161 | .set_ringparam = nsim_set_ringparam, |
162 | .get_channels = nsim_get_channels, |
163 | .set_channels = nsim_set_channels, |
164 | .get_fecparam = nsim_get_fecparam, |
165 | .set_fecparam = nsim_set_fecparam, |
166 | .get_ts_info = nsim_get_ts_info, |
167 | }; |
168 | |
169 | static void nsim_ethtool_ring_init(struct netdevsim *ns) |
170 | { |
171 | ns->ethtool.ring.rx_max_pending = 4096; |
172 | ns->ethtool.ring.rx_jumbo_max_pending = 4096; |
173 | ns->ethtool.ring.rx_mini_max_pending = 4096; |
174 | ns->ethtool.ring.tx_max_pending = 4096; |
175 | } |
176 | |
177 | void nsim_ethtool_init(struct netdevsim *ns) |
178 | { |
179 | struct dentry *ethtool, *dir; |
180 | |
181 | ns->netdev->ethtool_ops = &nsim_ethtool_ops; |
182 | |
183 | nsim_ethtool_ring_init(ns); |
184 | |
185 | ns->ethtool.fec.fec = ETHTOOL_FEC_NONE; |
186 | ns->ethtool.fec.active_fec = ETHTOOL_FEC_NONE; |
187 | |
188 | ns->ethtool.channels = ns->nsim_bus_dev->num_queues; |
189 | |
190 | ethtool = debugfs_create_dir(name: "ethtool" , parent: ns->nsim_dev_port->ddir); |
191 | |
192 | debugfs_create_u32(name: "get_err" , mode: 0600, parent: ethtool, value: &ns->ethtool.get_err); |
193 | debugfs_create_u32(name: "set_err" , mode: 0600, parent: ethtool, value: &ns->ethtool.set_err); |
194 | |
195 | dir = debugfs_create_dir(name: "pause" , parent: ethtool); |
196 | debugfs_create_bool(name: "report_stats_rx" , mode: 0600, parent: dir, |
197 | value: &ns->ethtool.pauseparam.report_stats_rx); |
198 | debugfs_create_bool(name: "report_stats_tx" , mode: 0600, parent: dir, |
199 | value: &ns->ethtool.pauseparam.report_stats_tx); |
200 | |
201 | dir = debugfs_create_dir(name: "ring" , parent: ethtool); |
202 | debugfs_create_u32(name: "rx_max_pending" , mode: 0600, parent: dir, |
203 | value: &ns->ethtool.ring.rx_max_pending); |
204 | debugfs_create_u32(name: "rx_jumbo_max_pending" , mode: 0600, parent: dir, |
205 | value: &ns->ethtool.ring.rx_jumbo_max_pending); |
206 | debugfs_create_u32(name: "rx_mini_max_pending" , mode: 0600, parent: dir, |
207 | value: &ns->ethtool.ring.rx_mini_max_pending); |
208 | debugfs_create_u32(name: "tx_max_pending" , mode: 0600, parent: dir, |
209 | value: &ns->ethtool.ring.tx_max_pending); |
210 | } |
211 | |