Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f266a683 authored by Arvid Brodin's avatar Arvid Brodin Committed by David S. Miller
Browse files

net/hsr: Better frame dispatch



This patch removes the separate paths for frames coming from the outside, and
frames sent from the HSR device, and instead makes all frames go through
hsr_forward_skb() in hsr_forward.c. This greatly improves code readability and
also opens up the possibility for future support of the HSR Interlink device
that is the basis for HSR RedBoxes and HSR QuadBoxes, as well as VLAN
compatibility.

Other improvements:
* A reduction in the number of times an skb is copied on machines without
  HAVE_EFFICIENT_UNALIGNED_ACCESS, which improves throughput somewhat.
* Headers are now created using the standard eth_header(), and using the
  standard hard_header_len.
* Each HSR slave now gets its own private skb, so slave-specific fields can be
  correctly set.

Signed-off-by: default avatarArvid Brodin <arvid.brodin@alten.se>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 4c3477dc
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -4,5 +4,5 @@

obj-$(CONFIG_HSR)	+= hsr.o

hsr-y			:= hsr_main.o hsr_framereg.o hsr_device.o hsr_netlink.o \
			   hsr_slave.o
hsr-y			:= hsr_main.o hsr_framereg.o hsr_device.o \
			   hsr_netlink.o hsr_slave.o hsr_forward.o
+23 −147
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@
#include "hsr_slave.h"
#include "hsr_framereg.h"
#include "hsr_main.h"
#include "hsr_forward.h"


static bool is_admin_up(struct net_device *dev)
@@ -231,141 +232,21 @@ static netdev_features_t hsr_fix_features(struct net_device *dev,
}


static void hsr_fill_tag(struct hsr_ethhdr *hsr_ethhdr, struct hsr_priv *hsr)
{
	unsigned long irqflags;

	/* IEC 62439-1:2010, p 48, says the 4-bit "path" field can take values
	 * between 0001-1001 ("ring identifier", for regular HSR frames),
	 * or 1111 ("HSR management", supervision frames). Unfortunately, the
	 * spec writers forgot to explain what a "ring identifier" is, or
	 * how it is used. So we just set this to 0001 for regular frames,
	 * and 1111 for supervision frames.
	 */
	set_hsr_tag_path(&hsr_ethhdr->hsr_tag, 0x1);

	/* IEC 62439-1:2010, p 12: "The link service data unit in an Ethernet
	 * frame is the content of the frame located between the Length/Type
	 * field and the Frame Check Sequence."
	 *
	 * IEC 62439-3, p 48, specifies the "original LPDU" to include the
	 * original "LT" field (what "LT" means is not explained anywhere as
	 * far as I can see - perhaps "Length/Type"?). So LSDU_size might
	 * equal original length + 2.
	 *   Also, the fact that this field is not used anywhere (might be used
	 * by a RedBox connecting HSR and PRP nets?) means I cannot test its
	 * correctness. Instead of guessing, I set this to 0 here, to make any
	 * problems immediately apparent. Anyone using this driver with PRP/HSR
	 * RedBoxes might need to fix this...
	 */
	set_hsr_tag_LSDU_size(&hsr_ethhdr->hsr_tag, 0);

	spin_lock_irqsave(&hsr->seqnr_lock, irqflags);
	hsr_ethhdr->hsr_tag.sequence_nr = htons(hsr->sequence_nr);
	hsr->sequence_nr++;
	spin_unlock_irqrestore(&hsr->seqnr_lock, irqflags);

	hsr_ethhdr->hsr_tag.encap_proto = hsr_ethhdr->ethhdr.h_proto;

	hsr_ethhdr->ethhdr.h_proto = htons(ETH_P_PRP);
}

static int slave_xmit(struct hsr_priv *hsr, struct sk_buff *skb,
		      enum hsr_port_type type)
{
	struct hsr_port *port;
	struct hsr_ethhdr *hsr_ethhdr;

	hsr_ethhdr = (struct hsr_ethhdr *) skb->data;

	rcu_read_lock();
	port = hsr_port_get_hsr(hsr, type);
	if (!port) {
		rcu_read_unlock();
		return NET_XMIT_DROP;
	}
	skb->dev = port->dev;

