Loading drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c +154 −4 Original line number Diff line number Diff line Loading @@ -14,9 +14,11 @@ */ #include <net/sock.h> #include <net/addrconf.h> #include <linux/module.h> #include <linux/netlink.h> #include <linux/netdevice.h> #include <linux/inetdevice.h> #include "rmnet_config.h" #include "rmnet_handlers.h" #include "rmnet_vnd.h" Loading Loading @@ -178,6 +180,9 @@ static int rmnet_newlink(struct net *src_net, struct net_device *dev, int err = 0; u16 mux_id; data_format = RMNET_INGRESS_FORMAT_IP_ROUTE | RMNET_EGRESS_FORMAT_IP_ROUTE; real_dev = __dev_get_by_index(src_net, nla_get_u32(tb[IFLA_LINK])); if (!real_dev || !dev) return -ENODEV; Loading Loading @@ -739,6 +744,132 @@ EXPORT_SYMBOL(rmnet_get_dlmarker_info); #endif struct rmnet_endpoint *rmnet_get_ip6_route_endpoint(struct rmnet_port *port, struct in6_addr *addr) { struct rmnet_endpoint *ep, *tmp = NULL; hlist_for_each_entry_rcu(ep, &port->muxed_ep[0], hlnode) { if (!memcmp(&ep->in6addr, addr, sizeof(struct in6_addr))) return ep; tmp = ep; } return tmp; } struct rmnet_endpoint *rmnet_get_ip4_route_endpoint(struct rmnet_port *port, __be32 *ifa_address) { struct rmnet_endpoint *ep, *tmp = NULL; hlist_for_each_entry_rcu(ep, &port->muxed_ep[0], hlnode) { if (!memcmp(&ep->ifa_address, ifa_address, sizeof(__be32))) return ep; tmp = ep; } return tmp; } static int rmnet_addr6_event(struct notifier_block *unused, unsigned long event, void *ptr) { struct inet6_ifaddr *if6 = (struct inet6_ifaddr *)ptr; struct net_device *dev = (struct net_device *)if6->idev->dev; struct rmnet_endpoint *ep; struct net_device *real_dev; struct rmnet_priv *priv; struct rmnet_port *port; if (!netif_is_rmnet(dev)) return NOTIFY_OK; priv = netdev_priv(dev); real_dev = priv->real_dev; port = rmnet_get_port_rtnl(real_dev); if (!port || port->data_format & ~(RMNET_INGRESS_FORMAT_IP_ROUTE | RMNET_EGRESS_FORMAT_IP_ROUTE)) return NOTIFY_OK; switch (event) { case NETDEV_UP: ep = kzalloc(sizeof(*ep), GFP_ATOMIC); if (!ep) return NOTIFY_OK; memcpy(&ep->in6addr, &if6->addr, sizeof(struct in6_addr)); ep->egress_dev = dev; hlist_add_head_rcu(&ep->hlnode, &port->muxed_ep[0]); break; case NETDEV_DOWN: ep = rmnet_get_ip6_route_endpoint(port, &if6->addr); if (!ep) return NOTIFY_OK; hlist_del_init_rcu(&ep->hlnode); kfree(ep); } return NOTIFY_OK; } static int rmnet_addr4_event(struct notifier_block *unused, unsigned long event, void *ptr) { struct in_ifaddr *if4 = (struct in_ifaddr *)ptr; struct net_device *dev = (struct net_device *)if4->ifa_dev->dev; struct rmnet_endpoint *ep; struct net_device *real_dev; struct rmnet_priv *priv; struct rmnet_port *port; if (!netif_is_rmnet(dev)) return NOTIFY_OK; priv = netdev_priv(dev); real_dev = priv->real_dev; port = rmnet_get_port_rtnl(real_dev); if (!port || port->data_format & ~(RMNET_INGRESS_FORMAT_IP_ROUTE | RMNET_EGRESS_FORMAT_IP_ROUTE)) return NOTIFY_OK; switch (event) { case NETDEV_UP: ep = kzalloc(sizeof(*ep), GFP_ATOMIC); if (!ep) return NOTIFY_OK; memcpy(&ep->ifa_address, &if4->ifa_address, sizeof(__be32)); ep->egress_dev = dev; hlist_add_head_rcu(&ep->hlnode, &port->muxed_ep[0]); break; case NETDEV_DOWN: ep = rmnet_get_ip4_route_endpoint(port, &if4->ifa_address); if (!ep) return NOTIFY_OK; hlist_del_init_rcu(&ep->hlnode); kfree(ep); } return NOTIFY_OK; } static struct notifier_block rmnet_addr6_notifier_block __read_mostly = { .notifier_call = rmnet_addr6_event, }; static struct notifier_block rmnet_addr4_notifier_block __read_mostly = { .notifier_call = rmnet_addr4_event, }; /* Startup/Shutdown */ static int __init rmnet_init(void) Loading @@ -750,15 +881,34 @@ static int __init rmnet_init(void) return rc; rc = rtnl_link_register(&rmnet_link_ops); if (rc != 0) { if (rc != 0) goto err0; rc = register_inet6addr_notifier(&rmnet_addr6_notifier_block); if (rc != 0) goto err1; rc = register_inetaddr_notifier(&rmnet_addr4_notifier_block); if (rc != 0) goto err2; return 0; err2: unregister_inet6addr_notifier(&rmnet_addr6_notifier_block); err1: rtnl_link_unregister(&rmnet_link_ops); err0: unregister_netdevice_notifier(&rmnet_dev_notifier); return rc; } return rc; } static void __exit rmnet_exit(void) { unregister_inetaddr_notifier(&rmnet_addr4_notifier_block); unregister_inet6addr_notifier(&rmnet_addr6_notifier_block); unregister_netdevice_notifier(&rmnet_dev_notifier); rtnl_link_unregister(&rmnet_link_ops); } Loading drivers/net/ethernet/qualcomm/rmnet/rmnet_config.h +15 −1 Original line number Diff line number Diff line Loading @@ -23,7 +23,11 @@ #define RMNET_MAX_VEID 4 struct rmnet_endpoint { union { u8 mux_id; __be32 ifa_address; struct in6_addr in6addr; }; struct net_device *egress_dev; struct hlist_node hlnode; }; Loading @@ -33,6 +37,11 @@ struct rmnet_agg_stats { u64 ul_agg_alloc; }; struct rmnet_ip_route_endpoint { struct in6_addr addr; struct net_device *egress_dev; }; struct rmnet_port_priv_stats { u64 dl_hdr_last_qmap_vers; u64 dl_hdr_last_ep_id; Loading Loading @@ -196,4 +205,9 @@ int rmnet_add_bridge(struct net_device *rmnet_dev, struct net_device *slave_dev); int rmnet_del_bridge(struct net_device *rmnet_dev, struct net_device *slave_dev); struct rmnet_endpoint *rmnet_get_ip6_route_endpoint(struct rmnet_port *port, struct in6_addr *addr); struct rmnet_endpoint *rmnet_get_ip4_route_endpoint(struct rmnet_port *port, __be32 *ifa_address); #endif /* _RMNET_CONFIG_H_ */ drivers/net/ethernet/qualcomm/rmnet/rmnet_handlers.c +83 −1 Original line number Diff line number Diff line /* Copyright (c) 2013-2019, The Linux Foundation. All rights reserved. /* Copyright (c) 2013-2020, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and Loading @@ -18,6 +18,7 @@ #include <linux/if_arp.h> #include <linux/ip.h> #include <linux/ipv6.h> #include <net/ip6_checksum.h> #include <net/sock.h> #include <linux/tracepoint.h> #include "rmnet_private.h" Loading Loading @@ -210,6 +211,77 @@ static void rmnet_deliver_skb_list(struct sk_buff_head *head, } } static void rmnet_ip_route_rcv(struct sk_buff *skb, struct rmnet_port *port) { struct rmnet_endpoint *ep; struct ipv6hdr *ip6h; int ip_len; __sum16 pseudo; __be16 frag_off; u16 pkt_len; u8 proto; skb_reset_transport_header(skb); skb_reset_network_header(skb); skb->pkt_type = PACKET_HOST; skb_set_mac_header(skb, 0); switch (rmnet_map_data_ptr(skb)[0] & 0xF0) { case RMNET_IP_VERSION_4: skb->protocol = htons(ETH_P_IP); ep = rmnet_get_ip4_route_endpoint(port, &(ip_hdr(skb)->daddr)); if (!ep) goto drop_skb; break; case RMNET_IP_VERSION_6: skb->protocol = htons(ETH_P_IPV6); ip6h = ipv6_hdr(skb); ep = rmnet_get_ip6_route_endpoint(port, &ip6h->daddr); if (!ep) goto drop_skb; proto = ip6h->nexthdr; ip_len = ipv6_skip_exthdr(skb, sizeof(*ip6h), &proto, &frag_off); if (ip_len < 0 || frag_off) break; pkt_len = skb->len - ip_len; pseudo = ~csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr, pkt_len, proto, 0); if (proto == IPPROTO_UDP) { struct udphdr *up = (struct udphdr *) (rmnet_map_data_ptr(skb) + ip_len); up->check = pseudo; skb->csum_offset = offsetof(struct udphdr, check); } else if (proto == IPPROTO_TCP) { struct tcphdr *tp = (struct tcphdr *) (rmnet_map_data_ptr(skb) + ip_len); tp->check = pseudo; skb->csum_offset = offsetof(struct tcphdr, check); } else { break; } skb->ip_summed = CHECKSUM_PARTIAL; skb->csum_start = skb->data + ip_len - skb->head; break; default: goto drop_skb; } skb->dev = ep->egress_dev; rmnet_vnd_rx_fixup(skb->dev, skb->len); netif_receive_skb(skb); return; drop_skb: kfree_skb(skb); } /* MAP handler */ static void Loading @@ -225,6 +297,11 @@ __rmnet_map_ingress_handler(struct sk_buff *skb, /* We don't need the spinlock since only we touch this */ __skb_queue_head_init(&list); if (port->data_format & RMNET_INGRESS_FORMAT_IP_ROUTE) { rmnet_ip_route_rcv(skb, port); return; } qmap = (struct rmnet_map_header *)rmnet_map_data_ptr(skb); if (qmap->cd_bit) { qmi_rmnet_set_dl_msg_active(port); Loading Loading @@ -474,6 +551,10 @@ void rmnet_egress_handler(struct sk_buff *skb) goto drop; skb_len = skb->len; if (port->data_format & RMNET_EGRESS_FORMAT_IP_ROUTE) goto direct_xmit; err = rmnet_map_egress_handler(skb, port, mux_id, orig_dev); if (err == -ENOMEM) goto drop; Loading @@ -482,6 +563,7 @@ void rmnet_egress_handler(struct sk_buff *skb) return; } direct_xmit: rmnet_vnd_tx_fixup(orig_dev, skb_len); dev_queue_xmit(skb); Loading drivers/net/ethernet/qualcomm/rmnet/rmnet_private.h +4 −0 Original line number Diff line number Diff line Loading @@ -36,6 +36,10 @@ RMNET_INGRESS_FORMAT_DL_MARKER_V2) /* UL Aggregation parameters */ #define RMNET_PAGE_RECYCLE BIT(0) /* IP-Mux feature */ #define RMNET_INGRESS_FORMAT_IP_ROUTE BIT(25) #define RMNET_EGRESS_FORMAT_IP_ROUTE BIT(24) /* Replace skb->dev to a virtual rmnet device and pass up the stack */ #define RMNET_EPMODE_VND (1) /* Pass the frame directly to another device with dev_queue_xmit() */ Loading drivers/net/ethernet/qualcomm/rmnet/rmnet_vnd.c +5 −0 Original line number Diff line number Diff line Loading @@ -411,3 +411,8 @@ int rmnet_vnd_do_flow_control(struct net_device *rmnet_dev, int enable) return 0; } int netif_is_rmnet(const struct net_device *dev) { return dev->netdev_ops == &rmnet_vnd_ops; } Loading
drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c +154 −4 Original line number Diff line number Diff line Loading @@ -14,9 +14,11 @@ */ #include <net/sock.h> #include <net/addrconf.h> #include <linux/module.h> #include <linux/netlink.h> #include <linux/netdevice.h> #include <linux/inetdevice.h> #include "rmnet_config.h" #include "rmnet_handlers.h" #include "rmnet_vnd.h" Loading Loading @@ -178,6 +180,9 @@ static int rmnet_newlink(struct net *src_net, struct net_device *dev, int err = 0; u16 mux_id; data_format = RMNET_INGRESS_FORMAT_IP_ROUTE | RMNET_EGRESS_FORMAT_IP_ROUTE; real_dev = __dev_get_by_index(src_net, nla_get_u32(tb[IFLA_LINK])); if (!real_dev || !dev) return -ENODEV; Loading Loading @@ -739,6 +744,132 @@ EXPORT_SYMBOL(rmnet_get_dlmarker_info); #endif struct rmnet_endpoint *rmnet_get_ip6_route_endpoint(struct rmnet_port *port, struct in6_addr *addr) { struct rmnet_endpoint *ep, *tmp = NULL; hlist_for_each_entry_rcu(ep, &port->muxed_ep[0], hlnode) { if (!memcmp(&ep->in6addr, addr, sizeof(struct in6_addr))) return ep; tmp = ep; } return tmp; } struct rmnet_endpoint *rmnet_get_ip4_route_endpoint(struct rmnet_port *port, __be32 *ifa_address) { struct rmnet_endpoint *ep, *tmp = NULL; hlist_for_each_entry_rcu(ep, &port->muxed_ep[0], hlnode) { if (!memcmp(&ep->ifa_address, ifa_address, sizeof(__be32))) return ep; tmp = ep; } return tmp; } static int rmnet_addr6_event(struct notifier_block *unused, unsigned long event, void *ptr) { struct inet6_ifaddr *if6 = (struct inet6_ifaddr *)ptr; struct net_device *dev = (struct net_device *)if6->idev->dev; struct rmnet_endpoint *ep; struct net_device *real_dev; struct rmnet_priv *priv; struct rmnet_port *port; if (!netif_is_rmnet(dev)) return NOTIFY_OK; priv = netdev_priv(dev); real_dev = priv->real_dev; port = rmnet_get_port_rtnl(real_dev); if (!port || port->data_format & ~(RMNET_INGRESS_FORMAT_IP_ROUTE | RMNET_EGRESS_FORMAT_IP_ROUTE)) return NOTIFY_OK; switch (event) { case NETDEV_UP: ep = kzalloc(sizeof(*ep), GFP_ATOMIC); if (!ep) return NOTIFY_OK; memcpy(&ep->in6addr, &if6->addr, sizeof(struct in6_addr)); ep->egress_dev = dev; hlist_add_head_rcu(&ep->hlnode, &port->muxed_ep[0]); break; case NETDEV_DOWN: ep = rmnet_get_ip6_route_endpoint(port, &if6->addr); if (!ep) return NOTIFY_OK; hlist_del_init_rcu(&ep->hlnode); kfree(ep); } return NOTIFY_OK; } static int rmnet_addr4_event(struct notifier_block *unused, unsigned long event, void *ptr) { struct in_ifaddr *if4 = (struct in_ifaddr *)ptr; struct net_device *dev = (struct net_device *)if4->ifa_dev->dev; struct rmnet_endpoint *ep; struct net_device *real_dev; struct rmnet_priv *priv; struct rmnet_port *port; if (!netif_is_rmnet(dev)) return NOTIFY_OK; priv = netdev_priv(dev); real_dev = priv->real_dev; port = rmnet_get_port_rtnl(real_dev); if (!port || port->data_format & ~(RMNET_INGRESS_FORMAT_IP_ROUTE | RMNET_EGRESS_FORMAT_IP_ROUTE)) return NOTIFY_OK; switch (event) { case NETDEV_UP: ep = kzalloc(sizeof(*ep), GFP_ATOMIC); if (!ep) return NOTIFY_OK; memcpy(&ep->ifa_address, &if4->ifa_address, sizeof(__be32)); ep->egress_dev = dev; hlist_add_head_rcu(&ep->hlnode, &port->muxed_ep[0]); break; case NETDEV_DOWN: ep = rmnet_get_ip4_route_endpoint(port, &if4->ifa_address); if (!ep) return NOTIFY_OK; hlist_del_init_rcu(&ep->hlnode); kfree(ep); } return NOTIFY_OK; } static struct notifier_block rmnet_addr6_notifier_block __read_mostly = { .notifier_call = rmnet_addr6_event, }; static struct notifier_block rmnet_addr4_notifier_block __read_mostly = { .notifier_call = rmnet_addr4_event, }; /* Startup/Shutdown */ static int __init rmnet_init(void) Loading @@ -750,15 +881,34 @@ static int __init rmnet_init(void) return rc; rc = rtnl_link_register(&rmnet_link_ops); if (rc != 0) { if (rc != 0) goto err0; rc = register_inet6addr_notifier(&rmnet_addr6_notifier_block); if (rc != 0) goto err1; rc = register_inetaddr_notifier(&rmnet_addr4_notifier_block); if (rc != 0) goto err2; return 0; err2: unregister_inet6addr_notifier(&rmnet_addr6_notifier_block); err1: rtnl_link_unregister(&rmnet_link_ops); err0: unregister_netdevice_notifier(&rmnet_dev_notifier); return rc; } return rc; } static void __exit rmnet_exit(void) { unregister_inetaddr_notifier(&rmnet_addr4_notifier_block); unregister_inet6addr_notifier(&rmnet_addr6_notifier_block); unregister_netdevice_notifier(&rmnet_dev_notifier); rtnl_link_unregister(&rmnet_link_ops); } Loading
drivers/net/ethernet/qualcomm/rmnet/rmnet_config.h +15 −1 Original line number Diff line number Diff line Loading @@ -23,7 +23,11 @@ #define RMNET_MAX_VEID 4 struct rmnet_endpoint { union { u8 mux_id; __be32 ifa_address; struct in6_addr in6addr; }; struct net_device *egress_dev; struct hlist_node hlnode; }; Loading @@ -33,6 +37,11 @@ struct rmnet_agg_stats { u64 ul_agg_alloc; }; struct rmnet_ip_route_endpoint { struct in6_addr addr; struct net_device *egress_dev; }; struct rmnet_port_priv_stats { u64 dl_hdr_last_qmap_vers; u64 dl_hdr_last_ep_id; Loading Loading @@ -196,4 +205,9 @@ int rmnet_add_bridge(struct net_device *rmnet_dev, struct net_device *slave_dev); int rmnet_del_bridge(struct net_device *rmnet_dev, struct net_device *slave_dev); struct rmnet_endpoint *rmnet_get_ip6_route_endpoint(struct rmnet_port *port, struct in6_addr *addr); struct rmnet_endpoint *rmnet_get_ip4_route_endpoint(struct rmnet_port *port, __be32 *ifa_address); #endif /* _RMNET_CONFIG_H_ */
drivers/net/ethernet/qualcomm/rmnet/rmnet_handlers.c +83 −1 Original line number Diff line number Diff line /* Copyright (c) 2013-2019, The Linux Foundation. All rights reserved. /* Copyright (c) 2013-2020, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and Loading @@ -18,6 +18,7 @@ #include <linux/if_arp.h> #include <linux/ip.h> #include <linux/ipv6.h> #include <net/ip6_checksum.h> #include <net/sock.h> #include <linux/tracepoint.h> #include "rmnet_private.h" Loading Loading @@ -210,6 +211,77 @@ static void rmnet_deliver_skb_list(struct sk_buff_head *head, } } static void rmnet_ip_route_rcv(struct sk_buff *skb, struct rmnet_port *port) { struct rmnet_endpoint *ep; struct ipv6hdr *ip6h; int ip_len; __sum16 pseudo; __be16 frag_off; u16 pkt_len; u8 proto; skb_reset_transport_header(skb); skb_reset_network_header(skb); skb->pkt_type = PACKET_HOST; skb_set_mac_header(skb, 0); switch (rmnet_map_data_ptr(skb)[0] & 0xF0) { case RMNET_IP_VERSION_4: skb->protocol = htons(ETH_P_IP); ep = rmnet_get_ip4_route_endpoint(port, &(ip_hdr(skb)->daddr)); if (!ep) goto drop_skb; break; case RMNET_IP_VERSION_6: skb->protocol = htons(ETH_P_IPV6); ip6h = ipv6_hdr(skb); ep = rmnet_get_ip6_route_endpoint(port, &ip6h->daddr); if (!ep) goto drop_skb; proto = ip6h->nexthdr; ip_len = ipv6_skip_exthdr(skb, sizeof(*ip6h), &proto, &frag_off); if (ip_len < 0 || frag_off) break; pkt_len = skb->len - ip_len; pseudo = ~csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr, pkt_len, proto, 0); if (proto == IPPROTO_UDP) { struct udphdr *up = (struct udphdr *) (rmnet_map_data_ptr(skb) + ip_len); up->check = pseudo; skb->csum_offset = offsetof(struct udphdr, check); } else if (proto == IPPROTO_TCP) { struct tcphdr *tp = (struct tcphdr *) (rmnet_map_data_ptr(skb) + ip_len); tp->check = pseudo; skb->csum_offset = offsetof(struct tcphdr, check); } else { break; } skb->ip_summed = CHECKSUM_PARTIAL; skb->csum_start = skb->data + ip_len - skb->head; break; default: goto drop_skb; } skb->dev = ep->egress_dev; rmnet_vnd_rx_fixup(skb->dev, skb->len); netif_receive_skb(skb); return; drop_skb: kfree_skb(skb); } /* MAP handler */ static void Loading @@ -225,6 +297,11 @@ __rmnet_map_ingress_handler(struct sk_buff *skb, /* We don't need the spinlock since only we touch this */ __skb_queue_head_init(&list); if (port->data_format & RMNET_INGRESS_FORMAT_IP_ROUTE) { rmnet_ip_route_rcv(skb, port); return; } qmap = (struct rmnet_map_header *)rmnet_map_data_ptr(skb); if (qmap->cd_bit) { qmi_rmnet_set_dl_msg_active(port); Loading Loading @@ -474,6 +551,10 @@ void rmnet_egress_handler(struct sk_buff *skb) goto drop; skb_len = skb->len; if (port->data_format & RMNET_EGRESS_FORMAT_IP_ROUTE) goto direct_xmit; err = rmnet_map_egress_handler(skb, port, mux_id, orig_dev); if (err == -ENOMEM) goto drop; Loading @@ -482,6 +563,7 @@ void rmnet_egress_handler(struct sk_buff *skb) return; } direct_xmit: rmnet_vnd_tx_fixup(orig_dev, skb_len); dev_queue_xmit(skb); Loading
drivers/net/ethernet/qualcomm/rmnet/rmnet_private.h +4 −0 Original line number Diff line number Diff line Loading @@ -36,6 +36,10 @@ RMNET_INGRESS_FORMAT_DL_MARKER_V2) /* UL Aggregation parameters */ #define RMNET_PAGE_RECYCLE BIT(0) /* IP-Mux feature */ #define RMNET_INGRESS_FORMAT_IP_ROUTE BIT(25) #define RMNET_EGRESS_FORMAT_IP_ROUTE BIT(24) /* Replace skb->dev to a virtual rmnet device and pass up the stack */ #define RMNET_EPMODE_VND (1) /* Pass the frame directly to another device with dev_queue_xmit() */ Loading
drivers/net/ethernet/qualcomm/rmnet/rmnet_vnd.c +5 −0 Original line number Diff line number Diff line Loading @@ -411,3 +411,8 @@ int rmnet_vnd_do_flow_control(struct net_device *rmnet_dev, int enable) return 0; } int netif_is_rmnet(const struct net_device *dev) { return dev->netdev_ops == &rmnet_vnd_ops; }