diff mbox

[net-next-2.6] tc: report informations for multiqueue devices

Message ID 4A9E6551.4030209@gmail.com
State Superseded, archived
Delegated to: David Miller
Headers show

Commit Message

Eric Dumazet Sept. 2, 2009, 12:30 p.m. UTC
David Miller a écrit :
> From: Eric Dumazet <eric.dumazet@gmail.com>
> Date: Wed, 02 Sep 2009 10:28:55 +0200
> 
>> What naming convention should we choose for multiqueue devices ?
> 
> We could give an index field to multiple root qdiscs assigned
> to a device.

Here is a patch then :)

Only point is that I am iterating from 0 to dev->real_num_tx_queues
instead of dev->num_tx_queues. I hope it's fine, because there are
allocated qdisc, but not really used.

Next patches to allow selective qdisc change/fetch (providing a TCA_QINDEX
selector value to kernel)

Thanks


[PATCH net-next-2.6] tc: report informations for multiqueue devices

qdisc and classes are not yet displayed by "tc -s -d {qdisc|class} show"
for multiqueue devices.

We use a new TCA_QINDEX attribute, to report queue index to user space.
iproute2 tc should be changed to eventually display this queue index as in :

$ tc -s -d qdisc
qdisc pfifo_fast 0: dev eth0 root bands 3 priomap  1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
 Sent 52498 bytes 465 pkt (dropped 0, overlimits 0 requeues 0)
 rate 0bit 0pps backlog 0b 0p requeues 0
qdisc pfifo_fast 0: dev eth0 qindex 1 root bands 3 priomap  1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
 Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0)
 rate 0bit 0pps backlog 0b 0p requeues 0



Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
---
 include/linux/rtnetlink.h |    1
 net/sched/sch_api.c       |  118 ++++++++++++++++++++----------------
 2 files changed, 67 insertions(+), 52 deletions(-)

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

Comments

Patrick McHardy Sept. 2, 2009, 1:18 p.m. UTC | #1
Eric Dumazet wrote:
> [PATCH net-next-2.6] tc: report informations for multiqueue devices
> 
> qdisc and classes are not yet displayed by "tc -s -d {qdisc|class} show"
> for multiqueue devices.
> 
> We use a new TCA_QINDEX attribute, to report queue index to user space.
> iproute2 tc should be changed to eventually display this queue index as in :
> 
> $ tc -s -d qdisc
> qdisc pfifo_fast 0: dev eth0 root bands 3 priomap  1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
>  Sent 52498 bytes 465 pkt (dropped 0, overlimits 0 requeues 0)
>  rate 0bit 0pps backlog 0b 0p requeues 0
> qdisc pfifo_fast 0: dev eth0 qindex 1 root bands 3 priomap  1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
>  Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0)
>  rate 0bit 0pps backlog 0b 0p requeues 0

This might confuse existing userspace since the handle is not unique
anymore. libnl f.i. will treat all but the first root qdisc as an
update and use it to update the state of the first one. There's also
no combined view for applications unaware of multiqueue.

Please have a look at the mail I just wrote for some possible ways
around this.
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Eric Dumazet Sept. 2, 2009, 1:49 p.m. UTC | #2
Patrick McHardy a écrit :
> Eric Dumazet wrote:
>> [PATCH net-next-2.6] tc: report informations for multiqueue devices
>>
>> qdisc and classes are not yet displayed by "tc -s -d {qdisc|class} show"
>> for multiqueue devices.
>>
>> We use a new TCA_QINDEX attribute, to report queue index to user space.
>> iproute2 tc should be changed to eventually display this queue index as in :
>>
>> $ tc -s -d qdisc
>> qdisc pfifo_fast 0: dev eth0 root bands 3 priomap  1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
>>  Sent 52498 bytes 465 pkt (dropped 0, overlimits 0 requeues 0)
>>  rate 0bit 0pps backlog 0b 0p requeues 0
>> qdisc pfifo_fast 0: dev eth0 qindex 1 root bands 3 priomap  1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
>>  Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0)
>>  rate 0bit 0pps backlog 0b 0p requeues 0
> 
> This might confuse existing userspace since the handle is not unique
> anymore. libnl f.i. will treat all but the first root qdisc as an
> update and use it to update the state of the first one. There's also
> no combined view for applications unaware of multiqueue.
> 
> Please have a look at the mail I just wrote for some possible ways
> around this.

