Netdev List
 help / color / mirror / Atom feed
* Re: RCU lock bug in 3.0.21 (bisected to: 682cb56a, fix NULL dereferences in check_peer_redir)
From: Ben Greear @ 2012-03-27 16:47 UTC (permalink / raw)
  To: Eric Dumazet; +Cc: David Miller, netdev, gregkh, Paul E. McKenney
In-Reply-To: <1332805148.3547.14.camel@edumazet-glaptop>

On 03/26/2012 04:39 PM, Eric Dumazet wrote:
> On Mon, 2012-03-26 at 16:06 -0700, Ben Greear wrote:
>> On 03/26/2012 02:53 PM, Ben Greear wrote:
>>> On 03/26/2012 02:49 PM, David Miller wrote:
>>>>
>>>> Looks like all of those strange undiagnosable reported Dave Jones
>>>> has been feeding us. Something in one part of the kernel leaves
>>>> a lock held, and this shows up as a warning elsewhere.
>>>
>>> Every (initial) bug printout fingers ipv6 and the 'ip' tool on my system.
>>
>> I added a patch to convert rcu_read_lock/unlock to macros so
>> that I could automatically grab the call site (_THIS_IP_)
>> and pass it into the lockdep framework instead of the (useless)
>> _THIS_IP_ in the old rcu_read_lock method which at best seems to
>> only indicate which module the issue relates to...
>
> Hi Ben
>
> Is this problem also appears with current tree ?
> (This could be a problem with the backport, as it was full of
> dependencies)
>
> Also, if you use a patch to better track rcu_read_lock()/unlock(), you
> could add new macros as well to track that a particular unlock() matches
> one given lock(). (maybe returning the rcu_preempt_depth at
> rcu_read_lock() time , but maybe a more absolute ref would be better)
>
> So we could have a warning if an unlock() doesnt match the lock()
>
> inet6_dump_fib () was already a suspect but we could not find why.


Ok, I tried the patch below, and got the result farther down.  Is this
what you were thinking of?  (The lockdep warning about rcu lock still
held happened immediately after this..so it appears the depth mis-match
does represent this problem...


[greearb@fs3 linux-3.0.dev.y]$ git diff
diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c
index 0f9b37a..ae3c7c9 100644
--- a/net/ipv6/ip6_fib.c
+++ b/net/ipv6/ip6_fib.c
@@ -366,6 +366,7 @@ static int inet6_dump_fib(struct sk_buff *skb, struct netlink_callback *cb)
         struct hlist_node *node;
         struct hlist_head *head;
         int res = 0;
+       int depth = current->lockdep_depth;

         s_h = cb->args[0];
         s_e = cb->args[1];
@@ -410,6 +411,8 @@ next:
         }
  out:
         rcu_read_unlock();
+       WARN(depth != current->lockdep_depth, "depth: %i  lockdep-depth: %i\n",
+            depth, current->lockdep_depth);
         cb->args[1] = e;
         cb->args[0] = h;



------------[ cut here ]------------
WARNING: at /home/greearb/git/linux-3.0.dev.y/net/ipv6/ip6_fib.c:415 inet6_dump_fib+0x25c/0x292 [ipv6]()
Hardware name: To be filled by O.E.M.
depth: 1  lockdep-depth: 2
Modules linked in: 8021q garp stp llc fuse macvlan pktgen coretemp hwmon sunrpc ipv6 uinput arc4 ath9k snd_hda_codec_realtek mac80211 snd_hda_intel 
snd_hda_codec snd_hwdep snd_seq ath9k_common ath9k_hw snd_seq_device snd_pcm ath snd_timer e1000e cfg80211 snd mei(C) ppdev microcode i2c_i801 iTCO_wdt 
soundcore serio_raw pcspkr snd_page_alloc iTCO_vendor_support parport_pc parport i915 drm_kms_helper drm i2c_algo_bit i2c_core video [last unloaded: scsi_wait_scan]
Pid: 6563, comm: ip Tainted: G         C  3.0.25+ #16
Call Trace:
  [<ffffffff81046866>] warn_slowpath_common+0x80/0x98
  [<ffffffff81046912>] warn_slowpath_fmt+0x41/0x43
  [<ffffffffa0251a3a>] inet6_dump_fib+0x25c/0x292 [ipv6]
  [<ffffffff813af450>] netlink_dump+0x5b/0x19b
  [<ffffffff81385da2>] ? consume_skb+0x28/0x2a
  [<ffffffff813af7bf>] netlink_recvmsg+0x1c7/0x2f8
  [<ffffffff8137c6cf>] __sock_recvmsg_nosec+0x65/0x6e
  [<ffffffff8137dde0>] __sock_recvmsg+0x49/0x54
  [<ffffffff8137e349>] sock_recvmsg+0xa6/0xbf
  [<ffffffff81072bf8>] ? lock_release_non_nested+0x9d/0x227
  [<ffffffff810ca002>] ? might_fault+0x4e/0x9e
  [<ffffffff810ca04b>] ? might_fault+0x97/0x9e
  [<ffffffff81387cae>] ? copy_from_user+0x2a/0x2c
  [<ffffffff810ca002>] ? might_fault+0x4e/0x9e
  [<ffffffff81388080>] ? verify_iovec+0x4f/0xa3
  [<ffffffff8137e0c4>] __sys_recvmsg+0x147/0x21e
  [<ffffffff81063868>] ? up_read+0x1e/0x36
  [<ffffffff810fc9fb>] ? fcheck_files+0xb7/0xee
  [<ffffffff810fcb30>] ? fget_light+0x3b/0xbc
  [<ffffffff8137e8a0>] sys_recvmsg+0x3d/0x5b
  [<ffffffff81450e92>] system_call_fastpath+0x16/0x1b
---[ end trace 5232c09c4fb31d15 ]---



-- 
Ben Greear <greearb@candelatech.com>
Candela Technologies Inc  http://www.candelatech.com

^ permalink raw reply related

* Re: RCU lock bug in 3.0.21 (bisected to: 682cb56a, fix NULL dereferences in check_peer_redir)
From: Paul E. McKenney @ 2012-03-27 16:47 UTC (permalink / raw)
  To: Ben Greear; +Cc: Eric Dumazet, David Miller, netdev, gregkh
In-Reply-To: <4F71508C.908@candelatech.com>

On Mon, Mar 26, 2012 at 10:30:52PM -0700, Ben Greear wrote:
> On 03/26/2012 10:11 PM, Paul E. McKenney wrote:
> >On Tue, Mar 27, 2012 at 02:07:14AM +0200, Eric Dumazet wrote:
> >>On Mon, 2012-03-26 at 16:46 -0700, Ben Greear wrote:
> >>
> >>>The 3.0.21 kernel doesn't appear to have a rcu_read_lock_return(),
> >>>so I can't use your patch below.
> >>
> >>This patch was only to show the point (I also CCed Paul, he might have
> >>some time to think about it, after he clears the inline stuff with
> >>Linus)
> >
> >There is an rcu_preempt_depth() that returns rcu_read_lock() nesting
> >level for CONFIG_PREEMPT_RCU=y on the one hand and returns zero
> >for CONFIG_PREEMPT_RCU=n on the other.  So if you can reproduce
> >with CONFIG_PREEMPT_RCU=y, you can substitute rcu_preempt_depth()
> >rcu_read_lock_return() in Eric's earlier patch.
> 
> I'll try looking at that tomorrow.  I tried adding some code to check for
> recursive calls to the fib-dump, and didn't see it ever hit, though
> the bug continued to happen readily.
> 
> I just #if 0 the part between rcu-read-lock and read-unlock, and
> the problem went away..but of course you can't dump ipv6
> routes then...
> 
> The actual logic to dump the fib is quite complex, full of
> opaque types and other stuff ripe for bugs.  But, I don't see
> how it could cause the rcu splats in such a repeatable manner.
> 
> The bug is always reported as being in the same place, so if
> there is any other debugging code you can think of to help
> shed light on this, I'll be happy to add it and give it a try.
> For instance, is there a way to dump (print) all current holders of
> the rcu_read_lock?  I could call that before/during/after in that
> method and maybe get a clue.

I would guess that CONFIG_PROVE_RCU's use of lockdep would permit
listing all tasks holding rcu_read_lock(), as lockdep does maintain
that state in that case.

							Thanx, Paul

^ permalink raw reply

* [PATCH net/core] dev_forward_skb() should clear skb_iif
From: Benjamin LaHaise @ 2012-03-27 16:29 UTC (permalink / raw)
  To: David S. Miller; +Cc: netdev

While investigating another bug, I found that the code on the incoming path
in __netif_receive_skb will only set skb->skb_iif if it is already 0.  When
dev_forward_skb() is used in the case of interfaces like veth, skb_iif may
already have been set.  Making dev_forward_skb() cause the packet to look
more like a newly received packet would seem to the the correct behaviour
here, as otherwise the wrong incoming interface can be reported for such a
packet.
---
 net/core/dev.c |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

diff --git a/net/core/dev.c b/net/core/dev.c
index 0090809..8751f6a 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -1597,6 +1597,7 @@ int dev_forward_skb(struct net_device *dev, struct sk_buff *skb)
 		kfree_skb(skb);
 		return NET_RX_DROP;
 	}
+	skb->skb_iif = 0;
 	skb_set_dev(skb, dev);
 	skb->tstamp.tv64 = 0;
 	skb->pkt_type = PACKET_HOST;
-- 
1.7.4.1

^ permalink raw reply related

* [PATCH ipv4 multicast] Fix IPv4 multicast over network namespaces
From: Benjamin LaHaise @ 2012-03-27 16:12 UTC (permalink / raw)
  To: David S. Miller; +Cc: netdev

When using multicast over a local bridge feeding a number of LXC guests
using veth, the LXC guests are unable to get a response from other guests
when pinging 224.0.0.1.  Multicast packets did not appear to be getting
delivered to the network namespaces of the guest hosts, and further
inspection showed that the incoming route was pointing to the loopback
device of the host, not the guest.  This lead to the wrong network namespace
being picked up by sockets (like ICMP).  Fix this by using the correct
network namespace when creating the inbound route entry.
---
 net/ipv4/route.c |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/net/ipv4/route.c b/net/ipv4/route.c
index 12ccf88..3b110a4 100644
--- a/net/ipv4/route.c
+++ b/net/ipv4/route.c
@@ -2042,7 +2042,7 @@ static int ip_route_input_mc(struct sk_buff *skb, __be32 daddr, __be32 saddr,
 		if (err < 0)
 			goto e_err;
 	}