	hsr_addr_subst_dest(port->hsr, &hsr_ethhdr->ethhdr, port);
	rcu_read_unlock();

	/* Address substitution (IEC62439-3 pp 26, 50): replace mac
	 * address of outgoing frame with that of the outgoing slave's.
	 */
	ether_addr_copy(hsr_ethhdr->ethhdr.h_source, skb->dev->dev_addr);

	return dev_queue_xmit(skb);
}

static int hsr_dev_xmit(struct sk_buff *skb, struct net_device *dev)
{
	struct hsr_priv *hsr;
	struct hsr_priv *hsr = netdev_priv(dev);
	struct hsr_port *master;
	struct hsr_ethhdr *hsr_ethhdr;
	struct sk_buff *skb2;
	int res1, res2;

	hsr = netdev_priv(dev);
	hsr_ethhdr = (struct hsr_ethhdr *) skb->data;

	if ((skb->protocol != htons(ETH_P_PRP)) ||
	    (hsr_ethhdr->ethhdr.h_proto != htons(ETH_P_PRP))) {
		hsr_fill_tag(hsr_ethhdr, hsr);
		skb->protocol = htons(ETH_P_PRP);
	}

	skb2 = pskb_copy(skb, GFP_ATOMIC);

	res1 = slave_xmit(hsr, skb, HSR_PT_SLAVE_A);
	if (skb2)
		res2 = slave_xmit(hsr, skb2, HSR_PT_SLAVE_B);
	else
		res2 = NET_XMIT_DROP;

	rcu_read_lock();
	master = hsr_port_get_hsr(hsr, HSR_PT_MASTER);
	if (likely(res1 == NET_XMIT_SUCCESS || res1 == NET_XMIT_CN ||
		   res2 == NET_XMIT_SUCCESS || res2 == NET_XMIT_CN)) {
		master->dev->stats.tx_packets++;
		master->dev->stats.tx_bytes += skb->len;
	} else {
		master->dev->stats.tx_dropped++;
	}
	rcu_read_unlock();
	skb->dev = master->dev;
	hsr_forward_skb(skb, master);

	return NETDEV_TX_OK;
}


static int hsr_header_create(struct sk_buff *skb, struct net_device *dev,
			     unsigned short type, const void *daddr,
			     const void *saddr, unsigned int len)
{
	int res;

	/* Make room for the HSR tag now. We will fill it in later (in
	 * hsr_dev_xmit)
	 */
	if (skb_headroom(skb) < HSR_HLEN + ETH_HLEN)
		return -ENOBUFS;
	skb_push(skb, HSR_HLEN);

	/* To allow VLAN/HSR combos we should probably use
	 * res = dev_hard_header(skb, dev, type, daddr, saddr, len + HSR_HLEN);
	 * here instead. It would require other changes too, though - e.g.
	 * separate headers for each slave etc...
	 */
	res = eth_header(skb, dev, type, daddr, saddr, len + HSR_HLEN);
	if (res <= 0)
		return res;
	skb_reset_mac_header(skb);

	return res + HSR_HLEN;
}


static const struct header_ops hsr_header_ops = {
	.create	 = hsr_header_create,
	.create	 = eth_header,
	.parse	 = eth_header_parse,
};

@@ -382,19 +263,13 @@ static int hsr_pad(int size)
	return min_size;
}