Hum, how can we combine infos on qdisc/class if in the future we allow each queue index
to have its own qdisc/classes ?

htb on queue index 0
cbq on queue index 1

Combining info would lock us and not allow for special configurations.
Say 
   macvlan device 0 mapped to queue index 0
   macvlan device 1 mapped to queue index 1...

For old apps, just give informations for queue 0 as we do now, and
allow kernel to give more informations only if new application provided a TCA_INDEX attribute
in its request ?

(-1 : all queue indexes,  >=0 for a given queue index)
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Patrick McHardy Sept. 2, 2009, 2:07 p.m. UTC | #3
Eric Dumazet wrote:
> Patrick McHardy a écrit :
>> Eric Dumazet wrote:
>>> [PATCH net-next-2.6] tc: report informations for multiqueue devices
>>>
>>> qdisc and classes are not yet displayed by "tc -s -d {qdisc|class} show"
>>> for multiqueue devices.
>>>
>>> We use a new TCA_QINDEX attribute, to report queue index to user space.
>>> iproute2 tc should be changed to eventually display this queue index as in :
>>>
>>> $ tc -s -d qdisc
>>> qdisc pfifo_fast 0: dev eth0 root bands 3 priomap  1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
>>>  Sent 52498 bytes 465 pkt (dropped 0, overlimits 0 requeues 0)
>>>  rate 0bit 0pps backlog 0b 0p requeues 0
>>> qdisc pfifo_fast 0: dev eth0 qindex 1 root bands 3 priomap  1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
>>>  Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0)
>>>  rate 0bit 0pps backlog 0b 0p requeues 0
>> This might confuse existing userspace since the handle is not unique
>> anymore. libnl f.i. will treat all but the first root qdisc as an
>> update and use it to update the state of the first one. There's also
>> no combined view for applications unaware of multiqueue.
>>
>> Please have a look at the mail I just wrote for some possible ways
>> around this.
> 
> Hum, how can we combine infos on qdisc/class if in the future we allow each queue index
> to have its own qdisc/classes ?
> 
> htb on queue index 0
> cbq on queue index 1

My suggestion was to only dump the statistics in the combined
view and use a virtual qdisc, something like:

qdisc multiqueue 0: dev eth0 root queues 8
  Sent ...
  rate ...

and show each real qdisc as child of this qdisc:

qdisc pfifo_fast <unique handle> dev eth0 parent 0: bands 3 ...
qdisc pfifo_fast <unique handle> dev eth0 parent 0: bands 3 ...

Configuration would be symetrical to this:

tc qdisc add dev eth0 handle 0: root multiqueue
tc qdisc add dev eth0 handle x: parent 0: pfifo_fast
...

without the virtual multiqueue qdisc, the root qdisc would simply
be shared among all queues as today.

> Combining info would lock us and not allow for special configurations.
> Say 
>    macvlan device 0 mapped to queue index 0
>    macvlan device 1 mapped to queue index 1...

Why not?

> For old apps, just give informations for queue 0 as we do now, and
> allow kernel to give more informations only if new application provided a TCA_INDEX attribute
> in its request ?
> 
> (-1 : all queue indexes,  >=0 for a given queue index)

