diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c
index 61b04c6c0390..9e66f3804de4 100644
--- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c
+++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c
@@ -143,6 +143,14 @@ static int rmnet_newlink(struct net_device *dev,
return -ENODEV;
}
+ if (data[IFLA_RMNET_FLAGS]) {
+ struct ifla_rmnet_flags *flags;
+
+ flags = nla_data(data[IFLA_RMNET_FLAGS]);
+ data_format &= ~flags->mask;
+ data_format |= flags->flags & flags->mask;
+ }
+
ep = kzalloc_obj(*ep);
if (!ep)
return -ENOMEM;@@ -154,7 +162,8 @@ static int rmnet_newlink(struct net_device *dev,
goto err0;
port = rmnet_get_port_rtnl(real_dev);
- err = rmnet_vnd_newlink(mux_id, dev, port, real_dev, ep, extack);
+ err = rmnet_vnd_newlink(mux_id, dev, port, real_dev, ep, extack,
+ data_format);
if (err)
goto err1;
@@ -162,24 +171,25 @@ static int rmnet_newlink(struct net_device *dev,
if (err < 0)
goto err2;
+ /* Update existing MTUs before publishing the shared data format. */
+ err = rmnet_vnd_update_dev_mtu(port, real_dev, data_format);
+ if (err) {
+ NL_SET_ERR_MSG_MOD(extack, "Failed to update MTU of rmnet devices");
+ goto err3;
+ }
+
port->rmnet_mode = mode;
port->rmnet_dev = dev;
hlist_add_head_rcu(&ep->hlnode, &port->muxed_ep[mux_id]);
- if (data[IFLA_RMNET_FLAGS]) {
- struct ifla_rmnet_flags *flags;
-
- flags = nla_data(data[IFLA_RMNET_FLAGS]);
- data_format &= ~flags->mask;
- data_format |= flags->flags & flags->mask;
- }
-
netdev_dbg(dev, "data format [0x%08X]\n", data_format);
WRITE_ONCE(port->data_format, data_format);
return 0;
+err3:
+ netdev_upper_dev_unlink(real_dev, dev);
err2:
unregister_netdevice(dev);
rmnet_vnd_dellink(mux_id, port, ep);@@ -301,9 +311,14 @@ static int rmnet_changelink(struct net_device *dev, struct nlattr *tb[],
struct netlink_ext_ack *extack)
{
struct rmnet_priv *priv = netdev_priv(dev);
+ struct rmnet_endpoint *ep = NULL;
+ struct ifla_rmnet_flags *flags;
struct net_device *real_dev;
struct rmnet_port *port;
+ u32 old_data_format;
+ u32 data_format;
u16 mux_id;
+ int err;
if (!dev)
return -ENODEV;@@ -320,12 +335,17 @@ static int rmnet_changelink(struct net_device *dev, struct nlattr *tb[],
port = rmnet_get_port_rtnl(real_dev);
+ if (data[IFLA_RMNET_FLAGS]) {
+ old_data_format = READ_ONCE(port->data_format);
+ flags = nla_data(data[IFLA_RMNET_FLAGS]);
+ data_format = old_data_format & ~flags->mask;
+ data_format |= flags->flags & flags->mask;
+ }
+
if (data[IFLA_RMNET_MUX_ID]) {
mux_id = nla_get_u16(data[IFLA_RMNET_MUX_ID]);
if (mux_id != priv->mux_id) {
- struct rmnet_endpoint *ep;
-
ep = rmnet_get_endpoint(port, priv->mux_id);
if (!ep)
return -ENODEV;@@ -335,32 +355,29 @@ static int rmnet_changelink(struct net_device *dev, struct nlattr *tb[],
"MUX ID already exists");
return -EINVAL;
}
-
- hlist_del_init_rcu(&ep->hlnode);
- WRITE_ONCE(ep->mux_id, mux_id);
- hlist_add_head_rcu(&ep->hlnode,
- &port->muxed_ep[mux_id]);
-
- WRITE_ONCE(priv->mux_id, mux_id);
}
}
if (data[IFLA_RMNET_FLAGS]) {
- struct ifla_rmnet_flags *flags;
- u32 old_data_format;
- u32 data_format;
+ err = rmnet_vnd_update_dev_mtu(port, real_dev, data_format);
+ if (err) {
+ NL_SET_ERR_MSG_MOD(extack,
+ "Failed to update MTU of rmnet devices");
+ return err;
+ }
- old_data_format = port->data_format;
- flags = nla_data(data[IFLA_RMNET_FLAGS]);
- data_format = old_data_format & ~flags->mask;
- data_format |= flags->flags & flags->mask;
WRITE_ONCE(port->data_format, data_format);
+ }
- if (rmnet_vnd_update_dev_mtu(port, real_dev)) {
- WRITE_ONCE(port->data_format, old_data_format);
- NL_SET_ERR_MSG_MOD(extack, "Invalid MTU on real dev");
- return -EINVAL;
- }
+ /* Nothing below can fail, so move the endpoint only after all the
+ * checks and the MTU update have succeeded.
+ */
+ if (ep) {
+ hlist_del_init_rcu(&ep->hlnode);
+ WRITE_ONCE(ep->mux_id, mux_id);
+ hlist_add_head_rcu(&ep->hlnode, &port->muxed_ep[mux_id]);
+
+ WRITE_ONCE(priv->mux_id, mux_id);
}
return 0;diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.h b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.h
index 5adda0323dda..47b8db3206b9 100644
--- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.h
+++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.h
@@ -19,6 +19,7 @@ struct rmnet_endpoint {
struct net_device *egress_dev;
struct hlist_node hlnode;
struct rcu_head rcu;
+ int saved_mtu;
};
struct rmnet_egress_agg_params {diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_vnd.c b/drivers/net/ethernet/qualcomm/rmnet/rmnet_vnd.c
index 5f921cddf82b..dce436e4c630 100644
--- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_vnd.c
+++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_vnd.c
@@ -66,29 +66,44 @@ static netdev_tx_t rmnet_vnd_start_xmit(struct sk_buff *skb,
return NETDEV_TX_OK;
}
-static int rmnet_vnd_headroom(struct rmnet_port *port)
+static int rmnet_vnd_headroom(u32 data_format)
{
u32 headroom;
headroom = sizeof(struct rmnet_map_header);
- if (port->data_format & RMNET_FLAGS_EGRESS_MAP_CKSUMV4)
+ if (data_format & RMNET_FLAGS_EGRESS_MAP_CKSUMV4)
headroom += sizeof(struct rmnet_map_ul_csum_header);
+ else if (data_format & RMNET_FLAGS_EGRESS_MAP_CKSUMV5)
+ headroom += sizeof(struct rmnet_map_v5_csum_header);
return headroom;
}
-static int rmnet_vnd_change_mtu(struct net_device *rmnet_dev, int new_mtu)
+/* Largest MTU an rmnet device can use on top of real_dev for the given data
+ * format, or -EINVAL if real_dev cannot even fit the headers.
+ */
+static int rmnet_vnd_target_mtu(struct net_device *real_dev, u32 data_format)
{
- struct rmnet_priv *priv = netdev_priv(rmnet_dev);
- struct rmnet_port *port;
u32 headroom;
- port = rmnet_get_port_rtnl(priv->real_dev);
+ headroom = rmnet_vnd_headroom(data_format);
+ if (real_dev->mtu < headroom)
+ return -EINVAL;
+
+ return min_t(int, real_dev->mtu - headroom, RMNET_MAX_PACKET_SIZE);
+}
+
+static int rmnet_vnd_change_mtu_with_format(struct net_device *rmnet_dev,
+ int new_mtu, u32 data_format)
+{
+ struct rmnet_priv *priv = netdev_priv(rmnet_dev);
+ u32 headroom;
- headroom = rmnet_vnd_headroom(port);
+ headroom = rmnet_vnd_headroom(data_format);
- if (new_mtu < 0 || new_mtu > RMNET_MAX_PACKET_SIZE ||
+ if (priv->real_dev->mtu < headroom ||
+ new_mtu < 0 || new_mtu > RMNET_MAX_PACKET_SIZE ||
new_mtu > (priv->real_dev->mtu - headroom))
return -EINVAL;
@@ -96,6 +111,17 @@ static int rmnet_vnd_change_mtu(struct net_device *rmnet_dev, int new_mtu)
return 0;
}
+static int rmnet_vnd_change_mtu(struct net_device *rmnet_dev, int new_mtu)
+{
+ struct rmnet_priv *priv = netdev_priv(rmnet_dev);
+ struct rmnet_port *port;
+
+ port = rmnet_get_port_rtnl(priv->real_dev);
+
+ return rmnet_vnd_change_mtu_with_format(rmnet_dev, new_mtu,
+ READ_ONCE(port->data_format));
+}
+
static int rmnet_vnd_get_iflink(const struct net_device *dev)
{
struct rmnet_priv *priv = netdev_priv(dev);@@ -308,11 +334,10 @@ int rmnet_vnd_newlink(u8 id, struct net_device *rmnet_dev,
struct rmnet_port *port,
struct net_device *real_dev,
struct rmnet_endpoint *ep,
- struct netlink_ext_ack *extack)
-
+ struct netlink_ext_ack *extack, u32 data_format)
{
struct rmnet_priv *priv = netdev_priv(rmnet_dev);
- u32 headroom;
+ int new_mtu;
int rc;
if (rmnet_get_endpoint(port, id)) {@@ -326,9 +351,9 @@ int rmnet_vnd_newlink(u8 id, struct net_device *rmnet_dev,
priv->real_dev = real_dev;
- headroom = rmnet_vnd_headroom(port);
-
- if (rmnet_vnd_change_mtu(rmnet_dev, real_dev->mtu - headroom)) {
+ new_mtu = rmnet_vnd_target_mtu(real_dev, data_format);
+ if (new_mtu < 0 ||
+ rmnet_vnd_change_mtu_with_format(rmnet_dev, new_mtu, data_format)) {
NL_SET_ERR_MSG_MOD(extack, "Invalid MTU on real dev");
return -EINVAL;
}@@ -375,17 +400,17 @@ int rmnet_vnd_do_flow_control(struct net_device *rmnet_dev, int enable)
return 0;
}
-int rmnet_vnd_validate_real_dev_mtu(struct net_device *real_dev)
+static int rmnet_vnd_validate_mtu(struct rmnet_port *port,
+ struct net_device *real_dev, u32 data_format)
{
struct hlist_node *tmp_ep;
struct rmnet_endpoint *ep;
- struct rmnet_port *port;
unsigned long bkt_ep;
u32 headroom;
- port = rmnet_get_port_rtnl(real_dev);
-
- headroom = rmnet_vnd_headroom(port);
+ headroom = rmnet_vnd_headroom(data_format);
+ if (real_dev->mtu < headroom)
+ return -EINVAL;
hash_for_each_safe(port->muxed_ep, bkt_ep, tmp_ep, ep, hlnode) {
if (ep->egress_dev->mtu > (real_dev->mtu - headroom))@@ -395,24 +420,55 @@ int rmnet_vnd_validate_real_dev_mtu(struct net_device *real_dev)
return 0;
}
+int rmnet_vnd_validate_real_dev_mtu(struct net_device *real_dev)
+{
+ struct rmnet_port *port;
+
+ port = rmnet_get_port_rtnl(real_dev);
+
+ return rmnet_vnd_validate_mtu(port, real_dev,
+ READ_ONCE(port->data_format));
+}
+
int rmnet_vnd_update_dev_mtu(struct rmnet_port *port,
- struct net_device *real_dev)
+ struct net_device *real_dev, u32 data_format)
{
+ struct rmnet_endpoint *fail_ep = NULL;
struct hlist_node *tmp_ep;
struct rmnet_endpoint *ep;
unsigned long bkt_ep;
- u32 headroom;
+ int new_mtu;
+ int err = 0;
- headroom = rmnet_vnd_headroom(port);
+ new_mtu = rmnet_vnd_target_mtu(real_dev, data_format);
+ if (new_mtu < 0)
+ return new_mtu;
hash_for_each_safe(port->muxed_ep, bkt_ep, tmp_ep, ep, hlnode) {
- if (ep->egress_dev->mtu <= (real_dev->mtu - headroom))
+ ep->saved_mtu = ep->egress_dev->mtu;
+ if (ep->saved_mtu <= new_mtu)
continue;
- if (rmnet_vnd_change_mtu(ep->egress_dev,
- real_dev->mtu - headroom))
- return -1;
+ err = dev_set_mtu(ep->egress_dev, new_mtu);
+ if (err) {
+ fail_ep = ep;
+ break;
+ }
}
- return 0;
+ if (!err)
+ return 0;
+
+ /* Restore the endpoints which were already resized. Failures here
+ * are ignored since the original error is the one to report.
+ */
+ hash_for_each_safe(port->muxed_ep, bkt_ep, tmp_ep, ep, hlnode) {
+ if (ep == fail_ep)
+ break;
+
+ if (ep->egress_dev->mtu != ep->saved_mtu)
+ dev_set_mtu(ep->egress_dev, ep->saved_mtu);
+ }
+
+ return err;
}diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_vnd.h b/drivers/net/ethernet/qualcomm/rmnet/rmnet_vnd.h
index c2b2baf86894..901ef36900ac 100644
--- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_vnd.h
+++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_vnd.h
@@ -12,7 +12,7 @@ int rmnet_vnd_newlink(u8 id, struct net_device *rmnet_dev,
struct rmnet_port *port,
struct net_device *real_dev,
struct rmnet_endpoint *ep,
- struct netlink_ext_ack *extack);
+ struct netlink_ext_ack *extack, u32 data_format);
int rmnet_vnd_dellink(u8 id, struct rmnet_port *port,
struct rmnet_endpoint *ep);
void rmnet_vnd_rx_fixup(struct sk_buff *skb, struct net_device *dev);
@@ -21,5 +21,5 @@ void rmnet_vnd_tx_fixup(struct sk_buff *skb, struct net_device *dev);
void rmnet_vnd_setup(struct net_device *dev);
int rmnet_vnd_validate_real_dev_mtu(struct net_device *real_dev);
int rmnet_vnd_update_dev_mtu(struct rmnet_port *port,
- struct net_device *real_dev);
+ struct net_device *real_dev, u32 data_format);
#endif /* _RMNET_VND_H_ */
--
2.34.1