static void send_hsr_supervision_frame(struct net_device *hsr_dev, u8 type)
static void send_hsr_supervision_frame(struct hsr_port *master, u8 type)
{
	struct hsr_priv *hsr;
	struct hsr_port *master;
	struct sk_buff *skb;
	int hlen, tlen;
	struct hsr_sup_tag *hsr_stag;
	struct hsr_sup_payload *hsr_sp;
	unsigned long irqflags;
	int res;

	hsr = netdev_priv(hsr_dev);
	master = hsr_port_get_hsr(hsr, HSR_PT_MASTER);

	hlen = LL_RESERVED_SPACE(master->dev);
	tlen = master->dev->needed_tailroom;
@@ -410,33 +285,30 @@ static void send_hsr_supervision_frame(struct net_device *hsr_dev, u8 type)
	skb->protocol = htons(ETH_P_PRP);
	skb->priority = TC_PRIO_CONTROL;

	res = dev_hard_header(skb, skb->dev, ETH_P_PRP,
			      hsr->sup_multicast_addr,
			      skb->dev->dev_addr, skb->len);
	if (res <= 0)
	if (dev_hard_header(skb, skb->dev, ETH_P_PRP,
			    master->hsr->sup_multicast_addr,
			    skb->dev->dev_addr, skb->len) <= 0)
		goto out;
	skb_reset_mac_header(skb);

	skb_pull(skb, sizeof(struct ethhdr));
	hsr_stag = (typeof(hsr_stag)) skb->data;
	hsr_stag = (typeof(hsr_stag)) skb_put(skb, sizeof(*hsr_stag));

	set_hsr_stag_path(hsr_stag, 0xf);
	set_hsr_stag_HSR_Ver(hsr_stag, 0);

	spin_lock_irqsave(&hsr->seqnr_lock, irqflags);
	hsr_stag->sequence_nr = htons(hsr->sequence_nr);
	hsr->sequence_nr++;
	spin_unlock_irqrestore(&hsr->seqnr_lock, irqflags);
	spin_lock_irqsave(&master->hsr->seqnr_lock, irqflags);
	hsr_stag->sequence_nr = htons(master->hsr->sequence_nr);
	master->hsr->sequence_nr++;
	spin_unlock_irqrestore(&master->hsr->seqnr_lock, irqflags);

	hsr_stag->HSR_TLV_Type = type;
	hsr_stag->HSR_TLV_Length = 12;

	skb_push(skb, sizeof(struct ethhdr));

	/* Payload: MacAddressA */
	hsr_sp = (typeof(hsr_sp)) skb_put(skb, sizeof(*hsr_sp));
	ether_addr_copy(hsr_sp->MacAddressA, master->dev->dev_addr);

	dev_queue_xmit(skb);
	hsr_forward_skb(skb, master);
	return;

out:
@@ -453,13 +325,15 @@ static void hsr_announce(unsigned long data)
	struct hsr_port *master;

	hsr = (struct hsr_priv *) data;

	rcu_read_lock();
	master = hsr_port_get_hsr(hsr, HSR_PT_MASTER);

	if (hsr->announce_count < 3) {
		send_hsr_supervision_frame(master->dev, HSR_TLV_ANNOUNCE);
		send_hsr_supervision_frame(master, HSR_TLV_ANNOUNCE);
		hsr->announce_count++;
	} else {
		send_hsr_supervision_frame(master->dev, HSR_TLV_LIFE_CHECK);
		send_hsr_supervision_frame(master, HSR_TLV_LIFE_CHECK);
	}

	if (hsr->announce_count < 3)
@@ -471,6 +345,8 @@ static void hsr_announce(unsigned long data)

	if (is_admin_up(master->dev))
		add_timer(&hsr->announce_timer);

	rcu_read_unlock();
}