If we don't combine the information, existing multiqueue unaware
applications will get incorrect information. There's also the
problem of non-unique handles, I think we should encode the queue
index in the handle instead of using a new attribute. This needs
a bit more thought though to avoid clashes with user-defined handles.
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jarek Poplawski Sept. 2, 2009, 10:17 p.m. UTC | #4
On Wed, Sep 02, 2009 at 04:07:03PM +0200, Patrick McHardy wrote:
> Eric Dumazet wrote:
> > Patrick McHardy a écrit :
> >> Eric Dumazet wrote:
> >>> [PATCH net-next-2.6] tc: report informations for multiqueue devices
> >>>
> >>> qdisc and classes are not yet displayed by "tc -s -d {qdisc|class} show"
> >>> for multiqueue devices.
> >>>
> >>> We use a new TCA_QINDEX attribute, to report queue index to user space.
> >>> iproute2 tc should be changed to eventually display this queue index as in :
> >>>
> >>> $ tc -s -d qdisc
> >>> qdisc pfifo_fast 0: dev eth0 root bands 3 priomap  1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
> >>>  Sent 52498 bytes 465 pkt (dropped 0, overlimits 0 requeues 0)
> >>>  rate 0bit 0pps backlog 0b 0p requeues 0
> >>> qdisc pfifo_fast 0: dev eth0 qindex 1 root bands 3 priomap  1 2 2 2 1 2 0 0 1 1 1 1 1 1 1 1
> >>>  Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0)
> >>>  rate 0bit 0pps backlog 0b 0p requeues 0
> >> This might confuse existing userspace since the handle is not unique
> >> anymore. libnl f.i. will treat all but the first root qdisc as an
> >> update and use it to update the state of the first one. There's also
> >> no combined view for applications unaware of multiqueue.
> >>
> >> Please have a look at the mail I just wrote for some possible ways
> >> around this.
> > 
> > Hum, how can we combine infos on qdisc/class if in the future we allow each queue index
> > to have its own qdisc/classes ?
> > 
> > htb on queue index 0
> > cbq on queue index 1
> 
> My suggestion was to only dump the statistics in the combined
> view and use a virtual qdisc, something like:
> 
> qdisc multiqueue 0: dev eth0 root queues 8
>   Sent ...
>   rate ...
> 
> and show each real qdisc as child of this qdisc:
> 
> qdisc pfifo_fast <unique handle> dev eth0 parent 0: bands 3 ...
> qdisc pfifo_fast <unique handle> dev eth0 parent 0: bands 3 ...
> 
> Configuration would be symetrical to this:
> 
> tc qdisc add dev eth0 handle 0: root multiqueue
> tc qdisc add dev eth0 handle x: parent 0: pfifo_fast
> ...

Actually, I wonder why it can't be a real "virtual" qdisc with
classes, similar to... multiq, doing such mappings inside, according
to the current api:

tc qdisc add dev eth0 handle 1: root multiqueue
tc qdisc add dev eth0 handle x: parent 1:1 pfifo_fast

Jarek P.

PS: but I'd prefer (more) different name, even mq, mtq or something.
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/include/linux/rtnetlink.h b/include/linux/rtnetlink.h
index ba3254e..b80e0f6 100644
--- a/include/linux/rtnetlink.h
+++ b/include/linux/rtnetlink.h
@@ -490,6 +490,7 @@  enum
 	TCA_FCNT,
 	TCA_STATS2,
 	TCA_STAB,
+	TCA_QINDEX,
 	__TCA_MAX
 };
 