-	rth = rt_dst_alloc(init_net.loopback_dev,
+	rth = rt_dst_alloc(dev_net(dev)->loopback_dev,
 			   IN_DEV_CONF_GET(in_dev, NOPOLICY), false);
 	if (!rth)
 		goto e_nobufs;
-- 
1.7.4.1

^ permalink raw reply related

* Re: [PATCH net V4 2/2] igb: offer a PTP Hardware Clock instead of the timecompare method
From: Richard Cochran @ 2012-03-27 15:33 UTC (permalink / raw)
  To: chetan loke
  Cc: Keller, Jacob E, netdev@vger.kernel.org,
	e1000-devel@lists.sourceforge.net, Kirsher, Jeffrey T,
	Ronciak, John, john.stultz@linaro.org, tglx@linutronix.de
In-Reply-To: <CAAsGZS5Kn0nnwE0=0Eoo9a9e4+9w2fQ4nWjbCNsNrL-yfE3BtQ@mail.gmail.com>

On Mon, Mar 26, 2012 at 01:11:32PM -0400, chetan loke wrote:
> 
> Why isn't ioctl-rate limiting acceptable?

Let me turn the question around.

What other kernel subsystem rate limits ioctls?

Richard

^ permalink raw reply

* Hello
From: laurarey111 @ 2012-03-27 15:07 UTC (permalink / raw)


Hello xxxxx,
 How are you doing over there? My name is Laura, Please let
me know if i got the right address so we can open
communication, here is my email address I am waiting for
your confirmation with care Laura...

^ permalink raw reply

* [PATCH V3 5/6] net/route: export symbol ip_tos2prio
From: Amir Vadai @ 2012-03-27 15:09 UTC (permalink / raw)
  To: David S. Miller
  Cc: netdev, Roland Dreier, Yevgeny Petrilin, Oren Duer, Amir Vadai,
	Amir Vadai
In-Reply-To: <1332860951-23042-1-git-send-email-amirv@mellanox.com>

Need to export this to enable drivers use rt_tos2priority()

Signed-off-by: Amir Vadai <amirv@mellanox.com>
---
 net/ipv4/route.c |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/net/ipv4/route.c b/net/ipv4/route.c
index 12ccf88..69e0b86 100644
--- a/net/ipv4/route.c
+++ b/net/ipv4/route.c
@@ -230,7 +230,7 @@ const __u8 ip_tos2prio[16] = {
 	TC_PRIO_INTERACTIVE_BULK,
 	ECN_OR_COST(INTERACTIVE_BULK)
 };
-
+EXPORT_SYMBOL(ip_tos2prio);
 
 /*
  * Route cache.
-- 
1.7.8.2

^ permalink raw reply related

* [PATCH V3 1/6] net/mlx4_en: Force user priority by QP attribute
From: Amir Vadai @ 2012-03-27 15:09 UTC (permalink / raw)
  To: David S. Miller
  Cc: netdev, Roland Dreier, Yevgeny Petrilin, Oren Duer, Amir Vadai,
	Amir Vadai
In-Reply-To: <1332860951-23042-1-git-send-email-amirv@mellanox.com>

Instead of relying on HW to change schedule queue by UP, schedule
queue is fixed for a tx_ring, and UP in WQE is ignored in this aspect.  This
resolves two issues with untagged traffic:
1. untagged traffic has no UP in packet which is needed for QoS. The change
   above allows setting the schedule queue (and by that the UP) of such a stream.
2. BlueFlame uses the same field used by vlan tag. So forcing UP from QPC
   allows using BF for untagged but prioritized traffic.

In old firmware that force UP is not supported, untagged traffic will not subject to
QoS.

Because UP is set by QP, need to always have a tx ring per UP, even if pfcrx
module paramter is false.

Signed-off-by: Amir Vadai <amirv@mellanox.com>
---
 drivers/net/ethernet/mellanox/mlx4/en_main.c      |    2 +-
 drivers/net/ethernet/mellanox/mlx4/en_netdev.c    |    3 ++-
 drivers/net/ethernet/mellanox/mlx4/en_resources.c |    6 +++++-
 drivers/net/ethernet/mellanox/mlx4/en_rx.c        |    4 ++--
 drivers/net/ethernet/mellanox/mlx4/en_tx.c        |   12 ++++--------
 drivers/net/ethernet/mellanox/mlx4/mlx4_en.h      |    6 +++---
 include/linux/mlx4/qp.h                           |    3 ++-
 7 files changed, 19 insertions(+), 17 deletions(-)

diff --git a/drivers/net/ethernet/mellanox/mlx4/en_main.c b/drivers/net/ethernet/mellanox/mlx4/en_main.c
index 2097a7d..346fdb2 100644
--- a/drivers/net/ethernet/mellanox/mlx4/en_main.c
+++ b/drivers/net/ethernet/mellanox/mlx4/en_main.c
@@ -114,7 +114,7 @@ static int mlx4_en_get_profile(struct mlx4_en_dev *mdev)
 		params->prof[i].tx_ring_size = MLX4_EN_DEF_TX_RING_SIZE;
 		params->prof[i].rx_ring_size = MLX4_EN_DEF_RX_RING_SIZE;
 		params->prof[i].tx_ring_num = MLX4_EN_NUM_TX_RINGS +
-			(!!pfcrx) * MLX4_EN_NUM_PPP_RINGS;
+			MLX4_EN_NUM_PPP_RINGS;
 		params->prof[i].rss_rings = 0;
 	}
 
diff --git a/drivers/net/ethernet/mellanox/mlx4/en_netdev.c b/drivers/net/ethernet/mellanox/mlx4/en_netdev.c
index 31b455a..2322622 100644
--- a/drivers/net/ethernet/mellanox/mlx4/en_netdev.c
+++ b/drivers/net/ethernet/mellanox/mlx4/en_netdev.c
@@ -650,7 +650,8 @@ int mlx4_en_start_port(struct net_device *dev)
 
 		/* Configure ring */
 		tx_ring = &priv->tx_ring[i];
-		err = mlx4_en_activate_tx_ring(priv, tx_ring, cq->mcq.cqn);
+		err = mlx4_en_activate_tx_ring(priv, tx_ring, cq->mcq.cqn,
+				max(0, i - MLX4_EN_NUM_TX_RINGS));
 		if (err) {
 			en_err(priv, "Failed allocating Tx ring\n");
 			mlx4_en_deactivate_cq(priv, cq);
diff --git a/drivers/net/ethernet/mellanox/mlx4/en_resources.c b/drivers/net/ethernet/mellanox/mlx4/en_resources.c
index bcbc54c..10c24c7 100644
--- a/drivers/net/ethernet/mellanox/mlx4/en_resources.c
+++ b/drivers/net/ethernet/mellanox/mlx4/en_resources.c
@@ -39,7 +39,7 @@
 
 void mlx4_en_fill_qp_context(struct mlx4_en_priv *priv, int size, int stride,
 			     int is_tx, int rss, int qpn, int cqn,
-			     struct mlx4_qp_context *context)
+			     int user_prio, struct mlx4_qp_context *context)
 {
 	struct mlx4_en_dev *mdev = priv->mdev;
 
@@ -57,6 +57,10 @@ void mlx4_en_fill_qp_context(struct mlx4_en_priv *priv, int size, int stride,
 	context->local_qpn = cpu_to_be32(qpn);
 	context->pri_path.ackto = 1 & 0x07;
 	context->pri_path.sched_queue = 0x83 | (priv->port - 1) << 6;
+	if (user_prio >= 0) {
+		context->pri_path.sched_queue |= user_prio << 3;
+		context->pri_path.feup = 1 << 6;
+	}
 	context->pri_path.counter_index = 0xff;
 	context->cqn_send = cpu_to_be32(cqn);
 	context->cqn_recv = cpu_to_be32(cqn);
diff --git a/drivers/net/ethernet/mellanox/mlx4/en_rx.c b/drivers/net/ethernet/mellanox/mlx4/en_rx.c
index 9adbd53..d49a7ac 100644
--- a/drivers/net/ethernet/mellanox/mlx4/en_rx.c
+++ b/drivers/net/ethernet/mellanox/mlx4/en_rx.c
@@ -823,7 +823,7 @@ static int mlx4_en_config_rss_qp(struct mlx4_en_priv *priv, int qpn,
 
 	memset(context, 0, sizeof *context);
 	mlx4_en_fill_qp_context(priv, ring->actual_size, ring->stride, 0, 0,
-				qpn, ring->cqn, context);
+				qpn, ring->cqn, -1, context);
 	context->db_rec_addr = cpu_to_be64(ring->wqres.db.dma);
 
 	/* Cancel FCS removal if FW allows */
@@ -890,7 +890,7 @@ int mlx4_en_config_rss_steer(struct mlx4_en_priv *priv)
 	}
 	rss_map->indir_qp.event = mlx4_en_sqp_event;
 	mlx4_en_fill_qp_context(priv, 0, 0, 0, 1, priv->base_qpn,
-				priv->rx_ring[0].cqn, &context);
+				priv->rx_ring[0].cqn, -1, &context);
 
 	if (!priv->prof->rss_rings || priv->prof->rss_rings > priv->rx_ring_num)
 		rss_rings = priv->rx_ring_num;
diff --git a/drivers/net/ethernet/mellanox/mlx4/en_tx.c b/drivers/net/ethernet/mellanox/mlx4/en_tx.c
index 1796824..94a605a 100644
--- a/drivers/net/ethernet/mellanox/mlx4/en_tx.c
+++ b/drivers/net/ethernet/mellanox/mlx4/en_tx.c
@@ -156,7 +156,7 @@ void mlx4_en_destroy_tx_ring(struct mlx4_en_priv *priv,
 
 int mlx4_en_activate_tx_ring(struct mlx4_en_priv *priv,
 			     struct mlx4_en_tx_ring *ring,
-			     int cq)
+			     int cq, int user_prio)
 {
 	struct mlx4_en_dev *mdev = priv->mdev;
 	int err;
@@ -174,7 +174,7 @@ int mlx4_en_activate_tx_ring(struct mlx4_en_priv *priv,
 	ring->doorbell_qpn = ring->qp.qpn << 8;
 
 	mlx4_en_fill_qp_context(priv, ring->size, ring->stride, 1, 0, ring->qpn,
-				ring->cqn, &ring->context);
+				ring->cqn, user_prio, &ring->context);
 	if (ring->bf_enabled)
 		ring->context.usr_page = cpu_to_be32(ring->bf.uar->index);
 
@@ -570,18 +570,14 @@ static void build_inline_wqe(struct mlx4_en_tx_desc *tx_desc, struct sk_buff *sk
 
 u16 mlx4_en_select_queue(struct net_device *dev, struct sk_buff *skb)
 {
-	struct mlx4_en_priv *priv = netdev_priv(dev);
 	u16 vlan_tag = 0;
 
-	/* If we support per priority flow control and the packet contains
-	 * a vlan tag, send the packet to the TX ring assigned to that priority
-	 */
-	if (priv->prof->rx_ppp && vlan_tx_tag_present(skb)) {
+	if (vlan_tx_tag_present(skb)) {
 		vlan_tag = vlan_tx_tag_get(skb);
 		return MLX4_EN_NUM_TX_RINGS + (vlan_tag >> 13);
 	}
 
-	return skb_tx_hash(dev, skb);
+	return __skb_tx_hash(dev, skb, MLX4_EN_NUM_TX_RINGS);
 }
 
 static void mlx4_bf_copy(void __iomem *dst, unsigned long *src, unsigned bytecnt)
diff --git a/drivers/net/ethernet/mellanox/mlx4/mlx4_en.h b/drivers/net/ethernet/mellanox/mlx4/mlx4_en.h
index 9e2b911..5bd7c2a 100644
--- a/drivers/net/ethernet/mellanox/mlx4/mlx4_en.h
+++ b/drivers/net/ethernet/mellanox/mlx4/mlx4_en.h
@@ -521,7 +521,7 @@ int mlx4_en_create_tx_ring(struct mlx4_en_priv *priv, struct mlx4_en_tx_ring *ri
 void mlx4_en_destroy_tx_ring(struct mlx4_en_priv *priv, struct mlx4_en_tx_ring *ring);
 int mlx4_en_activate_tx_ring(struct mlx4_en_priv *priv,
 			     struct mlx4_en_tx_ring *ring,
-			     int cq);
+			     int cq, int user_prio);
 void mlx4_en_deactivate_tx_ring(struct mlx4_en_priv *priv,
 				struct mlx4_en_tx_ring *ring);
 
@@ -539,8 +539,8 @@ int mlx4_en_process_rx_cq(struct net_device *dev,
 			  int budget);
 int mlx4_en_poll_rx_cq(struct napi_struct *napi, int budget);
 void mlx4_en_fill_qp_context(struct mlx4_en_priv *priv, int size, int stride,
-			     int is_tx, int rss, int qpn, int cqn,
-			     struct mlx4_qp_context *context);
+		int is_tx, int rss, int qpn, int cqn, int user_prio,
+		struct mlx4_qp_context *context);
 void mlx4_en_sqp_event(struct mlx4_qp *qp, enum mlx4_event event);
 int mlx4_en_map_buffer(struct mlx4_buf *buf);
 void mlx4_en_unmap_buffer(struct mlx4_buf *buf);
diff --git a/include/linux/mlx4/qp.h b/include/linux/mlx4/qp.h
index 091f9e7..96005d7 100644
--- a/include/linux/mlx4/qp.h
+++ b/include/linux/mlx4/qp.h
@@ -139,7 +139,8 @@ struct mlx4_qp_path {
 	u8			rgid[16];
 	u8			sched_queue;
 	u8			vlan_index;
-	u8			reserved3[2];
+	u8			feup;
+	u8			reserved3;
 	u8			reserved4[2];
 	u8			dmac[6];
 };
-- 
1.7.8.2

^ permalink raw reply related

* [PATCH V3 0/6] net/mlx4_en: DCB QoS support
From: Amir Vadai @ 2012-03-27 15:09 UTC (permalink / raw)
  To: David S. Miller
  Cc: netdev, Roland Dreier, Yevgeny Petrilin, Oren Duer, Amir Vadai,
	Amir Vadai, John Fastabend, Eric Dumazet

DCBX version 802.1qaz is supported.
User Priority (UP) is set in QP context instead of in WQE (QP Work Queue
Element), which means that all traffic from a queue will have the same UP.
UP is also set for untagged traffic to be able to classify such traffic too.

Mapping from sk_prio to User Priority is done by sch_mqprio mapping. Although
confusingly sch_mqprio maps sk_prio to something called TC, it is not related
to DCBX's TC, and is interpreted by mlx4_en driver as UP.

CC: John Fastabend <john.r.fastabend@intel.com>
CC: Eric Dumazet <eric.dumazet@gmail.com>

---
Changes from V2:
- Removed patch 4 who deal with setting ratelimit through sysfs - Still under discussion
- Patch 2/7 - set port QoS attributes
  - ratelimit is given in Kbps instead of Mbps units.
Changes from V1:
- Patch 1/7 - Force user priority by QP attribute
  - removed unneeded comment
- Patch 2/7 - set port QoS attributes
  - ratelimit is given in Mbps instead of 100Mbps units.
- Patch 3/7 - DCB QoS support
  - Split mlx4_en_config_port_scheduler from mlx4_en_dcbnl_ieee_setets - enable
    setting ratelimit only, without setting up to tc mapping
- Patch 4/7 - Set max rate-limit for a TC
  - Rewritten patch according to comment from Eric D.
  - setting ratelimit is through sysfs files:
    /sys/class/net/<intf>/ratelimit/tc0
    /sys/class/net/<intf>/ratelimit/tc1
    ...
    /sys/class/net/<intf>/ratelimit/tc7
- Patch 5/7 - sk_prio <=> UP for untagged traffic  
  - Using skb_tx_hash for queue selection of untagged traffic
- Added Patch 6/7 - export symbol ip_tos2prio
  - Fixed patch according to comment from Eric D.
- Patch 7/7 - TOS <=> UP mapping for IBoE
  - No need to clone code from route.c anymore - removed it
    
Changes from V0:
- Removed patches 6 and 7 who deal with the interaction between the kernel HW
  QoS constructs to the queue selection logic are still under discussion with
  John and some changes might be needed there.

Amir Vadai (6):
  net/mlx4_en: Force user priority by QP attribute
  net/mlx4_core: set port QoS attributes
  net/mlx4_en: DCB QoS support
  net/mlx4_en: sk_prio <=> UP for untagged traffic
  net/route: export symbol ip_tos2prio
  IB/rdma_cm: TOS <=> UP mapping for IBoE

 drivers/infiniband/core/cma.c                     |    6 +-
 drivers/net/ethernet/mellanox/mlx4/Kconfig        |   12 ++
 drivers/net/ethernet/mellanox/mlx4/Makefile       |    1 +
 drivers/net/ethernet/mellanox/mlx4/en_dcb_nl.c    |  211 +++++++++++++++++++++
 drivers/net/ethernet/mellanox/mlx4/en_main.c      |    2 +-
 drivers/net/ethernet/mellanox/mlx4/en_netdev.c    |   37 ++++-
 drivers/net/ethernet/mellanox/mlx4/en_port.h      |    2 +
 drivers/net/ethernet/mellanox/mlx4/en_resources.c |    6 +-
 drivers/net/ethernet/mellanox/mlx4/en_rx.c        |    4 +-
 drivers/net/ethernet/mellanox/mlx4/en_tx.c        |   10 +-
 drivers/net/ethernet/mellanox/mlx4/mlx4.h         |   21 ++
 drivers/net/ethernet/mellanox/mlx4/mlx4_en.h      |   27 +++-
 drivers/net/ethernet/mellanox/mlx4/port.c         |   67 +++++++
 include/linux/mlx4/cmd.h                          |    4 +
 include/linux/mlx4/device.h                       |    3 +
 include/linux/mlx4/qp.h                           |    3 +-
 net/ipv4/route.c                                  |    2 +-
 17 files changed, 400 insertions(+), 18 deletions(-)
 create mode 100644 drivers/net/ethernet/mellanox/mlx4/en_dcb_nl.c

-- 
1.7.8.2

^ permalink raw reply

* [PATCH V3 6/6] IB/rdma_cm: TOS <=> UP mapping for IBoE
From: Amir Vadai @ 2012-03-27 15:09 UTC (permalink / raw)
  To: David S. Miller
  Cc: netdev, Roland Dreier, Yevgeny Petrilin, Oren Duer, Amir Vadai,
	Amir Vadai, Sean Hefty
In-Reply-To: <1332860951-23042-1-git-send-email-amirv@mellanox.com>

Both tagged traffic and untagged traffic use tc tool mapping.
Treat RDMA TOS same as IP TOS when mapping to SL

Signed-off-by: Amir Vadai <amirv@mellanox.com>
CC: Sean Hefty <sean.hefty@intel.com>
---
 drivers/infiniband/core/cma.c |    6 +++++-
 1 files changed, 5 insertions(+), 1 deletions(-)

diff --git a/drivers/infiniband/core/cma.c b/drivers/infiniband/core/cma.c
index e3e470f..59fbd70 100644
--- a/drivers/infiniband/core/cma.c
+++ b/drivers/infiniband/core/cma.c
@@ -42,6 +42,7 @@
 #include <linux/inetdevice.h>
 #include <linux/slab.h>
 #include <linux/module.h>
+#include <net/route.h>
 
 #include <net/tcp.h>
 #include <net/ipv6.h>
@@ -1826,7 +1827,10 @@ static int cma_resolve_iboe_route(struct rdma_id_private *id_priv)
 	route->path_rec->reversible = 1;
 	route->path_rec->pkey = cpu_to_be16(0xffff);
 	route->path_rec->mtu_selector = IB_SA_EQ;
-	route->path_rec->sl = id_priv->tos >> 5;
+	route->path_rec->sl = netdev_get_prio_tc_map(
+			ndev->priv_flags & IFF_802_1Q_VLAN ?
+				vlan_dev_real_dev(ndev) : ndev,
+			rt_tos2priority(id_priv->tos));
 
 	route->path_rec->mtu = iboe_get_mtu(ndev->mtu);
 	route->path_rec->rate_selector = IB_SA_EQ;
-- 
1.7.8.2

^ permalink raw reply related

* [PATCH V3 2/6] net/mlx4_core: set port QoS attributes
From: Amir Vadai @ 2012-03-27 15:09 UTC (permalink / raw)
  To: David S. Miller
  Cc: netdev, Roland Dreier, Yevgeny Petrilin, Oren Duer, Amir Vadai,
	Amir Vadai
In-Reply-To: <1332860951-23042-1-git-send-email-amirv@mellanox.com>

Adding QoS firmware commands:
- mlx4_en_SET_PORT_PRIO2TC - set UP <=> TC
- mlx4_en_SET_PORT_SCHEDULER - set promised BW, max BW and PG number

Signed-off-by: Amir Vadai <amirv@mellanox.com>
---
 drivers/net/ethernet/mellanox/mlx4/en_port.h |    2 +
 drivers/net/ethernet/mellanox/mlx4/mlx4.h    |   21 ++++++++
 drivers/net/ethernet/mellanox/mlx4/port.c    |   67 ++++++++++++++++++++++++++
 include/linux/mlx4/cmd.h                     |    4 ++
 include/linux/mlx4/device.h                  |    3 +
 5 files changed, 97 insertions(+), 0 deletions(-)

diff --git a/drivers/net/ethernet/mellanox/mlx4/en_port.h b/drivers/net/ethernet/mellanox/mlx4/en_port.h
index 6934fd7..745090b 100644
--- a/drivers/net/ethernet/mellanox/mlx4/en_port.h
+++ b/drivers/net/ethernet/mellanox/mlx4/en_port.h
@@ -39,6 +39,8 @@
 #define SET_PORT_PROMISC_SHIFT	31
 #define SET_PORT_MC_PROMISC_SHIFT	30
 
+#define MLX4_EN_NUM_TC		8
+
 #define VLAN_FLTR_SIZE	128
 struct mlx4_set_vlan_fltr_mbox {
 	__be32 entry[VLAN_FLTR_SIZE];
diff --git a/drivers/net/ethernet/mellanox/mlx4/mlx4.h b/drivers/net/ethernet/mellanox/mlx4/mlx4.h
index 2a0ff2c..98cbb2f 100644
--- a/drivers/net/ethernet/mellanox/mlx4/mlx4.h
+++ b/drivers/net/ethernet/mellanox/mlx4/mlx4.h
@@ -53,6 +53,27 @@
 #define DRV_VERSION	"1.1"
 #define DRV_RELDATE	"Dec, 2011"
 
+#define MLX4_NUM_UP		8
+#define MLX4_NUM_TC		8
+#define MLX4_RATELIMIT_UNITS 3 /* 100 Mbps */
+#define MLX4_RATELIMIT_KB_TO_HW(_mb) ((_mb) / 100000)
+#define MLX4_RATELIMIT_DEFAULT 0xffff
+
+struct mlx4_set_port_prio2tc_context {
+	u8 prio2tc[4];
+};
+
+struct mlx4_port_scheduler_tc_cfg_be {
+	__be16 pg;
+	__be16 bw_precentage;
+	__be16 max_bw_units; /* 3-100Mbps, 4-1Gbps, other values - reserved */
+	__be16 max_bw_value;
+};
+
+struct mlx4_set_port_scheduler_context {
+	struct mlx4_port_scheduler_tc_cfg_be tc[MLX4_NUM_TC];
+};
+
 enum {
 	MLX4_HCR_BASE		= 0x80680,
 	MLX4_HCR_SIZE		= 0x0001c,
diff --git a/drivers/net/ethernet/mellanox/mlx4/port.c b/drivers/net/ethernet/mellanox/mlx4/port.c
index 77535ff..e619cd5 100644
--- a/drivers/net/ethernet/mellanox/mlx4/port.c
+++ b/drivers/net/ethernet/mellanox/mlx4/port.c
@@ -834,6 +834,73 @@ int mlx4_SET_PORT_qpn_calc(struct mlx4_dev *dev, u8 port, u32 base_qpn,
 }
 EXPORT_SYMBOL(mlx4_SET_PORT_qpn_calc);
 
+int mlx4_SET_PORT_PRIO2TC(struct mlx4_dev *dev, u8 port, u8 *prio2tc)
+{
+	struct mlx4_cmd_mailbox *mailbox;
+	struct mlx4_set_port_prio2tc_context *context;
+	int err;
+	u32 in_mod;
+	int i;
+
+	mailbox = mlx4_alloc_cmd_mailbox(dev);
+	if (IS_ERR(mailbox))
+		return PTR_ERR(mailbox);
+	context = mailbox->buf;
+	memset(context, 0, sizeof *context);
+
+	for (i = 0; i < MLX4_NUM_UP; i += 2)
+		context->prio2tc[i >> 1] = prio2tc[i] << 4 | prio2tc[i + 1];
+
+	in_mod = MLX4_SET_PORT_PRIO2TC << 8 | port;
+	err = mlx4_cmd(dev, mailbox->dma, in_mod, 1, MLX4_CMD_SET_PORT,
+		       MLX4_CMD_TIME_CLASS_B, MLX4_CMD_NATIVE);
+
+	mlx4_free_cmd_mailbox(dev, mailbox);
+	return err;
+}
+EXPORT_SYMBOL(mlx4_SET_PORT_PRIO2TC);
+
+int mlx4_SET_PORT_SCHEDULER(struct mlx4_dev *dev, u8 port, u8 *tc_tx_bw,
+		u8 *pg, u64 *ratelimit)
+{
+	struct mlx4_cmd_mailbox *mailbox;
+	struct mlx4_set_port_scheduler_context *context;
+	int err;
+	u32 in_mod;
+	int i;
+
+	mailbox = mlx4_alloc_cmd_mailbox(dev);
+	if (IS_ERR(mailbox))
+		return PTR_ERR(mailbox);
+	context = mailbox->buf;
+	memset(context, 0, sizeof *context);
+
+	for (i = 0; i < MLX4_NUM_TC; i++) {
+		struct mlx4_port_scheduler_tc_cfg_be *tc = &context->tc[i];
+		u16 r = 0;
+
+		if (ratelimit)
+			r = MLX4_RATELIMIT_KB_TO_HW(ratelimit[i]);
+
+		if (!r)
+			r = MLX4_RATELIMIT_DEFAULT;
+
+		tc->pg = htons(pg[i]);
+		tc->bw_precentage = htons(tc_tx_bw[i]);
+
+		tc->max_bw_units = htons(MLX4_RATELIMIT_UNITS);
+		tc->max_bw_value = htons(r);
+	}
+
+	in_mod = MLX4_SET_PORT_SCHEDULER << 8 | port;
+	err = mlx4_cmd(dev, mailbox->dma, in_mod, 1, MLX4_CMD_SET_PORT,
+		       MLX4_CMD_TIME_CLASS_B, MLX4_CMD_NATIVE);
+
+	mlx4_free_cmd_mailbox(dev, mailbox);
+	return err;
+}
+EXPORT_SYMBOL(mlx4_SET_PORT_SCHEDULER);
+
 int mlx4_SET_MCAST_FLTR_wrapper(struct mlx4_dev *dev, int slave,
 				struct mlx4_vhcr *vhcr,
 				struct mlx4_cmd_mailbox *inbox,
diff --git a/include/linux/mlx4/cmd.h b/include/linux/mlx4/cmd.h
index 9958ff2..1f3860a 100644
--- a/include/linux/mlx4/cmd.h
+++ b/include/linux/mlx4/cmd.h
@@ -150,6 +150,10 @@ enum {
 	/* statistics commands */
 	MLX4_CMD_QUERY_IF_STAT	 = 0X54,
 	MLX4_CMD_SET_IF_STAT	 = 0X55,
+
+	/* set port opcode modifiers */
+	MLX4_SET_PORT_PRIO2TC = 0x8,
+	MLX4_SET_PORT_SCHEDULER  = 0x9,
 };
 
 enum {
diff --git a/include/linux/mlx4/device.h b/include/linux/mlx4/device.h
index 834c96c..6627136 100644
--- a/include/linux/mlx4/device.h
+++ b/include/linux/mlx4/device.h
@@ -628,6 +628,9 @@ int mlx4_SET_PORT_general(struct mlx4_dev *dev, u8 port, int mtu,
 			  u8 pptx, u8 pfctx, u8 pprx, u8 pfcrx);
 int mlx4_SET_PORT_qpn_calc(struct mlx4_dev *dev, u8 port, u32 base_qpn,
 			   u8 promisc);
+int mlx4_SET_PORT_PRIO2TC(struct mlx4_dev *dev, u8 port, u8 *prio2tc);
+int mlx4_SET_PORT_SCHEDULER(struct mlx4_dev *dev, u8 port, u8 *tc_tx_bw,
+		u8 *pg, u64 *ratelimit);
 int mlx4_find_cached_vlan(struct mlx4_dev *dev, u8 port, u16 vid, int *idx);
 int mlx4_register_vlan(struct mlx4_dev *dev, u8 port, u16 vlan, int *index);
 void mlx4_unregister_vlan(struct mlx4_dev *dev, u8 port, int index);
-- 
1.7.8.2

^ permalink raw reply related

* [PATCH V3 3/6] net/mlx4_en: DCB QoS support
From: Amir Vadai @ 2012-03-27 15:09 UTC (permalink / raw)
  To: David S. Miller
  Cc: netdev, Roland Dreier, Yevgeny Petrilin, Oren Duer, Amir Vadai,
	Amir Vadai
In-Reply-To: <1332860951-23042-1-git-send-email-amirv@mellanox.com>

Set TSA, promised BW and PFC using IEEE 802.1qaz netlink commands.

Signed-off-by: Amir Vadai <amirv@mellanox.com>
---
 drivers/net/ethernet/mellanox/mlx4/Kconfig     |   12 ++
 drivers/net/ethernet/mellanox/mlx4/Makefile    |    1 +
 drivers/net/ethernet/mellanox/mlx4/en_dcb_nl.c |  211 ++++++++++++++++++++++++
 drivers/net/ethernet/mellanox/mlx4/en_netdev.c |   16 ++
 drivers/net/ethernet/mellanox/mlx4/mlx4_en.h   |   21 +++
 5 files changed, 261 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/ethernet/mellanox/mlx4/en_dcb_nl.c

diff --git a/drivers/net/ethernet/mellanox/mlx4/Kconfig b/drivers/net/ethernet/mellanox/mlx4/Kconfig
index 1bb9353..5f027f9 100644
--- a/drivers/net/ethernet/mellanox/mlx4/Kconfig
+++ b/drivers/net/ethernet/mellanox/mlx4/Kconfig
@@ -11,6 +11,18 @@ config MLX4_EN
 	  This driver supports Mellanox Technologies ConnectX Ethernet
 	  devices.
 
+config MLX4_EN_DCB
+	bool "Data Center Bridging (DCB) Support"
+	default y
+	depends on MLX4_EN && DCB
+	---help---
+	  Say Y here if you want to use Data Center Bridging (DCB) in the
+	  driver.
+	  If set to N, will not be able to configure QoS and ratelimit attributes.
+	  This flag is depended on the kernel's DCB support.
+
+	  If unsure, set to Y
+
 config MLX4_CORE
 	tristate
 	depends on PCI
diff --git a/drivers/net/ethernet/mellanox/mlx4/Makefile b/drivers/net/ethernet/mellanox/mlx4/Makefile
index 4a40ab9..293127d 100644
--- a/drivers/net/ethernet/mellanox/mlx4/Makefile
+++ b/drivers/net/ethernet/mellanox/mlx4/Makefile
@@ -7,3 +7,4 @@ obj-$(CONFIG_MLX4_EN)               += mlx4_en.o
 
 mlx4_en-y := 	en_main.o en_tx.o en_rx.o en_ethtool.o en_port.o en_cq.o \
 		en_resources.o en_netdev.o en_selftest.o
+mlx4_en-$(CONFIG_MLX4_EN_DCB) += en_dcb_nl.o
diff --git a/drivers/net/ethernet/mellanox/mlx4/en_dcb_nl.c b/drivers/net/ethernet/mellanox/mlx4/en_dcb_nl.c
new file mode 100644
index 0000000..5bf0106
--- /dev/null
+++ b/drivers/net/ethernet/mellanox/mlx4/en_dcb_nl.c
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 2011 Mellanox Technologies. All rights reserved.
+ *
+ * This software is available to you under a choice of one of two
+ * licenses.  You may choose to be licensed under the terms of the GNU
+ * General Public License (GPL) Version 2, available from the file
+ * COPYING in the main directory of this source tree, or the
+ * OpenIB.org BSD license below:
+ *
+ *     Redistribution and use in source and binary forms, with or
+ *     without modification, are permitted provided that the following
+ *     conditions are met:
+ *
+ *      - Redistributions of source code must retain the above
+ *        copyright notice, this list of conditions and the following
+ *        disclaimer.
+ *
+ *      - Redistributions in binary form must reproduce the above
+ *        copyright notice, this list of conditions and the following
+ *        disclaimer in the documentation and/or other materials
+ *        provided with the distribution.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ */
+
+#include <linux/dcbnl.h>
+
+#include "mlx4_en.h"
+
+static int mlx4_en_dcbnl_ieee_getets(struct net_device *dev,
+				   struct ieee_ets *ets)
+{
+	struct mlx4_en_priv *priv = netdev_priv(dev);
+	struct ieee_ets *my_ets = priv->mlx4_en_ieee_ets;
+
+	/* No IEEE PFC settings available */
+	if (!my_ets)
+		return -EINVAL;
+
+	ets->ets_cap = IEEE_8021QAZ_MAX_TCS;
+	ets->cbs = my_ets->cbs;
+	memcpy(ets->tc_tx_bw, my_ets->tc_tx_bw, sizeof(ets->tc_tx_bw));
+	memcpy(ets->tc_tsa, my_ets->tc_tsa, sizeof(ets->tc_tsa));
+	memcpy(ets->prio_tc, my_ets->prio_tc, sizeof(ets->prio_tc));
+
+	return 0;
+}
+
+static int mlx4_en_ets_validate(struct mlx4_en_priv *priv, struct ieee_ets *ets)
+{
+	int i;
+	int total_ets_bw = 0;
+	int has_ets_tc = 0;
+
+	for (i = 0; i < IEEE_8021QAZ_MAX_TCS; i++) {
+		if (ets->prio_tc[i] > MLX4_EN_NUM_UP) {
+			en_err(priv, "Bad priority in UP <=> TC mapping. "
+					"TC: %d, UP: %d\n", i, ets->prio_tc[i]);
+			return -EINVAL;
+		}
+
+		switch (ets->tc_tsa[i]) {
+		case IEEE_8021QAZ_TSA_STRICT:
+			break;
+		case IEEE_8021QAZ_TSA_ETS:
+			has_ets_tc = 1;
+			total_ets_bw += ets->tc_tx_bw[i];
+			break;
+		default:
+			en_err(priv, "TC[%d]: Not supported TSA: %d\n",
+					i, ets->tc_tsa[i]);
+			return -ENOTSUPP;
+		}
+	}
+
+	if (has_ets_tc && total_ets_bw != MLX4_EN_BW_MAX) {
+		en_err(priv, "Bad ETS BW sum: %d. Should be exactly 100%%\n",
+				total_ets_bw);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int mlx4_en_config_port_scheduler(struct mlx4_en_priv *priv,
+		struct ieee_ets *ets, u64 *ratelimit)
+{
+	struct mlx4_en_dev *mdev = priv->mdev;
+	int num_strict = 0;
+	int i;
+	__u8 tc_tx_bw[IEEE_8021QAZ_MAX_TCS] = { 0 };
+	__u8 pg[IEEE_8021QAZ_MAX_TCS] = { 0 };
+
+	ets = ets ?: priv->mlx4_en_ieee_ets;
+
+	/* higher TC means higher priority => lower pg */
+	for (i = IEEE_8021QAZ_MAX_TCS - 1; i >= 0; i--) {
+		switch (ets->tc_tsa[i]) {
+		case IEEE_8021QAZ_TSA_STRICT:
+			pg[i] = num_strict++;
+			tc_tx_bw[i] = MLX4_EN_BW_MAX;
+			break;
+		case IEEE_8021QAZ_TSA_ETS:
+			pg[i] = MLX4_EN_TC_ETS;
+			tc_tx_bw[i] = ets->tc_tx_bw[i] ?: MLX4_EN_BW_MIN;
+			break;
+		}
+	}
+
+	return mlx4_SET_PORT_SCHEDULER(mdev->dev, priv->port, tc_tx_bw, pg,
+			ratelimit);
+}
+
+static int
+mlx4_en_dcbnl_ieee_setets(struct net_device *dev, struct ieee_ets *ets)
+{
+	struct mlx4_en_priv *priv = netdev_priv(dev);
+	struct mlx4_en_dev *mdev = priv->mdev;
+	int err;
+
+	err = mlx4_en_ets_validate(priv, ets);
+	if (err)
+		return err;
+
+	err = mlx4_SET_PORT_PRIO2TC(mdev->dev, priv->port, ets->prio_tc);
+	if (err)
+		return err;
+
+	err = mlx4_en_config_port_scheduler(priv, ets, NULL);
+	if (err)
+		return err;
+
+	if (ets != priv->mlx4_en_ieee_ets)
+		memcpy(priv->mlx4_en_ieee_ets, ets,
+				sizeof(*priv->mlx4_en_ieee_ets));
+
+	return 0;
+}
+
+static int mlx4_en_dcbnl_ieee_getpfc(struct net_device *dev,
+		struct ieee_pfc *pfc)
+{
+	struct mlx4_en_priv *priv = netdev_priv(dev);
+
+	pfc->pfc_cap = IEEE_8021QAZ_MAX_TCS;
+	pfc->pfc_en = priv->prof->tx_ppp;
+
+	return 0;
+}
+
+static int mlx4_en_dcbnl_ieee_setpfc(struct net_device *dev,
+		struct ieee_pfc *pfc)
+{
+	struct mlx4_en_priv *priv = netdev_priv(dev);
+	struct mlx4_en_dev *mdev = priv->mdev;
+	int err;
+
+	en_dbg(DRV, priv, "cap: 0x%x en: 0x%x mbc: 0x%x delay: %d\n",
+			pfc->pfc_cap,
+			pfc->pfc_en,
+			pfc->mbc,
+			pfc->delay);
+
+	priv->prof->rx_pause = priv->prof->tx_pause = !!pfc->pfc_en;
+	priv->prof->rx_ppp = priv->prof->tx_ppp = pfc->pfc_en;
+
+	err = mlx4_SET_PORT_general(mdev->dev, priv->port,
+				    priv->rx_skb_size + ETH_FCS_LEN,
+				    priv->prof->tx_pause,
+				    priv->prof->tx_ppp,
+				    priv->prof->rx_pause,
+				    priv->prof->rx_ppp);
+	if (err)
+		en_err(priv, "Failed setting pause params\n");
+
+	return err;
+}
+
+static u8 mlx4_en_dcbnl_getdcbx(struct net_device *dev)
+{
+	return DCB_CAP_DCBX_VER_IEEE;
+}
+
+static u8 mlx4_en_dcbnl_setdcbx(struct net_device *dev, u8 mode)
+{
+	if ((mode & DCB_CAP_DCBX_LLD_MANAGED) ||
+	    (mode & DCB_CAP_DCBX_VER_CEE) ||
+	    !(mode & DCB_CAP_DCBX_VER_IEEE) ||
+	    !(mode & DCB_CAP_DCBX_HOST))
+		return 1;
+
+	return 0;
+}
+
+const struct dcbnl_rtnl_ops mlx4_en_dcbnl_ops = {
+	.ieee_getets	= mlx4_en_dcbnl_ieee_getets,
+	.ieee_setets	= mlx4_en_dcbnl_ieee_setets,
+	.ieee_getpfc	= mlx4_en_dcbnl_ieee_getpfc,
+	.ieee_setpfc	= mlx4_en_dcbnl_ieee_setpfc,
+
+	.getdcbx	= mlx4_en_dcbnl_getdcbx,
+	.setdcbx	= mlx4_en_dcbnl_setdcbx,
+};
diff --git a/drivers/net/ethernet/mellanox/mlx4/en_netdev.c b/drivers/net/ethernet/mellanox/mlx4/en_netdev.c
index 2322622..9b456ae 100644
--- a/drivers/net/ethernet/mellanox/mlx4/en_netdev.c
+++ b/drivers/net/ethernet/mellanox/mlx4/en_netdev.c
@@ -967,6 +967,11 @@ void mlx4_en_destroy_netdev(struct net_device *dev)
 	mutex_unlock(&mdev->state_lock);
 
 	mlx4_en_free_resources(priv);
+
+#ifdef CONFIG_MLX4_EN_DCB
+	vfree(priv->mlx4_en_ieee_ets);
+#endif
+
 	free_netdev(dev);
 }
 
@@ -1080,6 +1085,17 @@ int mlx4_en_init_netdev(struct mlx4_en_dev *mdev, int port,
 	INIT_WORK(&priv->watchdog_task, mlx4_en_restart);
 	INIT_WORK(&priv->linkstate_task, mlx4_en_linkstate);
 	INIT_DELAYED_WORK(&priv->stats_task, mlx4_en_do_get_stats);
+#ifdef CONFIG_MLX4_EN_DCB
+	if (!mlx4_is_slave(priv->mdev->dev)) {
+		dev->dcbnl_ops = &mlx4_en_dcbnl_ops;
+
+		priv->mlx4_en_ieee_ets = vzalloc(sizeof(struct ieee_ets));
+		if (!priv->mlx4_en_ieee_ets) {
+			err = -ENOMEM;
+			goto out;
+		}
+	}
+#endif
 
 	/* Query for default mac and max mtu */
 	priv->max_mtu = mdev->dev->caps.eth_mtu_cap[priv->port];
diff --git a/drivers/net/ethernet/mellanox/mlx4/mlx4_en.h b/drivers/net/ethernet/mellanox/mlx4/mlx4_en.h
index 5bd7c2a..fa09792 100644
--- a/drivers/net/ethernet/mellanox/mlx4/mlx4_en.h
+++ b/drivers/net/ethernet/mellanox/mlx4/mlx4_en.h
@@ -40,6 +40,9 @@
 #include <linux/mutex.h>
 #include <linux/netdevice.h>
 #include <linux/if_vlan.h>
+#ifdef CONFIG_MLX4_EN_DCB
+#include <linux/dcbnl.h>
+#endif
 
 #include <linux/mlx4/device.h>
 #include <linux/mlx4/qp.h>
@@ -110,6 +113,7 @@ enum {
 #define MLX4_EN_NUM_TX_RINGS		8
 #define MLX4_EN_NUM_PPP_RINGS		8
 #define MAX_TX_RINGS			(MLX4_EN_NUM_TX_RINGS + MLX4_EN_NUM_PPP_RINGS)
+#define MLX4_EN_NUM_UP			8
 #define MLX4_EN_DEF_TX_RING_SIZE	512
 #define MLX4_EN_DEF_RX_RING_SIZE  	1024
 
@@ -410,6 +414,15 @@ struct mlx4_en_frag_info {
 
 };
 
+#ifdef CONFIG_MLX4_EN_DCB
+/* Minimal TC BW - setting to 0 will block traffic */
+#define MLX4_EN_BW_MIN 1
+#define MLX4_EN_BW_MAX 100 /* Utilize 100% of the line */
+
+#define MLX4_EN_TC_ETS 7
+
+#endif
+
 struct mlx4_en_priv {
 	struct mlx4_en_dev *mdev;
 	struct mlx4_en_port_profile *prof;
@@ -483,6 +496,10 @@ struct mlx4_en_priv {
 	int vids[128];
 	bool wol;
 	struct device *ddev;
+
+#ifdef CONFIG_MLX4_EN_DCB
+	struct ieee_ets *mlx4_en_ieee_ets;
+#endif
 };
 
 enum mlx4_en_wol {
@@ -557,6 +574,10 @@ int mlx4_SET_VLAN_FLTR(struct mlx4_dev *dev, struct mlx4_en_priv *priv);
 int mlx4_en_DUMP_ETH_STATS(struct mlx4_en_dev *mdev, u8 port, u8 reset);
 int mlx4_en_QUERY_PORT(struct mlx4_en_dev *mdev, u8 port);
 
+#ifdef CONFIG_MLX4_EN_DCB
+extern const struct dcbnl_rtnl_ops mlx4_en_dcbnl_ops;
+#endif
+
 #define MLX4_EN_NUM_SELF_TEST	5
 void mlx4_en_ex_selftest(struct net_device *dev, u32 *flags, u64 *buf);
 u64 mlx4_en_mac_to_u64(u8 *addr);
-- 
1.7.8.2

^ permalink raw reply related

* [PATCH V3 4/6] net/mlx4_en: sk_prio <=> UP for untagged traffic
From: Amir Vadai @ 2012-03-27 15:09 UTC (permalink / raw)
  To: David S. Miller
  Cc: netdev, Roland Dreier, Yevgeny Petrilin, Oren Duer, Amir Vadai,
	Amir Vadai
In-Reply-To: <1332860951-23042-1-git-send-email-amirv@mellanox.com>

Since vlan egress map is only good for tagged traffic, need to have other
mapping to be used by untagged traffic.
For that, the driver uses sch_mqprio mapping. This mapping could be set by
using tc tool from iproute2 package.
Mapped UP will be used by the HW for QoS purposes, but won't go out on the
wire.

Signed-off-by: Amir Vadai <amirv@mellanox.com>
---
 drivers/net/ethernet/mellanox/mlx4/en_netdev.c |   18 ++++++++++++++++++
 drivers/net/ethernet/mellanox/mlx4/en_tx.c     |    2 +-
 2 files changed, 19 insertions(+), 1 deletions(-)

diff --git a/drivers/net/ethernet/mellanox/mlx4/en_netdev.c b/drivers/net/ethernet/mellanox/mlx4/en_netdev.c
index 9b456ae..44dbe1f 100644
--- a/drivers/net/ethernet/mellanox/mlx4/en_netdev.c
+++ b/drivers/net/ethernet/mellanox/mlx4/en_netdev.c
@@ -45,6 +45,14 @@
 #include "mlx4_en.h"
 #include "en_port.h"
 
+static int mlx4_en_setup_tc(struct net_device *dev, u8 up)
+{
+	if (up != MLX4_EN_NUM_UP)
+		return -EINVAL;
+
+	return 0;
+}
+
 static int mlx4_en_vlan_rx_add_vid(struct net_device *dev, unsigned short vid)
 {
 	struct mlx4_en_priv *priv = netdev_priv(dev);
@@ -1042,6 +1050,7 @@ static const struct net_device_ops mlx4_netdev_ops = {
 	.ndo_poll_controller	= mlx4_en_netpoll,
 #endif
 	.ndo_set_features	= mlx4_en_set_features,
+	.ndo_setup_tc		= mlx4_en_setup_tc,
 };
 
 int mlx4_en_init_netdev(struct mlx4_en_dev *mdev, int port,
@@ -1130,6 +1139,15 @@ int mlx4_en_init_netdev(struct mlx4_en_dev *mdev, int port,
 	netif_set_real_num_tx_queues(dev, priv->tx_ring_num);
 	netif_set_real_num_rx_queues(dev, priv->rx_ring_num);
 
+	netdev_set_num_tc(dev, MLX4_EN_NUM_UP);
+
+	/* First 9 rings are for UP 0 */
+	netdev_set_tc_queue(dev, 0, MLX4_EN_NUM_TX_RINGS + 1, 0);
+
+	/* Partition Tx queues evenly amongst UP's 1-7 */
+	for (i = 1; i < MLX4_EN_NUM_UP; i++)
+		netdev_set_tc_queue(dev, i, 1, MLX4_EN_NUM_TX_RINGS + i);
+
 	SET_ETHTOOL_OPS(dev, &mlx4_en_ethtool_ops);
 
 	/* Set defualt MAC */
diff --git a/drivers/net/ethernet/mellanox/mlx4/en_tx.c b/drivers/net/ethernet/mellanox/mlx4/en_tx.c
index 94a605a..d9bab53 100644
--- a/drivers/net/ethernet/mellanox/mlx4/en_tx.c
+++ b/drivers/net/ethernet/mellanox/mlx4/en_tx.c
@@ -577,7 +577,7 @@ u16 mlx4_en_select_queue(struct net_device *dev, struct sk_buff *skb)
 		return MLX4_EN_NUM_TX_RINGS + (vlan_tag >> 13);
 	}
 
-	return __skb_tx_hash(dev, skb, MLX4_EN_NUM_TX_RINGS);
+	return skb_tx_hash(dev, skb);
 }
 
 static void mlx4_bf_copy(void __iomem *dst, unsigned long *src, unsigned bytecnt)
-- 
1.7.8.2

^ permalink raw reply related

* Re: [PATCH] net/ethernet: ks8851_mll fix rx frame buffer overflow
From: Eric Dumazet @ 2012-03-27 14:39 UTC (permalink / raw)
  To: Davide Ciminaghi
  Cc: David S. Miller, Alexey Dobriyan, Thomas Meyer, Wan ZongShun,
	Lucas De Marchi, netdev, raffaele.recalcati
In-Reply-To: <20120327130144.GA555@recalcati>

Le mardi 27 mars 2012 à 15:01 +0200, Davide Ciminaghi a écrit :
> If interrupts are disabled long enough to allow for more than
> 32 frames to accumulate in the MAC's internal buffers, a buffer
> overflow occurs. This patch fixes the problem by making the
> driver's frame_head_info buffer bigger enough.
> 
> Signed-off-by: Davide Ciminaghi <ciminaghi@gnudd.com>
> Signed-off-by: Raffaele Recalcati <raffaele.recalcati@bticino.it>
> ---
>  drivers/net/ethernet/micrel/ks8851_mll.c |    2 +-
>  1 files changed, 1 insertions(+), 1 deletions(-)
> 
> diff --git a/drivers/net/ethernet/micrel/ks8851_mll.c b/drivers/net/ethernet/micrel/ks8851_mll.c
> index 2784bc7..a158e89 100644
> --- a/drivers/net/ethernet/micrel/ks8851_mll.c
> +++ b/drivers/net/ethernet/micrel/ks8851_mll.c
> @@ -40,7 +40,7 @@
>  #define	DRV_NAME	"ks8851_mll"
>  
>  static u8 KS_DEFAULT_MAC_ADDRESS[] = { 0x00, 0x10, 0xA1, 0x86, 0x95, 0x11 };
> -#define MAX_RECV_FRAMES			32
> +#define MAX_RECV_FRAMES			256
>  #define MAX_BUF_SIZE			2048
>  #define TX_BUF_SIZE			2000
>  #define RX_BUF_SIZE			2000

How can this fix the problem for good ?

^ permalink raw reply

* Re: [RFC PATCH 1/1] NUMA aware scheduling per cpu vhost thread
From: Thomas Lendacky @ 2012-03-27 14:34 UTC (permalink / raw)
  To: David Ahern; +Cc: Shirley Ma, Michael S. Tsirkin, netdev, kvm
In-Reply-To: <4F6D0B24.1010300@gmail.com>

On Friday, March 23, 2012 05:45:40 PM David Ahern wrote:
> On 3/23/12 12:32 PM, Thomas Lendacky wrote:
> > Quick description of the tests:
> >    TCP_RR and UDP_RR using 256 byte request/response size in 1, 10, 30
> >    and 60 instances
> >    TCP_STREAM and TCP_MAERTS using 256, 1K, 4K and 16K message sizes
> >    and 1 and 4 instances
> >    
> >    Remote host to VM using 1, 4, 12 and 24 VMs (2 vCPUs) with the tests
> >    running between an external host and each VM.
> >    
> >    Local VM to VM using 2, 4, 12 and 24 VMs (2 vCPUs) with the tests
> >    running between VM pairs on the same host (no TCP_MAERTS done in
> >    this situation).
> > 
> > For TCP_RR and UDP_RR tests I report the transaction rate as the
> > score and the transaction rate / KVMhost CPU% as the efficiency.
> > 
> > For TCP_STREAM and TCP_MAERTS tests I report the throughput in Mbps
> > as the score and the throughput / KVMhost CPU% as the efficiency.
> 
> Would you mind sharing the netperf commands you are running and an
> example of the math done to arrive at the summaries presented?

I'm actually using uperf not netperf.  Uperf allows me to launch
multiple instances of a test with one executable. I've provided the
XML profiles for the tests below.

The math is simply taking the score (for TCP_RR it is the tranaction
rate and for TCP_STREAM/TCP_MAERTS it is the throughput) and dividing
by the CPU utilization of the KVM host (obtained from running sar
during the test).

Here are the uperf profiles that were used. The destination,
instances and message sizes are set using environment variables.

TCP_RR
  <?xml version="1.0"?>
  <!--
   Note: uperf reports operations/second. A transaction is made up of
         two operations, so to get transactions/second (like netperf)
         you must divide the operations/second by 2.
  -->
  <profile name="TCP_RR">
   <group nprocs="$uperf_instances">
    <transaction iterations="1">
     <flowop type="connect" options="remotehost=$uperf_dest
       protocol=tcp"/>
     </transaction>
     <transaction duration="$uperf_duration">
      <flowop type="write" options="size=$uperf_tx_msgsize"/>
      <flowop type="read"  options="size=$uperf_rx_msgsize"/>
     </transaction>
     <transaction iterations="1">
      <flowop type="disconnect" />
     </transaction>
   </group>
  </profile>

UDP_RR:
 <?xml version="1.0"?>
 <!--
  Note: uperf reports operations/second. A transaction is made up of
        two operations, so to get transactions/second (like netperf)
        you must divide the operations/second by 2.
 -->
 <profile name="UDP_RR">
  <group nprocs="$uperf_instances">
   <transaction iterations="1">
    <flowop type="connect" options="remotehost=$uperf_dest
      protocol=udp"/>
   </transaction>
   <transaction duration="$uperf_duration">
    <flowop type="write" options="size=$uperf_tx_msgsize"/>
    <flowop type="read"  options="size=$uperf_rx_msgsize"/>
   </transaction>
   <transaction iterations="1">
    <flowop type="disconnect" />
   </transaction>
  </group>
 </profile>

TCP_STREAM:
  <?xml version="1.0"?>
  <profile name="TCP_STREAM">
   <group nprocs="$uperf_instances">
    <transaction iterations="1">
     <flowop type="connect" options="remotehost=$uperf_dest
       protocol=tcp"/>
    </transaction>
    <transaction duration="$uperf_duration">
     <flowop type="write" options="count=16 size=$uperf_tx_msgsize"/>
    </transaction>
    <transaction iterations="1">
     <flowop type="disconnect" />
    </transaction>
   </group>
  </profile>

TCP_MAERTS:
  <?xml version="1.0"?>
  <profile name="TCP_MAERTS">
   <group nprocs="$uperf_instances">
    <transaction iterations="1">
     <flowop type="accept"  options="remotehost=$uperf_dest
       protocol=tcp"/>
    </transaction>
    <transaction duration="$uperf_duration">
     <flowop type="read"  options="count=16 size=$uperf_rx_msgsize"/>
    </transaction>
    <transaction iterations="1">
     <flowop type="disconnect" />
    </transaction>
  </group>
 </profile>

Tom

> 
> David
> --
> To unsubscribe from this list: send the line "unsubscribe kvm" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html


^ permalink raw reply

* [PATCH] rose_dev: fix memcpy-bug in rose_set_mac_address
From: Daniel Borkmann @ 2012-03-27 13:27 UTC (permalink / raw)
  To: Ralf Baechle; +Cc: David S. Miller, netdev@vger.kernel.org

This patch fixes a small bug in rose_set_mac_address. If the current and new
MAC addresses match, then nothing needs to be done. However memcpy was used
instead of memcmp for comparison.

The patch is against the latest net-tree.

Signed-off-by: Daniel Borkmann <daniel.borkmann@tik.ee.ethz.ch>
Cc: Ralf Baechle <ralf@linux-mips.org>
---
 net/rose/rose_dev.c |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/net/rose/rose_dev.c b/net/rose/rose_dev.c
index 178ff4f..3c5ce5e 100644
--- a/net/rose/rose_dev.c
+++ b/net/rose/rose_dev.c
@@ -96,7 +96,7 @@ static int rose_set_mac_address(struct net_device *dev, void *addr)
 	struct sockaddr *sa = addr;
 	int err;
 
-	if (!memcpy(dev->dev_addr, sa->sa_data, dev->addr_len))
+	if (!memcmp(dev->dev_addr, sa->sa_data, dev->addr_len))
 		return 0;
 
 	if (dev->flags & IFF_UP) {

^ permalink raw reply related

* Re: [PATCH] cdc-phonet: fix skb truesize underestimation
From: Rémi Denis-Courmont @ 2012-03-27 13:22 UTC (permalink / raw)
  To: ext Eric Dumazet
  Cc: David Miller, Greg Kroah-Hartman, linux-usb, netdev, Felipe Balbi
In-Reply-To: <1332854246.3248.18.camel@edumazet-laptop>

Le mardi 27 mars 2012 06:17:26 ext Eric Dumazet a écrit :
> Now skb_add_rx_frag() has a truesize parameter, we can fix cdc-phonet to
> properly account truesize of each fragment : a full page.
> 
> Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
> Cc: Felipe Balbi <balbi@ti.com>
> Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
> Cc: Rémi Denis-Courmont <remi.denis-courmont@nokia.com>

Acked-by: Rémi Denis-Courmont <remi.denis-courmont@nokia.com>

-- 
Rémi Denis-Courmont
http://www.remlab.net/

^ permalink raw reply

* [PATCH] net/ethernet: ks8851_mll fix rx frame buffer overflow
From: Davide Ciminaghi @ 2012-03-27 13:01 UTC (permalink / raw)
  To: David S. Miller, Alexey Dobriyan, Thomas Meyer, Wan ZongShun,
	Lucas De Marchi, netdev
  Cc: raffaele.recalcati

If interrupts are disabled long enough to allow for more than
32 frames to accumulate in the MAC's internal buffers, a buffer
overflow occurs. This patch fixes the problem by making the
driver's frame_head_info buffer bigger enough.

Signed-off-by: Davide Ciminaghi <ciminaghi@gnudd.com>
Signed-off-by: Raffaele Recalcati <raffaele.recalcati@bticino.it>
---
 drivers/net/ethernet/micrel/ks8851_mll.c |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/drivers/net/ethernet/micrel/ks8851_mll.c b/drivers/net/ethernet/micrel/ks8851_mll.c
index 2784bc7..a158e89 100644
--- a/drivers/net/ethernet/micrel/ks8851_mll.c
+++ b/drivers/net/ethernet/micrel/ks8851_mll.c
@@ -40,7 +40,7 @@
 #define	DRV_NAME	"ks8851_mll"
 
 static u8 KS_DEFAULT_MAC_ADDRESS[] = { 0x00, 0x10, 0xA1, 0x86, 0x95, 0x11 };
-#define MAX_RECV_FRAMES			32
+#define MAX_RECV_FRAMES			256
 #define MAX_BUF_SIZE			2048
 #define TX_BUF_SIZE			2000
 #define RX_BUF_SIZE			2000
-- 
1.7.0.4

^ permalink raw reply related

* [RFC] net/hsr: Add support for IEC 62439-3 High-availability Seamless Redundancy
From: Arvid Brodin @ 2012-03-27 13:20 UTC (permalink / raw)
  To: David S. Miller; +Cc: netdev, Stephen Hemminger, Bruno Ferreira

Hi!

This is a patch-in-progress for adding support for instant-failover
redundant Ethernet networks using less and easier cabling than what's
required by e.g. bonding/pairing. General info about HSR can be
found at

http://en.wikipedia.org/wiki/High-availability_Seamless_Redundancy

but basically, HSR is a network topology where all nodes are equipped
with (at least) two physical Ethernet interfaces, and are daisy-
chained together to form a ring. Each node forwards all frames that
are not exclusively destined to itself, except for frames that have
been forwarded before. Each frame is sent in both directions on the
ring by the source node.


I have two wishes with this RFC:

1) General code review. I haven't worked with neither kernel network code nor
   used rcu locking before, so I feel I need a sanity check of the code. Am I
   doing things in a sane way?

   There are some debug printouts and "//" comments in this code. These will be
   removed in the final patch, please ignore them.

   Missing in this patch are mechanisms for detecting and reporting network
   problems to user space (via Netlink), which I'm working on now.


2) I have a locking problem that I haven't managed to figure out. This happens
   the first time I send any packet (hsr_dev_xmit() in hsr_device.c:121, called
   from hsr_device.c:147). It happens even if I set skb2 to NULL (i.e. only send
   one copy):

=============================================
[ INFO: possible recursive locking detected ]
2.6.37 #118
---------------------------------------------
swapper/0 is trying to acquire lock:
 (_xmit_ETHER#2){+.-...}, at: [<901bf38e>] sch_direct_xmit+0x24/0x152

but task is already holding lock:
 (_xmit_ETHER#2){+.-...}, at: [<901b4d1a>] dev_queue_xmit+0x31e/0x3cc

other info that might help us debug this:
4 locks held by swapper/0:
 #0:  (&n->timer){+.-...}, at: [<9002bc20>] run_timer_softirq+0x98/0x184
 #1:  (rcu_read_lock_bh){.+....}, at: [<901b49fc>] dev_queue_xmit+0x0/0x3cc
 #2:  (_xmit_ETHER#2){+.-...}, at: [<901b4d1a>] dev_queue_xmit+0x31e/0x3cc
 #3:  (rcu_read_lock_bh){.+....}, at: [<901b49fc>] dev_queue_xmit+0x0/0x3cc

stack backtrace:
Call trace:
 [<9001c640>] dump_stack+0x18/0x20
 [<90040eac>] validate_chain+0x40c/0x9ac
 [<90041a58>] __lock_acquire+0x60c/0x670
 [<90042f32>] lock_acquire+0x3a/0x48
 [<902201a4>] _raw_spin_lock+0x20/0x44
 [<901bf38e>] sch_direct_xmit+0x24/0x152
 [<901b4c14>] dev_queue_xmit+0x218/0x3cc
 [<9021c2e0>] slave_xmit+0x10/0x14
 [<9021c540>] hsr_dev_xmit+0x88/0x8c
 [<901b4942>] dev_hard_start_xmit+0x3c6/0x480
 [<901b4d34>] dev_queue_xmit+0x338/0x3cc
 [<901e3cd8>] arp_xmit+0x8/0xc
 [<901e4436>] arp_send+0x2a/0x2c
 [<901e4e74>] arp_solicit+0x15c/0x170
 [<901bad0c>] neigh_timer_handler+0x1c0/0x204
 [<9002bc8a>] run_timer_softirq+0x102/0x184
 [<900287d8>] __do_softirq+0x64/0xe0
 [<9002896a>] do_softirq+0x26/0x48
 [<90028a66>] irq_exit+0x2e/0x64
 [<90019f16>] do_IRQ+0x46/0x5c
 [<90018428>] irq_level0+0x18/0x60
 [<9021cc16>] rest_init+0x72/0x98
 [<9000063c>] start_kernel+0x21c/0x258
 [<00000000>] 0x0

   Any idea why this happens? I need help!


And here's the actual patch (against linux-next-20120306):

---
 include/linux/if_ether.h |    1 +
 include/linux/if_link.h  |   11 +
 net/Kconfig              |    1 +
 net/Makefile             |    1 +
 net/hsr/Kconfig          |   84 ++++++++
 net/hsr/Makefile         |    7 +
 net/hsr/hsr_device.c     |  518 ++++++++++++++++++++++++++++++++++++++++++++++
 net/hsr/hsr_device.h     |   22 ++
 net/hsr/hsr_framereg.c   |  157 ++++++++++++++
 net/hsr/hsr_framereg.h   |   23 ++
 net/hsr/hsr_main.c       |  383 ++++++++++++++++++++++++++++++++++
 net/hsr/hsr_netlink.c    |   78 +++++++
 net/hsr/hsr_netlink.h    |   21 ++
 net/hsr/hsr_private.h    |  114 ++++++++++
 14 files changed, 1418 insertions(+), 0 deletions(-)

diff --git a/include/linux/if_ether.h b/include/linux/if_ether.h
index 56d907a..0d0e2f9 100644
--- a/include/linux/if_ether.h
+++ b/include/linux/if_ether.h
@@ -83,6 +83,7 @@
 #define ETH_P_TIPC	0x88CA		/* TIPC 			*/
 #define ETH_P_8021AH	0x88E7          /* 802.1ah Backbone Service Tag */
 #define ETH_P_1588	0x88F7		/* IEEE 1588 Timesync */
+#define ETH_P_HSR	0x88FB		/* IEC 62439-3 HSR/PRP		*/
 #define ETH_P_FCOE	0x8906		/* Fibre Channel over Ethernet  */
 #define ETH_P_TDLS	0x890D          /* TDLS */
 #define ETH_P_FIP	0x8914		/* FCoE Initialization Protocol */
diff --git a/include/linux/if_link.h b/include/linux/if_link.h
index 4b24ff4..3e3efb4 100644
--- a/include/linux/if_link.h
+++ b/include/linux/if_link.h
@@ -391,4 +391,15 @@ struct ifla_port_vsi {
 	__u8 pad[3];
 };
 
+/* HSR section */
+
+enum {
+	IFLA_HSR_UNSPEC,
+	IFLA_HSR_SLAVE1,
+	IFLA_HSR_SLAVE2,
+	__IFLA_HSR_MAX,
+};
+
+#define IFLA_HSR_MAX (__IFLA_HSR_MAX - 1)
+
 #endif /* _LINUX_IF_LINK_H */
diff --git a/net/Kconfig b/net/Kconfig
index e07272d..99d666d 100644
--- a/net/Kconfig
+++ b/net/Kconfig
@@ -216,6 +216,7 @@ source "net/dcb/Kconfig"
 source "net/dns_resolver/Kconfig"
 source "net/batman-adv/Kconfig"
 source "net/openvswitch/Kconfig"
+source "net/hsr/Kconfig"
 
 config RPS
 	boolean
diff --git a/net/Makefile b/net/Makefile
index ad432fa..7d8787f 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -70,3 +70,4 @@ obj-$(CONFIG_CEPH_LIB)		+= ceph/
 obj-$(CONFIG_BATMAN_ADV)	+= batman-adv/
 obj-$(CONFIG_NFC)		+= nfc/
 obj-$(CONFIG_OPENVSWITCH)	+= openvswitch/
+obj-$(CONFIG_HSR)		+= hsr/
diff --git a/net/hsr/Kconfig b/net/hsr/Kconfig
new file mode 100644
index 0000000..895afda
--- /dev/null
+++ b/net/hsr/Kconfig
@@ -0,0 +1,84 @@
+#
+# IEC 62439-3 High-availability Seamless Redundancy
+#
+
+config HSR
+	tristate "High-availability Seamless Redundancy (HSR)"
+	---help---
+	  If you say Y here, then your Linux box will be able to act as a
+	  DANH ("Doubly attached node implementing HSR"). For this to work,
+	  your Linux box needs (at least) two physical Ethernet interfaces,
+	  and you need to enslave these to a virtual hsr interface using the
+	  appropriate user space tool, i.e.:
+
+	  # ip link add name hsr0 type hsr dev1 dev2
+
+	  Your Linux box must be connected as a node in a ring network
+	  together with other HSR capable nodes.
+
+	  All Ethernet frames sent over the hsr device will be sent in both
+	  directions on the ring (over both slave ports), giving a redundant,
+	  instant fail-over network.
+
+	  Each HSR node in the ring acts like a bridge for HSR frames, but
+	  filters frames that have been forwarded earlier.
+
+	  This code is a "best effort" to comply with the HSR standard as
+	  described in IEC 62439-3, but no compliancy tests have been made.
+	  You need to perform any and all necessary tests yourself before
+	  relying on this code in a safety critical system. In particular, the
+	  standard is very diffuse on how to use the Ring ID field in the HSR
+	  tag, and it's probable that this code does not do the right thing.
+
+	  If unsure, say N.
+
+if HAVE_EFFICIENT_UNALIGNED_ACCESS
+
+config NONSTANDARD_HSR
+	bool "HSR: Use efficient tag (breaks HSR standard, read help!)"
+	depends on HSR
+	---help---
+	  The HSR standard specifies a 6-byte HSR tag to be inserted into the
+	  transmitted network frames. This breaks the 32-bit alignment that the
+	  Linux network stack relies on, and would cause kernel panics on
+	  certain architectures. To avoid this, the whole frame payload is
+	  memmoved 2 bytes on reception on these architectures - which is very
+	  inefficient!
+
+	  If you select Y here, 2 bytes of padding is inserted into the HSR tag,
+	  which makes it possible to skip the memmove. This however breaks
+	  compatibility with compliant HSR devices. I.e., either all or none of
+	  the devices in your HSR ring needs to have this option set.
+
+	  Your architecture has HAVE_EFFICIENT_UNALIGNED_ACCESS, so you do not
+	  need this unless you have other nodes in your ring which have this
+	  option set.
+
+	  If unsure, say N.
+
+endif # HAVE_EFFICIENT_UNALIGNED_ACCESS
+if !HAVE_EFFICIENT_UNALIGNED_ACCESS
+
+config NONSTANDARD_HSR
+	bool "HSR: Use efficient tag (breaks HSR standard, read help!)"
+	depends on HSR
+	---help---
+	  The HSR standard specifies a 6-byte HSR tag to be inserted into the
+	  transmitted network frames. This breaks the 32-bit alignment that the
+	  Linux network stack relies on, and would cause kernel panics on
+	  certain architectures. To avoid this, the whole frame payload is
+	  memmoved 2 bytes on reception on these architectures - which is very
+	  inefficient!
+
+	  If you select Y here, 2 bytes of padding is inserted into the HSR tag,
+	  which makes it possible to skip the memmove. This however breaks
+	  compatibility with compliant HSR devices. I.e., either all or none of
+	  the devices in your HSR ring needs to have this option set.
+
+	  Your architecture does not have HAVE_EFFICIENT_UNALIGNED_ACCESS, so
+	  you should seriously consider saying Y here if performance is at all
+	  important to you.
+
+	  If unsure, say N.
+
+endif # !HAVE_EFFICIENT_UNALIGNED_ACCESS
diff --git a/net/hsr/Makefile b/net/hsr/Makefile
new file mode 100644
index 0000000..b68359f
--- /dev/null
+++ b/net/hsr/Makefile
@@ -0,0 +1,7 @@
+#
+# Makefile for HSR
+#
+
+obj-$(CONFIG_HSR)	+= hsr.o
+
+hsr-y			:= hsr_main.o hsr_framereg.o hsr_device.o hsr_netlink.o
diff --git a/net/hsr/hsr_device.c b/net/hsr/hsr_device.c
new file mode 100644
index 0000000..f83bdf5
--- /dev/null
+++ b/net/hsr/hsr_device.c
@@ -0,0 +1,518 @@
+/*
+ * Copyright 2011-2012 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-2012 Arvid Brodin, arvid.brodin@enea.com
+ *
+ * This file contains device methods for creating, using and destroying
+ * virtual HSR devices.
+ */
+
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <linux/etherdevice.h>
+#include <linux/if_arp.h>
+#include <linux/rtnetlink.h>
+#include <linux/netfilter.h>
+#include "hsr_framereg.h"
+#include "hsr_private.h"
+
+
+static int is_up(struct net_device *dev)
+{
+	return (dev->flags & IFF_UP);
+}
+
+
+void hsr_check_announce(struct net_device *hsr_dev, struct net_device *slave1,
+						struct net_device *slave2)
+{
+	struct hsr_priv *hsr_priv;
+
+	hsr_priv = netdev_priv(hsr_dev);
+
+	if (!hsr_priv->online && is_up(hsr_dev) &&
+					(is_up(slave1) || is_up(slave2))) {
+		hsr_priv->online = 1;
+		hsr_priv->announce_count = 0;
+		hsr_priv->announce_timer.expires = jiffies +
+					HSR_ANNOUNCE_INTERVAL * HZ / 1000;
+		add_timer(&hsr_priv->announce_timer);
+	} else {
+		hsr_priv->online = 0;
+		del_timer(&hsr_priv->announce_timer);
+	}
+}
+
+static int hsr_dev_open(struct net_device *dev)
+{
+	struct hsr_priv *hsr_priv;
+
+	hsr_priv = netdev_priv(dev);
+
+	dev_open(hsr_priv->slave_data[0].dev_data.dev);
+	dev_open(hsr_priv->slave_data[1].dev_data.dev);
+
+	return 0;
+}
+
+static int hsr_dev_close(struct net_device *dev)
+{
+	// FIXME: reset status of slaves
+	return 0;
+}
+
+
+static void hsr_fill_tag(struct hsr_ethhdr *hsr_ethhdr, struct hsr_priv *hsr_priv)
+{
+	u16 path;
+	u16 LSDU_size;
+	unsigned long irqflags;
+
+	/*
+	 * IEC 62439-1, 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.
+	 */
+	path = 0x1;
+
+	/*
+	 * IEC 62439-1, 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...
+	 */
+	LSDU_size = 0;
+	hsr_ethhdr->hsr_tag.path_and_LSDU_size =
+					htons((u16) (path << 12) | LSDU_size);
+
+	spin_lock_irqsave(&hsr_priv->seqlock, irqflags);
+	hsr_ethhdr->hsr_tag.sequence_nr = htons(hsr_priv->sequence_nr);
+	hsr_priv->sequence_nr++;
+	spin_unlock_irqrestore(&hsr_priv->seqlock, irqflags);
+
+	hsr_ethhdr->hsr_tag.encap_proto = hsr_ethhdr->ethhdr.h_proto;
+
+	hsr_ethhdr->ethhdr.h_proto = htons(ETH_P_HSR);
+}
+
+static int slave_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	skb->dev = dev;
+	skb->priority = 1; // FIXME: what does this mean?
+
+	return dev_queue_xmit(skb);
+}
+
+
+static int hsr_dev_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	struct hsr_priv *hsr_priv;
+	struct hsr_ethhdr *hsr_ethhdr;
+	struct sk_buff *skb2;
+	int res1, res2;
+
+	hsr_priv = netdev_priv(dev);
+	hsr_ethhdr = (struct hsr_ethhdr *) skb->data;
+
+	if ((ntohs(skb->protocol) != ETH_P_HSR) ||
+			(ntohs(hsr_ethhdr->ethhdr.h_proto) != ETH_P_HSR)) {
+
+		hsr_fill_tag(hsr_ethhdr, hsr_priv);
+		skb->protocol = htons(ETH_P_HSR);
+	}
+
+	skb2 = skb_clone(skb, GFP_ATOMIC);
+
+	res1 = NET_XMIT_DROP;
+	res2 = NET_XMIT_DROP;
+	if (!register_frame(&hsr_priv->slave_data[0].dev_data, skb))
+		res1 = slave_xmit(skb, hsr_priv->slave_data[0].dev_data.dev);
+	if (skb2 && !register_frame(&hsr_priv->slave_data[1].dev_data, skb2))
+		res2 = slave_xmit(skb2, hsr_priv->slave_data[1].dev_data.dev);
+
+	if (likely(res1 == NET_XMIT_SUCCESS || res1 == NET_XMIT_CN ||
+			res2 == NET_XMIT_SUCCESS || res2 == NET_XMIT_CN)) {
+		hsr_priv->dev_data.dev->stats.tx_packets++;
+		hsr_priv->dev_data.dev->stats.tx_bytes += skb->len;
+	} else
+		hsr_priv->dev_data.dev->stats.tx_dropped++;
+
+	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) */
+	skb_push(skb, HSR_TAGLEN);
+	res = eth_header(skb, dev, type, daddr, saddr, len + HSR_TAGLEN);
+	if (res <= 0)
+		return res;
+	skb_reset_mac_header(skb);
+
+	return res + HSR_TAGLEN;
+}
+
+
+static const struct header_ops hsr_header_ops = {
+	.create	 = hsr_header_create,
+	.parse	 = eth_header_parse,
+};
+
+
+static int set_mac_address(struct net_device *dev, const char new_mac[ETH_ALEN])
+{
+	struct sockaddr sockaddr;
+	int res;
+
+	memcpy(sockaddr.sa_data, new_mac, dev->addr_len);
+	sockaddr.sa_family = dev->type;
+	res = dev_set_mac_address(dev, &sockaddr);
+
+	return res;
+}
+
+
+static void send_hsr_supervision_frame(struct net_device *hsr_dev, u8 type)
+{
+	struct hsr_priv *hsr_priv;
+	struct sk_buff *skb;
+	struct hsr_ethhdr *hsr_ethhdr;
+	unsigned char *MacAddressA;
+	u16 path, HSR_Ver, HSR_TLV_Length;
+	unsigned long irqflags;
+
+	skb = alloc_skb(sizeof(struct ethhdr) +
+				sizeof(struct hsr_supervision_tag) +
+				LL_RESERVED_SPACE(hsr_dev) +
+				hsr_dev->needed_tailroom, GFP_ATOMIC);
+	if (skb == NULL)
+		return;
+
+	hsr_priv = netdev_priv(hsr_dev);
+
+	skb_reserve(skb, LL_RESERVED_SPACE(hsr_dev));
+	skb_reset_network_header(skb);
+	MacAddressA = (unsigned char *) skb_put(skb, ETH_ALEN);
+	memcpy(MacAddressA, hsr_dev->dev_addr, ETH_ALEN);
+	*MacAddressA = 0xff;
+
+	skb->dev = hsr_dev;
+	skb->protocol = htons(ETH_P_HSR);
+
+	if (dev_hard_header(skb, skb->dev, ETH_P_HSR, hsr_multicast_addr,
+					skb->dev->dev_addr, skb->len) < 0)
+		goto out;
+
+	hsr_ethhdr = (struct hsr_ethhdr *) skb->data;
+
+	path = 0x0f;
+	HSR_Ver = 0;
+	hsr_ethhdr->hsr_tag.path_and_LSDU_size = htons(path << 12 | HSR_Ver);
+
+	spin_lock_irqsave(&hsr_priv->seqlock, irqflags);
+	hsr_ethhdr->hsr_tag.sequence_nr = htons(hsr_priv->sequence_nr);
+	hsr_priv->sequence_nr++;
+	spin_unlock_irqrestore(&hsr_priv->seqlock, irqflags);
+
+	HSR_TLV_Length = 12;
+	hsr_ethhdr->hsr_tag.encap_proto = htons(type << 8 | HSR_TLV_Length);
+
+	dev_queue_xmit(skb);
+	return;
+
+out:
+	kfree_skb(skb);
+}
+
+
+/*
+ * Announce (supervision frame) timer function
+ */
+static void hsr_announce(unsigned long data)
+{
+	struct hsr_priv *hsr_priv;
+
+	hsr_priv = (struct hsr_priv *) data;
+
+	if (hsr_priv->announce_count < 3) {
+		send_hsr_supervision_frame(hsr_priv->dev_data.dev, 22);
+		hsr_priv->announce_count++;
+	} else
+		send_hsr_supervision_frame(hsr_priv->dev_data.dev, 23);
+
+	if (hsr_priv->announce_count < 3)
+		hsr_priv->announce_timer.expires = jiffies +
+					HSR_ANNOUNCE_INTERVAL * HZ / 1000;
+	else
+		hsr_priv->announce_timer.expires = jiffies +
+					HSR_LIFE_CHECK_INTERVAL * HZ / 1000;
+
+	if (hsr_priv->online)
+		add_timer(&hsr_priv->announce_timer);
+}
+
+
+
+
+static void restore_slaves(struct net_device *hsr_dev)
+{
+	struct hsr_priv *hsr_priv;
+	struct net_device *slave[2];
+	int i;
+	int res;
+
+	hsr_priv = netdev_priv(hsr_dev);
+	for (i = 0; i < 2; i++)
+		slave[i] = hsr_priv->slave_data[i].dev_data.dev;
+
+	rtnl_lock();
+
+	/* Restore promiscuity */
+	for (i = 0; i < 2; i++) {
+		if (!hsr_priv->slave_data[i].promisc)
+			continue;
+		res = dev_set_promiscuity(slave[i],
+					-hsr_priv->slave_data[i].promisc);
+		if (res)
+			pr_info("HSR: Cannot restore promiscuity (%s, %d)\n",
+							slave[i]->name,
+							res);
+	}
+
+	/* Restore MAC addresses */
+	for (i = 0; i < 2; i++) {
+		if (!memcmp(hsr_priv->slave_data[i].orig_mac,
+							slave[i]->dev_addr,
+							slave[i]->addr_len))
+			continue;
+		dev_close(slave[i]);
+		res = set_mac_address(slave[i],
+					hsr_priv->slave_data[i].orig_mac);
+		if (res)
+			pr_info("HSR: Could not restore MAC address "
+							"(%s, %d)\n",
+							slave[i]->name,
+							res);
+		}
+
+	/* Restore up state */
+	for (i = 0; i < 2; i++)
+		if (hsr_priv->slave_data[i].was_up)
+			dev_open(slave[i]);
+		else
+			dev_close(slave[i]);
+
+	rtnl_unlock();
+}
+
+static void reclaim_hsr_dev(struct rcu_head *rh)
+{
+	struct hsr_priv *hsr_priv;
+
+	hsr_priv = container_of(rh, struct hsr_priv, rcu_head);
+	free_netdev(hsr_priv->dev_data.dev);
+}
+
+/*
+ * According to comments in the declaration of struct net_device, this function
+ * is "Called from unregister, can be used to call free_netdev". Ok then...
+ */
+static void hsr_dev_destroy(struct net_device *hsr_dev)
+{
+	struct hsr_priv *hsr_priv;
+
+	hsr_priv = netdev_priv(hsr_dev);
+
+	unregister_hsr_master(hsr_priv);    /* calls list_del_rcu on hsr_priv */
+	restore_slaves(hsr_dev);
+	call_rcu(&hsr_priv->rcu_head, reclaim_hsr_dev);   /* reclaim hsr_priv */
+}
+
+static struct net_device_ops hsr_device_ops = {
+	.ndo_open = hsr_dev_open,
+	.ndo_stop = hsr_dev_close,
+	.ndo_start_xmit = hsr_dev_xmit,
+};
+
+
+void hsr_dev_setup(struct net_device *dev)
+{
+	random_ether_addr(dev->dev_addr);
+
+	ether_setup(dev);
+	dev->header_ops		 = &hsr_header_ops;
+	dev->netdev_ops		 = &hsr_device_ops;
+	dev->hard_header_len	+= HSR_TAGLEN;
+	dev->mtu		-= HSR_TAGLEN;
+	dev->tx_queue_len	 = 0;
+
+	dev->destructor = hsr_dev_destroy;
+}
+
+
+/*
+ * If dev is a HSR master, return 1; otherwise, return 0.
+ */
+int is_hsr_master(struct net_device *dev)
+{
+	return (dev->netdev_ops->ndo_start_xmit == hsr_dev_xmit);
+}
+
+static int check_slave_ok(struct net_device *dev)
+{
+	/* Don't allow HSR on non-ethernet like devices */
+	if ((dev->flags & IFF_LOOPBACK) || (dev->type != ARPHRD_ETHER) ||
+						(dev->addr_len != ETH_ALEN)) {
+		pr_info("%s: Cannot enslave loopback or non-ethernet device\n",
+								dev->name);
+		return -EINVAL;
+	}
+
+	/* Don't allow enslaving hsr devices */
+	if (is_hsr_master(dev)) {
+		pr_info("%s: Don't try to create trees of hsr devices!\n",
+								dev->name);
+		return -ELOOP;
+	}
+
+	/* FIXME: What about VLAN devices, bonded devices, etc? */
+
+	return 0;
+}
+
+static void init_dev_data(struct hsr_dev_data *dev_data, struct net_device *dev)
+{
+	dev_data->dev = dev;
+	INIT_LIST_HEAD(&dev_data->frame_db);
+}
+
+int hsr_dev_finalize(struct net_device *hsr_dev, struct net_device *slave[2])
+{
+	struct hsr_priv *hsr_priv;
+	struct hsr_slave_data *mac_slave_data1, *mac_slave_data2;
+	int i;
+	int res;
+
+	hsr_priv = netdev_priv(hsr_dev);
+	init_dev_data(&hsr_priv->dev_data, hsr_dev);
+	for (i = 0; i < 2; i++)
+		init_dev_data(&hsr_priv->slave_data[i].dev_data, slave[i]);
+
+	spin_lock_init(&hsr_priv->seqlock);
+	hsr_priv->sequence_nr = 0;
+
+	init_timer(&hsr_priv->announce_timer);
+	hsr_priv->announce_timer.function = hsr_announce;
+	hsr_priv->announce_timer.data = (unsigned long) hsr_priv;
+
+
+/*
+ * FIXME: do I need to set the value of these?
+ *
+ * - hsr_dev->flags
+ * - hsr_dev->priv_flags
+ */
+
+	for (i = 0; i < 2; i++) {
+		res = check_slave_ok(slave[i]);
+		if (res)
+			return res;
+	}
+
+	hsr_dev->features = slave[0]->features & slave[1]->features;
+
+	/* Save/init data needed for restore */
+	for (i = 0; i < 2; i++) {
+		hsr_priv->slave_data[i].was_up = slave[i]->flags & IFF_UP;
+		memcpy(hsr_priv->slave_data[i].orig_mac, slave[i]->dev_addr,
+								ETH_ALEN);
+		hsr_priv->slave_data[i].promisc = 0;
+	}
+
+	/* MAC addresses */
+	if ((!slave[1]->netdev_ops->ndo_set_mac_address) &&
+				(slave[0]->netdev_ops->ndo_set_mac_address)) {
+		mac_slave_data1 = &hsr_priv->slave_data[1];
+		mac_slave_data2 = &hsr_priv->slave_data[0];
+	} else {
+		mac_slave_data1 = &hsr_priv->slave_data[0];
+		mac_slave_data2 = &hsr_priv->slave_data[1];
+	}
+
+	/* Set hsr_dev's MAC address to that of mac_slave1 */
+	memcpy(hsr_dev->dev_addr, mac_slave_data1->dev_data.dev->dev_addr,
+							hsr_dev->addr_len);
+
+	/* Set mac_slave2's MAC address to that of the hsr device and slave1 */
+	if (!mac_slave_data2->dev_data.dev->netdev_ops->ndo_set_mac_address) {
+		pr_info("None of the slaves support changing MAC address; at "
+			 "least one slave must support this for HSR\n");
+		return -EOPNOTSUPP;
+	}
+	dev_close(mac_slave_data2->dev_data.dev);
+	res = set_mac_address(mac_slave_data2->dev_data.dev, hsr_dev->dev_addr);
+	if (res) {
+		pr_info("HSR: dev_set_mac_address failed (%s, %d)\n",
+					mac_slave_data2->dev_data.dev->name,
+					res);
+		goto fail;
+	}
+	if (mac_slave_data2->was_up)
+		dev_open(mac_slave_data2->dev_data.dev);
+
+	/* MTU */
+	for (i = 0; i < 2; i++)
+		if (slave[i]->mtu < hsr_dev->mtu)
+			hsr_dev->mtu = slave[i]->mtu;
+
+
+	/* Promiscuity */
+	for (i = 0; i < 2; i++) {
+		res = dev_set_promiscuity(slave[i], 1);
+		if (res) {
+			pr_info("HSR: Cannot set promiscuous mode (%s, %d)\n",
+								slave[i]->name,
+								res);
+			goto fail;
+		}
+		/* Remember what we have done so we can restore it later */
+		hsr_priv->slave_data[i].promisc = 1;
+	}
+
+	res = register_netdevice(hsr_dev);
+	if (res)
+		goto fail;
+
+	register_hsr_master(hsr_priv);
+
+	return 0;
+
+fail:
+	restore_slaves(hsr_dev);
+	return res;
+}
diff --git a/net/hsr/hsr_device.h b/net/hsr/hsr_device.h
new file mode 100644
index 0000000..a1adab4
--- /dev/null
+++ b/net/hsr/hsr_device.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2011-2012 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-2012 Arvid Brodin, arvid.brodin@enea.com
+ */
+
+#ifndef __HSR_DEVICE_H
+#define __HSR_DEVICE_H
+
+#include <linux/netdevice.h>
+
+void hsr_dev_setup(struct net_device *dev);
+int hsr_dev_finalize(struct net_device *hsr_dev, struct net_device *dev1,
+						struct net_device *dev2);
+
+#endif /* __HSR_DEVICE_H */
diff --git a/net/hsr/hsr_framereg.c b/net/hsr/hsr_framereg.c
new file mode 100644
index 0000000..8747242
--- /dev/null
+++ b/net/hsr/hsr_framereg.c
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2011-2012 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-2012 Arvid Brodin, arvid.brodin@enea.com
+ *
+ * The HSR spec says never to forward the same frame twice on the same
+ * interface. A frame is identified by its source MAC address and its HSR
+ * sequence number. This code keeps track of senders and their sequence numbers
+ * to allow filtering of duplicate frames.
+ */
+
+#include <linux/if_ether.h>
+#include <linux/etherdevice.h>
+#include <linux/slab.h>
+#include <linux/rculist.h>
+#include "hsr_private.h"
+#include "hsr_framereg.h"
+
+
+/*
+	TODO: use hash lists for mac addresses (linux/jhash.h)?
+*/
+
+struct mac_entry {
+	struct list_head mac_list;
+	unsigned char addr[ETH_ALEN];
+	unsigned long timestamp;
+	u16 last_seq;
+	struct rcu_head rcu_head;
+};
+
+/*
+ * Search for mac entry. Caller must hold rcu read lock.
+ */
+static struct mac_entry *find_mac_entry(struct list_head *frame_db,
+						unsigned char addr[ETH_ALEN])
+{
+	struct mac_entry *mac_entry;
+
+	list_for_each_entry_rcu(mac_entry, frame_db, mac_list)
+		if (!compare_ether_addr(mac_entry->addr, addr))
+			return mac_entry;
+
+	return NULL;
+}
+
+
+/*
+ * above(a, b) - return 1 if a > b, 0 otherwise.
+ * Uses C 16-bit unsigned arithmetic, with differences > (1 << 15) interpreted
+ * as negative.
+ */
+#define	MAX_RANGE_DIFF	(1 << 15)
+static int above(u16 a, u16 b)
+{
+	if ((u16) (a - b) == (u16) (b - a))
+		return (a > b);
+	return (((u16) (a - b) > (u16) 0) &&
+		((u16) (a - b) <= (u16) MAX_RANGE_DIFF));
+}
+#define below(a, b)		above((b), (a))
+#define above_or_equal(a, b)	(!below((a), (b)))
+#define below_or_equal(a, b)	(!above((a), (b)))
+
+
+/*
+ * Parameters:
+ *	'skb' is a HSR Ethernet frame (with a HSR tag inserted), with a valid
+ *	ethhdr->h_source address and skb->mac_header set.
+ *
+ * Return:
+ *	 1 if frame can be shown to have been sent recently on this interface,
+ *	 0 otherwise, or
+ *	-1 on error
+ */
+int register_frame(struct hsr_dev_data *dev_data, struct sk_buff *skb)
+{
+	struct mac_entry *mac_entry;
+	struct hsr_ethhdr *hsr_ethhdr;
+
+	if (!skb_mac_header_was_set(skb)) {
+		printk(KERN_INFO "%s:%d: MAC header not set\n", __func__, __LINE__);
+		return -1;
+	}
+	hsr_ethhdr = (struct hsr_ethhdr *) skb_mac_header(skb);
+
+	rcu_read_lock();
+	mac_entry = find_mac_entry(&dev_data->frame_db, hsr_ethhdr->ethhdr.h_source);
+	if (!mac_entry) {
+		rcu_read_unlock();
+
+		mac_entry = kmalloc(sizeof(*mac_entry), GFP_ATOMIC);
+		if (!mac_entry)
+			return -1;
+
+		memcpy(mac_entry->addr, hsr_ethhdr->ethhdr.h_source, ETH_ALEN);
+		mac_entry->last_seq = hsr_ethhdr->hsr_tag.sequence_nr;
+		mac_entry->timestamp = jiffies;
+		list_add_tail_rcu(&mac_entry->mac_list, &dev_data->frame_db);
+
+		return 0;
+	}
+
+	if (below_or_equal(hsr_ethhdr->hsr_tag.sequence_nr, mac_entry->last_seq)) {
+		rcu_read_unlock();
+		return 1;
+	}
+
+	mac_entry->last_seq = hsr_ethhdr->hsr_tag.sequence_nr;
+	mac_entry->timestamp = jiffies;
+	rcu_read_unlock();
+	return 0;
+}
+
+/*
+ * Caller must hold rcu read lock.
+ */
+void update_node_seqnr(struct list_head *frame_db, u8 ethaddr[ETH_ALEN],
+							u16 sequence_nr)
+{
+	struct mac_entry *mac_entry;
+
+	mac_entry = find_mac_entry(frame_db, ethaddr);
+	if (mac_entry) {
+		mac_entry->last_seq = sequence_nr;
+		mac_entry->timestamp = jiffies;
+	}
+}
+
+
+static void mac_entry_reclaim(struct rcu_head *rh)
+{
+	kfree(container_of(rh, struct mac_entry, rcu_head));
+}
+
+/*
+ * Remove stale sequence_nr records. Called by timer every
+ * HSR_LIFE_CHECK_INTERVAL (two seconds or so). This is also the only function
+ * that removes mac_entries; it shouldn't need to be rcu_read_lock():ed.
+ */
+void prune_mac_entries(struct list_head *frame_db)
+{
+	struct mac_entry *mac_entry, *mac_entry_next;
+
+	list_for_each_entry_safe(mac_entry, mac_entry_next, frame_db, mac_list)
+		if (time_after(jiffies, mac_entry->timestamp +
+					HSR_NODE_FORGET_TIME * HZ / 1000)) {
+			list_del_rcu(&mac_entry->mac_list);
+			call_rcu(&mac_entry->rcu_head, mac_entry_reclaim);
+		}
+}
diff --git a/net/hsr/hsr_framereg.h b/net/hsr/hsr_framereg.h
new file mode 100644
index 0000000..970181e
--- /dev/null
+++ b/net/hsr/hsr_framereg.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2011-2012 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-2012 Arvid Brodin, arvid.brodin@enea.com
+ */
+
+#ifndef _HSR_FRAMEREG_H
+#define _HSR_FRAMEREG_H
+
+#include "hsr_private.h"
+
+int register_frame(struct hsr_dev_data *dev_data, struct sk_buff *skb);
+void update_node_seqnr(struct list_head *frame_db, u8 ethaddr[ETH_ALEN],
+							u16 sequence_nr);
+void prune_mac_entries(struct list_head *frame_db);
+
+#endif /* _HSR_FRAMEREG_H */
diff --git a/net/hsr/hsr_main.c b/net/hsr/hsr_main.c
new file mode 100644
index 0000000..dda5062
--- /dev/null
+++ b/net/hsr/hsr_main.c
@@ -0,0 +1,383 @@
+/*
+ * Copyright 2011-2012 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-2012 Arvid Brodin, arvid.brodin@enea.com
+ *
+ * In addition to routines for registering and unregistering HSR support, this
+ * file also contains the receive routine that handles all incoming frames with
+ * Ethertype (protocol) ETH_P_HSR.
+ */
+
+#include <linux/netdevice.h>
+#include <linux/rculist.h>
+#include <linux/timer.h>
+#include <linux/etherdevice.h>
+#include "hsr_private.h"
+#include "hsr_framereg.h"
+#include "hsr_netlink.h"
+
+
+/* Multicast address for HSR Supervision frames */
+const u8 hsr_multicast_addr[ETH_ALEN] = {0x01, 0x15, 0x4e, 0x00, 0x01, 0x00};
+
+
+/* List of all registered virtual HSR devices */
+static LIST_HEAD(hsr_list);
+
+void register_hsr_master(struct hsr_priv *hsr_priv)
+{
+	list_add_tail_rcu(&hsr_priv->hsr_list, &hsr_list);
+}
+
+void unregister_hsr_master(struct hsr_priv *hsr_priv)
+{
+	struct hsr_priv *hsr_priv_it;
+
+	list_for_each_entry(hsr_priv_it, &hsr_list, hsr_list)
+		if (hsr_priv_it == hsr_priv) {
+			list_del_rcu(&hsr_priv_it->hsr_list);
+			return;
+		}
+}
+
+
+/*
+ * If dev is a HSR slave device, return the virtual master device. Return NULL
+ * otherwise.
+ */
+static struct hsr_priv *get_hsr_master(struct net_device *dev)
+{
+	struct hsr_priv *hsr_priv;
+
+	rcu_read_lock();
+	list_for_each_entry_rcu(hsr_priv, &hsr_list, hsr_list)
+		if ((dev == hsr_priv->slave_data[0].dev_data.dev) ||
+				(dev == hsr_priv->slave_data[1].dev_data.dev)) {
+			rcu_read_unlock();
+			return hsr_priv;
+		}
+
+	rcu_read_unlock();
+	return NULL;
+}
+
+/*
+ * If dev is a HSR slave device, return the other slave device. Return NULL
+ * otherwise.
+ */
+static struct hsr_slave_data *get_other_slave(struct hsr_priv *hsr_priv,
+							struct net_device *dev)
+{
+	if (dev == hsr_priv->slave_data[0].dev_data.dev)
+		return (&hsr_priv->slave_data[1]);
+	if (dev == hsr_priv->slave_data[1].dev_data.dev)
+		return (&hsr_priv->slave_data[0]);
+
+	return NULL;
+}
+
+
+static int hsr_netdev_notify(struct notifier_block *nb, unsigned long event, void *ptr)
+{
+
+/*
+ * Should do:
+ *
+ * - error monitoring (broken link)
+ * - slave monitoring (disallow down, reconfiguring ?)
+
+	register_netdevice_notifier(...);
+	NETDEV_GOING_DOWN
+	NETDEV_CHANGEADDR
+	NETDEV_CHANGE (dev->flags)
+	NETDEV_UNREGISTER
+ */
+
+	struct net_device *slave, *other_slave;
+	struct hsr_priv *hsr_priv;
+	struct hsr_slave_data *other_data;
+
+	hsr_priv = get_hsr_master(ptr);
+	if (hsr_priv) {
+		slave = ptr;
+		other_data = get_other_slave(hsr_priv, slave);
+		other_slave = other_data->dev_data.dev;
+	} else {
+		if (!is_hsr_master(ptr))
+			return NOTIFY_DONE;
+		hsr_priv = netdev_priv(ptr);
+		slave = hsr_priv->slave_data[0].dev_data.dev;
+		other_slave = hsr_priv->slave_data[1].dev_data.dev;
+	}
+
+	switch (event) {
+	case NETDEV_UP:
+	case NETDEV_DOWN:
+//		printk(KERN_INFO "Got %s event\n", ((struct net_device *) ptr)->name);
+		hsr_check_announce(hsr_priv->dev_data.dev, slave, other_slave);
+	}
+
+	return NOTIFY_DONE;
+}
+
+
+static struct timer_list prune_timer;
+
+static void hsr_prune_nodes(unsigned long data)
+{
+	struct hsr_priv *hsr_priv;
+
+	rcu_read_lock();
+	list_for_each_entry_rcu(hsr_priv, &hsr_list, hsr_list) {
+		prune_mac_entries(&hsr_priv->dev_data.frame_db);
+		prune_mac_entries(&hsr_priv->slave_data[0].dev_data.frame_db);
+		prune_mac_entries(&hsr_priv->slave_data[1].dev_data.frame_db);
+	}
+	rcu_read_unlock();
+
+	prune_timer.expires = jiffies + PRUNE_PERIOD * HZ / 1000;
+	add_timer(&prune_timer);
+}
+
+/*
+ * Find all occurences of node with source addr ethaddr, and set their
+ * sequence number to sequence_nr. Used e.g. when a node re-announces itself
+ * after a reboot. Without this, its frames might get filtered until its
+ * entry expires.
+ */
+static void hsr_update_nodes(u8 ethaddr[ETH_ALEN], u16 sequence_nr)
+{
+	struct hsr_priv *hsr_priv;
+
+	rcu_read_lock();
+	list_for_each_entry_rcu(hsr_priv, &hsr_list, hsr_list) {
+		update_node_seqnr(&hsr_priv->dev_data.frame_db,
+							ethaddr, sequence_nr);
+		update_node_seqnr(&hsr_priv->slave_data[0].dev_data.frame_db,
+							ethaddr, sequence_nr);
+		update_node_seqnr(&hsr_priv->slave_data[1].dev_data.frame_db,
+							ethaddr, sequence_nr);
+	}
+	rcu_read_unlock();
+}
+
+
+static struct sk_buff *strip_hsr_tag(struct sk_buff *skb)
+{
+	struct hsr_tag *hsr_tag;
+	struct sk_buff *skb2;
+
+	skb2 = skb_share_check(skb, GFP_ATOMIC);
+	if (unlikely(!skb2))
+		goto err_free;
+	skb = skb2;
+
+	if (unlikely(!pskb_may_pull(skb, HSR_TAGLEN)))
+		goto err_free;
+
+	hsr_tag = (struct hsr_tag *) skb->data;
+	skb->protocol = hsr_tag->encap_proto;
+	skb_reset_network_header(skb);  // Huh???
+	skb_pull_rcsum(skb, HSR_TAGLEN);
+
+	return skb;
+
+err_free:
+	kfree_skb(skb);
+	return NULL;
+}
+
+
+/*
+ * The only 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.
+ * 2) Perhaps use the LifeCheck frames to detect ring breaks? I.e., the
+ *    LifeCheck frames, since they are periodically sent, will make sure broken
+ *    connections are detected even if the network is completely silent
+ *    otherwise. They don't need any special treatment for this though; just
+ *    register them as normal frames and throw them away.
+ */
+static int handle_supervision_frame(struct hsr_priv *hsr_priv,
+							struct sk_buff *skb)
+{
+	struct hsr_supervision_tag *hsr_stag;
+
+	if (compare_ether_addr(eth_hdr(skb)->h_dest, hsr_multicast_addr))
+		return 0;
+
+	hsr_stag = (struct hsr_supervision_tag *) skb->data;
+	if (ntohs(hsr_stag->path_and_HSR_ver) >> 12 != 0x0f)
+		return 0;
+	if ((hsr_stag->HSR_TLV_Type != 22) && (hsr_stag->HSR_TLV_Type != 23))
+		return 0;
+	if (hsr_stag->HSR_TLV_Length != 12)
+		return 0;
+
+//	printk("%s:%d: got HSR supervision frame (type %d)\n", __FILE__, __LINE__, hsr_stag->HSR_TLV_Type);
+
+	/* Ok, we have a valid HSR supervision frame. */
+	if (hsr_stag->HSR_TLV_Type == 23)
+		hsr_update_nodes(hsr_stag->MacAddressA, hsr_stag->sequence_nr);
+
+	return 1;
+}
+
+
+/*
+ * Implementation somewhat according to IEC-62439-3, p. 43
+ */
+static int hsr_rcv(struct sk_buff *skb, struct net_device *dev,
+			struct packet_type *pt, struct net_device *orig_dev)
+{
+	struct hsr_priv *hsr_priv;
+	struct hsr_slave_data *other_slave_data;
+	int deliver_to_self;
+	struct sk_buff *skb_deliver;
+	int ret;
+
+	hsr_priv = get_hsr_master(dev);
+
+	if (!hsr_priv) {
+		printk(KERN_INFO "HSR: Got HSR frame on non-HSR device; dropping it.\n");
+		kfree_skb(skb);
+		return NET_RX_DROP;
+	}
+
+	/* Receive this frame? */
+	deliver_to_self = 0;
+// ???	if (is_etherdev_addr(hsr_dev, eth_hdr(skb)->h_dest)) {
+	if ((skb->pkt_type == PACKET_HOST) ||
+				(skb->pkt_type == PACKET_MULTICAST) ||
+				(skb->pkt_type == PACKET_BROADCAST))
+		if (register_frame(&hsr_priv->dev_data, skb) == 0)
+			deliver_to_self = 1;
+
+	if (handle_supervision_frame(hsr_priv, skb))
+		deliver_to_self = 0;
+
+	/* Forward this frame? */
+	other_slave_data = NULL;
+	if (skb->pkt_type != PACKET_HOST) {
+		other_slave_data = get_other_slave(hsr_priv, dev);
+		if (register_frame(&other_slave_data->dev_data, skb) == 1)
+			other_slave_data = NULL;
+	}
+
+	if (!deliver_to_self && !other_slave_data) {
+		kfree_skb(skb);
+		return NET_RX_SUCCESS;
+	}
+
+	skb_deliver = skb;
+	if (deliver_to_self && other_slave_data) {
+#if !defined(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS) && \
+						!defined(CONFIG_NONSTANDARD_HSR)
+		/* We have to memmove the whole payload below */
+		skb_deliver = skb_copy(skb, GFP_ATOMIC);
+#else
+		skb_deliver = skb_clone(skb, GFP_ATOMIC);
+#endif
+		if (!skb_deliver) {
+			deliver_to_self = 0;
+			hsr_priv->dev_data.dev->stats.rx_dropped++;
+		}
+	}
+
+	if (deliver_to_self) {
+		skb_deliver = strip_hsr_tag(skb_deliver);
+		if (!skb_deliver) {
+			hsr_priv->dev_data.dev->stats.rx_dropped++;
+			goto forward;
+		}
+#if !defined(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS) && \
+						!defined(CONFIG_NONSTANDARD_HSR)
+		/*
+		 * skb_deliver should be linear here, after the call to
+		 * skb_copy() in the block above. We need to memmove the
+		 * whole payload to work around alignment problems caused by
+		 * the 6-byte HSR tag.
+		 */
+		memmove(skb_deliver->data - HSR_TAGLEN, skb_deliver->data,
+							skb_deliver->len);
+		skb_deliver->data -= HSR_TAGLEN;
+		skb_deliver->tail -= HSR_TAGLEN;
+		skb_reset_network_header(skb_deliver);
+#endif
+		skb_deliver->dev = hsr_priv->dev_data.dev;
+		ret = netif_rx(skb_deliver);
+		// ^^ Pass frame to hsrX (receive);
+		/*	netif_rx(skb)? VLAN code does this...
+			dev_forward_skb(hsr_dev, skb)? (loopback)
+			netif_receive_skb(skb)? bridge code does this:
+				NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_IN, skb, indev, NULL, netif_receive_skb);
+			What's the "right way"?
+		 */
+		if (ret == NET_RX_DROP)
+			hsr_priv->dev_data.dev->stats.rx_dropped++;
+		else {
+			hsr_priv->dev_data.dev->stats.rx_packets++;
+			hsr_priv->dev_data.dev->stats.rx_bytes += skb->len;
+		}
+	}
+
+forward:
+	if (other_slave_data) {
+		skb_push(skb, ETH_HLEN);
+		skb->dev = other_slave_data->dev_data.dev;
+		dev_queue_xmit(skb);
+	}
+
+	return NET_RX_SUCCESS;
+}
+
+
+static struct packet_type hsr_pt __read_mostly = {
+	.type = htons(ETH_P_HSR),
+	.func = hsr_rcv,
+};
+
+static struct notifier_block hsr_nb = {
+	.notifier_call = hsr_netdev_notify,	/* Slave event notifications */
+};
+
+
+static int __init hsr_init(void)
+{
+	int res;
+
+	BUG_ON(sizeof(struct hsr_tag) != HSR_TAGLEN);
+	BUG_ON(sizeof(struct hsr_ethhdr) != ETH_HLEN + HSR_TAGLEN);
+
+	dev_add_pack(&hsr_pt);
+
+	init_timer(&prune_timer);
+	prune_timer.function = hsr_prune_nodes;
+	prune_timer.data = 0;
+	prune_timer.expires = jiffies + PRUNE_PERIOD * HZ / 1000;
+	add_timer(&prune_timer);
+
+	register_netdevice_notifier(&hsr_nb);
+
+	res = hsr_netlink_init();
+
+	return res;
+}
+
+static void __exit hsr_exit(void)
+{
+	unregister_netdevice_notifier(&hsr_nb);
+	del_timer(&prune_timer);
+	hsr_netlink_exit();
+	dev_remove_pack(&hsr_pt);
+}
+
+module_init(hsr_init);
+module_exit(hsr_exit);
+MODULE_LICENSE("GPL");
diff --git a/net/hsr/hsr_netlink.c b/net/hsr/hsr_netlink.c
new file mode 100644
index 0000000..f391a12
--- /dev/null
+++ b/net/hsr/hsr_netlink.c
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2011-2012 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-2012 Arvid Brodin, arvid.brodin@enea.com
+ *
+ * Routines for handling Netlink messages for HSR.
+ */
+
+#include "hsr_netlink.h"
+#include <linux/kernel.h>
+#include <net/rtnetlink.h>
+#include "hsr_private.h"
+
+static const struct nla_policy hsr_policy[IFLA_HSR_MAX + 1] = {
+	[IFLA_HSR_SLAVE1]	= { .type = NLA_U32 },
+	[IFLA_HSR_SLAVE2]	= { .type = NLA_U32 },
+};
+
+
+/*
+ * Here, it seems a netdevice has already been allocated for us, and the
+ * hsr_dev_setup routine has been executed. Nice!
+ */
+static int hsr_newlink(struct net *src_net, struct net_device *dev,
+				struct nlattr *tb[], struct nlattr *data[])
+{
+	struct net_device *link[2];
+
+	if (!data[IFLA_HSR_SLAVE1]) {
+		printk(KERN_INFO "IFLA_HSR_SLAVE1 missing!\n");
+		return -EINVAL;
+	}
+	link[0] = __dev_get_by_index(src_net, nla_get_u32(data[IFLA_HSR_SLAVE1]));
+	if (!data[IFLA_HSR_SLAVE2]) {
+		printk(KERN_INFO "IFLA_HSR_SLAVE2 missing!\n");
+		return -EINVAL;
+	}
+	link[1] = __dev_get_by_index(src_net, nla_get_u32(data[IFLA_HSR_SLAVE2]));
+
+	if (!link[0] || !link[1])
+		return -ENODEV;
+	if (link[0] == link[1])
+		return -EINVAL;
+
+	return hsr_dev_finalize(dev, link);
+}
+
+static struct rtnl_link_ops hsr_link_ops __read_mostly = {
+	.kind		= "hsr",
+	.maxtype	= IFLA_HSR_MAX,
+	.policy		= hsr_policy,
+	.priv_size	= sizeof(struct hsr_priv),
+	.setup		= hsr_dev_setup,
+//	.validate	= vlan_validate,
+	.newlink	= hsr_newlink,
+//	.changelink	= vlan_changelink,
+//	.dellink	= hsr_dellink,  dev->destructor() called automatically?
+//	.get_size	= vlan_get_size,
+//	.fill_info	= vlan_fill_info,
+};
+
+int __init hsr_netlink_init(void)
+{
+	return rtnl_link_register(&hsr_link_ops);
+}
+
+void __exit hsr_netlink_exit(void)
+{
+	rtnl_link_unregister(&hsr_link_ops);
+}
+
+MODULE_ALIAS_RTNL_LINK("hsr");
diff --git a/net/hsr/hsr_netlink.h b/net/hsr/hsr_netlink.h
new file mode 100644
index 0000000..9d8a14d
--- /dev/null
+++ b/net/hsr/hsr_netlink.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2011-2012 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-2012 Arvid Brodin, arvid.brodin@enea.com
+ */
+
+#ifndef __HSR_NETLINK_H
+#define __HSR_NETLINK_H
+
+#include <linux/module.h>
+
+int __init hsr_netlink_init(void);
+void __exit hsr_netlink_exit(void);
+
+#endif /* __HSR_NETLINK_H */
diff --git a/net/hsr/hsr_private.h b/net/hsr/hsr_private.h
new file mode 100644
index 0000000..2bab99e
--- /dev/null
+++ b/net/hsr/hsr_private.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2011-2012 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-2012 Arvid Brodin, arvid.brodin@enea.com
+ */
+
+#ifndef _HSR_PRIVATE_H
+#define _HSR_PRIVATE_H
+
+#include <linux/netdevice.h>
+#include <linux/list.h>
+
+
+/*
+ * Time constants as specified in the HSR specification (IEC-62439-3) Table 8.
+ * All values in milliseconds.
+ */
+#define HSR_LIFE_CHECK_INTERVAL		 2000
+#define HSR_NODE_FORGET_TIME		60000
+#define HSR_ANNOUNCE_INTERVAL		  100
+
+/* How often shall we check if node entry is older than HSR_NODE_FORGET_TIME? */
+#define PRUNE_PERIOD			 5000
+
+/*
+ * HSR Tag.
+ * As defined in IEC-62439-3, the HSR tag is really { ethertype = 0x88FB, path,
+ * LSDU_size, sequence Nr }. But we let eth_header() create { h_dest, h_source,
+ * h_proto = 0x88FB }, and add { path, LSDU_size, sequence Nr, encapsulated
+ * protocol } instead.
+ */
+#ifdef CONFIG_NONSTANDARD_HSR
+#define HSR_TAGLEN	8
+#else
+#define HSR_TAGLEN	6
+#endif
+struct hsr_tag {
+/*
+	This is nice but I'm not sure it is "portably compatible" with
+	endianness swaps:
+	__be16		path:4;
+	__be16		LSDU_size:12;
+*/
+	__be16		path_and_LSDU_size;
+	__be16		sequence_nr;
+	__be16		encap_proto;
+#ifdef CONFIG_NONSTANDARD_HSR
+	__be16		padding;
+#endif
+} __attribute__((packed));
+
+struct hsr_ethhdr {
+	struct ethhdr	ethhdr;
+	struct hsr_tag	hsr_tag;
+} __attribute__((packed));
+
+
+struct hsr_supervision_tag {
+	__be16		path_and_HSR_ver;
+	__be16		sequence_nr;
+	__u8		HSR_TLV_Type;
+	__u8		HSR_TLV_Length;
+#ifdef CONFIG_NONSTANDARD_HSR
+	__be16		padding;
+#endif
+	unsigned char	MacAddressA[ETH_ALEN];
+} __attribute__((packed));
+
+
+struct hsr_dev_data {
+	struct net_device *dev;
+	struct list_head frame_db;
+};
+
+struct hsr_slave_data {
+	struct hsr_dev_data dev_data;
+	unsigned char orig_mac[ETH_ALEN];
+	int promisc;
+	int was_up;
+};
+
+struct hsr_priv {
+	struct list_head hsr_list;		/* List of hsr devices */
+	struct rcu_head rcu_head;
+	struct hsr_dev_data dev_data;
+	struct hsr_slave_data slave_data[2];
+	struct timer_list announce_timer;	/* Supervision frame dispatch */
+	int announce_count;
+	int online;
+	u16 sequence_nr;
+	spinlock_t seqlock;
+};
+
+extern const u8 hsr_multicast_addr[ETH_ALEN];
+
+void register_hsr_master(struct hsr_priv *hsr_priv);
+void unregister_hsr_master(struct hsr_priv *hsr_priv);
+
+void hsr_dev_setup(struct net_device *dev);
+int hsr_dev_finalize(struct net_device *hsr_dev, struct net_device *slave[2]);
+
+void hsr_check_announce(struct net_device *hsr_dev, struct net_device *slave1,
+						struct net_device *slave2);
+int is_hsr_master(struct net_device *dev);
+
+void skb_print(struct sk_buff *skb, const char *file, const int line);
+
+#endif /*  _HSR_PRIVATE_H */


-- 
Arvid Brodin
Enea Services Stockholm AB - since February 16 a part of Xdin in the Alten
Group. Soon we will be working under the common brand Xdin. Read more at
www.xdin.com.

^ permalink raw reply related

* [PATCH] cdc-phonet: fix skb truesize underestimation
From: Eric Dumazet @ 2012-03-27 13:17 UTC (permalink / raw)
  To: David Miller
  Cc: Greg Kroah-Hartman, linux-usb, netdev, Felipe Balbi,
	Rémi Denis-Courmont
In-Reply-To: <1332853442.3248.15.camel@edumazet-laptop>

Now skb_add_rx_frag() has a truesize parameter, we can fix cdc-phonet to
properly account truesize of each fragment : a full page.

Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
Cc: Felipe Balbi <balbi@ti.com>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Rémi Denis-Courmont <remi.denis-courmont@nokia.com>
---
 drivers/net/usb/cdc-phonet.c |    4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/net/usb/cdc-phonet.c b/drivers/net/usb/cdc-phonet.c
index 3886b30..3e41b00 100644
--- a/drivers/net/usb/cdc-phonet.c
+++ b/drivers/net/usb/cdc-phonet.c
@@ -165,13 +165,13 @@ static void rx_complete(struct urb *req)
 				memcpy(skb_put(skb, 1), page_address(page), 1);
 				skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags,
 						page, 1, req->actual_length,
-						req->actual_length);
+						PAGE_SIZE);
 				page = NULL;
 			}
 		} else {
 			skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags,
 					page, 0, req->actual_length,
-					req->actual_length);
+					PAGE_SIZE);
 			page = NULL;
 		}
 		if (req->actual_length < PAGE_SIZE)

^ permalink raw reply related

* [PATCH] f_phonet: fix skb truesize underestimation
From: Eric Dumazet @ 2012-03-27 13:04 UTC (permalink / raw)
  To: David Miller
  Cc: Greg Kroah-Hartman, linux-usb-u79uwXL29TY76Z2rM5mHXA, netdev,
	Felipe Balbi, Rémi Denis-Courmont

Now skb_add_rx_frag() has a truesize parameter, we can fix f_phonet to
properly account truesize of each fragment : a full page.

Signed-off-by: Eric Dumazet <eric.dumazet-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
Cc: Felipe Balbi <balbi-l0cyMroinI0@public.gmane.org>
Cc: Greg Kroah-Hartman <gregkh-hQyY1W1yCW8ekmWlsbkhG0B+6BGkLq7r@public.gmane.org>
Cc: Rémi Denis-Courmont <remi.denis-courmont-xNZwKgViW5gAvxtiuMwx3w@public.gmane.org>
---
 drivers/usb/gadget/f_phonet.c |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/usb/gadget/f_phonet.c b/drivers/usb/gadget/f_phonet.c
index 85a5ceb..965a629 100644
--- a/drivers/usb/gadget/f_phonet.c
+++ b/drivers/usb/gadget/f_phonet.c
@@ -345,7 +345,7 @@ static void pn_rx_complete(struct usb_ep *ep, struct usb_request *req)
 		}
 
 		skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags, page,
-				skb->len <= 1, req->actual, req->actual);
+				skb->len <= 1, req->actual, PAGE_SIZE);
 		page = NULL;
 
 		if (req->actual < req->length) { /* Last fragment */


--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply related

* [ANNOUNCE] Netfilter releases: iptables 1.4.13, nfacct 1.0.0 and libnetfilter_acct 1.0.0
From: Pablo Neira Ayuso @ 2012-03-27 12:17 UTC (permalink / raw)
  To: netfilter; +Cc: netdev, netfilter-devel, netfilter-announce, lwn

[-- Attachment #1: Type: text/plain, Size: 947 bytes --]

Hi!

The Netfilter project proudly presents:

        iptables 1.4.13
	nfacct 1.0.0
	libnetfilter_acct 1.0.0

Changes in iptables include:

* rpfilter support from Florian Westphal.

* IPv6 ECN capable version from Patrick McHardy.

* a couple of fixes for internal libiptc library.

* fix leaking file descriptor to avoid annoying log messsages in SELinux from
  Maciej enczykowski.

* nfacct match support by myself.

For nfacct and libnetfilter_acct, this is the initial release. These require
Linux kernel 3.3 or any later.

You can download all these from:

http://www.netfilter.org/projects/iptables/downloads.html
ftp://ftp.netfilter.org/pub/iptables/

http://www.netfilter.org/projects/nfacct/downloads.html
ftp://ftp.netfilter.org/pub/nfacct/

http://www.netfilter.org/projects/libnetfilter_acct/downloads.html
ftp://ftp.netfilter.org/pub/libnetfilter_acct/

You can also find the changelog to this email for more information.

Have fun!

[-- Attachment #2: changes-iptables-1.4.13.txt --]
[-- Type: text/plain, Size: 599 bytes --]

Florian Westphal (1):
      extensions: add rpfilter module

Franz Flasch (2):
      iptables: missing free() in function cache_add_entry()
      iptables: missing free() in function delete_entry()

Jonh Wendell (1):
      libiptc: Returns the position the entry was inserted

Maciej Żenczykowski (1):
      src: mark newly opened fds as FD_CLOEXEC (close on exec)

Pablo Neira Ayuso (7):
      Revert "libiptc: Returns the position the entry was inserted"
      extensions: add nfacct match
      Bump version to 1.4.13

Patrick McHardy (1):
      extensions: add IPv6 capable ECN match extension

[-- Attachment #3: changes-nfacct-1.0.0.txt --]
[-- Type: text/plain, Size: 509 bytes --]

Pablo Neira Ayuso (11):
      initial import
      nfacct: use return instead of exit(EXIT_FAILURE)
      build: remove bogus dependencies
      nfacct: check for too many arguments
      nfacct: check for wrong arguments
      nfacct: replace one leftover perror by nfacct_perror
      doc: minor fixes for nfacct.8
      nfacct: fix licensing terms, this is GPLv2 software
      nfacct: add XML output support
      nfacct: update manpage to include xml and reset options
      build: nfacct release 1.0.0


[-- Attachment #4: changes-libnetfilter_acct-1.0.0.txt --]
[-- Type: text/plain, Size: 1499 bytes --]

Jan Engelhardt (5):
      build: enable automake warnings
      build: remove unnecessary AC_EXEEXT
      Update .gitignore
      Add examples/.gitignore
      build: add missing LIBMNL_CFLAGS to compilation process

Pablo Neira Ayuso (27):
      intial import of libnetfilter_acct
      fix crash if no data pointer is passed to nfacct_list_cb
      src: improve nfacct-add example
      add missing .gitignore
      add missing Makefile.am files
      update headers to use new nfnetlink subsys
      examples: nfacct-get: display pkt and byte counters
      fix endianness in 64-bits packet and bytes counters
      examples: nfacct-add: initialize counters, otherwise we add garbage
      add README file
      add NFNL_MSG_ACCT_GET_CTRZERO
      examples: add nfacct-del to delete accounting objects
      extend nfacct_list to allow to zero counters
      examples: nfacct-get allows to zero counters with `-z' option
      update nfnetlink_acct.h
      src: major API redesign
      src: add library documentation in doxygen
      add COPYING file and other licensing stuff
      update README file
      build: update configure.ac and gcc4_visibility.m4
      update .gitignore file
      nfacct: modify nfacct_snprint output
      nfacct: use NFACCT_ATTR_* flags in nfacct_nlmsg_build_payload
      nfacct: check if attribute is set in nfacct_attr_get*
      extend README
      nfacct: change prototype of nfacct_snprintf() to support output in XML
      examples: fix nfacct-get example


^ permalink raw reply

* Re: [PATCH V2 4/7] net/mlx4_en: Set max rate-limit for a TC
From: Amir Vadai @ 2012-03-27 12:03 UTC (permalink / raw)
  To: John Fastabend
  Cc: Or Gerlitz, Ben Hutchings, netdev, Yevgeny Petrilin, Oren Duer,
	Amir Vadai, Liran Liss
In-Reply-To: <4F70BB98.4090806@intel.com>


On 03/26/2012 08:55 PM, John Fastabend wrote:
> On 3/26/2012 10:00 AM, Or Gerlitz wrote:
>> On Mon, Mar 26, 2012 at 4:46 PM, Ben Hutchings
>> <bhutchings@solarflare.com>  wrote:
>>>> We used sysfs since max bw isn't part of the ETS / DCBX NL support, and we're
>>>> open to other suggestions to add generic support for max bw, e.g add call to
>>>> the DCBX NL API.
>>
>>> netlink interfaces are generally easily extensible and it doesn't make
>>> sense to me to augment such an interface through sysfs.  Perhaps you're
>>> concerned that netlink extensions won't be supported in older kernel
>>> versions running your OOT driver?  That's unfortunate, but let's not
>>> standardise an ugly interface based on a temporary problem like that.
>>
>> As written above, that was done since ratelimit isn't part of ETS, we can
>> that through netlink extensions that you mentioned, if this is the preffered
>> way to go, David? Eric? Ben - could you provide pointer to these extensions?
>>
>> Or.
>> --
>
> I think I original suggested it didn't belong in DCBNL because it
> wasn't part of ETS (802.1Qaz). But it _is_ a traffic selection
> algorithm and could fall into the vendor specific part of 802.1Q.
>
> I would suggest either adding it as an option to mqprio to take
> a max bandwidth. The advantage here is it would be tied in with
> the usual QOS tooling 'tc'.
>
> # tc qdisc add dev eth3 root mqprio help
> Usage: ... mqprio [num_tc NUMBER] [map P0 P1 ...]
>                    [queues count1@offset1 count2@offset2 ...] [hw 1|0]
> 		  [max_rate rate@tc ...]
>
This could be elegant, but since tc here is a logical traffic class and
ratelimit in our context is an attribute of ETS TC, it could be
problematic.

>
> Or extending DCBNL being careful not to break backwards
> compatibility. I tend to think extending mqprio is cleaner but
> a DCBNL extension could likely work as well. Would need a
> 'DCBNL_IEEE_SET_MAXRATE' and 'DCBNL_IEEE_GET_MAXRATE' for this
> I expect.
I do prefer this option. I'm checking now if it is possible to add
infrastructure for a vendor specific netlink command. It could be
connected by lldpad to the vendor TLV in DCBX.

>
> .John

I will send a V3 of this patchset without the ratelimit patch, to make 
sure the patchset will get into net-next on time.

Thanks,
Amir

^ permalink raw reply

* Re: [PATCH 1/1] Add support 2 SATA ports for Maui and change filename from sata_dwc_460ex.c to sata_dwc_4xx.c
From: Stefan Roese @ 2012-03-27 11:16 UTC (permalink / raw)
  To: Thang Q. Nguyen
  Cc: Benjamin Herrenschmidt, Paul Mackerras, David Miller, Josh Boyer,
	linuxppc-dev, linux-kernel, netdev
In-Reply-To: <1332843983-24254-1-git-send-email-tqnguyen@apm.com>

On Tuesday 27 March 2012 12:26:23 Thang Q. Nguyen wrote:
> Support 2 native SATA ports on APM821XX and change the existing file name
> from sata_dwc_460ex.c to sata_dwc_4xx.c. This supports both 460EX and
> APM821XX.
> 
> Signed-off-by: Thang Q. Nguyen <tqnguyen@apm.com>
> ---
>  arch/powerpc/boot/dts/bluestone.dts |   21 +
>  drivers/ata/Makefile                |    2 +-
>  drivers/ata/sata_dwc_4xx.c          | 2178 +++++++++++++++++++++++++++++++++++
>  3 files changed, 2200 insertions(+), 1 deletions(-)
>  create mode 100644 drivers/ata/sata_dwc_4xx.c

Please use -M option (find-renames) while generating this patch to see
changes in the driver. And you missed to remove the "old" driver.

Thanks,
Stefan

^ permalink raw reply

* [PATCH 1/1] Add support 2 SATA ports for Maui and change filename from sata_dwc_460ex.c to sata_dwc_4xx.c
From: Thang Q. Nguyen @ 2012-03-27 10:26 UTC (permalink / raw)
  To: Benjamin Herrenschmidt, Paul Mackerras, David Miller, Josh Boyer,
	Stefan Roese
  Cc: linuxppc-dev, linux-kernel, netdev, Thang Q. Nguyen

Support 2 native SATA ports on APM821XX and change the existing file name from sata_dwc_460ex.c to sata_dwc_4xx.c. This supports both 460EX and APM821XX.

Signed-off-by: Thang Q. Nguyen <tqnguyen@apm.com>
---
 arch/powerpc/boot/dts/bluestone.dts |   21 +
 drivers/ata/Makefile                |    2 +-
 drivers/ata/sata_dwc_4xx.c          | 2178 +++++++++++++++++++++++++++++++++++
 3 files changed, 2200 insertions(+), 1 deletions(-)
 create mode 100644 drivers/ata/sata_dwc_4xx.c

diff --git a/arch/powerpc/boot/dts/bluestone.dts b/arch/powerpc/boot/dts/bluestone.dts
index 2a56a0d..1396e2c 100644
--- a/arch/powerpc/boot/dts/bluestone.dts
+++ b/arch/powerpc/boot/dts/bluestone.dts
@@ -145,6 +145,27 @@
 					/*RXDE*/  0x5 0x4>;
 		};
 
+		/* SATA DWC devices */
+		SATA0: sata@bffd1000 {
+			compatible = "amcc,sata-apm821xx";
+			reg = <4 0xbffd1000 0x800   /* SATA0 */
+			       4 0xbffd0800 0x400>; /* AHBDMA */
+			dma-channel=<0>;
+			interrupt-parent = <&UIC0>;
+			interrupts = <26 4    /* SATA0 */
+			              25 4>;  /* AHBDMA */
+		};
+
+		SATA1: sata@bffd1800 {
+			compatible = "amcc,sata-apm821xx";
+			reg = <4 0xbffd1800 0x800   /* SATA1 */
+			       4 0xbffd0800 0x400>; /* AHBDMA */
+			dma-channel=<1>;
+			interrupt-parent = <&UIC0>;
+			interrupts = <27 4    /* SATA1 */
+			              25 4>;  /* AHBDMA */
+		};
+
 		POB0: opb {
 			compatible = "ibm,opb";
 			#address-cells = <1>;
diff --git a/drivers/ata/Makefile b/drivers/ata/Makefile
index 6ece5b7..d225c0c 100644
--- a/drivers/ata/Makefile
+++ b/drivers/ata/Makefile
@@ -8,7 +8,7 @@ obj-$(CONFIG_SATA_AHCI_PLATFORM) += ahci_platform.o libahci.o
 obj-$(CONFIG_SATA_FSL)		+= sata_fsl.o
 obj-$(CONFIG_SATA_INIC162X)	+= sata_inic162x.o
 obj-$(CONFIG_SATA_SIL24)	+= sata_sil24.o
-obj-$(CONFIG_SATA_DWC)		+= sata_dwc_460ex.o
+obj-$(CONFIG_SATA_DWC)		+= sata_dwc_4xx.o
 
 # SFF w/ custom DMA
 obj-$(CONFIG_PDC_ADMA)		+= pdc_adma.o
diff --git a/drivers/ata/sata_dwc_4xx.c b/drivers/ata/sata_dwc_4xx.c
new file mode 100644
index 0000000..f354155
--- /dev/null
+++ b/drivers/ata/sata_dwc_4xx.c
@@ -0,0 +1,2178 @@
+/*
+ * drivers/ata/sata_dwc_4xx.c
+ *
+ * Synopsys DesignWare Cores (DWC) SATA host driver
+ *
+ * Author: Mark Miesfeld <mmiesfeld@amcc.com>
+ *
+ * Ported from 2.6.19.2 to 2.6.25/26 by Stefan Roese <sr@denx.de>
+ * Copyright 2008 DENX Software Engineering
+ *
+ * Based on versions provided by AMCC and Synopsys which are:
+ *          Copyright 2006 Applied Micro Circuits Corporation
+ *          COPYRIGHT (C) 2005  SYNOPSYS, INC.  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 as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ *
+ * CHANGES:
+ * - Version 1.4:
+ *   + Change filename from sata_dwc_460ex.c to sata_dwc_4xx.c
+ *   + This driver supports more than one SATA port. Each SATA port has its
+ *     own private attribute. Move sata_dwc_host_priv structure to
+ *     sata_dwc_device and sata_dwc_device_port structures.
+ */
+
+#ifdef CONFIG_SATA_DWC_DEBUG
+#define DEBUG
+#endif
+
+#ifdef CONFIG_SATA_DWC_VDEBUG
+#define VERBOSE_DEBUG
+#define DEBUG_NCQ
+#endif
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/libata.h>
+#include <linux/slab.h>
+#include "libata.h"
+
+#include <scsi/scsi_host.h>
+#include <scsi/scsi_cmnd.h>
+
+/* These two are defined in "libata.h" */
+#undef  DRV_NAME
+#undef  DRV_VERSION
+#define DRV_NAME        "sata-dwc"
+#define DRV_VERSION     "1.4"
+
+#define DMA_NUM_CHANS		2
+#define DMA_NUM_CHAN_REGS	8
+
+#define AHB_DMA_BRST_DFLT	64	/* 16 data items burst length*/
+
+struct dmareg {
+	u32 low;		/* Low bits 0-31 */
+	u32 high;		/* High bits 32-63 */
+};
+
+/* DMA Per Channel registers */
+struct dma_chan_regs {
+	struct dmareg sar;	/* Source Address */
+	struct dmareg dar;	/* Destination address */
+	struct dmareg llp;	/* Linked List Pointer */
+	struct dmareg ctl;	/* Control */
+	struct dmareg sstat;	/* Source Status not implemented in core */
+	struct dmareg dstat;	/* Destination Status not implemented in core*/
+	struct dmareg sstatar;	/* Source Status Address not impl in core */
+	struct dmareg dstatar;	/* Destination Status Address not implemente */
+	struct dmareg cfg;	/* Config */
+	struct dmareg sgr;	/* Source Gather */
+	struct dmareg dsr;	/* Destination Scatter */
+};
+
+/* Generic Interrupt Registers */
+struct dma_interrupt_regs {
+	struct dmareg tfr;	/* Transfer Interrupt */
+	struct dmareg block;	/* Block Interrupt */
+	struct dmareg srctran;	/* Source Transfer Interrupt */
+	struct dmareg dsttran;	/* Dest Transfer Interrupt */
+	struct dmareg error;	/* Error */
+};
+
+struct ahb_dma_regs {
+	struct dma_chan_regs	chan_regs[DMA_NUM_CHAN_REGS];
+	struct dma_interrupt_regs interrupt_raw;	/* Raw Interrupt */
+	struct dma_interrupt_regs interrupt_status;	/* Interrupt Status */
+	struct dma_interrupt_regs interrupt_mask;	/* Interrupt Mask */
+	struct dma_interrupt_regs interrupt_clear;	/* Interrupt Clear */
+	struct dmareg		statusInt;	/* Interrupt combined*/
+	struct dmareg		rq_srcreg;	/* Src Trans Req */
+	struct dmareg		rq_dstreg;	/* Dst Trans Req */
+	struct dmareg		rq_sgl_srcreg;	/* Sngl Src Trans Req*/
+	struct dmareg		rq_sgl_dstreg;	/* Sngl Dst Trans Req*/
+	struct dmareg		rq_lst_srcreg;	/* Last Src Trans Req*/
+	struct dmareg		rq_lst_dstreg;	/* Last Dst Trans Req*/
+	struct dmareg		dma_cfg;		/* DMA Config */
+	struct dmareg		dma_chan_en;		/* DMA Channel Enable*/
+	struct dmareg		dma_id;			/* DMA ID */
+	struct dmareg		dma_test;		/* DMA Test */
+	struct dmareg		res1;			/* reserved */
+	struct dmareg		res2;			/* reserved */
+	/*
+	 * DMA Comp Params
+	 * Param 6 = dma_param[0], Param 5 = dma_param[1],
+	 * Param 4 = dma_param[2] ...
+	 */
+	struct dmareg		dma_params[6];
+};
+
+/* Data structure for linked list item */
+struct lli {
+	u32		sar;		/* Source Address */
+	u32		dar;		/* Destination address */
+	u32		llp;		/* Linked List Pointer */
+	struct dmareg	ctl;		/* Control */
+	struct dmareg	dstat;		/* Destination Status */
+};
+
+enum {
+	SATA_DWC_DMAC_LLI_SZ =	(sizeof(struct lli)),
+	SATA_DWC_DMAC_LLI_NUM =	256,
+	SATA_DWC_DMAC_LLI_TBL_SZ = (SATA_DWC_DMAC_LLI_SZ * \
+					SATA_DWC_DMAC_LLI_NUM),
+	SATA_DWC_DMAC_TWIDTH_BYTES = 4,
+	SATA_DWC_DMAC_CTRL_TSIZE_MAX = (0x00000800 * \
+						SATA_DWC_DMAC_TWIDTH_BYTES),
+};
+
+/* Host Controller ID */
+enum {
+	APM_460EX_SATA = 0,
+	APM_821XX_SATA = 1,
+};
+
+/* DMA Register Operation Bits */
+enum {
+	DMA_EN	=		0x00000001, /* Enable AHB DMA */
+	DMA_CTL_LLP_SRCEN =	0x10000000, /* Blk chain enable Src */
+	DMA_CTL_LLP_DSTEN =	0x08000000, /* Blk chain enable Dst */
+};
+
+#define DMA_CTL_BLK_TS(size)	((size) & 0x000000FFF)	/* Blk Transfer size */
+#define DMA_CHANNEL(ch)		(0x00000001 << (ch))	/* Select channel */
+	/* Enable channel */
+#define	DMA_ENABLE_CHAN(ch)	(0x00000101 << (ch))
+	/* Disable channel */
+#define	DMA_DISABLE_CHAN(ch)	(0x000000100 << (ch))
+	/* Transfer Type & Flow Controller */
+#define	DMA_CTL_TTFC(type)	(((type) & 0x7) << 20)
+#define	DMA_CTL_SMS(num)	(((num) & 0x3) << 25) /* Src Master Select */
+#define	DMA_CTL_DMS(num)	(((num) & 0x3) << 23)/* Dst Master Select */
+	/* Src Burst Transaction Length */
+#define DMA_CTL_SRC_MSIZE(size) (((size) & 0x7) << 14)
+	/* Dst Burst Transaction Length */
+#define	DMA_CTL_DST_MSIZE(size) (((size) & 0x7) << 11)
+	/* Source Transfer Width */
+#define	DMA_CTL_SRC_TRWID(size) (((size) & 0x7) << 4)
+	/* Destination Transfer Width */
+#define	DMA_CTL_DST_TRWID(size) (((size) & 0x7) << 1)
+
+/* Assign HW handshaking interface (x) to destination / source peripheral */
+#define	DMA_CFG_HW_HS_DEST(int_num) (((int_num) & 0xF) << 11)
+#define	DMA_CFG_HW_HS_SRC(int_num) (((int_num) & 0xF) << 7)
+#define	DMA_CFG_HW_CH_PRIOR(int_num) (((int_num) & 0xF) << 5)
+#define	DMA_LLP_LMS(addr, master) (((addr) & 0xfffffffc) | (master))
+
+/*
+ * This define is used to set block chaining disabled in the control low
+ * register.  It is already in little endian format so it can be &'d dirctly.
+ * It is essentially: cpu_to_le32(~(DMA_CTL_LLP_SRCEN | DMA_CTL_LLP_DSTEN))
+ */
+enum {
+	DMA_CTL_LLP_DISABLE_LE32 = 0xffffffe7,
+	DMA_CTL_TTFC_P2M_DMAC	= 0x00000002, /* Per to mem, DMAC cntr */
+	DMA_CTL_TTFC_M2P_PER = 0x00000003, /* Mem to per, peripheral cntr */
+	DMA_CTL_SINC_INC  = 0x00000000, /* Source Address Increment */
+	DMA_CTL_SINC_DEC  = 0x00000200,
+	DMA_CTL_SINC_NOCHANGE =	0x00000400,
+	DMA_CTL_DINC_INC  = 0x00000000, /* Destination Address Increment */
+	DMA_CTL_DINC_DEC  =	0x00000080,
+	DMA_CTL_DINC_NOCHANGE =	0x00000100,
+	DMA_CTL_INT_EN    = 0x00000001, /* Interrupt Enable */
+
+/* Channel Configuration Register high bits */
+	DMA_CFG_FCMOD_REQ = 0x00000001, /* Flow Control - request based */
+	DMA_CFG_PROTCTL   = (0x00000003 << 2),/* Protection Control */
+
+/* Channel Configuration Register low bits */
+	DMA_CFG_RELD_DST  = 0x80000000, /* Reload Dest / Src Addr */
+	DMA_CFG_RELD_SRC  = 0x40000000,
+	DMA_CFG_HS_SELSRC = 0x00000800, /* Software handshake Src/ Dest */
+	DMA_CFG_HS_SELDST = 0x00000400,
+	DMA_CFG_FIFOEMPTY = (0x00000001 << 9), /* FIFO Empty bit */
+
+/* Channel Linked List Pointer Register */
+	DMA_LLP_AHBMASTER1 = 0,	/* List Master Select */
+	DMA_LLP_AHBMASTER2 = 1,
+
+	SATA_DWC_MAX_PORTS = 1,
+
+	SATA_DWC_SCR_OFFSET = 0x24,
+	SATA_DWC_REG_OFFSET = 0x64,
+};
+
+/* DWC SATA Registers */
+struct sata_dwc_regs {
+	u32 fptagr;		/* 1st party DMA tag */
+	u32 fpbor;		/* 1st party DMA buffer offset */
+	u32 fptcr;		/* 1st party DMA Xfr count */
+	u32 dmacr;		/* DMA Control */
+	u32 dbtsr;		/* DMA Burst Transac size */
+	u32 intpr;		/* Interrupt Pending */
+	u32 intmr;		/* Interrupt Mask */
+	u32 errmr;		/* Error Mask */
+	u32 llcr;		/* Link Layer Control */
+	u32 phycr;		/* PHY Control */
+	u32 physr;		/* PHY Status */
+	u32 rxbistpd;		/* Recvd BIST pattern def register */
+	u32 rxbistpd1;		/* Recvd BIST data dword1 */
+	u32 rxbistpd2;		/* Recvd BIST pattern data dword2 */
+	u32 txbistpd;		/* Trans BIST pattern def register */
+	u32 txbistpd1;		/* Trans BIST data dword1 */
+	u32 txbistpd2;		/* Trans BIST data dword2 */
+	u32 bistcr;		/* BIST Control Register */
+	u32 bistfctr;		/* BIST FIS Count Register */
+	u32 bistsr;		/* BIST Status Register */
+	u32 bistdecr;		/* BIST Dword Error count register */
+	u32 res[15];		/* Reserved locations */
+	u32 testr;		/* Test Register */
+	u32 versionr;		/* Version Register */
+	u32 idr;		/* ID Register */
+	u32 unimpl[192];	/* Unimplemented */
+	u32 dmadr[256];		/* FIFO Locations in DMA Mode */
+};
+
+enum {
+	SCR_SCONTROL_DET_ENABLE	= 0x00000001,
+	SCR_SSTATUS_DET_PRESENT	= 0x00000001,
+	SCR_SERROR_DIAG_X	= 0x04000000,
+/* DWC SATA Register Operations */
+	SATA_DWC_TXFIFO_DEPTH	= 0x01FF,
+	SATA_DWC_RXFIFO_DEPTH	= 0x01FF,
+	SATA_DWC_DMACR_TMOD_TXCHEN = 0x00000004,
+	SATA_DWC_DMACR_TXCHEN	= (0x00000001 | SATA_DWC_DMACR_TMOD_TXCHEN),
+	SATA_DWC_DMACR_RXCHEN	= (0x00000002 | SATA_DWC_DMACR_TMOD_TXCHEN),
+	SATA_DWC_DMACR_TXRXCH_CLEAR =	SATA_DWC_DMACR_TMOD_TXCHEN,
+	SATA_DWC_INTPR_DMAT	= 0x00000001,
+	SATA_DWC_INTPR_NEWFP	= 0x00000002,
+	SATA_DWC_INTPR_PMABRT	= 0x00000004,
+	SATA_DWC_INTPR_ERR	= 0x00000008,
+	SATA_DWC_INTPR_NEWBIST	= 0x00000010,
+	SATA_DWC_INTPR_IPF	= 0x10000000,
+	SATA_DWC_INTMR_DMATM	= 0x00000001,
+	SATA_DWC_INTMR_NEWFPM	= 0x00000002,
+	SATA_DWC_INTMR_PMABRTM	= 0x00000004,
+	SATA_DWC_INTMR_ERRM	= 0x00000008,
+	SATA_DWC_INTMR_NEWBISTM	= 0x00000010,
+	SATA_DWC_LLCR_SCRAMEN	= 0x00000001,
+	SATA_DWC_LLCR_DESCRAMEN	= 0x00000002,
+	SATA_DWC_LLCR_RPDEN	= 0x00000004,
+/* This is all error bits, zero's are reserved fields. */
+	SATA_DWC_SERROR_ERR_BITS = 0x0FFF0F03
+};
+
+#define SATA_DWC_SCR0_SPD_GET(v)	(((v) >> 4) & 0x0000000F)
+#define SATA_DWC_DMACR_TX_CLEAR(v)	(((v) & ~SATA_DWC_DMACR_TXCHEN) |\
+						 SATA_DWC_DMACR_TMOD_TXCHEN)
+#define SATA_DWC_DMACR_RX_CLEAR(v)	(((v) & ~SATA_DWC_DMACR_RXCHEN) |\
+						 SATA_DWC_DMACR_TMOD_TXCHEN)
+#define SATA_DWC_DBTSR_MWR(size)	(((size)/4) & SATA_DWC_TXFIFO_DEPTH)
+#define SATA_DWC_DBTSR_MRD(size)	((((size)/4) & SATA_DWC_RXFIFO_DEPTH)\
+						 << 16)
+struct sata_dwc_device {
+	struct device		*dev;		/* generic device struct */
+	struct ata_host		*host;
+	u8			*reg_base;
+	struct sata_dwc_regs	*sata_dwc_regs;	/* DW Synopsys SATA specific */
+	u8			*scr_base;
+	int			dma_channel;	/* DWC SATA DMA channel */
+	int			irq_dma;
+	int			hostID;
+};
+
+#define SATA_DWC_QCMD_MAX	32
+
+struct sata_dwc_device_port {
+	struct sata_dwc_device	*hsdev;
+	struct lli		*llit[SATA_DWC_QCMD_MAX];  /* DMA LLI table */
+	dma_addr_t		llit_dma[SATA_DWC_QCMD_MAX];
+	u32			dma_chan[SATA_DWC_QCMD_MAX];
+	int			dma_pending[SATA_DWC_QCMD_MAX];
+	u32			sata_dwc_sactive_issued;
+	u32			sata_dwc_sactive_queued;
+	u32			dma_interrupt_count;
+};
+
+/*
+ * Commonly used DWC SATA driver Macros
+ */
+#define HSDEV_FROM_HOST(host)	((struct sata_dwc_device *)\
+					(host)->private_data)
+#define HSDEV_FROM_AP(ap)	((struct sata_dwc_device *)\
+					(ap)->host->private_data)
+#define HSDEVP_FROM_AP(ap)	((struct sata_dwc_device_port *)\
+					(ap)->private_data)
+#define HSDEV_FROM_QC(qc)	((struct sata_dwc_device *)\
+					(qc)->ap->host->private_data)
+#define HSDEV_FROM_HSDEVP(p)	((struct sata_dwc_device *)\
+					(hsdevp)->hsdev)
+
+enum {
+	SATA_DWC_DMA_PENDING_NONE	= 0,
+	SATA_DWC_DMA_PENDING_TX		= 1,
+	SATA_DWC_DMA_PENDING_RX		= 2,
+};
+
+/*
+ * Globals
+ */
+static struct sata_dwc_device *dwc_dev_list[2];
+static struct ahb_dma_regs *sata_dma_regs;
+/*
+ * Prototypes
+ */
+static void sata_dwc_bmdma_start_by_tag(struct ata_queued_cmd *qc, u8 tag);
+static int sata_dwc_qc_complete(struct ata_port *ap, struct ata_queued_cmd *qc,
+				u32 check_status);
+static void sata_dwc_port_stop(struct ata_port *ap);
+static void sata_dwc_clear_dmacr(struct sata_dwc_device_port *hsdevp, u8 tag);
+static int dwc_dma_init(struct sata_dwc_device *hsdev, int irq);
+static int dwc_dma_xfer_setup(struct ata_port *ap, dma_addr_t dma_lli);
+static void dwc_dma_xfer_start(int dma_ch);
+static void sata_dwc_init_port(struct ata_port *ap);
+
+
+static const char *get_prot_descript(u8 protocol)
+{
+	switch ((enum ata_tf_protocols)protocol) {
+	case ATA_PROT_NODATA:
+		return "ATA no data";
+	case ATA_PROT_PIO:
+		return "ATA PIO";
+	case ATA_PROT_DMA:
+		return "ATA DMA";
+	case ATA_PROT_NCQ:
+		return "ATA NCQ";
+	case ATAPI_PROT_NODATA:
+		return "ATAPI no data";
+	case ATAPI_PROT_PIO:
+		return "ATAPI PIO";
+	case ATAPI_PROT_DMA:
+		return "ATAPI DMA";
+	default:
+		return "unknown";
+	}
+}
+
+static const char *get_dma_dir_descript(int dma_dir)
+{
+	switch ((enum dma_data_direction)dma_dir) {
+	case DMA_BIDIRECTIONAL:
+		return "bidirectional";
+	case DMA_TO_DEVICE:
+		return "to device";
+	case DMA_FROM_DEVICE:
+		return "from device";
+	default:
+		return "none";
+	}
+}
+
+static void sata_dwc_tf_dump(struct device *dwc_dev, struct ata_taskfile *tf)
+{
+	dev_vdbg(dwc_dev, "taskfile cmd: 0x%02x protocol: %s flags:"
+		"0x%lx device: %x\n", tf->command,
+		get_prot_descript(tf->protocol), tf->flags, tf->device);
+	dev_vdbg(dwc_dev, "feature: 0x%02x nsect: 0x%x lbal: 0x%x "
+		"lbam: 0x%x lbah: 0x%x\n", tf->feature, tf->nsect, tf->lbal,
+		 tf->lbam, tf->lbah);
+	dev_vdbg(dwc_dev, "hob_feature: 0x%02x hob_nsect: 0x%x "
+		"hob_lbal: 0x%x hob_lbam: 0x%x hob_lbah: 0x%x\n",
+		tf->hob_feature, tf->hob_nsect, tf->hob_lbal, tf->hob_lbam,
+		tf->hob_lbah);
+}
+
+/*
+ * Calculate value to be programmed in register corresponding to data length
+ * This value is effectively the log(base 2) of the length
+ */
+static int get_burst_length_encode(int datalength)
+{
+	int items = datalength >> 2;	/* div by 4 to get lword count */
+
+	if (items >= 64)
+		return 5;
+
+	if (items >= 32)
+		return 4;
+
+	if (items >= 16)
+		return 3;
+
+	if (items >= 8)
+		return 2;
+
+	if (items >= 4)
+		return 1;
+
+	return 0;
+}
+
+/*
+ * Clear channel interrupt. No interrupt for the specified channel
+ * generated until it is enabled again.
+ */
+static void clear_chan_interrupts(int c)
+{
+	out_le32(&(sata_dma_regs->interrupt_clear.tfr.low), DMA_CHANNEL(c));
+	out_le32(&(sata_dma_regs->interrupt_clear.block.low), DMA_CHANNEL(c));
+	out_le32(&(sata_dma_regs->interrupt_clear.srctran.low),
+		 DMA_CHANNEL(c));
+	out_le32(&(sata_dma_regs->interrupt_clear.dsttran.low),
+		 DMA_CHANNEL(c));
+	out_le32(&(sata_dma_regs->interrupt_clear.error.low), DMA_CHANNEL(c));
+}
+
+/*
+ * Check if the selected DMA channel is currently enabled.
+ */
+static int sata_dwc_dma_chk_en(int ch)
+{
+	u32 dma_chan_reg;
+	/* Read the DMA channel register */
+	dma_chan_reg = in_le32(&(sata_dma_regs->dma_chan_en.low));
+	/* Check if it is currently enabled */
+	if (dma_chan_reg & DMA_CHANNEL(ch))
+		return 1;
+	return 0;
+}
+
+/*
+ * Terminate the current DMA transfer
+ *
+ * Refer to the "Abnormal Transfer Termination" section
+ * Disable the corresponding bit in the ChEnReg register
+ * and poll that register to until the channel is terminated.
+ */
+static void sata_dwc_dma_terminate(struct ata_port *ap, int dma_ch)
+{
+	int enabled = sata_dwc_dma_chk_en(dma_ch);
+	/* If the channel is currenly in use, release it. */
+	if (enabled)  {
+		dev_dbg(ap->dev,
+			"%s terminate DMA on channel=%d (mask=0x%08x) ...",
+			__func__, dma_ch, DMA_DISABLE_CHAN(dma_ch));
+		dev_dbg(ap->dev, "ChEnReg=0x%08x\n",
+			in_le32(&(sata_dma_regs->dma_chan_en.low)));
+		/* Disable the selected channel */
+		out_le32(&(sata_dma_regs->dma_chan_en.low),
+			DMA_DISABLE_CHAN(dma_ch));
+
+		/* Wait for the channel is disabled */
+		do {
+			enabled = sata_dwc_dma_chk_en(dma_ch);
+			ndelay(1000);
+		} while (enabled);
+		dev_dbg(ap->dev, "done\n");
+	}
+}
+
+/*
+ * Check if the DMA channel is currently available for transferring data
+ * on the specified ata_port.
+ */
+static int dma_request_channel(struct ata_port *ap)
+{
+	struct sata_dwc_device *hsdev = HSDEV_FROM_AP(ap);
+
+	/* Check if the channel is not currently in use */
+	if (!(in_le32(&(sata_dma_regs->dma_chan_en.low)) &\
+		DMA_CHANNEL(hsdev->dma_channel)))
+		return hsdev->dma_channel;
+
+	dev_err(ap->dev, "%s Channel %d is currently in use\n", __func__,
+		hsdev->dma_channel);
+	return -1;
+}
+
+/*
+ * Processing DMA transfer complete interrupt
+ */
+static irqreturn_t dwc_dma_interrupt(int irq, struct sata_dwc_device *hsdev)
+{
+	int chan;
+	u32 tfr_reg, err_reg;
+	unsigned long flags;
+	struct ata_host *host = (struct ata_host *)hsdev->host;
+	struct ata_port *ap;
+	struct sata_dwc_device_port *hsdevp;
+	u8 tag;
+	unsigned int port = 0;
+
+	spin_lock_irqsave(&host->lock, flags);
+	ap = host->ports[port];
+	hsdevp = HSDEVP_FROM_AP(ap);
+
+	/*
+	 * Find the right tag value for the current DMA transfer.
+	 * In case of NCQ transfer, tag for the current transfer is set to
+	 * active_tag.
+	 * For DMA transfer, tag is 0 (active_tag=ATA_TAG_POISION).
+	 */
+	if (ap->link.active_tag != ATA_TAG_POISON)
+		tag = ap->link.active_tag;
+	else
+		tag = 0;
+
+	tfr_reg = in_le32(&(sata_dma_regs->interrupt_status.tfr.low));
+	err_reg = in_le32(&(sata_dma_regs->interrupt_status.error.low));
+	dev_dbg(ap->dev, "eot=0x%08x err=0x%08x pending=%d active port=%d\n",
+		tfr_reg, err_reg, hsdevp->dma_pending[tag], port);
+
+	chan = hsdev->dma_channel;
+	if (chan >= 0) {
+		if (tfr_reg & DMA_CHANNEL(chan)) {
+			/* Clear DMA config after transfer complete */
+			sata_dwc_clear_dmacr(hsdevp, tag);
+
+			/* Clear the interrupt */
+			out_le32(&(sata_dma_regs->interrupt_clear.tfr.low),
+				 DMA_CHANNEL(chan));
+		}
+
+		/* Check for error interrupt, not expect error happens */
+		if (unlikely(err_reg & DMA_CHANNEL(chan))) {
+			dev_err(ap->dev, "error interrupt err_reg=0x%08x\n",
+				err_reg);
+
+			/* Clear the interrupt. */
+			out_le32(&(sata_dma_regs->interrupt_clear\
+				.error.low),
+				 DMA_CHANNEL(chan));
+		}
+	}
+	hsdevp->dma_pending[tag] = SATA_DWC_DMA_PENDING_NONE;
+
+	if (hsdevp->sata_dwc_sactive_queued == 0)
+		ap->link.active_tag = ATA_TAG_POISON;
+
+	spin_unlock_irqrestore(&host->lock, flags);
+	return IRQ_HANDLED;
+}
+
+/*
+ * Handle DMA transfer complete interrupt. This checks and passes the
+ * processing to the appropriate ATA port.
+ */
+static irqreturn_t dma_dwc_handler(int irq, void *hsdev_instance)
+{
+	u32 tfr_reg, err_reg;
+	int chan;
+
+	tfr_reg = in_le32(&(sata_dma_regs->interrupt_status.tfr.low));
+	err_reg = in_le32(&(sata_dma_regs->interrupt_status.error.low));
+
+	for (chan = 0; chan < DMA_NUM_CHANS; chan++) {
+		/* Check for end-of-transfer interrupt. */
+		if (tfr_reg & DMA_CHANNEL(chan))
+			dwc_dma_interrupt(0, dwc_dev_list[chan]);
+
+		/* Check for error interrupt. */
+		if (err_reg & DMA_CHANNEL(chan))
+			dwc_dma_interrupt(0, dwc_dev_list[chan]);
+	}
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * Registers ISR for a particular DMA channel interrupt
+ */
+static int dma_request_interrupts(struct sata_dwc_device *hsdev, int irq)
+{
+	int retval;
+
+	/* Unmask error interrupt */
+	out_le32(&sata_dma_regs->interrupt_mask.error.low,
+		in_le32(&sata_dma_regs->interrupt_mask.error.low) |
+		 DMA_ENABLE_CHAN(hsdev->dma_channel));
+
+	/* Unmask end-of-transfer interrupt */
+	out_le32(&sata_dma_regs->interrupt_mask.tfr.low,
+		in_le32(&sata_dma_regs->interrupt_mask.tfr.low) |
+		 DMA_ENABLE_CHAN(hsdev->dma_channel));
+
+	retval = request_irq(irq, dma_dwc_handler, IRQF_SHARED, "SATA DMA",
+		hsdev);
+	if (retval) {
+		dev_err(hsdev->dev, "%s: could not get IRQ %d\n",\
+		__func__, irq);
+		return -ENODEV;
+	}
+
+	/* Mark this interrupt as requested */
+	hsdev->irq_dma = irq;
+
+	return 0;
+}
+
+/*
+ * The Synopsis driver has a comment proposing that better performance
+ * is possible by only enabling interrupts on the last item in the linked list.
+ * However, it seems that could be a problem if an error happened on one of the
+ * first items.  The transfer would halt, but no error interrupt would occur.
+ * Currently this function sets interrupts enabled for each linked list item:
+ * DMA_CTL_INT_EN.
+ */
+static int map_sg_to_lli(struct ata_port *ap, struct scatterlist *sg,
+			int num_elems, struct lli *lli, dma_addr_t dma_lli,
+			void __iomem *dmadr_addr, int dir)
+{
+	struct device *dwc_dev = ap->dev;
+	struct sata_dwc_device_port *hsdevp = HSDEVP_FROM_AP(ap);
+	int i, idx = 0;
+	int fis_len = 0;
+	dma_addr_t next_llp;
+	int bl;
+	u32 sms_val, dms_val;
+
+	dev_dbg(dwc_dev, "%s: sg=%p nelem=%d lli=%p dma_lli=0x%08x"
+		" dmadr=0x%08x\n", __func__, sg, num_elems, lli, (u32)dma_lli,
+		(u32)dmadr_addr);
+
+	bl = get_burst_length_encode(AHB_DMA_BRST_DFLT);
+
+	for (i = 0; i < num_elems; i++, sg++) {
+		u32 addr, offset;
+		u32 sg_len, len;
+
+		addr = (u32) sg_dma_address(sg);
+		sg_len = sg_dma_len(sg);
+
+		dev_dbg(dwc_dev, "%s: elem=%d sg_addr=0x%x sg_len"
+			"=%d\n", __func__, i, addr, sg_len);
+
+		while (sg_len) {
+			if (unlikely(idx >= SATA_DWC_DMAC_LLI_NUM)) {
+				/* The LLI table is not large enough. */
+				dev_err(dwc_dev, "LLI table overrun "
+				"(idx=%d)\n", idx);
+				break;
+			}
+			len = (sg_len > SATA_DWC_DMAC_CTRL_TSIZE_MAX) ?
+				SATA_DWC_DMAC_CTRL_TSIZE_MAX : sg_len;
+
+			offset = addr & 0xffff;
+			if ((offset + sg_len) > 0x10000)
+				len = 0x10000 - offset;
+
+			/*
+			 * Make sure a LLI block is not created that will span
+			 * 8K max FIS boundary.  If the block spans such a FIS
+			 * boundary, there is a chance that a DMA burst will
+			 * cross that boundary -- this results in an error in
+			 * the host controller.
+			 */
+			if (unlikely(fis_len + len > 8192)) {
+				dev_dbg(dwc_dev, "SPLITTING: fis_len="
+					"%d(0x%x) len=%d(0x%x)\n", fis_len,
+					 fis_len, len, len);
+				len = 8192 - fis_len;
+				fis_len = 0;
+			} else {
+				fis_len += len;
+			}
+			if (fis_len == 8192)
+				fis_len = 0;
+
+			/*
+			 * Set DMA addresses and lower half of control register
+			 * based on direction.
+			 */
+			if (hsdevp->hsdev->hostID == APM_821XX_SATA) {
+				sms_val = 1+hsdevp->hsdev->dma_channel;
+				dms_val = 0;
+			} else {
+				sms_val = 0;
+				dms_val = 1+hsdevp->hsdev->dma_channel;
+			}
+
+			if (dir == DMA_FROM_DEVICE) {
+				lli[idx].dar = cpu_to_le32(addr);
+				lli[idx].sar = cpu_to_le32((u32)dmadr_addr);
+
+				lli[idx].ctl.low = cpu_to_le32(
+					DMA_CTL_TTFC(DMA_CTL_TTFC_P2M_DMAC) |
+					DMA_CTL_SMS(sms_val) |
+					DMA_CTL_DMS(dms_val) |
+					DMA_CTL_SRC_MSIZE(bl) |
+					DMA_CTL_DST_MSIZE(bl) |
+					DMA_CTL_SINC_NOCHANGE |
+					DMA_CTL_SRC_TRWID(2) |
+					DMA_CTL_DST_TRWID(2) |
+					DMA_CTL_INT_EN |
+					DMA_CTL_LLP_SRCEN |
+					DMA_CTL_LLP_DSTEN);
+			} else { /* DMA_TO_DEVICE */
+				lli[idx].sar = cpu_to_le32(addr);
+				lli[idx].dar = cpu_to_le32((u32)dmadr_addr);
+
+				lli[idx].ctl.low = cpu_to_le32(
+					DMA_CTL_TTFC(DMA_CTL_TTFC_M2P_PER) |
+					DMA_CTL_SMS(dms_val) |
+					DMA_CTL_DMS(sms_val) |
+					DMA_CTL_SRC_MSIZE(bl) |
+					DMA_CTL_DST_MSIZE(bl) |
+					DMA_CTL_DINC_NOCHANGE |
+					DMA_CTL_SRC_TRWID(2) |
+					DMA_CTL_DST_TRWID(2) |
+					DMA_CTL_INT_EN |
+					DMA_CTL_LLP_SRCEN |
+					DMA_CTL_LLP_DSTEN);
+			}
+
+			dev_dbg(dwc_dev, "%s setting ctl.high len: "
+				"0x%08x val: 0x%08x\n", __func__,
+				len, DMA_CTL_BLK_TS(len / 4));
+
+			/* Program the LLI CTL high register */
+			lli[idx].ctl.high = cpu_to_le32(DMA_CTL_BLK_TS\
+						(len / 4));
+
+			/* Program the next pointer.  The next pointer must be
+			 * the physical address, not the virtual address.
+			 */
+			next_llp = (dma_lli + ((idx + 1) * sizeof(struct \
+							lli)));
+
+			/* The last 2 bits encode the list master select. */
+			if (hsdevp->hsdev->hostID == APM_460EX_SATA) {
+				next_llp = DMA_LLP_LMS(next_llp,
+					DMA_LLP_AHBMASTER2);
+			} else {
+				next_llp = DMA_LLP_LMS(next_llp,
+					DMA_LLP_AHBMASTER1);
+			}
+
+			lli[idx].llp = cpu_to_le32(next_llp);
+			idx++;
+			sg_len -= len;
+			addr += len;
+		}
+	}
+
+	/*
+	 * The last next ptr has to be zero and the last control low register
+	 * has to have LLP_SRC_EN and LLP_DST_EN (linked list pointer source
+	 * and destination enable) set back to 0 (disabled.) This is what tells
+	 * the core that this is the last item in the linked list.
+	 */
+	if (likely(idx)) {
+		lli[idx-1].llp = 0x00000000;
+		lli[idx-1].ctl.low &= DMA_CTL_LLP_DISABLE_LE32;
+
+		/* Flush cache to memory */
+		dma_cache_sync(NULL, lli, (sizeof(struct lli) * idx),
+			       DMA_BIDIRECTIONAL);
+	}
+
+	return idx;
+}
+
+/*
+ * Enables the DMA channel to start transferring data
+ */
+static void dwc_dma_xfer_start(int dma_ch)
+{
+	/* Enable the DMA channel */
+	out_le32(&(sata_dma_regs->dma_chan_en.low),
+		 in_le32(&(sata_dma_regs->dma_chan_en.low)) |
+		 DMA_ENABLE_CHAN(dma_ch));
+}
+
+
+static int dwc_dma_xfer_setup(struct ata_port *ap, dma_addr_t dma_lli)
+{
+	int dma_ch;
+	struct sata_dwc_device *hsdev = HSDEV_FROM_AP(ap);
+	/* Acquire DMA channel */
+	dma_ch = dma_request_channel(ap);
+	if (unlikely(dma_ch == -1)) {
+		dev_err(ap->dev, "%s: dma channel unavailable\n", __func__);
+		return -EAGAIN;
+	}
+
+	clear_chan_interrupts(dma_ch);
+
+	/* Program the CFG register. */
+	out_le32(&(sata_dma_regs->chan_regs[dma_ch].cfg.high),
+		 DMA_CFG_HW_HS_SRC(dma_ch) | DMA_CFG_HW_HS_DEST(dma_ch) | \
+		 DMA_CFG_PROTCTL | DMA_CFG_FCMOD_REQ);
+
+	out_le32(&(sata_dma_regs->chan_regs[dma_ch].cfg.low),
+		DMA_CFG_HW_CH_PRIOR(dma_ch));
+
+	/* Program the address of the linked list */
+	if (hsdev->hostID == APM_460EX_SATA) {
+		out_le32(&(sata_dma_regs->chan_regs[dma_ch].llp.low),
+			DMA_LLP_LMS(dma_lli, DMA_LLP_AHBMASTER2));
+	} else {
+		out_le32(&(sata_dma_regs->chan_regs[dma_ch].llp.low),
+			DMA_LLP_LMS(dma_lli, DMA_LLP_AHBMASTER1));
+	}
+
+	/* Program the CTL register with src enable / dst enable */
+	out_le32(&(sata_dma_regs->chan_regs[dma_ch].ctl.low),
+		 DMA_CTL_LLP_SRCEN | DMA_CTL_LLP_DSTEN);
+	return dma_ch;
+}
+
+/*
+ * Initializes the SATA DMA driver
+ */
+static int dwc_dma_init(struct sata_dwc_device *hsdev, int irq)
+{
+	int err;
+
+	err = dma_request_interrupts(hsdev, irq);
+	if (err) {
+		dev_err(hsdev->dev, "%s: dma_request_interrupts returns %d\n",
+			__func__, err);
+		return err;
+	}
+
+	/* Enabe DMA support */
+	out_le32(&(sata_dma_regs->dma_cfg.low), DMA_EN);
+
+	dev_notice(hsdev->dev, "DMA initialized\n");
+	dev_dbg(hsdev->dev, "SATA DMA registers=0x%p\n", sata_dma_regs);
+
+	return 0;
+}
+
+static int sata_dwc_scr_read(struct ata_link *link, unsigned int scr, u32 *val)
+{
+	if (unlikely(scr > SCR_NOTIFICATION)) {
+		dev_err(link->ap->dev, "%s: Incorrect SCR offset 0x%02x\n",
+			__func__, scr);
+		return -EINVAL;
+	}
+
+	*val = in_le32((void *)link->ap->ioaddr.scr_addr + (scr * 4));
+	dev_dbg(link->ap->dev, "%s: id=%d reg=%d val=0x%08x\n",
+		__func__, link->ap->print_id, scr, *val);
+
+	return 0;
+}
+
+static int sata_dwc_scr_write(struct ata_link *link, unsigned int scr, u32 val)
+{
+	dev_dbg(link->ap->dev, "%s: id=%d reg=%d val=val=0x%08x\n",
+		__func__, link->ap->print_id, scr, val);
+	if (unlikely(scr > SCR_NOTIFICATION)) {
+		dev_err(link->ap->dev, "%s: Incorrect SCR offset 0x%02x\n",
+			 __func__, scr);
+		return -EINVAL;
+	}
+	out_le32((void *)link->ap->ioaddr.scr_addr + (scr * 4), val);
+
+	return 0;
+}
+
+static u32 core_scr_read(struct ata_port *ap, unsigned int scr)
+{
+	struct sata_dwc_device *hsdev = HSDEV_FROM_AP(ap);
+	return in_le32((void __iomem *)hsdev->scr_base + (scr * 4));
+}
+
+
+static void core_scr_write(struct ata_port *ap, unsigned int scr, u32 val)
+{
+	struct sata_dwc_device *hsdev = HSDEV_FROM_AP(ap);
+	out_le32((void __iomem *)hsdev->scr_base + (scr * 4), val);
+}
+
+static void clear_serror(struct ata_port *ap)
+{
+	struct sata_dwc_device *hsdev = HSDEV_FROM_AP(ap);
+	out_le32((void __iomem *)hsdev->scr_base + 4,
+		in_le32((void __iomem *)hsdev->scr_base + 4));
+
+}
+
+static void clear_interrupt_bit(struct sata_dwc_device *hsdev, u32 bit)
+{
+	out_le32(&hsdev->sata_dwc_regs->intpr,
+		 in_le32(&hsdev->sata_dwc_regs->intpr));
+}
+
+/*
+ * Porting the ata_bus_softreset function from the libata-sff.c library.
+ */
+static int sata_dwc_bus_softreset(struct ata_port *ap, unsigned int devmask,
+		unsigned long deadline)
+{
+	struct ata_ioports *ioaddr = &ap->ioaddr;
+
+	DPRINTK("ata%u: bus reset via SRST\n", ap->print_id);
+
+	/* Software reset.  causes dev0 to be selected */
+	iowrite8(ap->ctl, ioaddr->ctl_addr);
+	udelay(20);	/* FIXME: flush */
+	iowrite8(ap->ctl | ATA_SRST, ioaddr->ctl_addr);
+	udelay(20);	/* FIXME: flush */
+	iowrite8(ap->ctl, ioaddr->ctl_addr);
+	ap->last_ctl = ap->ctl;
+
+	/* Wait the port to become ready */
+	return ata_sff_wait_after_reset(&ap->link, devmask, deadline);
+}
+
+/*
+ * Do soft reset on the current SATA link.
+ */
+static int sata_dwc_softreset(struct ata_link *link, unsigned int *classes,
+				unsigned long deadline)
+{
+	int rc;
+	u8 err;
+	struct ata_port *ap = link->ap;
+	unsigned int devmask = 0;
+	struct sata_dwc_device *hsdev = HSDEV_FROM_AP(ap);
+
+	/* Select device 0 again */
+	ap->ops->sff_dev_select(ap, 0);
+
+	DPRINTK("about to softreset, devmask=%x\n", devmask);
+	rc = sata_dwc_bus_softreset(ap, devmask, deadline);
+
+	/* If link is occupied, -ENODEV too is an error */
+	if (rc && (rc != -ENODEV || sata_scr_valid(link))) {
+		ata_link_printk(link, KERN_ERR, "SRST failed(errno=%d)\n", rc);
+		return rc;
+	}
+
+	/* Determine by signature whether we have ATA or ATAPI devices */
+	classes[0] = ata_sff_dev_classify(&link->device[0],
+				devmask & (1 << 0), &err);
+
+	DPRINTK("EXIT, classes[0]=%u [1]=%u\n", classes[0], classes[1]);
+	clear_serror(link->ap);
+
+	/* Terminate DMA if it is currently in use */
+	sata_dwc_dma_terminate(link->ap, hsdev->dma_channel);
+
+	return rc;
+}
+
+/*
+ * Reset all internal parameters to default value.
+ * This function should be called in hardreset
+ */
+static void dwc_reset_internal_params(struct ata_port *ap)
+{
+	struct sata_dwc_device_port *hsdevp = HSDEVP_FROM_AP(ap);
+	int tag;
+	for (tag = 0; tag < SATA_DWC_QCMD_MAX; tag++)
+		hsdevp->dma_pending[tag] = SATA_DWC_DMA_PENDING_NONE;
+
+	hsdevp->sata_dwc_sactive_issued = 0;
+	hsdevp->sata_dwc_sactive_queued = 0;
+}
+
+static int sata_dwc_hardreset(struct ata_link *link, unsigned int *classes,
+			unsigned long deadline)
+{
+	int rc;
+	const unsigned long *timing = sata_ehc_deb_timing(&link->eh_context);
+	bool online;
+
+	/* Reset internal parameters to default values */
+	dwc_reset_internal_params(link->ap);
+
+	/* Call standard hard reset */
+	rc = sata_link_hardreset(link, timing, deadline, &online, NULL);
+
+	/* Reconfigure the port after hard reset */
+	if (ata_link_online(link))
+		sata_dwc_init_port(link->ap);
+
+	return online ? -EAGAIN : rc;
+}
+
+static u32 qcmd_tag_to_mask(u8 tag)
+{
+	return 0x00000001 << (tag & 0x1f);
+}
+
+static void sata_dwc_error_intr(struct ata_port *ap,
+				struct sata_dwc_device *hsdev, uint intpr)
+{
+	struct sata_dwc_device_port *hsdevp = HSDEVP_FROM_AP(ap);
+	struct ata_eh_info *ehi = &ap->link.eh_info;
+	unsigned int err_mask = 0, action = 0;
+	struct ata_queued_cmd *qc;
+	u32 serror;
+	u8 status, tag;
+	u32 err_reg;
+
+	ata_ehi_clear_desc(ehi);
+
+	serror = core_scr_read(ap, SCR_ERROR);
+	status = ap->ops->sff_check_status(ap);
+
+	err_reg = in_le32(&(sata_dma_regs->interrupt_status.error.low));
+	tag = ap->link.active_tag;
+
+	dev_err(ap->dev, "%s SCR_ERROR=0x%08x intpr=0x%08x status=0x%08x "
+		" pending=%d dma_err_status=0x%08x\n",
+		__func__, serror, intpr, status, hsdevp->dma_pending[tag],
+		err_reg);
+
+	/* Clear error register and interrupt bit */
+	clear_serror(ap);
+	clear_interrupt_bit(hsdev, SATA_DWC_INTPR_ERR);
+
+	/* This is the only error happening now.  TODO check for exact error */
+	err_mask |= AC_ERR_HOST_BUS;
+	action |= ATA_EH_RESET;
+
+	/* Pass this on to EH */
+	ehi->serror |= serror;
+	ehi->action |= action;
+
+	qc = ata_qc_from_tag(ap, tag);
+	if (qc)
+		qc->err_mask |= err_mask;
+	else
+		ehi->err_mask |= err_mask;
+
+	ata_port_abort(ap);
+}
+
+/*
+ * This Interrupt handler called via port ops registered function.
+ */
+static irqreturn_t sata_dwc_isr(int irq, void *dev_instance)
+{
+	struct ata_host *host = (struct ata_host *)dev_instance;
+	struct sata_dwc_device *hsdev = HSDEV_FROM_HOST(host);
+	struct ata_port *ap;
+	struct ata_queued_cmd *qc;
+	u8 status, tag;
+	int handled, port = 0;
+	int num_lli;
+	uint intpr, sactive, tag_mask;
+	struct sata_dwc_device_port *hsdevp;
+	u32 mask;
+
+	spin_lock(&host->lock);
+
+	/* Read the interrupt register */
+	intpr = in_le32(&hsdev->sata_dwc_regs->intpr);
+
+	ap = host->ports[port];
+	hsdevp = HSDEVP_FROM_AP(ap);
+
+	dev_dbg(ap->dev, "%s intpr=0x%08x active_tag=%d\n", __func__, intpr,
+		ap->link.active_tag);
+
+	/* Check for error interrupt */
+	if (intpr & SATA_DWC_INTPR_ERR) {
+		sata_dwc_error_intr(ap, hsdev, intpr);
+		handled = 1;
+		goto DONE;
+	}
+
+	/* Check for DMA SETUP FIS (FP DMA) interrupt */
+	if (intpr & SATA_DWC_INTPR_NEWFP) {
+		clear_interrupt_bit(hsdev, SATA_DWC_INTPR_NEWFP);
+		if (ap->qc_allocated == 0x0) {
+			handled = 1;
+			goto DONE;
+		}
+
+		tag = (u8)(in_le32(&hsdev->sata_dwc_regs->fptagr));
+		mask = qcmd_tag_to_mask(tag);
+		dev_dbg(ap->dev, "%s: NEWFP tag=%d\n", __func__, tag);
+		if ((hsdevp->sata_dwc_sactive_queued & mask) == 0)
+			dev_warn(ap->dev, "CMD tag=%d not pending?\n", tag);
+
+		qc = ata_qc_from_tag(ap, tag);
+		/*
+		 * Start FP DMA for NCQ command.  At this point the tag is the
+		 * active tag.  It is the tag that matches the command about to
+		 * be completed.
+		 */
+		if (qc) {
+			hsdevp->sata_dwc_sactive_issued |= mask;
+			/* Prevent to issue more commands */
+			qc->ap->link.active_tag = tag;
+			qc->dev->link->sactive |= (1 << qc->tag);
+			num_lli = map_sg_to_lli(ap, qc->sg, qc->n_elem, \
+				hsdevp->llit[tag], hsdevp->llit_dma[tag], \
+				(void *__iomem)(&hsdev->sata_dwc_regs->dmadr), \
+				qc->dma_dir);
+			sata_dwc_bmdma_start_by_tag(qc, tag);
+			wmb();
+			qc->ap->hsm_task_state = HSM_ST_LAST;
+		} else {
+		    hsdevp->sata_dwc_sactive_issued &= ~mask;
+		    dev_warn(ap->dev, "No QC available for tag %d (intpr="
+		    "0x%08x, qc_allocated=0x%08x, qc_active=0x%08x)\n", tag,\
+			intpr, ap->qc_allocated, ap->qc_active);
+		}
+
+		handled = 1;
+		goto DONE;
+	}
+
+	sactive = core_scr_read(ap, SCR_ACTIVE);
+	tag_mask = (hsdevp->sata_dwc_sactive_issued | sactive) ^ sactive;
+
+	/*
+	 * If no sactive issued and tag_mask is zero then this is not NCQ.
+	 * Do actions for transfer completion interrupt.
+	 */
+	if (hsdevp->sata_dwc_sactive_issued == 0 && tag_mask == 0) {
+		if (ap->link.active_tag == ATA_TAG_POISON)
+			tag = 0;
+		else
+			tag = ap->link.active_tag;
+		qc = ata_qc_from_tag(ap, tag);
+
+		/* Device interrupt without active qc? */
+		if (unlikely(!qc || (qc->tf.flags & ATA_TFLAG_POLLING))) {
+			dev_err(ap->dev, "%s interrupt with no active qc "
+				"qc=%p\n", __func__, qc);
+			ap->ops->sff_check_status(ap);
+			handled = 1;
+			goto DONE;
+		}
+		/* Get current status and clear interrupt */
+		status = ap->ops->sff_check_status(ap);
+
+		if (status & ATA_ERR) {
+			dev_dbg(ap->dev, "interrupt ATA_ERR (0x%x)\n", status);
+			sata_dwc_qc_complete(ap, qc, 1);
+			handled = 1;
+			goto DONE;
+		}
+
+		dev_dbg(ap->dev, "%s non-NCQ cmd interrupt, protocol: %s\n",
+			__func__, get_prot_descript(qc->tf.protocol));
+DRVSTILLBUSY:
+		/* Do complete action for the current QC */
+		if (ata_is_dma(qc->tf.protocol)) {
+			sata_dwc_qc_complete(ap, qc, 1);
+		} else if ((ata_is_pio(qc->tf.protocol)) ||
+			(ata_is_nodata(qc->tf.protocol))) {
+			ata_sff_hsm_move(ap, qc, status, 0);
+		} else {
+			if (unlikely(sata_dwc_qc_complete(ap, qc, 1)))
+				goto DRVSTILLBUSY;
+		}
+
+		handled = 1;
+		goto DONE;
+	}
+
+	/*
+	 * This is a NCQ command. At this point we need to figure out for which
+	 * tags we have gotten a completion interrupt.  One interrupt may serve
+	 * as completion for more than one operation when commands are queued
+	 * (NCQ).  We need to process each completed command.
+	 */
+	if (sactive != 0 || hsdevp->sata_dwc_sactive_issued > 1 || \
+							tag_mask > 1) {
+		dev_dbg(ap->dev, "%s NCQ:sactive=0x%08x  sactive_issued=0x%08x"
+			"tag_mask=0x%08x\n", __func__, sactive,
+			hsdevp->sata_dwc_sactive_issued, tag_mask);
+	}
+
+	if (unlikely((tag_mask | hsdevp->sata_dwc_sactive_issued) != \
+					hsdevp->sata_dwc_sactive_issued)) {
+		dev_warn(ap->dev, "Bad tag mask?  sactive=0x%08x "
+			 "sata_dwc_sactive_issued=0x%08x  tag_mask"
+			 "=0x%08x\n", sactive, hsdevp->sata_dwc_sactive_issued,
+			  tag_mask);
+	}
+
+	/* Read just to clear ... not bad if currently still busy */
+	status = ap->ops->sff_check_status(ap);
+	dev_dbg(ap->dev, "%s ATA status register=0x%x\n", __func__, status);
+
+	for (tag = 0; tag < 32; tag++) {
+		if (tag_mask & qcmd_tag_to_mask(tag)) {
+			qc = ata_qc_from_tag(ap, tag);
+			if (!qc) {
+				dev_info(ap->dev, "error: Tag %d is set but " \
+					"not available\n", tag);
+				continue;
+			}
+			sata_dwc_qc_complete(ap, qc, 1);
+		}
+	}
+	handled = 1;
+
+DONE:
+	spin_unlock(&host->lock);
+	return IRQ_RETVAL(handled);
+}
+
+static void sata_dwc_clear_dmacr(struct sata_dwc_device_port *hsdevp, u8 tag)
+{
+	struct sata_dwc_device *hsdev = HSDEV_FROM_HSDEVP(hsdevp);
+
+	if (hsdevp->dma_pending[tag] == SATA_DWC_DMA_PENDING_RX) {
+		out_le32(&(hsdev->sata_dwc_regs->dmacr),
+			 SATA_DWC_DMACR_RX_CLEAR(
+				 in_le32(&(hsdev->sata_dwc_regs->dmacr))));
+	} else if (hsdevp->dma_pending[tag] == SATA_DWC_DMA_PENDING_TX) {
+		out_le32(&(hsdev->sata_dwc_regs->dmacr),
+			 SATA_DWC_DMACR_TX_CLEAR(
+				 in_le32(&(hsdev->sata_dwc_regs->dmacr))));
+	} else {
+		/*
+		 * This should not happen, it indicates the driver is out of
+		 * sync.  If it does happen, clear dmacr anyway.
+		 */
+		dev_err(hsdev->dev, "%s DMA protocol RX and"
+			" TX DMA not pending tag=0x%02x pending=%d"
+			" dmacr: 0x%08x\n", __func__, tag,
+			hsdevp->dma_pending[tag],
+			in_le32(&(hsdev->sata_dwc_regs->dmacr)));
+		out_le32(&(hsdev->sata_dwc_regs->dmacr),
+			SATA_DWC_DMACR_TXRXCH_CLEAR);
+	}
+}
+
+
+static int sata_dwc_qc_complete(struct ata_port *ap, struct ata_queued_cmd *qc,
+				u32 check_status)
+{
+	u8 status;
+	int i;
+	u8 tag = qc->tag;
+	struct sata_dwc_device_port *hsdevp = HSDEVP_FROM_AP(ap);
+	u32 serror;
+	dev_dbg(ap->dev, "%s checkstatus? %x\n", __func__, check_status);
+
+	/* Check main status, clearing INTRQ */
+	status = ap->ops->sff_check_status(ap);
+
+	if (check_status) {
+		i = 0;
+		while (status & ATA_BUSY) {
+			if (++i > 10)
+				break;
+			status = ap->ops->sff_check_altstatus(ap);
+		};
+
+		if (unlikely(status & ATA_BUSY))
+			dev_err(ap->dev, "QC complete cmd=0x%02x STATUS BUSY "
+				"(0x%02x) [%d]\n", qc->tf.command, status, i);
+		serror = core_scr_read(ap, SCR_ERROR);
+		if (unlikely(serror & SATA_DWC_SERROR_ERR_BITS))
+			dev_err(ap->dev, "****** SERROR=0x%08x ******\n",
+				serror);
+	}
+	dev_dbg(ap->dev, "QC complete cmd=0x%02x status=0x%02x ata%u:"
+		" protocol=%d\n", qc->tf.command, status, ap->print_id,
+		 qc->tf.protocol);
+
+	hsdevp->sata_dwc_sactive_issued &= ~qcmd_tag_to_mask(tag);
+
+	/* Complete taskfile transaction (does not read SCR registers) */
+	if (ata_is_atapi(qc->tf.protocol))
+		ata_sff_hsm_move(ap, qc, status, 0);
+	else
+		ata_qc_complete(qc);
+
+	if (hsdevp->sata_dwc_sactive_queued == 0)
+		ap->link.active_tag = ATA_TAG_POISON;
+
+	return 0;
+}
+
+static void sata_dwc_enable_interrupts(struct sata_dwc_device *hsdev)
+{
+	/* Enable selective interrupts by setting the interrupt maskregister*/
+	out_le32(&hsdev->sata_dwc_regs->intmr,
+		 SATA_DWC_INTMR_ERRM |
+		 SATA_DWC_INTMR_NEWFPM |
+		 SATA_DWC_INTMR_PMABRTM |
+		 SATA_DWC_INTMR_DMATM |
+		 SATA_DWC_INTPR_IPF);
+	/*
+	 * Unmask the error bits that should trigger an error interrupt by
+	 * setting the error mask register.
+	 */
+	out_le32(&hsdev->sata_dwc_regs->errmr, SATA_DWC_SERROR_ERR_BITS);
+
+	dev_dbg(hsdev->dev, "%s: INTMR = 0x%08x, ERRMR = 0x%08x\n",
+		 __func__, in_le32(&hsdev->sata_dwc_regs->intmr),
+		in_le32(&hsdev->sata_dwc_regs->errmr));
+}
+
+static void sata_dwc_init_port(struct ata_port *ap)
+{
+	struct sata_dwc_device *hsdev = HSDEV_FROM_AP(ap);
+
+	/* Configure DMA */
+	if (ap->port_no == 0)  {
+		dev_dbg(ap->dev, "%s: clearing TXCHEN, RXCHEN in DMAC\n",
+				__func__);
+
+		/* Clear all transmit/receive bits */
+		out_le32(&hsdev->sata_dwc_regs->dmacr,
+			 SATA_DWC_DMACR_TXRXCH_CLEAR);
+
+		dev_dbg(ap->dev, "%s: setting burst size DBTSR\n", __func__);
+		out_le32(&hsdev->sata_dwc_regs->dbtsr,
+			 (SATA_DWC_DBTSR_MWR(AHB_DMA_BRST_DFLT) |
+			  SATA_DWC_DBTSR_MRD(AHB_DMA_BRST_DFLT)));
+	}
+
+	/* Enable interrupts */
+	sata_dwc_enable_interrupts(hsdev);
+}
+
+static void sata_dwc_setup_port(struct ata_ioports *port, unsigned long base)
+{
+	port->cmd_addr = (void *)base + 0x00;
+	port->data_addr = (void *)base + 0x00;
+
+	port->error_addr = (void *)base + 0x04;
+	port->feature_addr = (void *)base + 0x04;
+
+	port->nsect_addr = (void *)base + 0x08;
+
+	port->lbal_addr = (void *)base + 0x0c;
+	port->lbam_addr = (void *)base + 0x10;
+	port->lbah_addr = (void *)base + 0x14;
+
+	port->device_addr = (void *)base + 0x18;
+	port->command_addr = (void *)base + 0x1c;
+	port->status_addr = (void *)base + 0x1c;
+
+	port->altstatus_addr = (void *)base + 0x20;
+	port->ctl_addr = (void *)base + 0x20;
+}
+
+/*
+ * Allocates the scatter gather LLI table for AHB DMA
+ */
+static int sata_dwc_port_start(struct ata_port *ap)
+{
+	int err = 0;
+	struct sata_dwc_device *hsdev;
+	struct sata_dwc_device_port *hsdevp = NULL;
+	struct device *pdev;
+	u32 sstatus;
+	int i;
+
+	hsdev = HSDEV_FROM_AP(ap);
+
+	dev_dbg(ap->dev, "%s: port_no=%d\n", __func__, ap->port_no);
+
+	hsdev->host = ap->host;
+	pdev = ap->host->dev;
+	if (!pdev) {
+		dev_err(ap->dev, "%s: no ap->host->dev\n", __func__);
+		err = -ENODEV;
+		goto CLEANUP;
+	}
+
+	/* Allocate Port Struct */
+	hsdevp = kzalloc(sizeof(*hsdevp), GFP_KERNEL);
+	if (!hsdevp) {
+		dev_err(ap->dev, "%s: kmalloc failed for hsdevp\n", __func__);
+		err = -ENOMEM;
+		goto CLEANUP;
+	}
+	memset(hsdevp, 0, sizeof(*hsdevp));
+	hsdevp->hsdev = hsdev;
+
+	ap->bmdma_prd = 0;  /* set these so libata doesn't use them */
+	ap->bmdma_prd_dma = 0;
+
+	/*
+	 * DMA - Assign scatter gather LLI table. We can't use the libata
+	 * version since it's PRD is IDE PCI specific.
+	 */
+	for (i = 0; i < SATA_DWC_QCMD_MAX; i++) {
+		hsdevp->llit[i] = dma_alloc_coherent(pdev,
+						     SATA_DWC_DMAC_LLI_TBL_SZ,
+						     &(hsdevp->llit_dma[i]),
+						     GFP_ATOMIC);
+		if (!hsdevp->llit[i]) {
+			dev_err(ap->dev, "%s: dma_alloc_coherent failed\n",
+				 __func__);
+			err = -ENOMEM;
+			goto CLEANUP;
+		}
+	}
+
+	if (ap->port_no == 0)  {
+		dev_dbg(ap->dev, "%s: clearing TXCHEN, RXCHEN in DMAC\n",
+			__func__);
+		out_le32(&hsdev->sata_dwc_regs->dmacr,
+			 SATA_DWC_DMACR_TXRXCH_CLEAR);
+
+		dev_dbg(ap->dev, "%s: setting burst size in DBTSR\n",
+			 __func__);
+		out_le32(&hsdev->sata_dwc_regs->dbtsr,
+			 (SATA_DWC_DBTSR_MWR(AHB_DMA_BRST_DFLT) |
+			  SATA_DWC_DBTSR_MRD(AHB_DMA_BRST_DFLT)));
+	}
+
+	/* Clear any error bits before libata starts issuing commands */
+	clear_serror(ap);
+	ap->private_data = hsdevp;
+
+	/* Are we in Gen I or II */
+	sstatus = core_scr_read(ap, SCR_STATUS);
+	switch (SATA_DWC_SCR0_SPD_GET(sstatus)) {
+	case 0x0:
+		dev_info(ap->dev, "**** No neg speed (nothing attached?)\n");
+		break;
+	case 0x1:
+		dev_info(ap->dev, "**** GEN I speed rate negotiated\n");
+		break;
+	case 0x2:
+		dev_info(ap->dev, "**** GEN II speed rate negotiated\n");
+		break;
+	}
+
+	dev_dbg(ap->dev, "%s: done\n", __func__);
+	return 0;
+
+CLEANUP:
+	sata_dwc_port_stop(ap);
+	kfree(hsdevp);
+	dev_dbg(ap->dev, "%s: fail\n", __func__);
+
+	return err;
+}
+
+static void sata_dwc_port_stop(struct ata_port *ap)
+{
+	int i;
+	struct sata_dwc_device_port *hsdevp = HSDEVP_FROM_AP(ap);
+
+	dev_dbg(ap->dev, "%s: ap->id = %d\n", __func__, ap->print_id);
+
+	if (hsdevp) {
+		/* De-allocate LLI table */
+		for (i = 0; i < SATA_DWC_QCMD_MAX; i++) {
+			dma_free_coherent(ap->host->dev,
+				SATA_DWC_DMAC_LLI_TBL_SZ,
+				hsdevp->llit[i], hsdevp->llit_dma[i]);
+		}
+
+		kfree(hsdevp);
+	}
+	ap->private_data = NULL;
+}
+
+/*
+ * As our SATA is master only, no dev_select function needed.
+ * This just overwrite the ata_sff_dev_select() function in
+ * libata-sff
+ */
+void sata_dwc_dev_select(struct ata_port *ap, unsigned int device)
+{
+	ndelay(100);
+}
+
+/**
+ * Filter ATAPI cmds which are unsuitable for DMA.
+ *
+ * The bmdma engines cannot handle speculative data sizes
+ * (bytecount under/over flow). So only allow DMA for
+ * data transfer commands with known data sizes.
+ */
+static int sata_dwc_check_atapi_dma(struct ata_queued_cmd *qc)
+{
+	struct scsi_cmnd *scmd = qc->scsicmd;
+	int pio = 1; /* ATAPI DMA disabled by default */
+	unsigned int lba;
+
+	if (scmd) {
+		switch (scmd->cmnd[0]) {
+		case WRITE_6:
+		case WRITE_10:
+		case WRITE_12:
+		case READ_6:
+		case READ_10:
+		case READ_12:
+			pio = 0; /* DMA is safe */
+			break;
+		}
+
+		/* Command WRITE_10 with LBA between -45150 (FFFF4FA2)
+		 * and -1 (FFFFFFFF) shall use PIO mode */
+		if (scmd->cmnd[0] == WRITE_10) {
+			lba = (scmd->cmnd[2] << 24) |
+				(scmd->cmnd[3] << 16) |
+				(scmd->cmnd[4] << 8) |
+				 scmd->cmnd[5];
+			if (lba >= 0xFFFF4FA2)
+				pio = 1;
+		}
+		/*
+		* WORK AROUND: Fix DMA issue when blank CD/DVD disc
+		* in the drive and user use the 'fdisk -l' command.
+		* No DMA data returned so we can not complete the QC.
+		*/
+		if (scmd->cmnd[0] == READ_10) {
+			lba = (scmd->cmnd[2] << 24) |
+				  (scmd->cmnd[3] << 16) |
+				  (scmd->cmnd[4] << 8) |
+				   scmd->cmnd[5];
+			if (lba < 0x20)
+				pio = 1;
+		}
+	}
+	dev_dbg(qc->ap->dev, "%s - using %s mode for command cmd=0x%02x\n", \
+		__func__, (pio ? "PIO" : "DMA"), scmd->cmnd[0]);
+	return pio;
+}
+
+/*
+ * Keeps track of individual command tag ids and calls ata_exec_command
+ * in libata
+ */
+static void sata_dwc_exec_command_by_tag(struct ata_port *ap,
+					 struct ata_taskfile *tf,
+					 u8 tag)
+{
+	unsigned long flags;
+	struct sata_dwc_device_port *hsdevp = HSDEVP_FROM_AP(ap);
+
+	dev_dbg(ap->dev, "%s cmd(0x%02x): %s tag=%d\n", __func__, tf->command,
+		ata_get_cmd_descript(tf->command), tag);
+
+	spin_lock_irqsave(&ap->host->lock, flags);
+	hsdevp->sata_dwc_sactive_queued |= qcmd_tag_to_mask(tag);
+	spin_unlock_irqrestore(&ap->host->lock, flags);
+	/*
+	 * Clear SError before executing a new command.
+	 * sata_dwc_scr_write and read can not be used here. Clearing the PM
+	 * managed SError register for the disk needs to be done before the
+	 * task file is loaded.
+	 */
+	clear_serror(ap);
+	ata_sff_exec_command(ap, tf);
+}
+
+
+static void sata_dwc_bmdma_setup(struct ata_queued_cmd *qc)
+{
+	u8 tag = qc->tag;
+
+	if (ata_is_ncq(qc->tf.protocol)) {
+		dev_dbg(qc->ap->dev, "%s: ap->link.sactive=0x%08x tag=%d\n",
+			__func__, qc->ap->link.sactive, tag);
+	} else {
+		tag = 0;
+	}
+
+	sata_dwc_exec_command_by_tag(qc->ap, &qc->tf, tag);
+}
+
+static void sata_dwc_bmdma_start_by_tag(struct ata_queued_cmd *qc, u8 tag)
+{
+	int start_dma;
+	u32 reg, dma_chan;
+	struct sata_dwc_device *hsdev = HSDEV_FROM_QC(qc);
+	struct ata_port *ap = qc->ap;
+	struct sata_dwc_device_port *hsdevp = HSDEVP_FROM_AP(ap);
+	int dir = qc->dma_dir;
+
+	/* Configure DMA before starting data transfer */
+	dma_chan = dwc_dma_xfer_setup(ap, hsdevp->llit_dma[tag]);
+	if (unlikely(dma_chan < 0)) {
+		dev_err(ap->dev, "%s: dma channel unavailable\n", __func__);
+		/* Offending this QC as no channel available for transfer */
+		qc->err_mask |= AC_ERR_TIMEOUT;
+		return;
+	}
+
+	/* Check if DMA should be started */
+	hsdevp->dma_chan[tag] = dma_chan;
+	if (hsdevp->sata_dwc_sactive_queued & qcmd_tag_to_mask(tag)) {
+		start_dma = 1;
+		if (dir == DMA_TO_DEVICE)
+			hsdevp->dma_pending[tag] = SATA_DWC_DMA_PENDING_TX;
+		else
+			hsdevp->dma_pending[tag] = SATA_DWC_DMA_PENDING_RX;
+	} else {
+		dev_err(ap->dev, "%s: No pending cmd at tag %d\n",
+			__func__, tag);
+		start_dma = 0;
+	}
+
+	dev_dbg(ap->dev, "%s qc=%p tag: %x cmd: 0x%02x dma_dir: %s "
+		"start_dma? %x\n", __func__, qc, tag, qc->tf.command,
+		get_dma_dir_descript(qc->dma_dir), start_dma);
+	sata_dwc_tf_dump(hsdev->dev, &(qc->tf));
+
+	/* Enable to start DMA transfer */
+	if (start_dma) {
+		reg = core_scr_read(ap, SCR_ERROR);
+		if (unlikely(reg & SATA_DWC_SERROR_ERR_BITS)) {
+			dev_err(ap->dev, "%s: ****** SError=0x%08x ******\n",
+				__func__, reg);
+		}
+
+		if (dir == DMA_TO_DEVICE) {
+			out_le32(&hsdev->sata_dwc_regs->dmacr,
+				SATA_DWC_DMACR_TXCHEN);
+		} else {
+			out_le32(&hsdev->sata_dwc_regs->dmacr,
+				SATA_DWC_DMACR_RXCHEN);
+		}
+
+		/* Enable AHB DMA transfer on the specified channel */
+		dwc_dma_xfer_start(dma_chan);
+		hsdevp->sata_dwc_sactive_queued &= ~qcmd_tag_to_mask(tag);
+	}
+}
+
+static void sata_dwc_bmdma_start(struct ata_queued_cmd *qc)
+{
+	u8 tag = qc->tag;
+
+	if (ata_is_ncq(qc->tf.protocol)) {
+		dev_dbg(qc->ap->dev, "%s: ap->link.sactive=0x%08x tag=%d\n",
+			__func__, qc->ap->link.sactive, tag);
+	} else {
+		tag = 0;
+	}
+	dev_dbg(qc->ap->dev, "%s\n", __func__);
+	sata_dwc_bmdma_start_by_tag(qc, tag);
+}
+
+static u8 sata_dwc_dma_status(struct ata_port *ap)
+{
+	u32 status = 0;
+	u32 tfr_reg, err_reg;
+	struct sata_dwc_device *hsdev = HSDEV_FROM_AP(ap);
+
+	/* Check DMA register for status */
+	tfr_reg = in_le32(&(sata_dma_regs->interrupt_status.tfr.low));
+	err_reg = in_le32(&(sata_dma_regs->interrupt_status.error.low));
+
+	if (unlikely(err_reg & DMA_CHANNEL(hsdev->dma_channel)))
+		status = ATA_DMA_ERR | ATA_DMA_INTR;
+	else if (tfr_reg & DMA_CHANNEL(hsdev->dma_channel))
+		status = ATA_DMA_INTR;
+	return status;
+}
+
+/*
+ * Prepare for a particular queued command based on tag
+ */
+static void sata_dwc_qc_prep_by_tag(struct ata_queued_cmd *qc, u8 tag)
+{
+	struct scatterlist *sg = qc->sg;
+	struct ata_port *ap = qc->ap;
+	int num_lli;
+	struct sata_dwc_device *hsdev = HSDEV_FROM_AP(ap);
+	struct sata_dwc_device_port *hsdevp = HSDEVP_FROM_AP(ap);
+
+	if ((qc->dma_dir == DMA_NONE) || (qc->tf.protocol == ATA_PROT_PIO))
+		return;
+	dev_dbg(ap->dev, "%s: port=%d dma dir=%s n_elem=%d\n",
+		__func__, ap->port_no, get_dma_dir_descript(qc->dma_dir),
+		 qc->n_elem);
+
+	if (!ata_is_ncq(qc->tf.protocol)) {
+		num_lli = map_sg_to_lli(qc->ap, sg, qc->n_elem,
+			hsdevp->llit[tag], hsdevp->llit_dma[tag],
+			(void *__iomem)(&hsdev->sata_dwc_regs->dmadr),
+			qc->dma_dir);
+	}
+}
+
+int sata_dwc_qc_defer(struct ata_queued_cmd *qc)
+{
+	struct ata_port *ap = qc->ap;
+	struct sata_dwc_device_port *hsdevp = HSDEVP_FROM_AP(ap);
+	u8 status;
+	int ret;
+
+	dev_dbg(qc->ap->dev, "%s -\n", __func__);
+	ret = ata_std_qc_defer(qc);
+	if (ret) {
+		printk(KERN_DEBUG "STD Defer %s cmd %s tag=%d\n",
+			(ret == ATA_DEFER_LINK) ? "LINK" : "PORT",
+			ata_get_cmd_descript(qc->tf.command), qc->tag);
+		return ret;
+	}
+
+	/* Check the SATA host for busy status */
+	if (ata_is_ncq(qc->tf.protocol)) {
+		status = ap->ops->sff_check_altstatus(ap);
+		if (status & ATA_BUSY) {
+			dev_dbg(ap->dev,
+				"Defer PORT cmd %s tag=%d as host is busy\n",
+				ata_get_cmd_descript(qc->tf.command), qc->tag);
+			return ATA_DEFER_PORT;/*HOST BUSY*/
+		}
+
+		/* This will prevent collision error */
+		if (hsdevp->sata_dwc_sactive_issued) {
+			dev_dbg(ap->dev, "Defer PORT cmd %s with tag %d " \
+				"because another dma xfer is outstanding\n",
+				ata_get_cmd_descript(qc->tf.command), qc->tag);
+
+			return ATA_DEFER_PORT;/*DEVICE&HOST BUSY*/
+		}
+
+	}
+
+	return 0;
+}
+
+void sata_dwc_exec_command(struct ata_port *ap, const struct ata_taskfile *tf)
+{
+	iowrite8(tf->command, ap->ioaddr.command_addr);
+	/* If we have an mmio device with no ctl and no altstatus
+	 * method, this will fail. No such devices are known to exist.
+	 */
+	if (ap->ioaddr.altstatus_addr)
+		ioread8(ap->ioaddr.altstatus_addr);
+
+	ndelay(400);
+}
+
+static unsigned int sata_dwc_qc_issue(struct ata_queued_cmd *qc)
+{
+	u32 sactive;
+	u8 tag = qc->tag;
+	struct ata_port *ap = qc->ap;
+	struct sata_dwc_device_port *hsdevp = HSDEVP_FROM_AP(qc->ap);
+	u8 status;
+
+#ifdef DEBUG_NCQ
+	if (qc->tag > 0 || ap->link.sactive > 1)
+		dev_info(ap->dev, "%s ap id=%d cmd(0x%02x)=%s qc tag=%d "
+			 "prot=%s ap active_tag=0x%08x ap sactive=0x%08x\n",
+			 __func__, ap->print_id, qc->tf.command,
+			 ata_get_cmd_descript(qc->tf.command),
+			 qc->tag, get_prot_descript(qc->tf.protocol),
+			 ap->link.active_tag, ap->link.sactive);
+#endif
+
+	if (!ata_is_ncq(qc->tf.protocol))
+		tag = 0;
+	sata_dwc_qc_prep_by_tag(qc, tag);
+
+	if (ata_is_ncq(qc->tf.protocol)) {
+		status = ap->ops->sff_check_altstatus(ap);
+		if (status & ATA_BUSY) {
+			/* Ignore the QC when device is BUSY */
+			sactive = core_scr_read(qc->ap, SCR_ACTIVE);
+			dev_info(ap->dev, "Ignore current QC as device BUSY"
+				"tag=%d, sactive=0x%08x)\n", qc->tag, sactive);
+			return AC_ERR_SYSTEM;
+		}
+
+		if (hsdevp->sata_dwc_sactive_issued)
+			return AC_ERR_SYSTEM;
+
+		sactive = core_scr_read(qc->ap, SCR_ACTIVE);
+		sactive |= (0x00000001 << tag);
+		qc->dev->link->sactive |= (0x00000001 << tag);
+		core_scr_write(qc->ap, SCR_ACTIVE, sactive);
+
+		dev_dbg(qc->ap->dev, "%s: tag=%d ap->link.sactive = 0x%08x "
+			"sactive=0x%x\n", __func__, tag, qc->ap->link.sactive,
+			sactive);
+
+		ap->ops->sff_tf_load(ap, &qc->tf);
+		sata_dwc_exec_command_by_tag(ap, &qc->tf, qc->tag);
+	} else {
+		ap->link.active_tag = qc->tag;
+		/* Pass QC to libata-sff to process */
+		ata_bmdma_qc_issue(qc);
+	}
+	return 0;
+}
+
+/*
+ * Prepare for a particular queued command
+ */
+
+static void sata_dwc_qc_prep(struct ata_queued_cmd *qc)
+{
+	if ((qc->dma_dir == DMA_NONE) || (qc->tf.protocol == ATA_PROT_PIO)
+		|| (qc->tf.protocol == ATAPI_PROT_PIO))
+		return;
+
+#ifdef DEBUG_NCQ
+	if (qc->tag > 0)
+		dev_info(qc->ap->dev, "%s: qc->tag=%d ap->active_tag=0x%08x\n",
+			 __func__, qc->tag, qc->ap->link.active_tag);
+#endif
+}
+
+/*
+ * Get the QC currently used for transferring data
+ */
+static struct ata_queued_cmd *sata_dwc_get_active_qc(struct ata_port *ap)
+{
+	struct ata_queued_cmd *qc;
+
+	qc = ata_qc_from_tag(ap, ap->link.active_tag);
+	if (qc && !(qc->tf.flags & ATA_TFLAG_POLLING))
+		return qc;
+	return NULL;
+}
+
+/*
+ * dwc_lost_interrupt -  check and process if interrupt is lost.
+ * @ap: ATA port
+ *
+ * Process the command when it is timeout.
+ * Check to see if interrupt is lost. If yes, complete the qc.
+ */
+static void sata_dwc_lost_interrupt(struct ata_port *ap)
+{
+	u8 status;
+	struct sata_dwc_device *hsdev = HSDEV_FROM_AP(ap);
+	struct ata_queued_cmd *qc;
+
+	dev_dbg(ap->dev, "%s -\n", __func__);
+	/* Only one outstanding command per SFF channel */
+	qc = sata_dwc_get_active_qc(ap);
+	/* We cannot lose an interrupt on a non-existent or polled command */
+	if (!qc)
+		return;
+
+	/* See if the controller thinks it is still busy - if so the command
+	 isn't a lost IRQ but is still in progress */
+	status = ap->ops->sff_check_altstatus(ap);
+	if (status & ATA_BUSY) {
+		ata_port_printk(ap, KERN_INFO, "%s - ATA_BUSY\n", __func__);
+		return;
+	}
+
+	/* There was a command running, we are no longer busy and we have
+	   no interrupt. */
+	ata_link_printk(qc->dev->link, KERN_WARNING,
+		"lost interrupt (Status 0x%x)\n", status);
+
+	if (sata_dwc_dma_chk_en(hsdev->dma_channel)) {
+		/* When DMA does transfer does not complete,
+		 see if DMA fails */
+		qc->err_mask |= AC_ERR_DEV;
+		ap->hsm_task_state = HSM_ST_ERR;
+		sata_dwc_dma_terminate(ap, hsdev->dma_channel);
+	}
+	sata_dwc_qc_complete(ap, qc, 1);
+}
+
+
+static void sata_dwc_error_handler(struct ata_port *ap)
+{
+	bool thaw = false;
+	struct ata_queued_cmd *qc;
+	u8 status = ap->ops->bmdma_status(ap);
+
+	qc = sata_dwc_get_active_qc(ap);
+	/* In case of DMA timeout, process it. */
+	if (qc && ata_is_dma(qc->tf.protocol)) {
+		if ((qc->err_mask == AC_ERR_TIMEOUT)
+			&& (status & ATA_DMA_ERR)) {
+			qc->err_mask = AC_ERR_HOST_BUS;
+			thaw = true;
+		}
+
+		if (thaw) {
+			ap->ops->sff_check_status(ap);
+			if (ap->ops->sff_irq_clear)
+				ap->ops->sff_irq_clear(ap);
+		}
+	}
+	if (thaw)
+		ata_eh_thaw_port(ap);
+
+	ata_sff_error_handler(ap);
+}
+
+u8 sata_dwc_check_status(struct ata_port *ap)
+{
+	return ioread8(ap->ioaddr.status_addr);
+}
+
+u8 sata_dwc_check_altstatus(struct ata_port *ap)
+{
+	return ioread8(ap->ioaddr.altstatus_addr);
+}
+
+/*
+ * scsi mid-layer and libata interface structures
+ */
+static struct scsi_host_template sata_dwc_sht = {
+	ATA_NCQ_SHT(DRV_NAME),
+	.sg_tablesize  = LIBATA_MAX_PRD,
+	/*
+	 * test-only: Currently this driver doesn't handle NCQ
+	 * correctly. We enable NCQ but set the queue depth to a
+	 * max of 1. This will get fixed in in a future release.
+	 */
+	.can_queue     = ATA_DEF_QUEUE, /*ATA_MAX_QUEUE, */
+	.dma_boundary  = ATA_DMA_BOUNDARY,
+};
+
+static struct ata_port_operations sata_dwc_ops = {
+	.inherits       = &ata_sff_port_ops,
+
+	.error_handler  = sata_dwc_error_handler,
+	.softreset      = sata_dwc_softreset,
+	.hardreset      = sata_dwc_hardreset,
+
+	.qc_defer       = sata_dwc_qc_defer,
+	.qc_prep        = sata_dwc_qc_prep,
+	.qc_issue       = sata_dwc_qc_issue,
+
+	.scr_read       = sata_dwc_scr_read,
+	.scr_write      = sata_dwc_scr_write,
+
+	.port_start     = sata_dwc_port_start,
+	.port_stop      = sata_dwc_port_stop,
+
+	.check_atapi_dma = sata_dwc_check_atapi_dma,
+	.bmdma_setup    = sata_dwc_bmdma_setup,
+	.bmdma_start    = sata_dwc_bmdma_start,
+	.bmdma_status   = sata_dwc_dma_status,
+
+	.sff_dev_select = sata_dwc_dev_select,
+	.sff_check_status = sata_dwc_check_status,
+	.sff_check_altstatus = sata_dwc_check_altstatus,
+	.sff_exec_command = sata_dwc_exec_command,
+
+	.lost_interrupt = sata_dwc_lost_interrupt,
+};
+
+static const struct ata_port_info sata_dwc_port_info[] = {
+	{
+		.flags		= ATA_FLAG_SATA | ATA_FLAG_NCQ,
+		.pio_mask	= ATA_PIO4,
+		.udma_mask	= ATA_UDMA6,
+		.port_ops	= &sata_dwc_ops,
+	},
+};
+
+static int sata_dwc_probe(struct platform_device *ofdev)
+{
+	struct sata_dwc_device *hsdev;
+	u32 idr, versionr;
+	char *ver = (char *)&versionr;
+	u8 *base = NULL;
+	int err = 0;
+	int irq, rc;
+	struct ata_host *host;
+	struct ata_port_info pi = sata_dwc_port_info[0];
+	const struct ata_port_info *ppi[] = { &pi, NULL };
+
+	const unsigned int *dma_chan;
+
+	/* Check if device is declared in device tree */
+	if (!of_device_is_available(ofdev->dev.of_node)) {
+		printk(KERN_INFO "%s: Port disabled via device-tree\n",
+		ofdev->dev.of_node->full_name);
+		return 0;
+	}
+
+	/* Allocate DWC SATA device */
+	hsdev = kmalloc(sizeof(*hsdev), GFP_KERNEL);
+	if (hsdev == NULL) {
+		dev_err(&ofdev->dev, "kmalloc failed for hsdev\n");
+		err = -ENOMEM;
+		goto error_out_5;
+	}
+	memset(hsdev, 0, sizeof(*hsdev));
+
+	/* Identify host controller using compatible attribute */
+	if (of_device_is_compatible(ofdev->dev.of_node, "amcc,sata-460ex")) {
+		printk(KERN_INFO "\n\nSATA is compatible for sata-460ex\n\n");
+		hsdev->hostID = APM_460EX_SATA;
+	} else {
+		printk(KERN_INFO "\n\nSATA is compatible for sata-821xx\n\n");
+		hsdev->hostID = APM_821XX_SATA;
+	}
+
+	/* Identify SATA controller index from the cell-index property */
+	dma_chan = of_get_property(ofdev->dev.of_node, "dma-channel", NULL);
+	if (dma_chan) {
+		dev_notice(&ofdev->dev, "Getting DMA channel %d\n", *dma_chan);
+		hsdev->dma_channel = *dma_chan;
+	} else {
+		hsdev->dma_channel = 0;
+	}
+
+	/* Get base address from device tree */
+	base = of_iomap(ofdev->dev.of_node, 0);
+	if (!base) {
+		dev_err(&ofdev->dev,
+			"ioremap failed for SATA register address\n");
+		err = -ENODEV;
+		goto error_out_4;
+	}
+	hsdev->reg_base = base;
+	dev_dbg(&ofdev->dev, "ioremap done for SATA register address\n");
+
+	/* Synopsys DWC SATA specific Registers */
+	hsdev->sata_dwc_regs = (void *__iomem)(base + SATA_DWC_REG_OFFSET);
+
+	/* Allocate and fill host */
+	host = ata_host_alloc_pinfo(&ofdev->dev, ppi, SATA_DWC_MAX_PORTS);
+	if (!host) {
+		dev_err(&ofdev->dev, "ata_host_alloc_pinfo failed\n");
+		err = -ENOMEM;
+		goto error_out_4;
+	}
+
+	host->private_data = hsdev;
+
+	/* Setup port */
+	host->ports[0]->ioaddr.cmd_addr = base;
+	host->ports[0]->ioaddr.scr_addr = base + SATA_DWC_SCR_OFFSET;
+	hsdev->scr_base = (u8 *)(base + SATA_DWC_SCR_OFFSET);
+	sata_dwc_setup_port(&host->ports[0]->ioaddr, (unsigned long)base);
+
+	/* Read the ID and Version Registers */
+	idr = in_le32(&hsdev->sata_dwc_regs->idr);
+	versionr = in_le32(&hsdev->sata_dwc_regs->versionr);
+	dev_notice(&ofdev->dev, "id %d, controller version %c.%c%c\n",
+		   idr, ver[0], ver[1], ver[2]);
+
+	/* Get SATA DMA interrupt number */
+	irq = irq_of_parse_and_map(ofdev->dev.of_node, 1);
+	if (irq == NO_IRQ) {
+		dev_err(&ofdev->dev, "no SATA DMA irq\n");
+		err = -ENODEV;
+		goto error_out_3;
+	}
+
+	/* Save dev for later use in dev_xxx() routines */
+	hsdev->dev = &ofdev->dev;
+
+	/* Init glovbal dev list */
+	dwc_dev_list[hsdev->dma_channel] = hsdev;
+
+	/* Get physical SATA DMA register base address */
+	if (sata_dma_regs == NULL) {
+		sata_dma_regs = of_iomap(ofdev->dev.of_node, 1);
+		if (sata_dma_regs == NULL) {
+			dev_err(&ofdev->dev,
+				"ioremap failed for AHBDMA register address\n");
+			err = -ENODEV;
+			goto error_out_2;
+		}
+
+		/* Initialize AHB DMAC */
+		rc = dwc_dma_init(hsdev, irq);
+		if (rc != 0)
+			goto error_out_1;
+	}
+
+	/* Enable SATA Interrupts */
+	sata_dwc_enable_interrupts(hsdev);
+
+	/* Get SATA interrupt number */
+	irq = irq_of_parse_and_map(ofdev->dev.of_node, 0);
+	if (irq == NO_IRQ) {
+		dev_err(&ofdev->dev, "no SATA irq\n");
+		err = -ENODEV;
+		goto error_out_1;
+	}
+
+	/*
+	 * Now, register with libATA core, this will also initiate the
+	 * device discovery process, invoking our port_start() handler &
+	 * error_handler() to execute a dummy Softreset EH session
+	 */
+	rc = ata_host_activate(host, irq, sata_dwc_isr, 0, &sata_dwc_sht);
+
+	if (rc != 0)
+		dev_err(&ofdev->dev, "failed to activate host");
+
+	dev_set_drvdata(&ofdev->dev, host);
+
+	return 0;
+
+error_out_1:
+	iounmap(sata_dma_regs);
+
+error_out_2:
+	free_irq(hsdev->irq_dma, hsdev);
+
+error_out_3:
+	iounmap(base);
+
+error_out_4:
+	kfree(hsdev);
+
+error_out_5:
+	return err;
+}
+
+static int sata_dwc_remove(struct platform_device *ofdev)
+{
+	struct device *dev = &ofdev->dev;
+	struct ata_host *host = dev_get_drvdata(dev);
+	struct sata_dwc_device *hsdev = host->private_data;
+
+	ata_host_detach(host);
+	dev_set_drvdata(dev, NULL);
+
+	/* Free SATA DMA resources */
+	iounmap(sata_dma_regs);
+	free_irq(hsdev->irq_dma, hsdev);
+
+	/* Free internal resources */
+	iounmap(hsdev->reg_base);
+	kfree(hsdev);
+	kfree(host);
+	dev_dbg(&ofdev->dev, "done\n");
+	return 0;
+}
+
+static const struct of_device_id sata_dwc_match[] = {
+	{ .compatible = "amcc,sata-460ex", },
+	{ .compatible = "amcc,sata-apm821xx", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, sata_dwc_match);
+
+static struct platform_driver sata_dwc_driver = {
+	.driver = {
+		.name = DRV_NAME,
+		.owner = THIS_MODULE,
+		.of_match_table = sata_dwc_match,
+	},
+	.probe = sata_dwc_probe,
+	.remove = sata_dwc_remove,
+};
+
+static int __init sata_dwc_init(void)
+{
+	return platform_driver_register(&sata_dwc_driver);
+}
+
+static void __exit sata_dwc_exit(void)
+{
+	platform_driver_unregister(&sata_dwc_driver);
+}
+
+module_init(sata_dwc_init);
+module_exit(sata_dwc_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Mark Miesfeld <mmiesfeld@amcc.com>");
+MODULE_DESCRIPTION("DesignWare Cores SATA controller low lever driver");
+MODULE_VERSION(DRV_VERSION);
-- 
1.7.3.4

CONFIDENTIALITY NOTICE: This e-mail message, including any attachments, 
is for the sole use of the intended recipient(s) and contains information 
that is confidential and proprietary to AppliedMicro Corporation or its subsidiaries. 
It is to be used solely for the purpose of furthering the parties' business relationship. 
All unauthorized review, use, disclosure or distribution is prohibited. 
If you are not the intended recipient, please contact the sender by reply e-mail 
and destroy all copies of the original message.

^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox