12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
21da177e4SLinus Torvalds /*
31da177e4SLinus Torvalds * Forwarding decision
41da177e4SLinus Torvalds * Linux ethernet bridge
51da177e4SLinus Torvalds *
61da177e4SLinus Torvalds * Authors:
71da177e4SLinus Torvalds * Lennert Buytenhek <buytenh@gnu.org>
81da177e4SLinus Torvalds */
91da177e4SLinus Torvalds
10025d89c2SHerbert Xu #include <linux/err.h>
115a0e3ad6STejun Heo #include <linux/slab.h>
121da177e4SLinus Torvalds #include <linux/kernel.h>
131da177e4SLinus Torvalds #include <linux/netdevice.h>
14c06ee961SWANG Cong #include <linux/netpoll.h>
151da177e4SLinus Torvalds #include <linux/skbuff.h>
1685ca719eSStephen Hemminger #include <linux/if_vlan.h>
171da177e4SLinus Torvalds #include <linux/netfilter_bridge.h>
181da177e4SLinus Torvalds #include "br_private.h"
191da177e4SLinus Torvalds
201a81a2e0Stanxiaojun /* Don't forward packets to originating port or forwarding disabled */
should_deliver(const struct net_bridge_port * p,const struct sk_buff * skb)211da177e4SLinus Torvalds static inline int should_deliver(const struct net_bridge_port *p,
221da177e4SLinus Torvalds const struct sk_buff *skb)
231da177e4SLinus Torvalds {
242594e906SNikolay Aleksandrov struct net_bridge_vlan_group *vg;
252594e906SNikolay Aleksandrov
26907b1e6eSNikolay Aleksandrov vg = nbp_vlan_group_rcu(p);
27a97bfc1dStanxiaojun return ((p->flags & BR_HAIRPIN_MODE) || skb->dev != p->dev) &&
28*c43046d7SElliot Ayrey (br_mst_is_enabled(p->br) || p->state == BR_STATE_FORWARDING) &&
29*c43046d7SElliot Ayrey br_allowed_egress(vg, skb) && nbp_switchdev_allowed_egress(p, skb) &&
307d850abdSNikolay Aleksandrov !br_skb_isolated(p, skb);
311da177e4SLinus Torvalds }
321da177e4SLinus Torvalds
br_dev_queue_push_xmit(struct net * net,struct sock * sk,struct sk_buff * skb)330c4b51f0SEric W. Biederman int br_dev_queue_push_xmit(struct net *net, struct sock *sk, struct sk_buff *skb)
341da177e4SLinus Torvalds {
3528c1382fSYunjian Wang skb_push(skb, ETH_HLEN);
36df356d5eSToshiaki Makita if (!is_skb_forwardable(skb->dev, skb))
37df356d5eSToshiaki Makita goto drop;
38df356d5eSToshiaki Makita
39a881e963SPeter Huang (Peng) br_drop_fake_rtable(skb);
40df356d5eSToshiaki Makita
41df356d5eSToshiaki Makita if (skb->ip_summed == CHECKSUM_PARTIAL &&
42a98c0c47SMenglong Dong eth_type_vlan(skb->protocol)) {
43df356d5eSToshiaki Makita int depth;
44df356d5eSToshiaki Makita
454063384eSEric Dumazet if (!vlan_get_protocol_and_depth(skb, skb->protocol, &depth))
46df356d5eSToshiaki Makita goto drop;
47df356d5eSToshiaki Makita
48df356d5eSToshiaki Makita skb_set_network_header(skb, depth);
491da177e4SLinus Torvalds }
501da177e4SLinus Torvalds
51c5381154SVladimir Oltean br_switchdev_frame_set_offload_fwd_mark(skb);
5247211192STobias Waldekranz
53df356d5eSToshiaki Makita dev_queue_xmit(skb);
54df356d5eSToshiaki Makita
55df356d5eSToshiaki Makita return 0;
56df356d5eSToshiaki Makita
57df356d5eSToshiaki Makita drop:
58df356d5eSToshiaki Makita kfree_skb(skb);
591da177e4SLinus Torvalds return 0;
601da177e4SLinus Torvalds }
6134666d46SPablo Neira Ayuso EXPORT_SYMBOL_GPL(br_dev_queue_push_xmit);
621da177e4SLinus Torvalds
br_forward_finish(struct net * net,struct sock * sk,struct sk_buff * skb)630c4b51f0SEric W. Biederman int br_forward_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
641da177e4SLinus Torvalds {
65de799101SMartin KaFai Lau skb_clear_tstamp(skb);
6629a26a56SEric W. Biederman return NF_HOOK(NFPROTO_BRIDGE, NF_BR_POST_ROUTING,
6729a26a56SEric W. Biederman net, sk, skb, NULL, skb->dev,
681da177e4SLinus Torvalds br_dev_queue_push_xmit);
691da177e4SLinus Torvalds
701da177e4SLinus Torvalds }
7134666d46SPablo Neira Ayuso EXPORT_SYMBOL_GPL(br_forward_finish);
721da177e4SLinus Torvalds
__br_forward(const struct net_bridge_port * to,struct sk_buff * skb,bool local_orig)7337b090e6SNikolay Aleksandrov static void __br_forward(const struct net_bridge_port *to,
7437b090e6SNikolay Aleksandrov struct sk_buff *skb, bool local_orig)
751da177e4SLinus Torvalds {
762594e906SNikolay Aleksandrov struct net_bridge_vlan_group *vg;
771da177e4SLinus Torvalds struct net_device *indev;
7837b090e6SNikolay Aleksandrov struct net *net;
7937b090e6SNikolay Aleksandrov int br_hook;
804906f998SHerbert Xu
8147211192STobias Waldekranz /* Mark the skb for forwarding offload early so that br_handle_vlan()
8247211192STobias Waldekranz * can know whether to pop the VLAN header on egress or keep it.
8347211192STobias Waldekranz */
8447211192STobias Waldekranz nbp_switchdev_frame_mark_tx_fwd_offload(to, skb);
8547211192STobias Waldekranz
86907b1e6eSNikolay Aleksandrov vg = nbp_vlan_group_rcu(to);
8711538d03SRoopa Prabhu skb = br_handle_vlan(to->br, to, vg, skb);
8878851988SVlad Yasevich if (!skb)
8978851988SVlad Yasevich return;
9078851988SVlad Yasevich
911da177e4SLinus Torvalds indev = skb->dev;
921da177e4SLinus Torvalds skb->dev = to->dev;
9337b090e6SNikolay Aleksandrov if (!local_orig) {
9437b090e6SNikolay Aleksandrov if (skb_warn_if_lro(skb)) {
9537b090e6SNikolay Aleksandrov kfree_skb(skb);
9637b090e6SNikolay Aleksandrov return;
9737b090e6SNikolay Aleksandrov }
9837b090e6SNikolay Aleksandrov br_hook = NF_BR_FORWARD;
9935fc92a9SHerbert Xu skb_forward_csum(skb);
10037b090e6SNikolay Aleksandrov net = dev_net(indev);
10137b090e6SNikolay Aleksandrov } else {
10237b090e6SNikolay Aleksandrov if (unlikely(netpoll_tx_running(to->br->dev))) {
10337b090e6SNikolay Aleksandrov skb_push(skb, ETH_HLEN);
10428c1382fSYunjian Wang if (!is_skb_forwardable(skb->dev, skb))
10528c1382fSYunjian Wang kfree_skb(skb);
10628c1382fSYunjian Wang else
10737b090e6SNikolay Aleksandrov br_netpoll_send_skb(to, skb);
10837b090e6SNikolay Aleksandrov return;
10937b090e6SNikolay Aleksandrov }
11037b090e6SNikolay Aleksandrov br_hook = NF_BR_LOCAL_OUT;
11137b090e6SNikolay Aleksandrov net = dev_net(skb->dev);
11237b090e6SNikolay Aleksandrov indev = NULL;
11337b090e6SNikolay Aleksandrov }
1141da177e4SLinus Torvalds
11537b090e6SNikolay Aleksandrov NF_HOOK(NFPROTO_BRIDGE, br_hook,
11637b090e6SNikolay Aleksandrov net, NULL, skb, indev, skb->dev,
1171da177e4SLinus Torvalds br_forward_finish);
1181da177e4SLinus Torvalds }
1191da177e4SLinus Torvalds
deliver_clone(const struct net_bridge_port * prev,struct sk_buff * skb,bool local_orig)12087faf3ccSDavid S. Miller static int deliver_clone(const struct net_bridge_port *prev,
12137b090e6SNikolay Aleksandrov struct sk_buff *skb, bool local_orig)
122025d89c2SHerbert Xu {
123025d89c2SHerbert Xu struct net_device *dev = BR_INPUT_SKB_CB(skb)->brdev;
124025d89c2SHerbert Xu
125fed396a5SHerbert Xu skb = skb_clone(skb, GFP_ATOMIC);
126fed396a5SHerbert Xu if (!skb) {
12744bdb313SEric Dumazet DEV_STATS_INC(dev, tx_dropped);
128025d89c2SHerbert Xu return -ENOMEM;
129025d89c2SHerbert Xu }
130025d89c2SHerbert Xu
13137b090e6SNikolay Aleksandrov __br_forward(prev, skb, local_orig);
132025d89c2SHerbert Xu return 0;
133025d89c2SHerbert Xu }
134025d89c2SHerbert Xu
13537b090e6SNikolay Aleksandrov /**
13637b090e6SNikolay Aleksandrov * br_forward - forward a packet to a specific port
13737b090e6SNikolay Aleksandrov * @to: destination port
13837b090e6SNikolay Aleksandrov * @skb: packet being forwarded
13937b090e6SNikolay Aleksandrov * @local_rcv: packet will be received locally after forwarding
14037b090e6SNikolay Aleksandrov * @local_orig: packet is locally originated
14137b090e6SNikolay Aleksandrov *
14237b090e6SNikolay Aleksandrov * Should be called with rcu_read_lock.
14337b090e6SNikolay Aleksandrov */
br_forward(const struct net_bridge_port * to,struct sk_buff * skb,bool local_rcv,bool local_orig)14437b090e6SNikolay Aleksandrov void br_forward(const struct net_bridge_port *to,
14537b090e6SNikolay Aleksandrov struct sk_buff *skb, bool local_rcv, bool local_orig)
14637b090e6SNikolay Aleksandrov {
1472756f68cSNikolay Aleksandrov if (unlikely(!to))
1482756f68cSNikolay Aleksandrov goto out;
1492756f68cSNikolay Aleksandrov
1502756f68cSNikolay Aleksandrov /* redirect to backup link if the destination port is down */
1512756f68cSNikolay Aleksandrov if (rcu_access_pointer(to->backup_port) && !netif_carrier_ok(to->dev)) {
1522756f68cSNikolay Aleksandrov struct net_bridge_port *backup_port;
1532756f68cSNikolay Aleksandrov
1542756f68cSNikolay Aleksandrov backup_port = rcu_dereference(to->backup_port);
1552756f68cSNikolay Aleksandrov if (unlikely(!backup_port))
1562756f68cSNikolay Aleksandrov goto out;
15729cfb2aaSIdo Schimmel BR_INPUT_SKB_CB(skb)->backup_nhid = READ_ONCE(to->backup_nhid);
1582756f68cSNikolay Aleksandrov to = backup_port;
1592756f68cSNikolay Aleksandrov }
1602756f68cSNikolay Aleksandrov
1612756f68cSNikolay Aleksandrov if (should_deliver(to, skb)) {
16237b090e6SNikolay Aleksandrov if (local_rcv)
16337b090e6SNikolay Aleksandrov deliver_clone(to, skb, local_orig);
16437b090e6SNikolay Aleksandrov else
16537b090e6SNikolay Aleksandrov __br_forward(to, skb, local_orig);
16637b090e6SNikolay Aleksandrov return;
16737b090e6SNikolay Aleksandrov }
16837b090e6SNikolay Aleksandrov
1692756f68cSNikolay Aleksandrov out:
17037b090e6SNikolay Aleksandrov if (!local_rcv)
17137b090e6SNikolay Aleksandrov kfree_skb(skb);
17237b090e6SNikolay Aleksandrov }
17337b090e6SNikolay Aleksandrov EXPORT_SYMBOL_GPL(br_forward);
17437b090e6SNikolay Aleksandrov
maybe_deliver(struct net_bridge_port * prev,struct net_bridge_port * p,struct sk_buff * skb,bool local_orig)175025d89c2SHerbert Xu static struct net_bridge_port *maybe_deliver(
176025d89c2SHerbert Xu struct net_bridge_port *prev, struct net_bridge_port *p,
17737b090e6SNikolay Aleksandrov struct sk_buff *skb, bool local_orig)
178025d89c2SHerbert Xu {
179847d44efSPablo Neira Ayuso u8 igmp_type = br_multicast_igmp_type(skb);
180025d89c2SHerbert Xu int err;
181025d89c2SHerbert Xu
182025d89c2SHerbert Xu if (!should_deliver(p, skb))
183025d89c2SHerbert Xu return prev;
184025d89c2SHerbert Xu
18547211192STobias Waldekranz nbp_switchdev_frame_mark_tx_fwd_to_hwdom(p, skb);
18647211192STobias Waldekranz
187025d89c2SHerbert Xu if (!prev)
188025d89c2SHerbert Xu goto out;
189025d89c2SHerbert Xu
19037b090e6SNikolay Aleksandrov err = deliver_clone(prev, skb, local_orig);
191025d89c2SHerbert Xu if (err)
192025d89c2SHerbert Xu return ERR_PTR(err);
193025d89c2SHerbert Xu out:
194847d44efSPablo Neira Ayuso br_multicast_count(p->br, p, skb, igmp_type, BR_MCAST_DIR_TX);
195847d44efSPablo Neira Ayuso
196025d89c2SHerbert Xu return p;
197025d89c2SHerbert Xu }
198025d89c2SHerbert Xu
19937b090e6SNikolay Aleksandrov /* called under rcu_read_lock */
br_flood(struct net_bridge * br,struct sk_buff * skb,enum br_pkt_type pkt_type,bool local_rcv,bool local_orig,u16 vid)20037b090e6SNikolay Aleksandrov void br_flood(struct net_bridge *br, struct sk_buff *skb,
201e408336aSIdo Schimmel enum br_pkt_type pkt_type, bool local_rcv, bool local_orig,
202e408336aSIdo Schimmel u16 vid)
2031da177e4SLinus Torvalds {
20437b090e6SNikolay Aleksandrov struct net_bridge_port *prev = NULL;
2051080ab95SNikolay Aleksandrov struct net_bridge_port *p;
2061da177e4SLinus Torvalds
2077b4858dfSIdo Schimmel br_tc_skb_miss_set(skb, pkt_type != BR_PKT_BROADCAST);
2087b4858dfSIdo Schimmel
2091da177e4SLinus Torvalds list_for_each_entry_rcu(p, &br->port_list, list) {
21099f906e9SMike Manning /* Do not flood unicast traffic to ports that turn it off, nor
21199f906e9SMike Manning * other traffic if flood off, except for traffic we originate
21299f906e9SMike Manning */
21399f906e9SMike Manning switch (pkt_type) {
21499f906e9SMike Manning case BR_PKT_UNICAST:
21599f906e9SMike Manning if (!(p->flags & BR_FLOOD))
216867a5943SVlad Yasevich continue;
21799f906e9SMike Manning break;
21899f906e9SMike Manning case BR_PKT_MULTICAST:
21999f906e9SMike Manning if (!(p->flags & BR_MCAST_FLOOD) && skb->dev != br->dev)
220b6cb5ac8SNikolay Aleksandrov continue;
22199f906e9SMike Manning break;
22299f906e9SMike Manning case BR_PKT_BROADCAST:
22399f906e9SMike Manning if (!(p->flags & BR_BCAST_FLOOD) && skb->dev != br->dev)
22499f906e9SMike Manning continue;
22599f906e9SMike Manning break;
22699f906e9SMike Manning }
22795850116SKyeyoon Park
22895850116SKyeyoon Park /* Do not flood to ports that enable proxy ARP */
22995850116SKyeyoon Park if (p->flags & BR_PROXYARP)
23095850116SKyeyoon Park continue;
231013a7ce8SIdo Schimmel if (BR_INPUT_SKB_CB(skb)->proxyarp_replied &&
2323aca683eSIdo Schimmel ((p->flags & BR_PROXYARP_WIFI) ||
2333aca683eSIdo Schimmel br_is_neigh_suppress_enabled(p, vid)))
234842a9ae0SJouni Malinen continue;
23595850116SKyeyoon Park
23637b090e6SNikolay Aleksandrov prev = maybe_deliver(prev, p, skb, local_orig);
237025d89c2SHerbert Xu if (IS_ERR(prev))
238b33084beSHerbert Xu goto out;
2391da177e4SLinus Torvalds }
2401da177e4SLinus Torvalds
241b33084beSHerbert Xu if (!prev)
242b33084beSHerbert Xu goto out;
243b33084beSHerbert Xu
244b35c5f63SNikolay Aleksandrov if (local_rcv)
24537b090e6SNikolay Aleksandrov deliver_clone(prev, skb, local_orig);
246025d89c2SHerbert Xu else
24737b090e6SNikolay Aleksandrov __br_forward(prev, skb, local_orig);
2481da177e4SLinus Torvalds return;
2491da177e4SLinus Torvalds
250b33084beSHerbert Xu out:
251b35c5f63SNikolay Aleksandrov if (!local_rcv)
2521da177e4SLinus Torvalds kfree_skb(skb);
2531da177e4SLinus Torvalds }
2541da177e4SLinus Torvalds
2555cb5e947SHerbert Xu #ifdef CONFIG_BRIDGE_IGMP_SNOOPING
maybe_deliver_addr(struct net_bridge_port * p,struct sk_buff * skb,const unsigned char * addr,bool local_orig)2565b9d6b15SArnd Bergmann static void maybe_deliver_addr(struct net_bridge_port *p, struct sk_buff *skb,
2575b9d6b15SArnd Bergmann const unsigned char *addr, bool local_orig)
2585b9d6b15SArnd Bergmann {
2595b9d6b15SArnd Bergmann struct net_device *dev = BR_INPUT_SKB_CB(skb)->brdev;
2605b9d6b15SArnd Bergmann const unsigned char *src = eth_hdr(skb)->h_source;
2611ce60741SFelix Fietkau struct sk_buff *nskb;
2625b9d6b15SArnd Bergmann
2635b9d6b15SArnd Bergmann if (!should_deliver(p, skb))
2645b9d6b15SArnd Bergmann return;
2655b9d6b15SArnd Bergmann
2665b9d6b15SArnd Bergmann /* Even with hairpin, no soliloquies - prevent breaking IPv6 DAD */
2675b9d6b15SArnd Bergmann if (skb->dev == p->dev && ether_addr_equal(src, addr))
2685b9d6b15SArnd Bergmann return;
2695b9d6b15SArnd Bergmann
2701ce60741SFelix Fietkau __skb_push(skb, ETH_HLEN);
2711ce60741SFelix Fietkau nskb = pskb_copy(skb, GFP_ATOMIC);
2721ce60741SFelix Fietkau __skb_pull(skb, ETH_HLEN);
2731ce60741SFelix Fietkau if (!nskb) {
27444bdb313SEric Dumazet DEV_STATS_INC(dev, tx_dropped);
2755b9d6b15SArnd Bergmann return;
2765b9d6b15SArnd Bergmann }
2775b9d6b15SArnd Bergmann
2781ce60741SFelix Fietkau skb = nskb;
2791ce60741SFelix Fietkau __skb_pull(skb, ETH_HLEN);
2805b9d6b15SArnd Bergmann if (!is_broadcast_ether_addr(addr))
2815b9d6b15SArnd Bergmann memcpy(eth_hdr(skb)->h_dest, addr, ETH_ALEN);
2825b9d6b15SArnd Bergmann
2835b9d6b15SArnd Bergmann __br_forward(p, skb, local_orig);
2845b9d6b15SArnd Bergmann }
2855b9d6b15SArnd Bergmann
2865cb5e947SHerbert Xu /* called with rcu_read_lock */
br_multicast_flood(struct net_bridge_mdb_entry * mdst,struct sk_buff * skb,struct net_bridge_mcast * brmctx,bool local_rcv,bool local_orig)28737b090e6SNikolay Aleksandrov void br_multicast_flood(struct net_bridge_mdb_entry *mdst,
288b35c5f63SNikolay Aleksandrov struct sk_buff *skb,
289adc47037SNikolay Aleksandrov struct net_bridge_mcast *brmctx,
29037b090e6SNikolay Aleksandrov bool local_rcv, bool local_orig)
2915cb5e947SHerbert Xu {
292afe0159dSstephen hemminger struct net_bridge_port *prev = NULL;
2935cb5e947SHerbert Xu struct net_bridge_port_group *p;
29436cfec73SNikolay Aleksandrov bool allow_mode_include = true;
2955cb5e947SHerbert Xu struct hlist_node *rp;
2965cb5e947SHerbert Xu
297adc47037SNikolay Aleksandrov rp = br_multicast_get_first_rport_node(brmctx, skb);
29844ebb081SLinus Lüssing
29936cfec73SNikolay Aleksandrov if (mdst) {
30036cfec73SNikolay Aleksandrov p = rcu_dereference(mdst->ports);
301adc47037SNikolay Aleksandrov if (br_multicast_should_handle_mode(brmctx, mdst->addr.proto) &&
30236cfec73SNikolay Aleksandrov br_multicast_is_star_g(&mdst->addr))
30336cfec73SNikolay Aleksandrov allow_mode_include = false;
30436cfec73SNikolay Aleksandrov } else {
30536cfec73SNikolay Aleksandrov p = NULL;
3067b4858dfSIdo Schimmel br_tc_skb_miss_set(skb, true);
30736cfec73SNikolay Aleksandrov }
30836cfec73SNikolay Aleksandrov
3095cb5e947SHerbert Xu while (p || rp) {
310afe0159dSstephen hemminger struct net_bridge_port *port, *lport, *rport;
311afe0159dSstephen hemminger
312085b53c8SNikolay Aleksandrov lport = p ? p->key.port : NULL;
31344ebb081SLinus Lüssing rport = br_multicast_rport_from_node_skb(rp, skb);
3145cb5e947SHerbert Xu
3156db6f0eaSFelix Fietkau if ((unsigned long)lport > (unsigned long)rport) {
3166db6f0eaSFelix Fietkau port = lport;
3176db6f0eaSFelix Fietkau
3186db6f0eaSFelix Fietkau if (port->flags & BR_MULTICAST_TO_UNICAST) {
3196db6f0eaSFelix Fietkau maybe_deliver_addr(lport, skb, p->eth_addr,
3206db6f0eaSFelix Fietkau local_orig);
3216db6f0eaSFelix Fietkau goto delivered;
3226db6f0eaSFelix Fietkau }
32336cfec73SNikolay Aleksandrov if ((!allow_mode_include &&
32436cfec73SNikolay Aleksandrov p->filter_mode == MCAST_INCLUDE) ||
32536cfec73SNikolay Aleksandrov (p->flags & MDB_PG_FLAGS_BLOCKED))
32636cfec73SNikolay Aleksandrov goto delivered;
3276db6f0eaSFelix Fietkau } else {
3286db6f0eaSFelix Fietkau port = rport;
3296db6f0eaSFelix Fietkau }
3305cb5e947SHerbert Xu
33137b090e6SNikolay Aleksandrov prev = maybe_deliver(prev, port, skb, local_orig);
3325cb5e947SHerbert Xu if (IS_ERR(prev))
3335cb5e947SHerbert Xu goto out;
334847d44efSPablo Neira Ayuso delivered:
3355cb5e947SHerbert Xu if ((unsigned long)lport >= (unsigned long)port)
33683f6a740Sstephen hemminger p = rcu_dereference(p->next);
3375cb5e947SHerbert Xu if ((unsigned long)rport >= (unsigned long)port)
338e8051688SEric Dumazet rp = rcu_dereference(hlist_next_rcu(rp));
3395cb5e947SHerbert Xu }
3405cb5e947SHerbert Xu
3415cb5e947SHerbert Xu if (!prev)
3425cb5e947SHerbert Xu goto out;
3435cb5e947SHerbert Xu
344b35c5f63SNikolay Aleksandrov if (local_rcv)
34537b090e6SNikolay Aleksandrov deliver_clone(prev, skb, local_orig);
3465cb5e947SHerbert Xu else
34737b090e6SNikolay Aleksandrov __br_forward(prev, skb, local_orig);
3485cb5e947SHerbert Xu return;
3495cb5e947SHerbert Xu
3505cb5e947SHerbert Xu out:
351b35c5f63SNikolay Aleksandrov if (!local_rcv)
3525cb5e947SHerbert Xu kfree_skb(skb);
3535cb5e947SHerbert Xu }
3545cb5e947SHerbert Xu #endif
355