@@ -570,7 +446,7 @@ int hsr_dev_finalize(struct net_device *hsr_dev, struct net_device *slave[2],

	spin_lock_init(&hsr->seqnr_lock);
	/* Overflow soon to find bugs easier: */
	hsr->sequence_nr = USHRT_MAX - 1024;
	hsr->sequence_nr = HSR_SEQNR_START;

	init_timer(&hsr->announce_timer);
	hsr->announce_timer.function = hsr_announce;

net/hsr/hsr_forward.c

0 → 100644
+368 −0
Original line number Diff line number Diff line
/* Copyright 2011-2014 Autronica Fire and Security AS
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * Author(s):
 *	2011-2014 Arvid Brodin, arvid.brodin@alten.se
 */

#include "hsr_forward.h"
#include <linux/types.h>
#include <linux/skbuff.h>
#include <linux/etherdevice.h>
#include <linux/if_vlan.h>
#include "hsr_main.h"
#include "hsr_framereg.h"


struct hsr_node;

struct hsr_frame_info {
	struct sk_buff *skb_std;
	struct sk_buff *skb_hsr;
	struct hsr_port *port_rcv;
	struct hsr_node *node_src;
	u16 sequence_nr;
	bool is_supervision;
	bool is_vlan;
	bool is_local_dest;
	bool is_local_exclusive;
};


/* The uses I can see for these HSR supervision frames are:
 * 1) Use the frames that are sent after node initialization ("HSR_TLV.Type =
 *    22") to reset any sequence_nr counters belonging to that node. Useful if
 *    the other node's counter has been reset for some reason.
 *    --
 *    Or not - resetting the counter and bridging the frame would create a
 *    loop, unfortunately.
 *
 * 2) Use the LifeCheck frames to detect ring breaks. I.e. if no LifeCheck
 *    frame is received from a particular node, we know something is wrong.
 *    We just register these (as with normal frames) and throw them away.
 *
 * 3) Allow different MAC addresses for the two slave interfaces, using the
 *    MacAddressA field.
 */
static bool is_supervision_frame(struct hsr_priv *hsr, struct sk_buff *skb)
{
	struct hsr_ethhdr_sp *hdr;

	WARN_ON_ONCE(!skb_mac_header_was_set(skb));
	hdr = (struct hsr_ethhdr_sp *) skb_mac_header(skb);

	if (!ether_addr_equal(hdr->ethhdr.h_dest,
			      hsr->sup_multicast_addr))
		return false;

	if (get_hsr_stag_path(&hdr->hsr_sup) != 0x0f)
		return false;
	if ((hdr->hsr_sup.HSR_TLV_Type != HSR_TLV_ANNOUNCE) &&
	    (hdr->hsr_sup.HSR_TLV_Type != HSR_TLV_LIFE_CHECK))
		return false;
	if (hdr->hsr_sup.HSR_TLV_Length != 12)
		return false;

	return true;
}


static struct sk_buff *create_stripped_skb(struct sk_buff *skb_in,
					   struct hsr_frame_info *frame)
{
	struct sk_buff *skb;
	int copylen;
	unsigned char *dst, *src;

	skb_pull(skb_in, HSR_HLEN);
	skb = __pskb_copy(skb_in, skb_headroom(skb_in) - HSR_HLEN, GFP_ATOMIC);
	skb_push(skb_in, HSR_HLEN);
	if (skb == NULL)
		return NULL;

	skb_reset_mac_header(skb);

	if (skb->ip_summed == CHECKSUM_PARTIAL)
		skb->csum_start -= HSR_HLEN;

	copylen = 2*ETH_ALEN;
	if (frame->is_vlan)
		copylen += VLAN_HLEN;
	src = skb_mac_header(skb_in);
	dst = skb_mac_header(skb);
	memcpy(dst, src, copylen);

	skb->protocol = eth_hdr(skb)->h_proto;
	return skb;
}

static struct sk_buff *frame_get_stripped_skb(struct hsr_frame_info *frame,
					      struct hsr_port *port)
{
	if (!frame->skb_std)
		frame->skb_std = create_stripped_skb(frame->skb_hsr, frame);
	return skb_clone(frame->skb_std, GFP_ATOMIC);
}


static void hsr_fill_tag(struct sk_buff *skb, struct hsr_frame_info *frame,
			 struct hsr_port *port)
{
	struct hsr_ethhdr *hsr_ethhdr;
	int lane_id;
	int lsdu_size;

	if (port->type == HSR_PT_SLAVE_A)
		lane_id = 0;
	else
		lane_id = 1;

	lsdu_size = skb->len - 14;
	if (frame->is_vlan)
		lsdu_size -= 4;

	hsr_ethhdr = (struct hsr_ethhdr *) skb_mac_header(skb);

	set_hsr_tag_path(&hsr_ethhdr->hsr_tag, lane_id);
	set_hsr_tag_LSDU_size(&hsr_ethhdr->hsr_tag, lsdu_size);
	hsr_ethhdr->hsr_tag.sequence_nr = htons(frame->sequence_nr);
	hsr_ethhdr->hsr_tag.encap_proto = hsr_ethhdr->ethhdr.h_proto;
	hsr_ethhdr->ethhdr.h_proto = htons(ETH_P_PRP);
}

static struct sk_buff *create_tagged_skb(struct sk_buff *skb_o,
					 struct hsr_frame_info *frame,
					 struct hsr_port *port)
{
	int movelen;
	unsigned char *dst, *src;
	struct sk_buff *skb;

	/* Create the new skb with enough headroom to fit the HSR tag */
	skb = __pskb_copy(skb_o, skb_headroom(skb_o) + HSR_HLEN, GFP_ATOMIC);
	if (skb == NULL)
		return NULL;
	skb_reset_mac_header(skb);

	if (skb->ip_summed == CHECKSUM_PARTIAL)
		skb->csum_start += HSR_HLEN;

	movelen = ETH_HLEN;
	if (frame->is_vlan)
		movelen += VLAN_HLEN;

	src = skb_mac_header(skb);
	dst = skb_push(skb, HSR_HLEN);
	memmove(dst, src, movelen);
	skb_reset_mac_header(skb);

	hsr_fill_tag(skb, frame, port);

	return skb;
}

/* If the original frame was an HSR tagged frame, just clone it to be sent
 * unchanged. Otherwise, create a private frame especially tagged for 'port'.
 */
static struct sk_buff *frame_get_tagged_skb(struct hsr_frame_info *frame,
					    struct hsr_port *port)
{
	if (frame->skb_hsr)
		return skb_clone(frame->skb_hsr, GFP_ATOMIC);

	if ((port->type != HSR_PT_SLAVE_A) && (port->type != HSR_PT_SLAVE_B)) {
		WARN_ONCE(1, "HSR: Bug: trying to create a tagged frame for a non-ring port");
		return NULL;
	}

	return create_tagged_skb(frame->skb_std, frame, port);
}


static void hsr_deliver_master(struct sk_buff *skb, struct net_device *dev,
			       struct hsr_node *node_src)
{
	bool was_multicast_frame;
	int res;

	was_multicast_frame = (skb->pkt_type == PACKET_MULTICAST);
	hsr_addr_subst_source(node_src, skb);
	skb_pull(skb, ETH_HLEN);
	res = netif_rx(skb);
	if (res == NET_RX_DROP) {
		dev->stats.rx_dropped++;
	} else {
		dev->stats.rx_packets++;
		dev->stats.rx_bytes += skb->len;
		if (was_multicast_frame)
			dev->stats.multicast++;
	}
}

static int hsr_xmit(struct sk_buff *skb, struct hsr_port *port,
		    struct hsr_frame_info *frame)
{
	if (frame->port_rcv->type == HSR_PT_MASTER) {
		hsr_addr_subst_dest(frame->node_src, skb, port);

		/* Address substitution (IEC62439-3 pp 26, 50): replace mac
		 * address of outgoing frame with that of the outgoing slave's.
		 */
		ether_addr_copy(eth_hdr(skb)->h_source, port->dev->dev_addr);
	}
	return dev_queue_xmit(skb);
}


/* Forward the frame through all devices except:
 * - Back through the receiving device
 * - If it's a HSR frame: through a device where it has passed before
 * - To the local HSR master only if the frame is directly addressed to it, or
 *   a non-supervision multicast or broadcast frame.
 *
 * HSR slave devices should insert a HSR tag into the frame, or forward the
 * frame unchanged if it's already tagged. Interlink devices should strip HSR
 * tags if they're of the non-HSR type (but only after duplicate discard). The
 * master device always strips HSR tags.
 */
static void hsr_forward_do(struct hsr_frame_info *frame)
{
	struct hsr_port *port;
	struct sk_buff *skb;

	hsr_for_each_port(frame->port_rcv->hsr, port) {
		/* Don't send frame back the way it came */
		if (port == frame->port_rcv)
			continue;

		/* Don't deliver locally unless we should */
		if ((port->type == HSR_PT_MASTER) && !frame->is_local_dest)
			continue;

		/* Deliver frames directly addressed to us to master only */
		if ((port->type != HSR_PT_MASTER) && frame->is_local_exclusive)
			continue;

		/* Don't send frame over port where it has been sent before */
		if (hsr_register_frame_out(port, frame->node_src,
					   frame->sequence_nr))
			continue;

		if (frame->is_supervision && (port->type == HSR_PT_MASTER)) {
			hsr_handle_sup_frame(frame->skb_hsr,
					     frame->node_src,
					     frame->port_rcv);
			continue;
		}

		if (port->type != HSR_PT_MASTER)
			skb = frame_get_tagged_skb(frame, port);
		else
			skb = frame_get_stripped_skb(frame, port);
		if (skb == NULL) {
			/* FIXME: Record the dropped frame? */
			continue;
		}

		skb->dev = port->dev;
		if (port->type == HSR_PT_MASTER)
			hsr_deliver_master(skb, port->dev, frame->node_src);
		else
			hsr_xmit(skb, port, frame);
	}
}


static void check_local_dest(struct hsr_priv *hsr, struct sk_buff *skb,
			     struct hsr_frame_info *frame)
{
	struct net_device *master_dev;

	master_dev = hsr_port_get_hsr(hsr, HSR_PT_MASTER)->dev;

	if (hsr_addr_is_self(hsr, eth_hdr(skb)->h_dest)) {
		frame->is_local_exclusive = true;
		skb->pkt_type = PACKET_HOST;
	} else {
		frame->is_local_exclusive = false;
	}

	if ((skb->pkt_type == PACKET_HOST) ||
	    (skb->pkt_type == PACKET_MULTICAST) ||
	    (skb->pkt_type == PACKET_BROADCAST)) {
		frame->is_local_dest = true;
	} else {
		frame->is_local_dest = false;
	}
}


static int hsr_fill_frame_info(struct hsr_frame_info *frame,
			       struct sk_buff *skb, struct hsr_port *port)
{
	struct ethhdr *ethhdr;
	unsigned long irqflags;

	frame->is_supervision = is_supervision_frame(port->hsr, skb);
	frame->node_src = hsr_get_node(&port->hsr->node_db, skb,
				       frame->is_supervision);
	if (frame->node_src == NULL)
		return -1; /* Unknown node and !is_supervision, or no mem */

	ethhdr = (struct ethhdr *) skb_mac_header(skb);
	frame->is_vlan = false;
	if (ethhdr->h_proto == htons(ETH_P_8021Q)) {
		frame->is_vlan = true;
		/* FIXME: */
		WARN_ONCE(1, "HSR: VLAN not yet supported");
	}
	if (ethhdr->h_proto == htons(ETH_P_PRP)) {
		frame->skb_std = NULL;
		frame->skb_hsr = skb;
		frame->sequence_nr = hsr_get_skb_sequence_nr(skb);
	} else {
		frame->skb_std = skb;
		frame->skb_hsr = NULL;
		/* Sequence nr for the master node */
		spin_lock_irqsave(&port->hsr->seqnr_lock, irqflags);
		frame->sequence_nr = port->hsr->sequence_nr;
		port->hsr->sequence_nr++;
		spin_unlock_irqrestore(&port->hsr->seqnr_lock, irqflags);
	}

	frame->port_rcv = port;
	check_local_dest(port->hsr, skb, frame);

	return 0;
}

/* Must be called holding rcu read lock (because of the port parameter) */
void hsr_forward_skb(struct sk_buff *skb, struct hsr_port *port)
{
	struct hsr_frame_info frame;

	if (skb_mac_header(skb) != skb->data) {
		WARN_ONCE(1, "%s:%d: Malformed frame (port_src %s)\n",
			  __FILE__, __LINE__, port->dev->name);
		goto out_drop;
	}

	if (hsr_fill_frame_info(&frame, skb, port) < 0)
		goto out_drop;
	hsr_register_frame_in(frame.node_src, port, frame.sequence_nr);
	hsr_forward_do(&frame);

	if (frame.skb_hsr != NULL)
		kfree_skb(frame.skb_hsr);
	if (frame.skb_std != NULL)
		kfree_skb(frame.skb_std);
	return;

out_drop:
	port->dev->stats.tx_dropped++;
	kfree_skb(skb);
}

net/hsr/hsr_forward.h

0 → 100644
+20 −0
Original line number Diff line number Diff line
/* Copyright 2011-2014 Autronica Fire and Security AS
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * Author(s):
 *	2011-2014 Arvid Brodin, arvid.brodin@alten.se
 */

#ifndef __HSR_FORWARD_H
#define __HSR_FORWARD_H

#include <linux/netdevice.h>
#include "hsr_main.h"

void hsr_forward_skb(struct sk_buff *skb, struct hsr_port *port);

#endif /* __HSR_FORWARD_H */
+161 −157

File changed.

Preview size limit exceeded, changes collapsed.

Loading