mirror of
https://github.com/torvalds/linux.git
synced 2025-12-07 20:06:24 +00:00
Merge branch 'dsa-simple-hsr-offload'
Vladimir Oltean says: ==================== DSA simple HSR offload Provide a "simple" form of HSR offload for 8 DSA drivers (just the NETIF_F_HW_HSR_DUP feature) based on the fact that their taggers use the dsa_xmit_port_mask() function. This is in patches 6-13/15. The helpers per se are introduced in patch 5/15, and documented in patch 15/15. Patch 14/15 is another small (and related) documentation update. For HSR interlink ports the offloading rules are not quite so clear, and for now we completely reject the offload. We can revise that once we see a full offload implementation and understand what is needed. To reject the offload, we need to know the port type, and patch 2/15 helps with that. xrs700x is another driver which should have rejected offload based on port type (patch 4/15). This is a bug fix submitted through net-next due to the extra API required to fix it. If necessary, it could also be picked up separately for backporting. There is also patch 3/15, which makes the HSR offload like the others supported by DSA: if we fall back to the software implementation, don't call port_hsr_leave(), because by definition there won't be anything to do. A slightly unrelated change is patch 1/15, but I noticed this along the way, and if I were to submit it separately, it would conflict with this work (it would appear in patch 12/15's context). Most of the driver additions are trivial. By far the most complex was ocelot (which I could test). Microchip ksz (which I cannot test, and did not patch) would also have some complexity. Essentially, ksz_hsr_join() could fall back to a partial offload through the simple helpers, if the full offload is not possible. But keeping track of which offload kind was used is necessary later in ksz_hsr_leave(). This is left as homework for interested developers. With this patch set, one can observe a 50% reduction in transmitted traffic over HSR interfaces. ==================== Link: https://patch.msgid.link/20251130131657.65080-1-vladimir.oltean@nxp.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
@@ -1104,12 +1104,11 @@ health of the network and for discovery of other nodes.
|
||||
In Linux, both HSR and PRP are implemented in the hsr driver, which
|
||||
instantiates a virtual, stackable network interface with two member ports.
|
||||
The driver only implements the basic roles of DANH (Doubly Attached Node
|
||||
implementing HSR) and DANP (Doubly Attached Node implementing PRP); the roles
|
||||
of RedBox and QuadBox are not implemented (therefore, bridging a hsr network
|
||||
interface with a physical switch port does not produce the expected result).
|
||||
implementing HSR), DANP (Doubly Attached Node implementing PRP) and RedBox
|
||||
(allows non-HSR devices to connect to the ring via Interlink ports).
|
||||
|
||||
A driver which is able of offloading certain functions of a DANP or DANH should
|
||||
declare the corresponding netdev features as indicated by the documentation at
|
||||
A driver which is able of offloading certain functions should declare the
|
||||
corresponding netdev features as indicated by the documentation at
|
||||
``Documentation/networking/netdev-features.rst``. Additionally, the following
|
||||
methods must be implemented:
|
||||
|
||||
@@ -1120,6 +1119,14 @@ methods must be implemented:
|
||||
- ``port_hsr_leave``: function invoked when a given switch port leaves a
|
||||
DANP/DANH and returns to normal operation as a standalone port.
|
||||
|
||||
Note that the ``NETIF_F_HW_HSR_DUP`` feature relies on transmission towards
|
||||
multiple ports, which is generally available whenever the tagging protocol uses
|
||||
the ``dsa_xmit_port_mask()`` helper function. If the helper is used, the HSR
|
||||
offload feature should also be set. The ``dsa_port_simple_hsr_join()`` and
|
||||
``dsa_port_simple_hsr_leave()`` methods can be used as generic implementations
|
||||
of ``port_hsr_join`` and ``port_hsr_leave``, if this is the only supported
|
||||
offload feature.
|
||||
|
||||
TODO
|
||||
====
|
||||
|
||||
|
||||
@@ -1926,6 +1926,8 @@ static const struct dsa_switch_ops hellcreek_ds_ops = {
|
||||
.port_vlan_filtering = hellcreek_vlan_filtering,
|
||||
.setup = hellcreek_setup,
|
||||
.teardown = hellcreek_teardown,
|
||||
.port_hsr_join = dsa_port_simple_hsr_join,
|
||||
.port_hsr_leave = dsa_port_simple_hsr_leave,
|
||||
};
|
||||
|
||||
static int hellcreek_probe(struct platform_device *pdev)
|
||||
|
||||
@@ -1652,6 +1652,8 @@ static const struct dsa_switch_ops gswip_switch_ops = {
|
||||
.get_sset_count = gswip_get_sset_count,
|
||||
.set_mac_eee = gswip_set_mac_eee,
|
||||
.support_eee = gswip_support_eee,
|
||||
.port_hsr_join = dsa_port_simple_hsr_join,
|
||||
.port_hsr_leave = dsa_port_simple_hsr_leave,
|
||||
};
|
||||
|
||||
void gswip_disable_switch(struct gswip_priv *priv)
|
||||
|
||||
@@ -3254,7 +3254,7 @@ static int mt7988_setup(struct dsa_switch *ds)
|
||||
return mt7531_setup_common(ds);
|
||||
}
|
||||
|
||||
const struct dsa_switch_ops mt7530_switch_ops = {
|
||||
static const struct dsa_switch_ops mt7530_switch_ops = {
|
||||
.get_tag_protocol = mtk_get_tag_protocol,
|
||||
.setup = mt753x_setup,
|
||||
.preferred_default_local_cpu_port = mt753x_preferred_default_local_cpu_port,
|
||||
@@ -3290,8 +3290,9 @@ const struct dsa_switch_ops mt7530_switch_ops = {
|
||||
.set_mac_eee = mt753x_set_mac_eee,
|
||||
.conduit_state_change = mt753x_conduit_state_change,
|
||||
.port_setup_tc = mt753x_setup_tc,
|
||||
.port_hsr_join = dsa_port_simple_hsr_join,
|
||||
.port_hsr_leave = dsa_port_simple_hsr_leave,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(mt7530_switch_ops);
|
||||
|
||||
static const struct phylink_mac_ops mt753x_phylink_mac_ops = {
|
||||
.mac_select_pcs = mt753x_phylink_mac_select_pcs,
|
||||
|
||||
@@ -939,7 +939,6 @@ static inline void INIT_MT7530_DUMMY_POLL(struct mt7530_dummy_poll *p,
|
||||
int mt7530_probe_common(struct mt7530_priv *priv);
|
||||
void mt7530_remove_common(struct mt7530_priv *priv);
|
||||
|
||||
extern const struct dsa_switch_ops mt7530_switch_ops;
|
||||
extern const struct mt753x_info mt753x_table[];
|
||||
|
||||
#endif /* __MT7530_H */
|
||||
|
||||
@@ -297,6 +297,8 @@ static const struct dsa_switch_ops mv88e6060_switch_ops = {
|
||||
.phy_read = mv88e6060_phy_read,
|
||||
.phy_write = mv88e6060_phy_write,
|
||||
.phylink_get_caps = mv88e6060_phylink_get_caps,
|
||||
.port_hsr_join = dsa_port_simple_hsr_join,
|
||||
.port_hsr_leave = dsa_port_simple_hsr_leave,
|
||||
};
|
||||
|
||||
static int mv88e6060_probe(struct mdio_device *mdiodev)
|
||||
|
||||
@@ -1233,6 +1233,7 @@ static int felix_port_enable(struct dsa_switch *ds, int port,
|
||||
{
|
||||
struct dsa_port *dp = dsa_to_port(ds, port);
|
||||
struct ocelot *ocelot = ds->priv;
|
||||
struct felix *felix = ocelot_to_felix(ocelot);
|
||||
|
||||
if (!dsa_port_is_user(dp))
|
||||
return 0;
|
||||
@@ -1246,7 +1247,25 @@ static int felix_port_enable(struct dsa_switch *ds, int port,
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
if (!dp->hsr_dev || felix->tag_proto == DSA_TAG_PROTO_OCELOT_8021Q)
|
||||
return 0;
|
||||
|
||||
return dsa_port_simple_hsr_join(ds, port, dp->hsr_dev, NULL);
|
||||
}
|
||||
|
||||
static void felix_port_disable(struct dsa_switch *ds, int port)
|
||||
{
|
||||
struct dsa_port *dp = dsa_to_port(ds, port);
|
||||
struct ocelot *ocelot = ds->priv;
|
||||
struct felix *felix = ocelot_to_felix(ocelot);
|
||||
|
||||
if (!dsa_port_is_user(dp))
|
||||
return;
|
||||
|
||||
if (!dp->hsr_dev || felix->tag_proto == DSA_TAG_PROTO_OCELOT_8021Q)
|
||||
return;
|
||||
|
||||
dsa_port_simple_hsr_leave(ds, port, dp->hsr_dev);
|
||||
}
|
||||
|
||||
static void felix_port_qos_map_init(struct ocelot *ocelot, int port)
|
||||
@@ -2232,6 +2251,52 @@ static void felix_get_mm_stats(struct dsa_switch *ds, int port,
|
||||
ocelot_port_get_mm_stats(ocelot, port, stats);
|
||||
}
|
||||
|
||||
/* Depending on port type, we may be able to support the offload later (with
|
||||
* the "ocelot"/"seville" tagging protocols), or never.
|
||||
* If we return 0, the dp->hsr_dev reference is kept for later; if we return
|
||||
* -EOPNOTSUPP, it is cleared (which helps to not bother
|
||||
* dsa_port_simple_hsr_leave() with an offload that didn't pass validation).
|
||||
*/
|
||||
static int felix_port_hsr_join(struct dsa_switch *ds, int port,
|
||||
struct net_device *hsr,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct ocelot *ocelot = ds->priv;
|
||||
struct felix *felix = ocelot_to_felix(ocelot);
|
||||
|
||||
if (felix->tag_proto == DSA_TAG_PROTO_OCELOT_8021Q) {
|
||||
int err;
|
||||
|
||||
err = dsa_port_simple_hsr_validate(ds, port, hsr, extack);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
NL_SET_ERR_MSG_MOD(extack,
|
||||
"Offloading not supported with \"ocelot-8021q\"");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!(dsa_to_port(ds, port)->user->flags & IFF_UP))
|
||||
return 0;
|
||||
|
||||
return dsa_port_simple_hsr_join(ds, port, hsr, extack);
|
||||
}
|
||||
|
||||
static int felix_port_hsr_leave(struct dsa_switch *ds, int port,
|
||||
struct net_device *hsr)
|
||||
{
|
||||
struct ocelot *ocelot = ds->priv;
|
||||
struct felix *felix = ocelot_to_felix(ocelot);
|
||||
|
||||
if (felix->tag_proto == DSA_TAG_PROTO_OCELOT_8021Q)
|
||||
return 0;
|
||||
|
||||
if (!(dsa_to_port(ds, port)->user->flags & IFF_UP))
|
||||
return 0;
|
||||
|
||||
return dsa_port_simple_hsr_leave(ds, port, hsr);
|
||||
}
|
||||
|
||||
static const struct phylink_mac_ops felix_phylink_mac_ops = {
|
||||
.mac_select_pcs = felix_phylink_mac_select_pcs,
|
||||
.mac_config = felix_phylink_mac_config,
|
||||
@@ -2262,6 +2327,7 @@ static const struct dsa_switch_ops felix_switch_ops = {
|
||||
.get_ts_info = felix_get_ts_info,
|
||||
.phylink_get_caps = felix_phylink_get_caps,
|
||||
.port_enable = felix_port_enable,
|
||||
.port_disable = felix_port_disable,
|
||||
.port_fast_age = felix_port_fast_age,
|
||||
.port_fdb_dump = felix_fdb_dump,
|
||||
.port_fdb_add = felix_fdb_add,
|
||||
@@ -2318,6 +2384,8 @@ static const struct dsa_switch_ops felix_switch_ops = {
|
||||
.port_del_dscp_prio = felix_port_del_dscp_prio,
|
||||
.port_set_host_flood = felix_port_set_host_flood,
|
||||
.port_change_conduit = felix_port_change_conduit,
|
||||
.port_hsr_join = felix_port_hsr_join,
|
||||
.port_hsr_leave = felix_port_hsr_leave,
|
||||
};
|
||||
|
||||
int felix_register_switch(struct device *dev, resource_size_t switch_base,
|
||||
|
||||
@@ -2134,6 +2134,8 @@ static const struct dsa_switch_ops rtl8365mb_switch_ops = {
|
||||
.get_stats64 = rtl8365mb_get_stats64,
|
||||
.port_change_mtu = rtl8365mb_port_change_mtu,
|
||||
.port_max_mtu = rtl8365mb_port_max_mtu,
|
||||
.port_hsr_join = dsa_port_simple_hsr_join,
|
||||
.port_hsr_leave = dsa_port_simple_hsr_leave,
|
||||
};
|
||||
|
||||
static const struct realtek_ops rtl8365mb_ops = {
|
||||
|
||||
@@ -1815,6 +1815,8 @@ static const struct dsa_switch_ops rtl8366rb_switch_ops = {
|
||||
.port_fast_age = rtl8366rb_port_fast_age,
|
||||
.port_change_mtu = rtl8366rb_change_mtu,
|
||||
.port_max_mtu = rtl8366rb_max_mtu,
|
||||
.port_hsr_join = dsa_port_simple_hsr_join,
|
||||
.port_hsr_leave = dsa_port_simple_hsr_leave,
|
||||
};
|
||||
|
||||
static const struct realtek_ops rtl8366rb_ops = {
|
||||
|
||||
@@ -1035,6 +1035,8 @@ static const struct dsa_switch_ops a5psw_switch_ops = {
|
||||
.port_fdb_add = a5psw_port_fdb_add,
|
||||
.port_fdb_del = a5psw_port_fdb_del,
|
||||
.port_fdb_dump = a5psw_port_fdb_dump,
|
||||
.port_hsr_join = dsa_port_simple_hsr_join,
|
||||
.port_hsr_leave = dsa_port_simple_hsr_leave,
|
||||
};
|
||||
|
||||
static int a5psw_mdio_wait_busy(struct a5psw *a5psw)
|
||||
|
||||
@@ -566,6 +566,7 @@ static int xrs700x_hsr_join(struct dsa_switch *ds, int port,
|
||||
struct xrs700x *priv = ds->priv;
|
||||
struct net_device *user;
|
||||
int ret, i, hsr_pair[2];
|
||||
enum hsr_port_type type;
|
||||
enum hsr_version ver;
|
||||
bool fwd = false;
|
||||
|
||||
@@ -589,6 +590,16 @@ static int xrs700x_hsr_join(struct dsa_switch *ds, int port,
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
ret = hsr_get_port_type(hsr, dsa_to_port(ds, port)->user, &type);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (type != HSR_PT_SLAVE_A && type != HSR_PT_SLAVE_B) {
|
||||
NL_SET_ERR_MSG_MOD(extack,
|
||||
"Only HSR slave ports can be offloaded");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
dsa_hsr_foreach_port(dp, ds, hsr) {
|
||||
if (dp->index != port) {
|
||||
partner = dp;
|
||||
|
||||
@@ -2874,6 +2874,9 @@ static const struct dsa_switch_ops yt921x_dsa_switch_ops = {
|
||||
/* mtu */
|
||||
.port_change_mtu = yt921x_dsa_port_change_mtu,
|
||||
.port_max_mtu = yt921x_dsa_port_max_mtu,
|
||||
/* hsr */
|
||||
.port_hsr_leave = dsa_port_simple_hsr_leave,
|
||||
.port_hsr_join = dsa_port_simple_hsr_join,
|
||||
/* mirror */
|
||||
.port_mirror_del = yt921x_dsa_port_mirror_del,
|
||||
.port_mirror_add = yt921x_dsa_port_mirror_add,
|
||||
|
||||
@@ -43,6 +43,8 @@ extern bool is_hsr_master(struct net_device *dev);
|
||||
extern int hsr_get_version(struct net_device *dev, enum hsr_version *ver);
|
||||
struct net_device *hsr_get_port_ndev(struct net_device *ndev,
|
||||
enum hsr_port_type pt);
|
||||
int hsr_get_port_type(struct net_device *hsr_dev, struct net_device *dev,
|
||||
enum hsr_port_type *type);
|
||||
#else
|
||||
static inline bool is_hsr_master(struct net_device *dev)
|
||||
{
|
||||
@@ -59,6 +61,13 @@ static inline struct net_device *hsr_get_port_ndev(struct net_device *ndev,
|
||||
{
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
static inline int hsr_get_port_type(struct net_device *hsr_dev,
|
||||
struct net_device *dev,
|
||||
enum hsr_port_type *type)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
#endif /* CONFIG_HSR */
|
||||
|
||||
#endif /*_LINUX_IF_HSR_H_*/
|
||||
|
||||
@@ -1322,6 +1322,15 @@ bool dsa_mdb_present_in_other_db(struct dsa_switch *ds, int port,
|
||||
const struct switchdev_obj_port_mdb *mdb,
|
||||
struct dsa_db db);
|
||||
|
||||
int dsa_port_simple_hsr_validate(struct dsa_switch *ds, int port,
|
||||
struct net_device *hsr,
|
||||
struct netlink_ext_ack *extack);
|
||||
int dsa_port_simple_hsr_join(struct dsa_switch *ds, int port,
|
||||
struct net_device *hsr,
|
||||
struct netlink_ext_ack *extack);
|
||||
int dsa_port_simple_hsr_leave(struct dsa_switch *ds, int port,
|
||||
struct net_device *hsr);
|
||||
|
||||
/* Keep inline for faster access in hot path */
|
||||
static inline bool netdev_uses_dsa(const struct net_device *dev)
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/if_hsr.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/netdevice.h>
|
||||
@@ -1766,6 +1767,70 @@ bool dsa_mdb_present_in_other_db(struct dsa_switch *ds, int port,
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dsa_mdb_present_in_other_db);
|
||||
|
||||
/* Helpers for switches without specific HSR offloads, but which can implement
|
||||
* NETIF_F_HW_HSR_DUP because their tagger uses dsa_xmit_port_mask()
|
||||
*/
|
||||
int dsa_port_simple_hsr_validate(struct dsa_switch *ds, int port,
|
||||
struct net_device *hsr,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
enum hsr_port_type type;
|
||||
int err;
|
||||
|
||||
err = hsr_get_port_type(hsr, dsa_to_port(ds, port)->user, &type);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (type != HSR_PT_SLAVE_A && type != HSR_PT_SLAVE_B) {
|
||||
NL_SET_ERR_MSG_MOD(extack,
|
||||
"Only HSR slave ports can be offloaded");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dsa_port_simple_hsr_validate);
|
||||
|
||||
int dsa_port_simple_hsr_join(struct dsa_switch *ds, int port,
|
||||
struct net_device *hsr,
|
||||
struct netlink_ext_ack *extack)
|
||||
{
|
||||
struct dsa_port *dp = dsa_to_port(ds, port), *other_dp;
|
||||
int err;
|
||||
|
||||
err = dsa_port_simple_hsr_validate(ds, port, hsr, extack);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
dsa_hsr_foreach_port(other_dp, ds, hsr) {
|
||||
if (other_dp != dp) {
|
||||
dp->user->features |= NETIF_F_HW_HSR_DUP;
|
||||
other_dp->user->features |= NETIF_F_HW_HSR_DUP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dsa_port_simple_hsr_join);
|
||||
|
||||
int dsa_port_simple_hsr_leave(struct dsa_switch *ds, int port,
|
||||
struct net_device *hsr)
|
||||
{
|
||||
struct dsa_port *dp = dsa_to_port(ds, port), *other_dp;
|
||||
|
||||
dsa_hsr_foreach_port(other_dp, ds, hsr) {
|
||||
if (other_dp != dp) {
|
||||
dp->user->features &= ~NETIF_F_HW_HSR_DUP;
|
||||
other_dp->user->features &= ~NETIF_F_HW_HSR_DUP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dsa_port_simple_hsr_leave);
|
||||
|
||||
static const struct dsa_stubs __dsa_stubs = {
|
||||
.conduit_hwtstamp_validate = __dsa_conduit_hwtstamp_validate,
|
||||
};
|
||||
|
||||
@@ -1909,6 +1909,9 @@ void dsa_port_hsr_leave(struct dsa_port *dp, struct net_device *hsr)
|
||||
struct dsa_switch *ds = dp->ds;
|
||||
int err;
|
||||
|
||||
if (!dp->hsr_dev)
|
||||
return;
|
||||
|
||||
dp->hsr_dev = NULL;
|
||||
|
||||
if (ds->ops->port_hsr_leave) {
|
||||
|
||||
@@ -690,6 +690,26 @@ struct net_device *hsr_get_port_ndev(struct net_device *ndev,
|
||||
}
|
||||
EXPORT_SYMBOL(hsr_get_port_ndev);
|
||||
|
||||
int hsr_get_port_type(struct net_device *hsr_dev, struct net_device *dev,
|
||||
enum hsr_port_type *type)
|
||||
{
|
||||
struct hsr_priv *hsr = netdev_priv(hsr_dev);
|
||||
struct hsr_port *port;
|
||||
|
||||
rcu_read_lock();
|
||||
hsr_for_each_port(hsr, port) {
|
||||
if (port->dev == dev) {
|
||||
*type = port->type;
|
||||
rcu_read_unlock();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
rcu_read_unlock();
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
EXPORT_SYMBOL(hsr_get_port_type);
|
||||
|
||||
/* Default multicast address for HSR Supervision frames */
|
||||
static const unsigned char def_multicast_addr[ETH_ALEN] __aligned(2) = {
|
||||
0x01, 0x15, 0x4e, 0x00, 0x01, 0x00
|
||||
|
||||
@@ -207,14 +207,14 @@ int hsr_add_port(struct hsr_priv *hsr, struct net_device *dev,
|
||||
port->type = type;
|
||||
ether_addr_copy(port->original_macaddress, dev->dev_addr);
|
||||
|
||||
list_add_tail_rcu(&port->port_list, &hsr->ports);
|
||||
|
||||
if (type != HSR_PT_MASTER) {
|
||||
res = hsr_portdev_setup(hsr, dev, port, extack);
|
||||
if (res)
|
||||
goto fail_dev_setup;
|
||||
}
|
||||
|
||||
list_add_tail_rcu(&port->port_list, &hsr->ports);
|
||||
|
||||
master = hsr_port_get_hsr(hsr, HSR_PT_MASTER);
|
||||
netdev_update_features(master->dev);
|
||||
dev_set_mtu(master->dev, hsr_get_max_mtu(hsr));
|
||||
@@ -222,7 +222,8 @@ int hsr_add_port(struct hsr_priv *hsr, struct net_device *dev,
|
||||
return 0;
|
||||
|
||||
fail_dev_setup:
|
||||
kfree(port);
|
||||
list_del_rcu(&port->port_list);
|
||||
kfree_rcu(port, rcu);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user