.. | .. |
---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later |
---|
1 | 2 | /* |
---|
2 | 3 | * Handling of a master device, switching frames via its switch fabric CPU port |
---|
3 | 4 | * |
---|
4 | 5 | * Copyright (c) 2017 Savoir-faire Linux Inc. |
---|
5 | 6 | * Vivien Didelot <vivien.didelot@savoirfairelinux.com> |
---|
6 | | - * |
---|
7 | | - * This program is free software; you can redistribute it and/or modify |
---|
8 | | - * it under the terms of the GNU General Public License as published by |
---|
9 | | - * the Free Software Foundation; either version 2 of the License, or |
---|
10 | | - * (at your option) any later version. |
---|
11 | 7 | */ |
---|
12 | 8 | |
---|
13 | 9 | #include "dsa_priv.h" |
---|
| 10 | + |
---|
| 11 | +static int dsa_master_get_regs_len(struct net_device *dev) |
---|
| 12 | +{ |
---|
| 13 | + struct dsa_port *cpu_dp = dev->dsa_ptr; |
---|
| 14 | + const struct ethtool_ops *ops = cpu_dp->orig_ethtool_ops; |
---|
| 15 | + struct dsa_switch *ds = cpu_dp->ds; |
---|
| 16 | + int port = cpu_dp->index; |
---|
| 17 | + int ret = 0; |
---|
| 18 | + int len; |
---|
| 19 | + |
---|
| 20 | + if (ops->get_regs_len) { |
---|
| 21 | + len = ops->get_regs_len(dev); |
---|
| 22 | + if (len < 0) |
---|
| 23 | + return len; |
---|
| 24 | + ret += len; |
---|
| 25 | + } |
---|
| 26 | + |
---|
| 27 | + ret += sizeof(struct ethtool_drvinfo); |
---|
| 28 | + ret += sizeof(struct ethtool_regs); |
---|
| 29 | + |
---|
| 30 | + if (ds->ops->get_regs_len) { |
---|
| 31 | + len = ds->ops->get_regs_len(ds, port); |
---|
| 32 | + if (len < 0) |
---|
| 33 | + return len; |
---|
| 34 | + ret += len; |
---|
| 35 | + } |
---|
| 36 | + |
---|
| 37 | + return ret; |
---|
| 38 | +} |
---|
| 39 | + |
---|
| 40 | +static void dsa_master_get_regs(struct net_device *dev, |
---|
| 41 | + struct ethtool_regs *regs, void *data) |
---|
| 42 | +{ |
---|
| 43 | + struct dsa_port *cpu_dp = dev->dsa_ptr; |
---|
| 44 | + const struct ethtool_ops *ops = cpu_dp->orig_ethtool_ops; |
---|
| 45 | + struct dsa_switch *ds = cpu_dp->ds; |
---|
| 46 | + struct ethtool_drvinfo *cpu_info; |
---|
| 47 | + struct ethtool_regs *cpu_regs; |
---|
| 48 | + int port = cpu_dp->index; |
---|
| 49 | + int len; |
---|
| 50 | + |
---|
| 51 | + if (ops->get_regs_len && ops->get_regs) { |
---|
| 52 | + len = ops->get_regs_len(dev); |
---|
| 53 | + if (len < 0) |
---|
| 54 | + return; |
---|
| 55 | + regs->len = len; |
---|
| 56 | + ops->get_regs(dev, regs, data); |
---|
| 57 | + data += regs->len; |
---|
| 58 | + } |
---|
| 59 | + |
---|
| 60 | + cpu_info = (struct ethtool_drvinfo *)data; |
---|
| 61 | + strlcpy(cpu_info->driver, "dsa", sizeof(cpu_info->driver)); |
---|
| 62 | + data += sizeof(*cpu_info); |
---|
| 63 | + cpu_regs = (struct ethtool_regs *)data; |
---|
| 64 | + data += sizeof(*cpu_regs); |
---|
| 65 | + |
---|
| 66 | + if (ds->ops->get_regs_len && ds->ops->get_regs) { |
---|
| 67 | + len = ds->ops->get_regs_len(ds, port); |
---|
| 68 | + if (len < 0) |
---|
| 69 | + return; |
---|
| 70 | + cpu_regs->len = len; |
---|
| 71 | + ds->ops->get_regs(ds, port, cpu_regs, data); |
---|
| 72 | + } |
---|
| 73 | +} |
---|
14 | 74 | |
---|
15 | 75 | static void dsa_master_get_ethtool_stats(struct net_device *dev, |
---|
16 | 76 | struct ethtool_stats *stats, |
---|
.. | .. |
---|
127 | 187 | } |
---|
128 | 188 | } |
---|
129 | 189 | |
---|
| 190 | +static int dsa_master_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) |
---|
| 191 | +{ |
---|
| 192 | + struct dsa_port *cpu_dp = dev->dsa_ptr; |
---|
| 193 | + struct dsa_switch *ds = cpu_dp->ds; |
---|
| 194 | + struct dsa_switch_tree *dst; |
---|
| 195 | + int err = -EOPNOTSUPP; |
---|
| 196 | + struct dsa_port *dp; |
---|
| 197 | + |
---|
| 198 | + dst = ds->dst; |
---|
| 199 | + |
---|
| 200 | + switch (cmd) { |
---|
| 201 | + case SIOCGHWTSTAMP: |
---|
| 202 | + case SIOCSHWTSTAMP: |
---|
| 203 | + /* Deny PTP operations on master if there is at least one |
---|
| 204 | + * switch in the tree that is PTP capable. |
---|
| 205 | + */ |
---|
| 206 | + list_for_each_entry(dp, &dst->ports, list) |
---|
| 207 | + if (dp->ds->ops->port_hwtstamp_get || |
---|
| 208 | + dp->ds->ops->port_hwtstamp_set) |
---|
| 209 | + return -EBUSY; |
---|
| 210 | + break; |
---|
| 211 | + } |
---|
| 212 | + |
---|
| 213 | + if (dev->netdev_ops->ndo_do_ioctl) |
---|
| 214 | + err = dev->netdev_ops->ndo_do_ioctl(dev, ifr, cmd); |
---|
| 215 | + |
---|
| 216 | + return err; |
---|
| 217 | +} |
---|
| 218 | + |
---|
| 219 | +static const struct dsa_netdevice_ops dsa_netdev_ops = { |
---|
| 220 | + .ndo_do_ioctl = dsa_master_ioctl, |
---|
| 221 | +}; |
---|
| 222 | + |
---|
130 | 223 | static int dsa_master_ethtool_setup(struct net_device *dev) |
---|
131 | 224 | { |
---|
132 | 225 | struct dsa_port *cpu_dp = dev->dsa_ptr; |
---|
.. | .. |
---|
141 | 234 | if (cpu_dp->orig_ethtool_ops) |
---|
142 | 235 | memcpy(ops, cpu_dp->orig_ethtool_ops, sizeof(*ops)); |
---|
143 | 236 | |
---|
| 237 | + ops->get_regs_len = dsa_master_get_regs_len; |
---|
| 238 | + ops->get_regs = dsa_master_get_regs; |
---|
144 | 239 | ops->get_sset_count = dsa_master_get_sset_count; |
---|
145 | 240 | ops->get_ethtool_stats = dsa_master_get_ethtool_stats; |
---|
146 | 241 | ops->get_strings = dsa_master_get_strings; |
---|
.. | .. |
---|
159 | 254 | cpu_dp->orig_ethtool_ops = NULL; |
---|
160 | 255 | } |
---|
161 | 256 | |
---|
| 257 | +static void dsa_netdev_ops_set(struct net_device *dev, |
---|
| 258 | + const struct dsa_netdevice_ops *ops) |
---|
| 259 | +{ |
---|
| 260 | + dev->dsa_ptr->netdev_ops = ops; |
---|
| 261 | +} |
---|
| 262 | + |
---|
| 263 | +static void dsa_master_set_promiscuity(struct net_device *dev, int inc) |
---|
| 264 | +{ |
---|
| 265 | + const struct dsa_device_ops *ops = dev->dsa_ptr->tag_ops; |
---|
| 266 | + |
---|
| 267 | + if (!ops->promisc_on_master) |
---|
| 268 | + return; |
---|
| 269 | + |
---|
| 270 | + rtnl_lock(); |
---|
| 271 | + dev_set_promiscuity(dev, inc); |
---|
| 272 | + rtnl_unlock(); |
---|
| 273 | +} |
---|
| 274 | + |
---|
| 275 | +static ssize_t tagging_show(struct device *d, struct device_attribute *attr, |
---|
| 276 | + char *buf) |
---|
| 277 | +{ |
---|
| 278 | + struct net_device *dev = to_net_dev(d); |
---|
| 279 | + struct dsa_port *cpu_dp = dev->dsa_ptr; |
---|
| 280 | + |
---|
| 281 | + return sprintf(buf, "%s\n", |
---|
| 282 | + dsa_tag_protocol_to_str(cpu_dp->tag_ops)); |
---|
| 283 | +} |
---|
| 284 | +static DEVICE_ATTR_RO(tagging); |
---|
| 285 | + |
---|
| 286 | +static struct attribute *dsa_slave_attrs[] = { |
---|
| 287 | + &dev_attr_tagging.attr, |
---|
| 288 | + NULL |
---|
| 289 | +}; |
---|
| 290 | + |
---|
| 291 | +static const struct attribute_group dsa_group = { |
---|
| 292 | + .name = "dsa", |
---|
| 293 | + .attrs = dsa_slave_attrs, |
---|
| 294 | +}; |
---|
| 295 | + |
---|
| 296 | +static void dsa_master_reset_mtu(struct net_device *dev) |
---|
| 297 | +{ |
---|
| 298 | + int err; |
---|
| 299 | + |
---|
| 300 | + rtnl_lock(); |
---|
| 301 | + err = dev_set_mtu(dev, ETH_DATA_LEN); |
---|
| 302 | + if (err) |
---|
| 303 | + netdev_dbg(dev, |
---|
| 304 | + "Unable to reset MTU to exclude DSA overheads\n"); |
---|
| 305 | + rtnl_unlock(); |
---|
| 306 | +} |
---|
| 307 | + |
---|
162 | 308 | static struct lock_class_key dsa_master_addr_list_lock_key; |
---|
163 | 309 | |
---|
164 | 310 | int dsa_master_setup(struct net_device *dev, struct dsa_port *cpu_dp) |
---|
165 | 311 | { |
---|
| 312 | + struct dsa_switch *ds = cpu_dp->ds; |
---|
| 313 | + struct device_link *consumer_link; |
---|
| 314 | + int ret; |
---|
| 315 | + |
---|
| 316 | + /* The DSA master must use SET_NETDEV_DEV for this to work. */ |
---|
| 317 | + consumer_link = device_link_add(ds->dev, dev->dev.parent, |
---|
| 318 | + DL_FLAG_AUTOREMOVE_CONSUMER); |
---|
| 319 | + if (!consumer_link) |
---|
| 320 | + netdev_err(dev, |
---|
| 321 | + "Failed to create a device link to DSA switch %s\n", |
---|
| 322 | + dev_name(ds->dev)); |
---|
| 323 | + |
---|
| 324 | + rtnl_lock(); |
---|
| 325 | + ret = dev_set_mtu(dev, ETH_DATA_LEN + cpu_dp->tag_ops->overhead); |
---|
| 326 | + rtnl_unlock(); |
---|
| 327 | + if (ret) |
---|
| 328 | + netdev_warn(dev, "error %d setting MTU to include DSA overhead\n", |
---|
| 329 | + ret); |
---|
| 330 | + |
---|
166 | 331 | /* If we use a tagging format that doesn't have an ethertype |
---|
167 | 332 | * field, make sure that all packets from this point on get |
---|
168 | 333 | * sent to the tag format's receive function. |
---|
.. | .. |
---|
173 | 338 | lockdep_set_class(&dev->addr_list_lock, |
---|
174 | 339 | &dsa_master_addr_list_lock_key); |
---|
175 | 340 | |
---|
176 | | - return dsa_master_ethtool_setup(dev); |
---|
| 341 | + dsa_master_set_promiscuity(dev, 1); |
---|
| 342 | + |
---|
| 343 | + ret = dsa_master_ethtool_setup(dev); |
---|
| 344 | + if (ret) |
---|
| 345 | + goto out_err_reset_promisc; |
---|
| 346 | + |
---|
| 347 | + dsa_netdev_ops_set(dev, &dsa_netdev_ops); |
---|
| 348 | + |
---|
| 349 | + ret = sysfs_create_group(&dev->dev.kobj, &dsa_group); |
---|
| 350 | + if (ret) |
---|
| 351 | + goto out_err_ndo_teardown; |
---|
| 352 | + |
---|
| 353 | + return ret; |
---|
| 354 | + |
---|
| 355 | +out_err_ndo_teardown: |
---|
| 356 | + dsa_netdev_ops_set(dev, NULL); |
---|
| 357 | + dsa_master_ethtool_teardown(dev); |
---|
| 358 | +out_err_reset_promisc: |
---|
| 359 | + dsa_master_set_promiscuity(dev, -1); |
---|
| 360 | + return ret; |
---|
177 | 361 | } |
---|
178 | 362 | |
---|
179 | 363 | void dsa_master_teardown(struct net_device *dev) |
---|
180 | 364 | { |
---|
| 365 | + sysfs_remove_group(&dev->dev.kobj, &dsa_group); |
---|
| 366 | + dsa_netdev_ops_set(dev, NULL); |
---|
181 | 367 | dsa_master_ethtool_teardown(dev); |
---|
| 368 | + dsa_master_reset_mtu(dev); |
---|
| 369 | + dsa_master_set_promiscuity(dev, -1); |
---|
182 | 370 | |
---|
183 | 371 | dev->dsa_ptr = NULL; |
---|
184 | 372 | |
---|