diff --git a/net/sched/sch_api.c b/net/sched/sch_api.c
index 24d17ce..74cde83 100644
--- a/net/sched/sch_api.c
+++ b/net/sched/sch_api.c
@@ -35,9 +35,9 @@ 
 #include <net/pkt_sched.h>
 
 static int qdisc_notify(struct sk_buff *oskb, struct nlmsghdr *n, u32 clid,
-			struct Qdisc *old, struct Qdisc *new);
+			struct Qdisc *old, struct Qdisc *new, int qnum);
 static int tclass_notify(struct sk_buff *oskb, struct nlmsghdr *n,
-			 struct Qdisc *q, unsigned long cl, int event);
+			 struct Qdisc *q, unsigned long cl, int event, int qnum);
 
 /*
 
@@ -671,10 +671,10 @@  void qdisc_tree_decrease_qlen(struct Qdisc *sch, unsigned int n)
 EXPORT_SYMBOL(qdisc_tree_decrease_qlen);
 
 static void notify_and_destroy(struct sk_buff *skb, struct nlmsghdr *n, u32 clid,
-			       struct Qdisc *old, struct Qdisc *new)
+			       struct Qdisc *old, struct Qdisc *new, int qnum)
 {
 	if (new || old)
-		qdisc_notify(skb, n, clid, old, new);
+		qdisc_notify(skb, n, clid, old, new, qnum);
 
 	if (old)
 		qdisc_destroy(old);
@@ -720,7 +720,7 @@  static int qdisc_graft(struct net_device *dev, struct Qdisc *parent,
 			if (new && i > 0)
 				atomic_inc(&new->refcnt);
 
-			notify_and_destroy(skb, n, classid, old, new);
+			notify_and_destroy(skb, n, classid, old, new, i);
 		}
 
 		if (dev->flags & IFF_UP)
@@ -738,7 +738,7 @@  static int qdisc_graft(struct net_device *dev, struct Qdisc *parent,
 			}
 		}
 		if (!err)
-			notify_and_destroy(skb, n, classid, old, new);
+			notify_and_destroy(skb, n, classid, old, new, 0);
 	}
 	return err;
 }
@@ -999,7 +999,7 @@  static int tc_get_qdisc(struct sk_buff *skb, struct nlmsghdr *n, void *arg)
 		if ((err = qdisc_graft(dev, p, skb, n, clid, NULL, q)) != 0)
 			return err;
 	} else {
-		qdisc_notify(skb, n, clid, NULL, q);
+		qdisc_notify(skb, n, clid, NULL, q, 0);
 	}
 	return 0;
 }
@@ -1116,7 +1116,7 @@  replay:
 		return -EINVAL;
 	err = qdisc_change(q, tca);
 	if (err == 0)
-		qdisc_notify(skb, n, clid, NULL, q);
+		qdisc_notify(skb, n, clid, NULL, q, 0);
 	return err;
 
 create_n_graft:
@@ -1148,7 +1148,7 @@  graft:
 }
 
 static int tc_fill_qdisc(struct sk_buff *skb, struct Qdisc *q, u32 clid,
-			 u32 pid, u32 seq, u16 flags, int event)
+			 u32 pid, u32 seq, u16 flags, int event, int qnum)
 {
 	struct tcmsg *tcm;
 	struct nlmsghdr  *nlh;
@@ -1187,6 +1187,9 @@  static int tc_fill_qdisc(struct sk_buff *skb, struct Qdisc *q, u32 clid,
 	if (gnet_stats_finish_copy(&d) < 0)
 		goto nla_put_failure;
 
+	if (qnum)
+		NLA_PUT_U32(skb, TCA_QINDEX, qnum);
+
 	nlh->nlmsg_len = skb_tail_pointer(skb) - b;
 	return skb->len;
 
@@ -1197,7 +1200,8 @@  nla_put_failure:
 }
 
 static int qdisc_notify(struct sk_buff *oskb, struct nlmsghdr *n,
-			u32 clid, struct Qdisc *old, struct Qdisc *new)
+			u32 clid, struct Qdisc *old, struct Qdisc *new,
+			int qnum)
 {
 	struct sk_buff *skb;
 	u32 pid = oskb ? NETLINK_CB(oskb).pid : 0;
@@ -1207,11 +1211,13 @@  static int qdisc_notify(struct sk_buff *oskb, struct nlmsghdr *n,
 		return -ENOBUFS;
 
 	if (old && old->handle) {
-		if (tc_fill_qdisc(skb, old, clid, pid, n->nlmsg_seq, 0, RTM_DELQDISC) < 0)
+		if (tc_fill_qdisc(skb, old, clid, pid, n->nlmsg_seq, 0,
+				  RTM_DELQDISC, qnum) < 0)
 			goto err_out;
 	}
 	if (new) {
-		if (tc_fill_qdisc(skb, new, clid, pid, n->nlmsg_seq, old ? NLM_F_REPLACE : 0, RTM_NEWQDISC) < 0)
+		if (tc_fill_qdisc(skb, new, clid, pid, n->nlmsg_seq,
+				  old ? NLM_F_REPLACE : 0, RTM_NEWQDISC, qnum) < 0)
 			goto err_out;
 	}
 
@@ -1230,7 +1236,7 @@  static bool tc_qdisc_dump_ignore(struct Qdisc *q)
 
 static int tc_dump_qdisc_root(struct Qdisc *root, struct sk_buff *skb,
 			      struct netlink_callback *cb,
-			      int *q_idx_p, int s_q_idx)
+			      int *q_idx_p, int s_q_idx, int qnum)
 {
 	int ret = 0, q_idx = *q_idx_p;
 	struct Qdisc *q;
@@ -1239,23 +1245,18 @@  static int tc_dump_qdisc_root(struct Qdisc *root, struct sk_buff *skb,
 		return 0;
 
 	q = root;
-	if (q_idx < s_q_idx) {
-		q_idx++;
-	} else {
-		if (!tc_qdisc_dump_ignore(q) &&
-		    tc_fill_qdisc(skb, q, q->parent, NETLINK_CB(cb->skb).pid,
-				  cb->nlh->nlmsg_seq, NLM_F_MULTI, RTM_NEWQDISC) <= 0)
-			goto done;
-		q_idx++;
-	}
+	if (q_idx >= s_q_idx &&
+	    !tc_qdisc_dump_ignore(q) &&
+	    tc_fill_qdisc(skb, q, q->parent, NETLINK_CB(cb->skb).pid,
+			  cb->nlh->nlmsg_seq, NLM_F_MULTI, RTM_NEWQDISC, qnum) <= 0)
+		goto done;
+	q_idx++;
+
 	list_for_each_entry(q, &root->list, list) {
-		if (q_idx < s_q_idx) {
-			q_idx++;
-			continue;
-		}
-		if (!tc_qdisc_dump_ignore(q) && 
+		if (q_idx >= s_q_idx &&
+		    !tc_qdisc_dump_ignore(q) && 
 		    tc_fill_qdisc(skb, q, q->parent, NETLINK_CB(cb->skb).pid,
-				  cb->nlh->nlmsg_seq, NLM_F_MULTI, RTM_NEWQDISC) <= 0)
+				  cb->nlh->nlmsg_seq, NLM_F_MULTI, RTM_NEWQDISC, qnum) <= 0)
 			goto done;
 		q_idx++;
 	}
@@ -1284,6 +1285,7 @@  static int tc_dump_qdisc(struct sk_buff *skb, struct netlink_callback *cb)
 	idx = 0;
 	for_each_netdev(&init_net, dev) {
 		struct netdev_queue *dev_queue;
+		int ntx;
 
 		if (idx < s_idx)
 			goto cont;
@@ -1291,12 +1293,15 @@  static int tc_dump_qdisc(struct sk_buff *skb, struct netlink_callback *cb)
 			s_q_idx = 0;
 		q_idx = 0;
 
-		dev_queue = netdev_get_tx_queue(dev, 0);
-		if (tc_dump_qdisc_root(dev_queue->qdisc_sleeping, skb, cb, &q_idx, s_q_idx) < 0)
-			goto done;
-
+		for (ntx = 0 ; ntx < dev->real_num_tx_queues; ntx++) {
+			dev_queue = netdev_get_tx_queue(dev, ntx);
+			if (tc_dump_qdisc_root(dev_queue->qdisc_sleeping, skb,
+					       cb, &q_idx, s_q_idx, ntx) < 0)
+				goto done;
+		}
 		dev_queue = &dev->rx_queue;
-		if (tc_dump_qdisc_root(dev_queue->qdisc_sleeping, skb, cb, &q_idx, s_q_idx) < 0)
+		if (tc_dump_qdisc_root(dev_queue->qdisc_sleeping, skb, cb,
+				       &q_idx, s_q_idx, 0) < 0)
 			goto done;
 
 cont:
@@ -1419,10 +1424,10 @@  static int tc_ctl_tclass(struct sk_buff *skb, struct nlmsghdr *n, void *arg)
 		case RTM_DELTCLASS:
 			err = cops->delete(q, cl);
 			if (err == 0)
-				tclass_notify(skb, n, q, cl, RTM_DELTCLASS);
+				tclass_notify(skb, n, q, cl, RTM_DELTCLASS, 0);
 			goto out;
 		case RTM_GETTCLASS:
-			err = tclass_notify(skb, n, q, cl, RTM_NEWTCLASS);
+			err = tclass_notify(skb, n, q, cl, RTM_NEWTCLASS, 0);
 			goto out;
 		default:
 			err = -EINVAL;
@@ -1433,7 +1438,7 @@  static int tc_ctl_tclass(struct sk_buff *skb, struct nlmsghdr *n, void *arg)
 	new_cl = cl;
 	err = cops->change(q, clid, pid, tca, &new_cl);
 	if (err == 0)
-		tclass_notify(skb, n, q, new_cl, RTM_NEWTCLASS);
+		tclass_notify(skb, n, q, new_cl, RTM_NEWTCLASS, 0);
 
 out:
 	if (cl)
@@ -1445,7 +1450,7 @@  out:
 
 static int tc_fill_tclass(struct sk_buff *skb, struct Qdisc *q,
 			  unsigned long cl,
-			  u32 pid, u32 seq, u16 flags, int event)
+			  u32 pid, u32 seq, u16 flags, int event, int qnum)
 {
 	struct tcmsg *tcm;
 	struct nlmsghdr  *nlh;
@@ -1474,6 +1479,9 @@  static int tc_fill_tclass(struct sk_buff *skb, struct Qdisc *q,
 	if (gnet_stats_finish_copy(&d) < 0)
 		goto nla_put_failure;
 
+	if (qnum)
+		NLA_PUT_U32(skb, TCA_QINDEX, qnum);
+
 	nlh->nlmsg_len = skb_tail_pointer(skb) - b;
 	return skb->len;
 
@@ -1484,7 +1492,8 @@  nla_put_failure:
 }
 
 static int tclass_notify(struct sk_buff *oskb, struct nlmsghdr *n,
-			  struct Qdisc *q, unsigned long cl, int event)
+			 struct Qdisc *q, unsigned long cl, int event,
+			 int qnum)
 {
 	struct sk_buff *skb;
 	u32 pid = oskb ? NETLINK_CB(oskb).pid : 0;
@@ -1493,7 +1502,7 @@  static int tclass_notify(struct sk_buff *oskb, struct nlmsghdr *n,
 	if (!skb)
 		return -ENOBUFS;
 
-	if (tc_fill_tclass(skb, q, cl, pid, n->nlmsg_seq, 0, event) < 0) {
+	if (tc_fill_tclass(skb, q, cl, pid, n->nlmsg_seq, 0, event, qnum) < 0) {
 		kfree_skb(skb);
 		return -EINVAL;
 	}
@@ -1503,9 +1512,10 @@  static int tclass_notify(struct sk_buff *oskb, struct nlmsghdr *n,
 
 struct qdisc_dump_args
 {
-	struct qdisc_walker w;
-	struct sk_buff *skb;
+	struct qdisc_walker	w;
+	struct sk_buff		*skb;
 	struct netlink_callback *cb;
+	int			qnum;
 };
 
 static int qdisc_class_dump(struct Qdisc *q, unsigned long cl, struct qdisc_walker *arg)
@@ -1513,12 +1523,13 @@  static int qdisc_class_dump(struct Qdisc *q, unsigned long cl, struct qdisc_walk
 	struct qdisc_dump_args *a = (struct qdisc_dump_args *)arg;
 
 	return tc_fill_tclass(a->skb, q, cl, NETLINK_CB(a->cb->skb).pid,
-			      a->cb->nlh->nlmsg_seq, NLM_F_MULTI, RTM_NEWTCLASS);
+			      a->cb->nlh->nlmsg_seq, NLM_F_MULTI, RTM_NEWTCLASS,
+			      a->qnum);
 }
 
 static int tc_dump_tclass_qdisc(struct Qdisc *q, struct sk_buff *skb,
 				struct tcmsg *tcm, struct netlink_callback *cb,
-				int *t_p, int s_t)
+				int *t_p, int s_t, int qnum)
 {
 	struct qdisc_dump_args arg;
 
@@ -1537,6 +1548,7 @@  static int tc_dump_tclass_qdisc(struct Qdisc *q, struct sk_buff *skb,
 	arg.w.stop  = 0;
 	arg.w.skip = cb->args[1];
 	arg.w.count = 0;
+	arg.qnum = qnum;
 	q->ops->cl_ops->walk(q, &arg.w);
 	cb->args[1] = arg.w.count;
 	if (arg.w.stop)
@@ -1547,18 +1559,18 @@  static int tc_dump_tclass_qdisc(struct Qdisc *q, struct sk_buff *skb,
 
 static int tc_dump_tclass_root(struct Qdisc *root, struct sk_buff *skb,
 			       struct tcmsg *tcm, struct netlink_callback *cb,
-			       int *t_p, int s_t)
+			       int *t_p, int s_t, int qnum)
 {
 	struct Qdisc *q;
 
 	if (!root)
 		return 0;
 
-	if (tc_dump_tclass_qdisc(root, skb, tcm, cb, t_p, s_t) < 0)
+	if (tc_dump_tclass_qdisc(root, skb, tcm, cb, t_p, s_t, qnum) < 0)
 		return -1;
 
 	list_for_each_entry(q, &root->list, list) {
-		if (tc_dump_tclass_qdisc(q, skb, tcm, cb, t_p, s_t) < 0)
+		if (tc_dump_tclass_qdisc(q, skb, tcm, cb, t_p, s_t, qnum) < 0)
 			return -1;
 	}
 
@@ -1571,7 +1583,7 @@  static int tc_dump_tclass(struct sk_buff *skb, struct netlink_callback *cb)
 	struct net *net = sock_net(skb->sk);
 	struct netdev_queue *dev_queue;
 	struct net_device *dev;
-	int t, s_t;
+	int t, s_t, ntx;
 
 	if (net != &init_net)
 		return 0;
@@ -1584,12 +1596,14 @@  static int tc_dump_tclass(struct sk_buff *skb, struct netlink_callback *cb)
 	s_t = cb->args[0];
 	t = 0;
 
-	dev_queue = netdev_get_tx_queue(dev, 0);
-	if (tc_dump_tclass_root(dev_queue->qdisc_sleeping, skb, tcm, cb, &t, s_t) < 0)
-		goto done;
-
+	for (ntx = 0 ; ntx < dev->real_num_tx_queues; ntx++) {
+		dev_queue = netdev_get_tx_queue(dev, ntx);
+		if (tc_dump_tclass_root(dev_queue->qdisc_sleeping, skb, tcm,
+					cb, &t, s_t, ntx) < 0)
+			goto done;
+	}
 	dev_queue = &dev->rx_queue;
-	if (tc_dump_tclass_root(dev_queue->qdisc_sleeping, skb, tcm, cb, &t, s_t) < 0)
+	if (tc_dump_tclass_root(dev_queue->qdisc_sleeping, skb, tcm, cb, &t, s_t, 0) < 0)
 		goto done;
 
 done: