Commit b3feb439 authored by David S. Miller's avatar David S. Miller

Merge branch 'usbnet-speed'

Grant Grundler says:

====================
usbnet: speed reporting for devices without MDIO

This series introduces support for USB network devices that report
speed as a part of their protocol, not emulating an MII to be accessed
over MDIO.

v2: rebased on recent upstream changes
v3: incorporated hints on naming and comments
v4: fix misplaced hunks; reword some commit messages;
    add same change for cdc_ether
v4-repost: added "net-next" to subject and Andrew Lunn's Reviewed-by

I'm reposting Oliver Neukum's <oneukum@suse.com> patch series with
fix ups for "misplaced hunks" (landed in the wrong patches).
Please fixup the "author" if "git am" fails to attribute the
patches 1-3 (of 4) to Oliver.

I've tested v4 series with "5.12-rc3+" kernel on Intel NUC6i5SYB
and + Sabrent NT-S25G. Google Pixelbook Go (chromeos-4.4 kernel)
+ Alpha Network AUE2500C were connected directly to the NT-S25G
to get 2.5Gbps link rate:
Settings for enx002427880815:
        Supported ports: [  ]
        Supported link modes:   Not reported
        Supported pause frame use: No
        Supports auto-negotiation: No
        Supported FEC modes: Not reported
        Advertised link modes:  Not reported
        Advertised pause frame use: No
        Advertised auto-negotiation: No
        Advertised FEC modes: Not reported
        Speed: 2500Mb/s
        Duplex: Half
        Auto-negotiation: off
        Port: Twisted Pair
        PHYAD: 0
        Transceiver: internal
        MDI-X: Unknown
        Current message level: 0x00000007 (7)
                               drv probe link
        Link detected: yes

"Duplex" is a lie since we get no information about it.

I expect "Auto-Negotiation" is always true for cdc_ncm and
cdc_ether devices and perhaps someone knows offhand how
to have ethtool report "true" instead.

But this is good step in the right direction.

