diff --git a/patchwork/parser.py b/patchwork/parser.py
index 794a5ea..c43fe76 100644
--- a/patchwork/parser.py
+++ b/patchwork/parser.py
@@ -911,7 +911,6 @@ def parse_mail(mail, list_id=None):
         #    * the version doesn't match
         #    * we have a patch with this number already
         if n and ((not series) or
-                  (series.version != version) or
                   (SeriesPatch.objects.filter(series=series, number=x).count()
                    )):
             series = Series(project=project,
diff --git a/patchwork/tests/series/base-different-versions.mbox b/patchwork/tests/series/base-different-versions.mbox
new file mode 100644
index 0000000..85bf888
--- /dev/null
+++ b/patchwork/tests/series/base-different-versions.mbox
@@ -0,0 +1,908 @@
+From f.fainelli@gmail.com Tue Jun 13 19:17:21 2017
+From: Florian Fainelli <f.fainelli@gmail.com>
+To: netdev@vger.kernel.org
+Cc: davem@davemloft.net, andrew@lunn.ch,
+	vivien.didelot@savoirfairelinux.com, john@phrozen.org,
+	Florian Fainelli <f.fainelli@gmail.com>
+Subject: [PATCH net-next v3 0/4] net: dsa: Multi-CPU ground work (v3)
+Date: Tue, 13 Jun 2017 12:17:21 -0700
+Message-Id: <20170613191725.26625-1-f.fainelli@gmail.com>
+X-Mailer: git-send-email 2.9.3
+List-ID: <netdev.vger.kernel.org>
+
+Hi all,
+
+This patch series prepares the ground for adding mutliple CPU port support to
+DSA, and starts by removing redundant pieces of information such as
+master_netdev which is cpu_dp->ethernet. Finally drivers are moved away from
+directly accessing ds->dst->cpu_dp and use appropriate helper functions.
+
+Note that if you have Device Tree blobs/platform configurations that are
+currently listing multiple CPU ports, the proposed behavior in
+dsa_ds_get_cpu_dp() will be to return the last bit set in ds->cpu_port_mask.
+
+Future plans include:
+- making dst->cpu_dp a flexible data structure (array, list, you name it)
+- having the ability for drivers to return a default/preferred CPU port (if
+  necessary)
+
+Changes in v3:
+
+- removed the last patch since it causes problems with bcm_sf2/b53 in a
+  dual-CPU case (root cause known, proper fix underway)
+
+- removed dsa_ds_get_cpu_dp()
+
+Changes in v2:
+
+- added Reviewed-by tags
+- assign port->cpu_dp earlier before ops->setup() has run
+
+Florian Fainelli (5):
+  net: dsa: Remove master_netdev and use dst->cpu_dp->netdev
+  net: dsa: Relocate master ethtool operations
+  net: dsa: Associate slave network device with CPU port
+  net: dsa: Introduce dsa_dst_get_cpu_dp()
+  net: dsa: Stop accessing ds->dst->cpu_dp in drivers
+
+ drivers/net/dsa/b53/b53_common.c |  4 +--
+ drivers/net/dsa/bcm_sf2.c        | 10 +++++---
+ drivers/net/dsa/mt7530.c         |  6 +++--
+ drivers/net/dsa/mv88e6060.c      |  2 +-
+ drivers/net/dsa/qca8k.c          |  2 +-
+ include/net/dsa.h                | 29 +++++++++-------------
+ net/dsa/dsa.c                    | 19 ++++----------
+ net/dsa/dsa2.c                   | 27 ++++++++++++--------
+ net/dsa/dsa_priv.h               | 10 ++++++++
+ net/dsa/legacy.c                 | 23 ++++++++++-------
+ net/dsa/slave.c                  | 53 ++++++++++++++++++++--------------------
+ net/dsa/tag_brcm.c               |  5 ++--
+ net/dsa/tag_ksz.c                |  5 ++--
+ net/dsa/tag_qca.c                |  3 ++-
+ net/dsa/tag_trailer.c            |  5 ++--
+ 15 files changed, 107 insertions(+), 96 deletions(-)
+
+
+From f.fainelli@gmail.com Tue Jun 13 19:17:22 2017
+From: Florian Fainelli <f.fainelli@gmail.com>
+To: netdev@vger.kernel.org
+Cc: davem@davemloft.net, andrew@lunn.ch,
+	vivien.didelot@savoirfairelinux.com, john@phrozen.org,
+	Florian Fainelli <f.fainelli@gmail.com>
+Subject: [PATCH 1/4] net: dsa: Remove master_netdev and use
+	dst->cpu_dp->netdev
+Date: Tue, 13 Jun 2017 12:17:22 -0700
+Message-Id: <20170613191725.26625-2-f.fainelli@gmail.com>
+X-Mailer: git-send-email 2.9.3
+In-Reply-To: <20170613191725.26625-1-f.fainelli@gmail.com>
+References: <20170613191725.26625-1-f.fainelli@gmail.com>
+List-ID: <netdev.vger.kernel.org>
+
+In preparation for supporting multiple CPU ports, remove
+dst->master_netdev and ds->master_netdev and replace them with only one
+instance of the common object we have for a port: struct
+dsa_port::netdev. ds->master_netdev is currently write only and would be
+helpful in the case where we have two switches, both with CPU ports, and
+also connected within each other, which the multi-CPU port patch series
+would address.
+
+While at it, introduce a helper function used in net/dsa/slave.c to
+immediately get a reference on the master network device called
+dsa_master_netdev().
+
+Reviewed-by: Vivien Didelot <vivien.didelot@savoirfairelinux.com>
+Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
+---
+ drivers/net/dsa/bcm_sf2.c |  4 ++--
+ drivers/net/dsa/mt7530.c  |  4 ++--
+ include/net/dsa.h         |  5 -----
+ net/dsa/dsa.c             |  9 ++-------
+ net/dsa/dsa2.c            | 18 +++++++-----------
+ net/dsa/dsa_priv.h        |  5 +++++
+ net/dsa/legacy.c          | 22 +++++++++++++---------
+ net/dsa/slave.c           | 20 +++++++++-----------
+ 8 files changed, 40 insertions(+), 47 deletions(-)
+
+diff --git a/drivers/net/dsa/bcm_sf2.c b/drivers/net/dsa/bcm_sf2.c
+index 687a8bae5d73..76e98e8ed315 100644
+--- a/drivers/net/dsa/bcm_sf2.c
++++ b/drivers/net/dsa/bcm_sf2.c
+@@ -806,7 +806,7 @@ static int bcm_sf2_sw_resume(struct dsa_switch *ds)
+ static void bcm_sf2_sw_get_wol(struct dsa_switch *ds, int port,
+ 			       struct ethtool_wolinfo *wol)
+ {
+-	struct net_device *p = ds->dst[ds->index].master_netdev;
++	struct net_device *p = ds->dst[ds->index].cpu_dp->netdev;
+ 	struct bcm_sf2_priv *priv = bcm_sf2_to_priv(ds);
+ 	struct ethtool_wolinfo pwol;
+ 
+@@ -829,7 +829,7 @@ static void bcm_sf2_sw_get_wol(struct dsa_switch *ds, int port,
+ static int bcm_sf2_sw_set_wol(struct dsa_switch *ds, int port,
+ 			      struct ethtool_wolinfo *wol)
+ {
+-	struct net_device *p = ds->dst[ds->index].master_netdev;
++	struct net_device *p = ds->dst[ds->index].cpu_dp->netdev;
+ 	struct bcm_sf2_priv *priv = bcm_sf2_to_priv(ds);
+ 	s8 cpu_port = ds->dst->cpu_dp->index;
+ 	struct ethtool_wolinfo pwol;
+diff --git a/drivers/net/dsa/mt7530.c b/drivers/net/dsa/mt7530.c
+index 25e00d5e0eec..1e46418a3b74 100644
+--- a/drivers/net/dsa/mt7530.c
++++ b/drivers/net/dsa/mt7530.c
+@@ -912,11 +912,11 @@ mt7530_setup(struct dsa_switch *ds)
+ 	struct device_node *dn;
+ 	struct mt7530_dummy_poll p;
+ 
+-	/* The parent node of master_netdev which holds the common system
++	/* The parent node of cpu_dp->netdev which holds the common system
+ 	 * controller also is the container for two GMACs nodes representing
+ 	 * as two netdev instances.
+ 	 */
+-	dn = ds->master_netdev->dev.of_node->parent;
++	dn = ds->dst->cpu_dp->netdev->dev.of_node->parent;
+ 	priv->ethernet = syscon_node_to_regmap(dn);
+ 	if (IS_ERR(priv->ethernet))
+ 		return PTR_ERR(priv->ethernet);
+diff --git a/include/net/dsa.h b/include/net/dsa.h
+index 2effb0af9d7c..b2fb53f5e28e 100644
+--- a/include/net/dsa.h
++++ b/include/net/dsa.h
+@@ -227,11 +227,6 @@ struct dsa_switch {
+ 	s8		rtable[DSA_MAX_SWITCHES];
+ 
+ 	/*
+-	 * The lower device this switch uses to talk to the host
+-	 */
+-	struct net_device *master_netdev;
+-
+-	/*
+ 	 * Slave mii_bus and devices for the individual ports.
+ 	 */
+ 	u32			dsa_port_mask;
+diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c
+index 517215391514..6aacc2314a8f 100644
+--- a/net/dsa/dsa.c
++++ b/net/dsa/dsa.c
+@@ -118,10 +118,7 @@ int dsa_cpu_port_ethtool_setup(struct dsa_port *cpu_dp)
+ 	struct net_device *master;
+ 	struct ethtool_ops *cpu_ops;
+ 
+-	master = ds->dst->master_netdev;
+-	if (ds->master_netdev)
+-		master = ds->master_netdev;
+-
++	master = ds->dst->cpu_dp->netdev;
+ 	cpu_ops = devm_kzalloc(ds->dev, sizeof(*cpu_ops), GFP_KERNEL);
+ 	if (!cpu_ops)
+ 		return -ENOMEM;
+@@ -142,9 +139,7 @@ void dsa_cpu_port_ethtool_restore(struct dsa_port *cpu_dp)
+ 	struct dsa_switch *ds = cpu_dp->ds;
+ 	struct net_device *master;
+ 
+-	master = ds->dst->master_netdev;
+-	if (ds->master_netdev)
+-		master = ds->master_netdev;
++	master = ds->dst->cpu_dp->netdev;
+ 
+ 	master->ethtool_ops = ds->dst->master_orig_ethtool_ops;
+ }
+diff --git a/net/dsa/dsa2.c b/net/dsa/dsa2.c
+index f88e1dddb74a..ab48c4f989da 100644
+--- a/net/dsa/dsa2.c
++++ b/net/dsa/dsa2.c
+@@ -337,7 +337,7 @@ static int dsa_ds_apply(struct dsa_switch_tree *dst, struct dsa_switch *ds)
+ 		return err;
+ 
+ 	if (ds->ops->set_addr) {
+-		err = ds->ops->set_addr(ds, dst->master_netdev->dev_addr);
++		err = ds->ops->set_addr(ds, dst->cpu_dp->netdev->dev_addr);
+ 		if (err < 0)
+ 			return err;
+ 	}
+@@ -444,7 +444,7 @@ static int dsa_dst_apply(struct dsa_switch_tree *dst)
+ 	 * sent to the tag format's receive function.
+ 	 */
+ 	wmb();
+-	dst->master_netdev->dsa_ptr = dst;
++	dst->cpu_dp->netdev->dsa_ptr = dst;
+ 	dst->applied = true;
+ 
+ 	return 0;
+@@ -458,7 +458,7 @@ static void dsa_dst_unapply(struct dsa_switch_tree *dst)
+ 	if (!dst->applied)
+ 		return;
+ 
+-	dst->master_netdev->dsa_ptr = NULL;
++	dst->cpu_dp->netdev->dsa_ptr = NULL;
+ 
+ 	/* If we used a tagging format that doesn't have an ethertype
+ 	 * field, make sure that all packets from this point get sent
+@@ -504,14 +504,10 @@ static int dsa_cpu_parse(struct dsa_port *port, u32 index,
+ 	if (!ethernet_dev)
+ 		return -EPROBE_DEFER;
+ 
+-	if (!ds->master_netdev)
+-		ds->master_netdev = ethernet_dev;
+-
+-	if (!dst->master_netdev)
+-		dst->master_netdev = ethernet_dev;
+-
+-	if (!dst->cpu_dp)
++	if (!dst->cpu_dp) {
+ 		dst->cpu_dp = port;
++		dst->cpu_dp->netdev = ethernet_dev;
++	}
+ 
+ 	tag_protocol = ds->ops->get_tag_protocol(ds);
+ 	dst->tag_ops = dsa_resolve_tag_protocol(tag_protocol);
+@@ -578,7 +574,7 @@ static int dsa_dst_parse(struct dsa_switch_tree *dst)
+ 			return err;
+ 	}
+ 
+-	if (!dst->master_netdev) {
++	if (!dst->cpu_dp->netdev) {
+ 		pr_warn("Tree has no master device\n");
+ 		return -EINVAL;
+ 	}
+diff --git a/net/dsa/dsa_priv.h b/net/dsa/dsa_priv.h
+index 66ee248796c8..5c510f4ba0ce 100644
+--- a/net/dsa/dsa_priv.h
++++ b/net/dsa/dsa_priv.h
+@@ -183,4 +183,9 @@ extern const struct dsa_device_ops qca_netdev_ops;
+ /* tag_trailer.c */
+ extern const struct dsa_device_ops trailer_netdev_ops;
+ 
++static inline struct net_device *dsa_master_netdev(struct dsa_slave_priv *p)
++{
++	return p->dp->ds->dst->cpu_dp->netdev;
++}
++
+ #endif
+diff --git a/net/dsa/legacy.c b/net/dsa/legacy.c
+index 3a56de8f51a8..5d4f6ffa3424 100644
+--- a/net/dsa/legacy.c
++++ b/net/dsa/legacy.c
+@@ -101,9 +101,12 @@ static int dsa_switch_setup_one(struct dsa_switch *ds, struct device *parent)
+ 	struct dsa_switch_tree *dst = ds->dst;
+ 	struct dsa_chip_data *cd = ds->cd;
+ 	bool valid_name_found = false;
++	struct net_device *master;
+ 	int index = ds->index;
+ 	int i, ret;
+ 
++	master = dst->cpu_dp->netdev;
++
+ 	/*
+ 	 * Validate supplied switch configuration.
+ 	 */
+@@ -116,7 +119,7 @@ static int dsa_switch_setup_one(struct dsa_switch *ds, struct device *parent)
+ 
+ 		if (!strcmp(name, "cpu")) {
+ 			if (dst->cpu_dp) {
+-				netdev_err(dst->master_netdev,
++				netdev_err(master,
+ 					   "multiple cpu ports?!\n");
+ 				return -EINVAL;
+ 			}
+@@ -168,7 +171,7 @@ static int dsa_switch_setup_one(struct dsa_switch *ds, struct device *parent)
+ 		return ret;
+ 
+ 	if (ops->set_addr) {
+-		ret = ops->set_addr(ds, dst->master_netdev->dev_addr);
++		ret = ops->set_addr(ds, master->dev_addr);
+ 		if (ret < 0)
+ 			return ret;
+ 	}
+@@ -195,14 +198,14 @@ static int dsa_switch_setup_one(struct dsa_switch *ds, struct device *parent)
+ 
+ 		ret = dsa_slave_create(ds, parent, i, cd->port_names[i]);
+ 		if (ret < 0)
+-			netdev_err(dst->master_netdev, "[%d]: can't create dsa slave device for port %d(%s): %d\n",
++			netdev_err(master, "[%d]: can't create dsa slave device for port %d(%s): %d\n",
+ 				   index, i, cd->port_names[i], ret);
+ 	}
+ 
+ 	/* Perform configuration of the CPU and DSA ports */
+ 	ret = dsa_cpu_dsa_setups(ds, parent);
+ 	if (ret < 0)
+-		netdev_err(dst->master_netdev, "[%d] : can't configure CPU and DSA ports\n",
++		netdev_err(master, "[%d] : can't configure CPU and DSA ports\n",
+ 			   index);
+ 
+ 	ret = dsa_cpu_port_ethtool_setup(ds->dst->cpu_dp);
+@@ -217,6 +220,7 @@ dsa_switch_setup(struct dsa_switch_tree *dst, int index,
+ 		 struct device *parent, struct device *host_dev)
+ {
+ 	struct dsa_chip_data *cd = dst->pd->chip + index;
++	struct net_device *master = dst->cpu_dp->netdev;
+ 	const struct dsa_switch_ops *ops;
+ 	struct dsa_switch *ds;
+ 	int ret;
+@@ -228,11 +232,11 @@ dsa_switch_setup(struct dsa_switch_tree *dst, int index,
+ 	 */
+ 	ops = dsa_switch_probe(parent, host_dev, cd->sw_addr, &name, &priv);
+ 	if (!ops) {
+-		netdev_err(dst->master_netdev, "[%d]: could not detect attached switch\n",
++		netdev_err(master, "[%d]: could not detect attached switch\n",
+ 			   index);
+ 		return ERR_PTR(-EINVAL);
+ 	}
+-	netdev_info(dst->master_netdev, "[%d]: detected a %s switch\n",
++	netdev_info(master, "[%d]: detected a %s switch\n",
+ 		    index, name);
+ 
+ 
+@@ -575,7 +579,7 @@ static int dsa_setup_dst(struct dsa_switch_tree *dst, struct net_device *dev,
+ 	unsigned configured = 0;
+ 
+ 	dst->pd = pd;
+-	dst->master_netdev = dev;
++	dst->cpu_dp->netdev = dev;
+ 
+ 	for (i = 0; i < pd->nr_chips; i++) {
+ 		struct dsa_switch *ds;
+@@ -671,7 +675,7 @@ static void dsa_remove_dst(struct dsa_switch_tree *dst)
+ {
+ 	int i;
+ 
+-	dst->master_netdev->dsa_ptr = NULL;
++	dst->cpu_dp->netdev->dsa_ptr = NULL;
+ 
+ 	/* If we used a tagging format that doesn't have an ethertype
+ 	 * field, make sure that all packets from this point get sent
+@@ -688,7 +692,7 @@ static void dsa_remove_dst(struct dsa_switch_tree *dst)
+ 
+ 	dsa_cpu_port_ethtool_restore(dst->cpu_dp);
+ 
+-	dev_put(dst->master_netdev);
++	dev_put(dst->cpu_dp->netdev);
+ }
+ 
+ static int dsa_remove(struct platform_device *pdev)
+diff --git a/net/dsa/slave.c b/net/dsa/slave.c
+index 5e45ae5c3f71..95031b31d64b 100644
+--- a/net/dsa/slave.c
++++ b/net/dsa/slave.c
+@@ -66,7 +66,7 @@ static int dsa_slave_get_iflink(const struct net_device *dev)
+ {
+ 	struct dsa_slave_priv *p = netdev_priv(dev);
+ 
+-	return p->dp->ds->dst->master_netdev->ifindex;
++	return dsa_master_netdev(p)->ifindex;
+ }
+ 
+ static int dsa_slave_open(struct net_device *dev)
+@@ -74,7 +74,7 @@ static int dsa_slave_open(struct net_device *dev)
+ 	struct dsa_slave_priv *p = netdev_priv(dev);
+ 	struct dsa_port *dp = p->dp;
+ 	struct dsa_switch *ds = dp->ds;
+-	struct net_device *master = ds->dst->master_netdev;
++	struct net_device *master = dsa_master_netdev(p);
+ 	u8 stp_state = dp->bridge_dev ? BR_STATE_BLOCKING : BR_STATE_FORWARDING;
+ 	int err;
+ 
+@@ -127,7 +127,7 @@ static int dsa_slave_open(struct net_device *dev)
+ static int dsa_slave_close(struct net_device *dev)
+ {
+ 	struct dsa_slave_priv *p = netdev_priv(dev);
+-	struct net_device *master = p->dp->ds->dst->master_netdev;
++	struct net_device *master = dsa_master_netdev(p);
+ 	struct dsa_switch *ds = p->dp->ds;
+ 
+ 	if (p->phy)
+@@ -154,7 +154,7 @@ static int dsa_slave_close(struct net_device *dev)
+ static void dsa_slave_change_rx_flags(struct net_device *dev, int change)
+ {
+ 	struct dsa_slave_priv *p = netdev_priv(dev);
+-	struct net_device *master = p->dp->ds->dst->master_netdev;
++	struct net_device *master = dsa_master_netdev(p);
+ 
+ 	if (change & IFF_ALLMULTI)
+ 		dev_set_allmulti(master, dev->flags & IFF_ALLMULTI ? 1 : -1);
+@@ -165,7 +165,7 @@ static void dsa_slave_change_rx_flags(struct net_device *dev, int change)
+ static void dsa_slave_set_rx_mode(struct net_device *dev)
+ {
+ 	struct dsa_slave_priv *p = netdev_priv(dev);
+-	struct net_device *master = p->dp->ds->dst->master_netdev;
++	struct net_device *master = dsa_master_netdev(p);
+ 
+ 	dev_mc_sync(master, dev);
+ 	dev_uc_sync(master, dev);
+@@ -174,7 +174,7 @@ static void dsa_slave_set_rx_mode(struct net_device *dev)
+ static int dsa_slave_set_mac_address(struct net_device *dev, void *a)
+ {
+ 	struct dsa_slave_priv *p = netdev_priv(dev);
+-	struct net_device *master = p->dp->ds->dst->master_netdev;
++	struct net_device *master = dsa_master_netdev(p);
+ 	struct sockaddr *addr = a;
+ 	int err;
+ 
+@@ -375,7 +375,7 @@ static netdev_tx_t dsa_slave_xmit(struct sk_buff *skb, struct net_device *dev)
+ 	/* Queue the SKB for transmission on the parent interface, but
+ 	 * do not modify its EtherType
+ 	 */
+-	nskb->dev = p->dp->ds->dst->master_netdev;
++	nskb->dev = dsa_master_netdev(p);
+ 	dev_queue_xmit(nskb);
+ 
+ 	return NETDEV_TX_OK;
+@@ -685,7 +685,7 @@ static int dsa_slave_netpoll_setup(struct net_device *dev,
+ {
+ 	struct dsa_slave_priv *p = netdev_priv(dev);
+ 	struct dsa_switch *ds = p->dp->ds;
+-	struct net_device *master = ds->dst->master_netdev;
++	struct net_device *master = dsa_master_netdev(p);
+ 	struct netpoll *netpoll;
+ 	int err = 0;
+ 
+@@ -1143,9 +1143,7 @@ int dsa_slave_create(struct dsa_switch *ds, struct device *parent,
+ 	struct dsa_slave_priv *p;
+ 	int ret;
+ 
+-	master = ds->dst->master_netdev;
+-	if (ds->master_netdev)
+-		master = ds->master_netdev;
++	master = ds->dst->cpu_dp->netdev;
+ 
+ 	slave_dev = alloc_netdev(sizeof(struct dsa_slave_priv), name,
+ 				 NET_NAME_UNKNOWN, ether_setup);
+
+
+From f.fainelli@gmail.com Tue Jun 13 19:17:23 2017
+From: Florian Fainelli <f.fainelli@gmail.com>
+To: netdev@vger.kernel.org
+Cc: davem@davemloft.net, andrew@lunn.ch,
+	vivien.didelot@savoirfairelinux.com, john@phrozen.org,
+	Florian Fainelli <f.fainelli@gmail.com>
+Subject: [PATCH 2/4] net: dsa: Relocate master ethtool operations
+Date: Tue, 13 Jun 2017 12:17:23 -0700
+Message-Id: <20170613191725.26625-3-f.fainelli@gmail.com>
+X-Mailer: git-send-email 2.9.3
+In-Reply-To: <20170613191725.26625-1-f.fainelli@gmail.com>
+References: <20170613191725.26625-1-f.fainelli@gmail.com>
+List-ID: <netdev.vger.kernel.org>
+
+Relocate master_ethtool_ops and master_orig_ethtool_ops into struct
+dsa_port in order to be both consistent, and make things self contained
+within the dsa_port structure.
+
+This is a preliminary change to supporting multiple CPU port interfaces.
+
+Reviewed-by: Vivien Didelot <vivien.didelot@savoirfairelinux.com>
+Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
+---
+ include/net/dsa.h | 17 +++++------------
+ net/dsa/dsa.c     | 16 ++++++----------
+ net/dsa/slave.c   | 16 ++++++++--------
+ 3 files changed, 19 insertions(+), 30 deletions(-)
+
+diff --git a/include/net/dsa.h b/include/net/dsa.h
+index b2fb53f5e28e..7e93869819f9 100644
+--- a/include/net/dsa.h
++++ b/include/net/dsa.h
+@@ -122,12 +122,6 @@ struct dsa_switch_tree {
+ 	 */
+ 	struct dsa_platform_data	*pd;
+ 
+-	/*
+-	 * Reference to network device to use, and which tagging
+-	 * protocol to use.
+-	 */
+-	struct net_device	*master_netdev;
+-
+ 	/* Copy of tag_ops->rcv for faster access in hot path */
+ 	struct sk_buff *	(*rcv)(struct sk_buff *skb,
+ 				       struct net_device *dev,
+@@ -135,12 +129,6 @@ struct dsa_switch_tree {
+ 				       struct net_device *orig_dev);
+ 
+ 	/*
+-	 * Original copy of the master netdev ethtool_ops
+-	 */
+-	struct ethtool_ops	master_ethtool_ops;
+-	const struct ethtool_ops *master_orig_ethtool_ops;
+-
+-	/*
+ 	 * The switch port to which the CPU is attached.
+ 	 */
+ 	struct dsa_port		*cpu_dp;
+@@ -189,6 +177,11 @@ struct dsa_port {
+ 	u8			stp_state;
+ 	struct net_device	*bridge_dev;
+ 	struct devlink_port	devlink_port;
++	/*
++	 * Original copy of the master netdev ethtool_ops
++	 */
++	struct ethtool_ops	ethtool_ops;
++	const struct ethtool_ops *orig_ethtool_ops;
+ };
+ 
+ struct dsa_switch {
+diff --git a/net/dsa/dsa.c b/net/dsa/dsa.c
+index 6aacc2314a8f..416ac4ef9ba9 100644
+--- a/net/dsa/dsa.c
++++ b/net/dsa/dsa.c
+@@ -118,15 +118,16 @@ int dsa_cpu_port_ethtool_setup(struct dsa_port *cpu_dp)
+ 	struct net_device *master;
+ 	struct ethtool_ops *cpu_ops;
+ 
+-	master = ds->dst->cpu_dp->netdev;
++	master = cpu_dp->netdev;
++
+ 	cpu_ops = devm_kzalloc(ds->dev, sizeof(*cpu_ops), GFP_KERNEL);
+ 	if (!cpu_ops)
+ 		return -ENOMEM;
+ 
+-	memcpy(&ds->dst->master_ethtool_ops, master->ethtool_ops,
++	memcpy(&cpu_dp->ethtool_ops, master->ethtool_ops,
+ 	       sizeof(struct ethtool_ops));
+-	ds->dst->master_orig_ethtool_ops = master->ethtool_ops;
+-	memcpy(cpu_ops, &ds->dst->master_ethtool_ops,
++	cpu_dp->orig_ethtool_ops = master->ethtool_ops;
++	memcpy(cpu_ops, &cpu_dp->ethtool_ops,
+ 	       sizeof(struct ethtool_ops));
+ 	dsa_cpu_port_ethtool_init(cpu_ops);
+ 	master->ethtool_ops = cpu_ops;
+@@ -136,12 +137,7 @@ int dsa_cpu_port_ethtool_setup(struct dsa_port *cpu_dp)
+ 
+ void dsa_cpu_port_ethtool_restore(struct dsa_port *cpu_dp)
+ {
+-	struct dsa_switch *ds = cpu_dp->ds;
+-	struct net_device *master;
+-
+-	master = ds->dst->cpu_dp->netdev;
+-
+-	master->ethtool_ops = ds->dst->master_orig_ethtool_ops;
++	cpu_dp->netdev->ethtool_ops = cpu_dp->orig_ethtool_ops;
+ }
+ 
+ void dsa_cpu_dsa_destroy(struct dsa_port *port)
+diff --git a/net/dsa/slave.c b/net/dsa/slave.c
+index 95031b31d64b..c33db4e3b445 100644
+--- a/net/dsa/slave.c
++++ b/net/dsa/slave.c
+@@ -524,10 +524,10 @@ static void dsa_cpu_port_get_ethtool_stats(struct net_device *dev,
+ 	s8 cpu_port = dst->cpu_dp->index;
+ 	int count = 0;
+ 
+-	if (dst->master_ethtool_ops.get_sset_count) {
+-		count = dst->master_ethtool_ops.get_sset_count(dev,
++	if (dst->cpu_dp->ethtool_ops.get_sset_count) {
++		count = dst->cpu_dp->ethtool_ops.get_sset_count(dev,
+ 							       ETH_SS_STATS);
+-		dst->master_ethtool_ops.get_ethtool_stats(dev, stats, data);
++		dst->cpu_dp->ethtool_ops.get_ethtool_stats(dev, stats, data);
+ 	}
+ 
+ 	if (ds->ops->get_ethtool_stats)
+@@ -540,8 +540,8 @@ static int dsa_cpu_port_get_sset_count(struct net_device *dev, int sset)
+ 	struct dsa_switch *ds = dst->cpu_dp->ds;
+ 	int count = 0;
+ 
+-	if (dst->master_ethtool_ops.get_sset_count)
+-		count += dst->master_ethtool_ops.get_sset_count(dev, sset);
++	if (dst->cpu_dp->ethtool_ops.get_sset_count)
++		count += dst->cpu_dp->ethtool_ops.get_sset_count(dev, sset);
+ 
+ 	if (sset == ETH_SS_STATS && ds->ops->get_sset_count)
+ 		count += ds->ops->get_sset_count(ds);
+@@ -565,10 +565,10 @@ static void dsa_cpu_port_get_strings(struct net_device *dev,
+ 	/* We do not want to be NULL-terminated, since this is a prefix */
+ 	pfx[sizeof(pfx) - 1] = '_';
+ 
+-	if (dst->master_ethtool_ops.get_sset_count) {
+-		mcount = dst->master_ethtool_ops.get_sset_count(dev,
++	if (dst->cpu_dp->ethtool_ops.get_sset_count) {
++		mcount = dst->cpu_dp->ethtool_ops.get_sset_count(dev,
+ 								ETH_SS_STATS);
+-		dst->master_ethtool_ops.get_strings(dev, stringset, data);
++		dst->cpu_dp->ethtool_ops.get_strings(dev, stringset, data);
+ 	}
+ 
+ 	if (stringset == ETH_SS_STATS && ds->ops->get_strings) {
+
+
+From f.fainelli@gmail.com Tue Jun 13 19:17:24 2017
+From: Florian Fainelli <f.fainelli@gmail.com>
+To: netdev@vger.kernel.org
+Cc: davem@davemloft.net, andrew@lunn.ch,
+	vivien.didelot@savoirfairelinux.com, john@phrozen.org,
+	Florian Fainelli <f.fainelli@gmail.com>
+Subject: [PATCH 3/4] net: dsa: Associate slave network device with CPU port
+Date: Tue, 13 Jun 2017 12:17:24 -0700
+Message-Id: <20170613191725.26625-4-f.fainelli@gmail.com>
+X-Mailer: git-send-email 2.9.3
+In-Reply-To: <20170613191725.26625-1-f.fainelli@gmail.com>
+References: <20170613191725.26625-1-f.fainelli@gmail.com>
+List-ID: <netdev.vger.kernel.org>
+
+In preparation for supporting multiple CPU ports with DSA, have the
+dsa_port structure know which CPU it is associated with. This will be
+important in order to make sure the correct CPU is used for transmission
+of the frames. If not for functional reasons, for performance (e.g: load
+balancing) and forwarding decisions.
+
+Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
+Reviewed-by: Vivien Didelot <vivien.didelot@savoirfairelinux.com>
+---
+ include/net/dsa.h  |  1 +
+ net/dsa/dsa2.c     | 11 +++++++++++
+ net/dsa/dsa_priv.h |  2 +-
+ net/dsa/legacy.c   |  1 +
+ net/dsa/slave.c    |  4 +++-
+ 5 files changed, 17 insertions(+), 2 deletions(-)
+
+diff --git a/include/net/dsa.h b/include/net/dsa.h
+index 7e93869819f9..58969b9a090c 100644
+--- a/include/net/dsa.h
++++ b/include/net/dsa.h
+@@ -171,6 +171,7 @@ struct dsa_port {
+ 	struct dsa_switch	*ds;
+ 	unsigned int		index;
+ 	const char		*name;
++	struct dsa_port		*cpu_dp;
+ 	struct net_device	*netdev;
+ 	struct device_node	*dn;
+ 	unsigned int		ageing_time;
+diff --git a/net/dsa/dsa2.c b/net/dsa/dsa2.c
+index ab48c4f989da..52af8401af07 100644
+--- a/net/dsa/dsa2.c
++++ b/net/dsa/dsa2.c
+@@ -490,6 +490,8 @@ static int dsa_cpu_parse(struct dsa_port *port, u32 index,
+ 	enum dsa_tag_protocol tag_protocol;
+ 	struct net_device *ethernet_dev;
+ 	struct device_node *ethernet;
++	struct dsa_port *p;
++	unsigned int i;
+ 
+ 	if (port->dn) {
+ 		ethernet = of_parse_phandle(port->dn, "ethernet", 0);
+@@ -507,6 +509,15 @@ static int dsa_cpu_parse(struct dsa_port *port, u32 index,
+ 	if (!dst->cpu_dp) {
+ 		dst->cpu_dp = port;
+ 		dst->cpu_dp->netdev = ethernet_dev;
++
++		for (i = 0; i < ds->num_ports; i++) {
++			p = &ds->ports[i];
++			if (!dsa_port_is_valid(p) ||
++			    i == index)
++				continue;
++
++			p->cpu_dp = port;
++		}
+ 	}
+ 
+ 	tag_protocol = ds->ops->get_tag_protocol(ds);
+diff --git a/net/dsa/dsa_priv.h b/net/dsa/dsa_priv.h
+index 5c510f4ba0ce..7c2326f3b538 100644
+--- a/net/dsa/dsa_priv.h
++++ b/net/dsa/dsa_priv.h
+@@ -185,7 +185,7 @@ extern const struct dsa_device_ops trailer_netdev_ops;
+ 
+ static inline struct net_device *dsa_master_netdev(struct dsa_slave_priv *p)
+ {
+-	return p->dp->ds->dst->cpu_dp->netdev;
++	return p->dp->cpu_dp->netdev;
+ }
+ 
+ #endif
+diff --git a/net/dsa/legacy.c b/net/dsa/legacy.c
+index 5d4f6ffa3424..e60906125375 100644
+--- a/net/dsa/legacy.c
++++ b/net/dsa/legacy.c
+@@ -129,6 +129,7 @@ static int dsa_switch_setup_one(struct dsa_switch *ds, struct device *parent)
+ 			ds->dsa_port_mask |= 1 << i;
+ 		} else {
+ 			ds->enabled_port_mask |= 1 << i;
++			ds->ports[i].cpu_dp = dst->cpu_dp;
+ 		}
+ 		valid_name_found = true;
+ 	}
+diff --git a/net/dsa/slave.c b/net/dsa/slave.c
+index c33db4e3b445..427f8afcfaf3 100644
+--- a/net/dsa/slave.c
++++ b/net/dsa/slave.c
+@@ -1141,9 +1141,11 @@ int dsa_slave_create(struct dsa_switch *ds, struct device *parent,
+ 	struct net_device *master;
+ 	struct net_device *slave_dev;
+ 	struct dsa_slave_priv *p;
++	struct dsa_port *cpu_dp;
+ 	int ret;
+ 
+-	master = ds->dst->cpu_dp->netdev;
++	cpu_dp = ds->dst->cpu_dp;
++	master = cpu_dp->netdev;
+ 
+ 	slave_dev = alloc_netdev(sizeof(struct dsa_slave_priv), name,
+ 				 NET_NAME_UNKNOWN, ether_setup);
+
+
+From f.fainelli@gmail.com Tue Jun 13 19:17:25 2017
+From: Florian Fainelli <f.fainelli@gmail.com>
+To: netdev@vger.kernel.org
+Cc: davem@davemloft.net, andrew@lunn.ch,
+	vivien.didelot@savoirfairelinux.com, john@phrozen.org,
+	Florian Fainelli <f.fainelli@gmail.com>
+Subject: [PATCH 4/4] net: dsa: Introduce dsa_get_cpu_port()
+Date: Tue, 13 Jun 2017 12:17:25 -0700
+Message-Id: <20170613191725.26625-5-f.fainelli@gmail.com>
+X-Mailer: git-send-email 2.9.3
+In-Reply-To: <20170613191725.26625-1-f.fainelli@gmail.com>
+References: <20170613191725.26625-1-f.fainelli@gmail.com>
+List-ID: <netdev.vger.kernel.org>
+
+Introduce a helper function which will return a reference to the CPU
+port used in a dsa_switch_tree. Right now this is a singleton, but this
+will change once we introduce multi-CPU port support, so ease the
+transition by converting the affected code paths.
+
+Reviewed-by: Vivien Didelot <vivien.didelot@savoirfairelinux.com>
+Signed-off-by: Florian Fainelli <f.fainelli@gmail.com>
+---
+ net/dsa/dsa_priv.h    |  5 +++++
+ net/dsa/slave.c       | 31 ++++++++++++++++---------------
+ net/dsa/tag_brcm.c    |  5 ++---
+ net/dsa/tag_ksz.c     |  5 ++---
+ net/dsa/tag_qca.c     |  3 ++-
+ net/dsa/tag_trailer.c |  5 ++---
+ 6 files changed, 29 insertions(+), 25 deletions(-)
+
+diff --git a/net/dsa/dsa_priv.h b/net/dsa/dsa_priv.h
+index 7c2326f3b538..55982cc39b24 100644
+--- a/net/dsa/dsa_priv.h
++++ b/net/dsa/dsa_priv.h
+@@ -188,4 +188,9 @@ static inline struct net_device *dsa_master_netdev(struct dsa_slave_priv *p)
+ 	return p->dp->cpu_dp->netdev;
+ }
+ 
++static inline struct dsa_port *dsa_get_cpu_port(struct dsa_switch_tree *dst)
++{
++	return dst->cpu_dp;
++}
++
+ #endif
+diff --git a/net/dsa/slave.c b/net/dsa/slave.c
+index 427f8afcfaf3..845e0c3f871e 100644
+--- a/net/dsa/slave.c
++++ b/net/dsa/slave.c
+@@ -520,14 +520,14 @@ static void dsa_cpu_port_get_ethtool_stats(struct net_device *dev,
+ 					   uint64_t *data)
+ {
+ 	struct dsa_switch_tree *dst = dev->dsa_ptr;
+-	struct dsa_switch *ds = dst->cpu_dp->ds;
+-	s8 cpu_port = dst->cpu_dp->index;
++	struct dsa_port *cpu_dp = dsa_get_cpu_port(dst);
++	struct dsa_switch *ds = cpu_dp->ds;
++	s8 cpu_port = cpu_dp->index;
+ 	int count = 0;
+ 
+-	if (dst->cpu_dp->ethtool_ops.get_sset_count) {
+-		count = dst->cpu_dp->ethtool_ops.get_sset_count(dev,
+-							       ETH_SS_STATS);
+-		dst->cpu_dp->ethtool_ops.get_ethtool_stats(dev, stats, data);
++	if (cpu_dp->ethtool_ops.get_sset_count) {
++		count = cpu_dp->ethtool_ops.get_sset_count(dev, ETH_SS_STATS);
++		cpu_dp->ethtool_ops.get_ethtool_stats(dev, stats, data);
+ 	}
+ 
+ 	if (ds->ops->get_ethtool_stats)
+@@ -537,11 +537,12 @@ static void dsa_cpu_port_get_ethtool_stats(struct net_device *dev,
+ static int dsa_cpu_port_get_sset_count(struct net_device *dev, int sset)
+ {
+ 	struct dsa_switch_tree *dst = dev->dsa_ptr;
+-	struct dsa_switch *ds = dst->cpu_dp->ds;
++	struct dsa_port *cpu_dp = dsa_get_cpu_port(dst);
++	struct dsa_switch *ds = cpu_dp->ds;
+ 	int count = 0;
+ 
+-	if (dst->cpu_dp->ethtool_ops.get_sset_count)
+-		count += dst->cpu_dp->ethtool_ops.get_sset_count(dev, sset);
++	if (cpu_dp->ethtool_ops.get_sset_count)
++		count += cpu_dp->ethtool_ops.get_sset_count(dev, sset);
+ 
+ 	if (sset == ETH_SS_STATS && ds->ops->get_sset_count)
+ 		count += ds->ops->get_sset_count(ds);
+@@ -553,8 +554,9 @@ static void dsa_cpu_port_get_strings(struct net_device *dev,
+ 				     uint32_t stringset, uint8_t *data)
+ {
+ 	struct dsa_switch_tree *dst = dev->dsa_ptr;
+-	struct dsa_switch *ds = dst->cpu_dp->ds;
+-	s8 cpu_port = dst->cpu_dp->index;
++	struct dsa_port *cpu_dp = dsa_get_cpu_port(dst);
++	struct dsa_switch *ds = cpu_dp->ds;
++	s8 cpu_port = cpu_dp->index;
+ 	int len = ETH_GSTRING_LEN;
+ 	int mcount = 0, count;
+ 	unsigned int i;
+@@ -565,10 +567,9 @@ static void dsa_cpu_port_get_strings(struct net_device *dev,
+ 	/* We do not want to be NULL-terminated, since this is a prefix */
+ 	pfx[sizeof(pfx) - 1] = '_';
+ 
+-	if (dst->cpu_dp->ethtool_ops.get_sset_count) {
+-		mcount = dst->cpu_dp->ethtool_ops.get_sset_count(dev,
+-								ETH_SS_STATS);
+-		dst->cpu_dp->ethtool_ops.get_strings(dev, stringset, data);
++	if (cpu_dp->ethtool_ops.get_sset_count) {
++		mcount = cpu_dp->ethtool_ops.get_sset_count(dev, ETH_SS_STATS);
++		cpu_dp->ethtool_ops.get_strings(dev, stringset, data);
+ 	}
+ 
+ 	if (stringset == ETH_SS_STATS && ds->ops->get_strings) {
+diff --git a/net/dsa/tag_brcm.c b/net/dsa/tag_brcm.c
+index c03860907f28..c697d9815177 100644
+--- a/net/dsa/tag_brcm.c
++++ b/net/dsa/tag_brcm.c
+@@ -93,12 +93,11 @@ static struct sk_buff *brcm_tag_rcv(struct sk_buff *skb, struct net_device *dev,
+ 				    struct net_device *orig_dev)
+ {
+ 	struct dsa_switch_tree *dst = dev->dsa_ptr;
+-	struct dsa_switch *ds;
++	struct dsa_port *cpu_dp = dsa_get_cpu_port(dst);
++	struct dsa_switch *ds = cpu_dp->ds;
+ 	int source_port;
+ 	u8 *brcm_tag;
+ 
+-	ds = dst->cpu_dp->ds;
+-
+ 	if (unlikely(!pskb_may_pull(skb, BRCM_TAG_LEN)))
+ 		return NULL;
+ 
+diff --git a/net/dsa/tag_ksz.c b/net/dsa/tag_ksz.c
+index b94a334a1d02..fab41de8e983 100644
+--- a/net/dsa/tag_ksz.c
++++ b/net/dsa/tag_ksz.c
+@@ -75,12 +75,11 @@ static struct sk_buff *ksz_rcv(struct sk_buff *skb, struct net_device *dev,
+ 			       struct net_device *orig_dev)
+ {
+ 	struct dsa_switch_tree *dst = dev->dsa_ptr;
+-	struct dsa_switch *ds;
++	struct dsa_port *cpu_dp = dsa_get_cpu_port(dst);
++	struct dsa_switch *ds = cpu_dp->ds;
+ 	u8 *tag;
+ 	int source_port;
+ 
+-	ds = dst->cpu_dp->ds;
+-
+ 	tag = skb_tail_pointer(skb) - KSZ_EGRESS_TAG_LEN;
+ 
+ 	source_port = tag[0] & 7;
+diff --git a/net/dsa/tag_qca.c b/net/dsa/tag_qca.c
+index 4f43cf0b4eff..1867a3d11f28 100644
+--- a/net/dsa/tag_qca.c
++++ b/net/dsa/tag_qca.c
+@@ -67,6 +67,7 @@ static struct sk_buff *qca_tag_rcv(struct sk_buff *skb, struct net_device *dev,
+ 				   struct net_device *orig_dev)
+ {
+ 	struct dsa_switch_tree *dst = dev->dsa_ptr;
++	struct dsa_port *cpu_dp = dsa_get_cpu_port(dst);
+ 	struct dsa_switch *ds;
+ 	u8 ver;
+ 	int port;
+@@ -95,7 +96,7 @@ static struct sk_buff *qca_tag_rcv(struct sk_buff *skb, struct net_device *dev,
+ 	/* This protocol doesn't support cascading multiple switches so it's
+ 	 * safe to assume the switch is first in the tree
+ 	 */
+-	ds = dst->cpu_dp->ds;
++	ds = cpu_dp->ds;
+ 	if (!ds)
+ 		return NULL;
+ 
+diff --git a/net/dsa/tag_trailer.c b/net/dsa/tag_trailer.c
+index b4f6db094409..172f13167896 100644
+--- a/net/dsa/tag_trailer.c
++++ b/net/dsa/tag_trailer.c
+@@ -61,12 +61,11 @@ static struct sk_buff *trailer_rcv(struct sk_buff *skb, struct net_device *dev,
+ 				   struct net_device *orig_dev)
+ {
+ 	struct dsa_switch_tree *dst = dev->dsa_ptr;
+-	struct dsa_switch *ds;
++	struct dsa_port *cpu_dp = dsa_get_cpu_port(dst);
++	struct dsa_switch *ds = cpu_dp->ds;
+ 	u8 *trailer;
+ 	int source_port;
+ 
+-	ds = dst->cpu_dp->ds;
+-
+ 	if (skb_linearize(skb))
+ 		return NULL;
+ 
diff --git a/patchwork/tests/test_series.py b/patchwork/tests/test_series.py
index 0a33b75..390ee51 100644
--- a/patchwork/tests/test_series.py
+++ b/patchwork/tests/test_series.py
@@ -185,6 +185,24 @@ class BaseSeriesTest(_BaseTestCase):
 
         self.assertSerialized(patches_a + patches_b, [2, 2])
 
+    def test_different_versions(self):
+        """Series received with different version on cover to patches.
+
+        Input:
+          - [PATCH net-next v3 0/4] net: dsa: Multi-CPU ground work (v3)
+            - [PATCH 1/4] net: dsa: Remove master_netdev and use
+                dst->cpu_dp->netdev
+            - [PATCH 2/4] net: dsa: Relocate master ethtool operations
+            - [PATCH 3/4] net: dsa: Associate slave network device with CPU
+                port
+            - [PATCH 4/4] net: dsa: Introduce dsa_get_cpu_port()
+        """
+        covers, patches, _ = self._parse_mbox(
+            'base-different-versions.mbox', [1, 4, 0])
+
+        self.assertSerialized(covers, [1])
+        self.assertSerialized(patches, [4])
+
     def test_multiple_references(self):
         """Series received with multiple reference headers.
 