base-commit: 1c273e10
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents e880f8b3 d42ebcbb
...@@ -125,8 +125,8 @@ static const struct ethtool_ops ax88172_ethtool_ops = { ...@@ -125,8 +125,8 @@ static const struct ethtool_ops ax88172_ethtool_ops = {
.get_eeprom = asix_get_eeprom, .get_eeprom = asix_get_eeprom,
.set_eeprom = asix_set_eeprom, .set_eeprom = asix_set_eeprom,
.nway_reset = usbnet_nway_reset, .nway_reset = usbnet_nway_reset,
.get_link_ksettings = usbnet_get_link_ksettings, .get_link_ksettings = usbnet_get_link_ksettings_mii,
.set_link_ksettings = usbnet_set_link_ksettings, .set_link_ksettings = usbnet_set_link_ksettings_mii,
}; };
static void ax88172_set_multicast(struct net_device *net) static void ax88172_set_multicast(struct net_device *net)
...@@ -291,8 +291,8 @@ static const struct ethtool_ops ax88772_ethtool_ops = { ...@@ -291,8 +291,8 @@ static const struct ethtool_ops ax88772_ethtool_ops = {
.get_eeprom = asix_get_eeprom, .get_eeprom = asix_get_eeprom,
.set_eeprom = asix_set_eeprom, .set_eeprom = asix_set_eeprom,
.nway_reset = usbnet_nway_reset, .nway_reset = usbnet_nway_reset,
.get_link_ksettings = usbnet_get_link_ksettings, .get_link_ksettings = usbnet_get_link_ksettings_mii,
.set_link_ksettings = usbnet_set_link_ksettings, .set_link_ksettings = usbnet_set_link_ksettings_mii,
}; };
static int ax88772_link_reset(struct usbnet *dev) static int ax88772_link_reset(struct usbnet *dev)
...@@ -782,8 +782,8 @@ static const struct ethtool_ops ax88178_ethtool_ops = { ...@@ -782,8 +782,8 @@ static const struct ethtool_ops ax88178_ethtool_ops = {
.get_eeprom = asix_get_eeprom, .get_eeprom = asix_get_eeprom,
.set_eeprom = asix_set_eeprom, .set_eeprom = asix_set_eeprom,
.nway_reset = usbnet_nway_reset, .nway_reset = usbnet_nway_reset,
.get_link_ksettings = usbnet_get_link_ksettings, .get_link_ksettings = usbnet_get_link_ksettings_mii,
.set_link_ksettings = usbnet_set_link_ksettings, .set_link_ksettings = usbnet_set_link_ksettings_mii,
}; };
static int marvell_phy_init(struct usbnet *dev) static int marvell_phy_init(struct usbnet *dev)
......
...@@ -92,6 +92,18 @@ void usbnet_cdc_update_filter(struct usbnet *dev) ...@@ -92,6 +92,18 @@ void usbnet_cdc_update_filter(struct usbnet *dev)
} }
EXPORT_SYMBOL_GPL(usbnet_cdc_update_filter); EXPORT_SYMBOL_GPL(usbnet_cdc_update_filter);
/* We need to override usbnet_*_link_ksettings in bind() */
static const struct ethtool_ops cdc_ether_ethtool_ops = {
.get_link = usbnet_get_link,
.nway_reset = usbnet_nway_reset,
.get_drvinfo = usbnet_get_drvinfo,
.get_msglevel = usbnet_get_msglevel,
.set_msglevel = usbnet_set_msglevel,
.get_ts_info = ethtool_op_get_ts_info,
.get_link_ksettings = usbnet_get_link_ksettings_internal,
.set_link_ksettings = NULL,
};
/* probes control interface, claims data interface, collects the bulk /* probes control interface, claims data interface, collects the bulk
* endpoints, activates data interface (if needed), maybe sets MTU. * endpoints, activates data interface (if needed), maybe sets MTU.
* all pure cdc, except for certain firmware workarounds, and knowing * all pure cdc, except for certain firmware workarounds, and knowing
...@@ -310,6 +322,9 @@ int usbnet_generic_cdc_bind(struct usbnet *dev, struct usb_interface *intf) ...@@ -310,6 +322,9 @@ int usbnet_generic_cdc_bind(struct usbnet *dev, struct usb_interface *intf)
return -ENODEV; return -ENODEV;
} }
/* override ethtool_ops */
dev->net->ethtool_ops = &cdc_ether_ethtool_ops;
return 0; return 0;
bad_desc: bad_desc:
...@@ -379,12 +394,10 @@ EXPORT_SYMBOL_GPL(usbnet_cdc_unbind); ...@@ -379,12 +394,10 @@ EXPORT_SYMBOL_GPL(usbnet_cdc_unbind);
* (by Brad Hards) talked with, with more functionality. * (by Brad Hards) talked with, with more functionality.
*/ */
static void dumpspeed(struct usbnet *dev, __le32 *speeds) static void speed_change(struct usbnet *dev, __le32 *speeds)
{ {
netif_info(dev, timer, dev->net, dev->tx_speed = __le32_to_cpu(speeds[0]);
"link speeds: %u kbps up, %u kbps down\n", dev->rx_speed = __le32_to_cpu(speeds[1]);
__le32_to_cpu(speeds[0]) / 1000,
__le32_to_cpu(speeds[1]) / 1000);
} }
void usbnet_cdc_status(struct usbnet *dev, struct urb *urb) void usbnet_cdc_status(struct usbnet *dev, struct urb *urb)
...@@ -396,7 +409,7 @@ void usbnet_cdc_status(struct usbnet *dev, struct urb *urb) ...@@ -396,7 +409,7 @@ void usbnet_cdc_status(struct usbnet *dev, struct urb *urb)
/* SPEED_CHANGE can get split into two 8-byte packets */ /* SPEED_CHANGE can get split into two 8-byte packets */
if (test_and_clear_bit(EVENT_STS_SPLIT, &dev->flags)) { if (test_and_clear_bit(EVENT_STS_SPLIT, &dev->flags)) {
dumpspeed(dev, (__le32 *) urb->transfer_buffer); speed_change(dev, (__le32 *) urb->transfer_buffer);
return; return;
} }
...@@ -413,7 +426,7 @@ void usbnet_cdc_status(struct usbnet *dev, struct urb *urb) ...@@ -413,7 +426,7 @@ void usbnet_cdc_status(struct usbnet *dev, struct urb *urb)
if (urb->actual_length != (sizeof(*event) + 8)) if (urb->actual_length != (sizeof(*event) + 8))
set_bit(EVENT_STS_SPLIT, &dev->flags); set_bit(EVENT_STS_SPLIT, &dev->flags);
else else
dumpspeed(dev, (__le32 *) &event[1]); speed_change(dev, (__le32 *) &event[1]);
break; break;
/* USB_CDC_NOTIFY_RESPONSE_AVAILABLE can happen too (e.g. RNDIS), /* USB_CDC_NOTIFY_RESPONSE_AVAILABLE can happen too (e.g. RNDIS),
* but there are no standard formats for the response data. * but there are no standard formats for the response data.
......
...@@ -142,8 +142,8 @@ static const struct ethtool_ops cdc_ncm_ethtool_ops = { ...@@ -142,8 +142,8 @@ static const struct ethtool_ops cdc_ncm_ethtool_ops = {
.get_sset_count = cdc_ncm_get_sset_count, .get_sset_count = cdc_ncm_get_sset_count,
.get_strings = cdc_ncm_get_strings, .get_strings = cdc_ncm_get_strings,
.get_ethtool_stats = cdc_ncm_get_ethtool_stats, .get_ethtool_stats = cdc_ncm_get_ethtool_stats,
.get_link_ksettings = usbnet_get_link_ksettings, .get_link_ksettings = usbnet_get_link_ksettings_internal,
.set_link_ksettings = usbnet_set_link_ksettings, .set_link_ksettings = NULL,
}; };
static u32 cdc_ncm_check_rx_max(struct usbnet *dev, u32 new_rx) static u32 cdc_ncm_check_rx_max(struct usbnet *dev, u32 new_rx)
...@@ -1825,33 +1825,9 @@ static void ...@@ -1825,33 +1825,9 @@ static void
cdc_ncm_speed_change(struct usbnet *dev, cdc_ncm_speed_change(struct usbnet *dev,
struct usb_cdc_speed_change *data) struct usb_cdc_speed_change *data)
{ {
uint32_t rx_speed = le32_to_cpu(data->DLBitRRate); /* RTL8156 shipped before 2021 sends notification about every 32ms. */
uint32_t tx_speed = le32_to_cpu(data->ULBitRate); dev->rx_speed = le32_to_cpu(data->DLBitRRate);
dev->tx_speed = le32_to_cpu(data->ULBitRate);
/* if the speed hasn't changed, don't report it.
* RTL8156 shipped before 2021 sends notification about every 32ms.
*/
if (dev->rx_speed == rx_speed && dev->tx_speed == tx_speed)
return;
dev->rx_speed = rx_speed;
dev->tx_speed = tx_speed;
/*
* Currently the USB-NET API does not support reporting the actual
* device speed. Do print it instead.
*/
if ((tx_speed > 1000000) && (rx_speed > 1000000)) {
netif_info(dev, link, dev->net,
"%u mbit/s downlink %u mbit/s uplink\n",
(unsigned int)(rx_speed / 1000000U),
(unsigned int)(tx_speed / 1000000U));
} else {
netif_info(dev, link, dev->net,
"%u kbit/s downlink %u kbit/s uplink\n",
(unsigned int)(rx_speed / 1000U),
(unsigned int)(tx_speed / 1000U));
}
} }
static void cdc_ncm_status(struct usbnet *dev, struct urb *urb) static void cdc_ncm_status(struct usbnet *dev, struct urb *urb)
...@@ -1877,6 +1853,9 @@ static void cdc_ncm_status(struct usbnet *dev, struct urb *urb) ...@@ -1877,6 +1853,9 @@ static void cdc_ncm_status(struct usbnet *dev, struct urb *urb)
* USB_CDC_NOTIFY_NETWORK_CONNECTION notification shall be * USB_CDC_NOTIFY_NETWORK_CONNECTION notification shall be
* sent by device after USB_CDC_NOTIFY_SPEED_CHANGE. * sent by device after USB_CDC_NOTIFY_SPEED_CHANGE.
*/ */
/* RTL8156 shipped before 2021 sends notification about
* every 32ms. Don't forward notification if state is same.
*/
if (netif_carrier_ok(dev->net) != !!event->wValue) if (netif_carrier_ok(dev->net) != !!event->wValue)
usbnet_link_change(dev, !!event->wValue, 0); usbnet_link_change(dev, !!event->wValue, 0);
break; break;
......
...@@ -282,8 +282,8 @@ static const struct ethtool_ops dm9601_ethtool_ops = { ...@@ -282,8 +282,8 @@ static const struct ethtool_ops dm9601_ethtool_ops = {
.get_eeprom_len = dm9601_get_eeprom_len, .get_eeprom_len = dm9601_get_eeprom_len,
.get_eeprom = dm9601_get_eeprom, .get_eeprom = dm9601_get_eeprom,
.nway_reset = usbnet_nway_reset, .nway_reset = usbnet_nway_reset,
.get_link_ksettings = usbnet_get_link_ksettings, .get_link_ksettings = usbnet_get_link_ksettings_mii,
.set_link_ksettings = usbnet_set_link_ksettings, .set_link_ksettings = usbnet_set_link_ksettings_mii,
}; };
static void dm9601_set_multicast(struct net_device *net) static void dm9601_set_multicast(struct net_device *net)
......
...@@ -452,8 +452,8 @@ static const struct ethtool_ops mcs7830_ethtool_ops = { ...@@ -452,8 +452,8 @@ static const struct ethtool_ops mcs7830_ethtool_ops = {
.get_msglevel = usbnet_get_msglevel, .get_msglevel = usbnet_get_msglevel,
.set_msglevel = usbnet_set_msglevel, .set_msglevel = usbnet_set_msglevel,
.nway_reset = usbnet_nway_reset, .nway_reset = usbnet_nway_reset,
.get_link_ksettings = usbnet_get_link_ksettings, .get_link_ksettings = usbnet_get_link_ksettings_mii,
.set_link_ksettings = usbnet_set_link_ksettings, .set_link_ksettings = usbnet_set_link_ksettings_mii,
}; };
static const struct net_device_ops mcs7830_netdev_ops = { static const struct net_device_ops mcs7830_netdev_ops = {
......
...@@ -629,8 +629,8 @@ static const struct ethtool_ops sierra_net_ethtool_ops = { ...@@ -629,8 +629,8 @@ static const struct ethtool_ops sierra_net_ethtool_ops = {
.get_msglevel = usbnet_get_msglevel, .get_msglevel = usbnet_get_msglevel,
.set_msglevel = usbnet_set_msglevel, .set_msglevel = usbnet_set_msglevel,
.nway_reset = usbnet_nway_reset, .nway_reset = usbnet_nway_reset,
.get_link_ksettings = usbnet_get_link_ksettings, .get_link_ksettings = usbnet_get_link_ksettings_mii,
.set_link_ksettings = usbnet_set_link_ksettings, .set_link_ksettings = usbnet_set_link_ksettings_mii,
}; };
static int sierra_net_get_fw_attr(struct usbnet *dev, u16 *datap) static int sierra_net_get_fw_attr(struct usbnet *dev, u16 *datap)
......
...@@ -741,8 +741,8 @@ static const struct ethtool_ops smsc75xx_ethtool_ops = { ...@@ -741,8 +741,8 @@ static const struct ethtool_ops smsc75xx_ethtool_ops = {
.set_eeprom = smsc75xx_ethtool_set_eeprom, .set_eeprom = smsc75xx_ethtool_set_eeprom,
.get_wol = smsc75xx_ethtool_get_wol, .get_wol = smsc75xx_ethtool_get_wol,
.set_wol = smsc75xx_ethtool_set_wol, .set_wol = smsc75xx_ethtool_set_wol,
.get_link_ksettings = usbnet_get_link_ksettings, .get_link_ksettings = usbnet_get_link_ksettings_mii,
.set_link_ksettings = usbnet_set_link_ksettings, .set_link_ksettings = usbnet_set_link_ksettings_mii,
}; };
static int smsc75xx_ioctl(struct net_device *netdev, struct ifreq *rq, int cmd) static int smsc75xx_ioctl(struct net_device *netdev, struct ifreq *rq, int cmd)
......
...@@ -250,8 +250,8 @@ static const struct ethtool_ops sr9700_ethtool_ops = { ...@@ -250,8 +250,8 @@ static const struct ethtool_ops sr9700_ethtool_ops = {
.get_eeprom_len = sr9700_get_eeprom_len, .get_eeprom_len = sr9700_get_eeprom_len,
.get_eeprom = sr9700_get_eeprom, .get_eeprom = sr9700_get_eeprom,
.nway_reset = usbnet_nway_reset, .nway_reset = usbnet_nway_reset,
.get_link_ksettings = usbnet_get_link_ksettings, .get_link_ksettings = usbnet_get_link_ksettings_mii,
.set_link_ksettings = usbnet_set_link_ksettings, .set_link_ksettings = usbnet_set_link_ksettings_mii,
}; };
static void sr9700_set_multicast(struct net_device *netdev) static void sr9700_set_multicast(struct net_device *netdev)
......
...@@ -527,8 +527,8 @@ static const struct ethtool_ops sr9800_ethtool_ops = { ...@@ -527,8 +527,8 @@ static const struct ethtool_ops sr9800_ethtool_ops = {
.get_eeprom_len = sr_get_eeprom_len, .get_eeprom_len = sr_get_eeprom_len,
.get_eeprom = sr_get_eeprom, .get_eeprom = sr_get_eeprom,
.nway_reset = usbnet_nway_reset, .nway_reset = usbnet_nway_reset,
.get_link_ksettings = usbnet_get_link_ksettings, .get_link_ksettings = usbnet_get_link_ksettings_mii,
.set_link_ksettings = usbnet_set_link_ksettings, .set_link_ksettings = usbnet_set_link_ksettings_mii,
}; };
static int sr9800_link_reset(struct usbnet *dev) static int sr9800_link_reset(struct usbnet *dev)
......
...@@ -944,7 +944,10 @@ EXPORT_SYMBOL_GPL(usbnet_open); ...@@ -944,7 +944,10 @@ EXPORT_SYMBOL_GPL(usbnet_open);
* they'll probably want to use this base set. * they'll probably want to use this base set.
*/ */
int usbnet_get_link_ksettings(struct net_device *net, /* These methods are written on the assumption that the device
* uses MII
*/
int usbnet_get_link_ksettings_mii(struct net_device *net,
struct ethtool_link_ksettings *cmd) struct ethtool_link_ksettings *cmd)
{ {
struct usbnet *dev = netdev_priv(net); struct usbnet *dev = netdev_priv(net);
...@@ -956,9 +959,30 @@ int usbnet_get_link_ksettings(struct net_device *net, ...@@ -956,9 +959,30 @@ int usbnet_get_link_ksettings(struct net_device *net,
return 0; return 0;
} }
EXPORT_SYMBOL_GPL(usbnet_get_link_ksettings); EXPORT_SYMBOL_GPL(usbnet_get_link_ksettings_mii);
int usbnet_get_link_ksettings_internal(struct net_device *net,
struct ethtool_link_ksettings *cmd)
{
struct usbnet *dev = netdev_priv(net);
/* the assumption that speed is equal on tx and rx
* is deeply engrained into the networking layer.
* For wireless stuff it is not true.
* We assume that rx_speed matters more.
*/
if (dev->rx_speed != SPEED_UNSET)
cmd->base.speed = dev->rx_speed / 1000000;
else if (dev->tx_speed != SPEED_UNSET)
cmd->base.speed = dev->tx_speed / 1000000;
else
cmd->base.speed = SPEED_UNKNOWN;
return 0;
}
EXPORT_SYMBOL_GPL(usbnet_get_link_ksettings_internal);
int usbnet_set_link_ksettings(struct net_device *net, int usbnet_set_link_ksettings_mii(struct net_device *net,
const struct ethtool_link_ksettings *cmd) const struct ethtool_link_ksettings *cmd)
{ {
struct usbnet *dev = netdev_priv(net); struct usbnet *dev = netdev_priv(net);
...@@ -978,7 +1002,7 @@ int usbnet_set_link_ksettings(struct net_device *net, ...@@ -978,7 +1002,7 @@ int usbnet_set_link_ksettings(struct net_device *net,
return retval; return retval;
} }
EXPORT_SYMBOL_GPL(usbnet_set_link_ksettings); EXPORT_SYMBOL_GPL(usbnet_set_link_ksettings_mii);
u32 usbnet_get_link (struct net_device *net) u32 usbnet_get_link (struct net_device *net)
{ {
...@@ -1043,8 +1067,8 @@ static const struct ethtool_ops usbnet_ethtool_ops = { ...@@ -1043,8 +1067,8 @@ static const struct ethtool_ops usbnet_ethtool_ops = {
.get_msglevel = usbnet_get_msglevel, .get_msglevel = usbnet_get_msglevel,
.set_msglevel = usbnet_set_msglevel, .set_msglevel = usbnet_set_msglevel,
.get_ts_info = ethtool_op_get_ts_info, .get_ts_info = ethtool_op_get_ts_info,
.get_link_ksettings = usbnet_get_link_ksettings, .get_link_ksettings = usbnet_get_link_ksettings_mii,
.set_link_ksettings = usbnet_set_link_ksettings, .set_link_ksettings = usbnet_set_link_ksettings_mii,
}; };
/*-------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------*/
...@@ -1661,6 +1685,8 @@ usbnet_probe (struct usb_interface *udev, const struct usb_device_id *prod) ...@@ -1661,6 +1685,8 @@ usbnet_probe (struct usb_interface *udev, const struct usb_device_id *prod)
dev->intf = udev; dev->intf = udev;
dev->driver_info = info; dev->driver_info = info;
dev->driver_name = name; dev->driver_name = name;
dev->rx_speed = SPEED_UNSET;
dev->tx_speed = SPEED_UNSET;
net->tstats = netdev_alloc_pcpu_stats(struct pcpu_sw_netstats); net->tstats = netdev_alloc_pcpu_stats(struct pcpu_sw_netstats);
if (!net->tstats) if (!net->tstats)
......
...@@ -53,6 +53,9 @@ struct usbnet { ...@@ -53,6 +53,9 @@ struct usbnet {
u32 hard_mtu; /* count any extra framing */ u32 hard_mtu; /* count any extra framing */
size_t rx_urb_size; /* size for rx urbs */ size_t rx_urb_size; /* size for rx urbs */
struct mii_if_info mii; struct mii_if_info mii;
long rx_speed; /* If MII not used */
long tx_speed; /* If MII not used */
# define SPEED_UNSET -1
/* various kinds of pending driver work */ /* various kinds of pending driver work */
struct sk_buff_head rxq; struct sk_buff_head rxq;
...@@ -81,8 +84,6 @@ struct usbnet { ...@@ -81,8 +84,6 @@ struct usbnet {
# define EVENT_LINK_CHANGE 11 # define EVENT_LINK_CHANGE 11
# define EVENT_SET_RX_MODE 12 # define EVENT_SET_RX_MODE 12
# define EVENT_NO_IP_ALIGN 13 # define EVENT_NO_IP_ALIGN 13
u32 rx_speed; /* in bps - NOT Mbps */
u32 tx_speed; /* in bps - NOT Mbps */
}; };
static inline struct usb_driver *driver_of(struct usb_interface *intf) static inline struct usb_driver *driver_of(struct usb_interface *intf)
...@@ -267,10 +268,12 @@ extern void usbnet_pause_rx(struct usbnet *); ...@@ -267,10 +268,12 @@ extern void usbnet_pause_rx(struct usbnet *);
extern void usbnet_resume_rx(struct usbnet *); extern void usbnet_resume_rx(struct usbnet *);
extern void usbnet_purge_paused_rxq(struct usbnet *); extern void usbnet_purge_paused_rxq(struct usbnet *);
extern int usbnet_get_link_ksettings(struct net_device *net, extern int usbnet_get_link_ksettings_mii(struct net_device *net,
struct ethtool_link_ksettings *cmd); struct ethtool_link_ksettings *cmd);
extern int usbnet_set_link_ksettings(struct net_device *net, extern int usbnet_set_link_ksettings_mii(struct net_device *net,
const struct ethtool_link_ksettings *cmd); const struct ethtool_link_ksettings *cmd);
extern int usbnet_get_link_ksettings_internal(struct net_device *net,
struct ethtool_link_ksettings *cmd);
extern u32 usbnet_get_link(struct net_device *net); extern u32 usbnet_get_link(struct net_device *net);
extern u32 usbnet_get_msglevel(struct net_device *); extern u32 usbnet_get_msglevel(struct net_device *);
extern void usbnet_set_msglevel(struct net_device *, u32); extern void usbnet_set_msglevel(struct net_device *, u32);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment