Skip to content

Commit

Permalink
net/sched: taprio: fix picos_per_byte miscalculation
Browse files Browse the repository at this point in the history
The Time Aware Priority Scheduler is heavily dependent to link speed,
it relies on it to calculate transmission bytes per cycle, we can't
properly calculate the so called budget if the device has failed
to report the link speed.

In that case we can't dequeue packets assuming a wrong budget.
This patch makes sure we fail to dequeue case:

1) __ethtool_get_link_ksettings() reports error or 2) the ethernet
driver failed to set the ksettings' speed value (setting link speed
to SPEED_UNKNOWN).

Additionally we re calculate the budget whenever the link speed is
changed.

Fixes: 5a781cc ("tc: Add support for configuring the taprio scheduler")
Signed-off-by: Leandro Dorileo <leandro.maciel.dorileo@intel.com>
Reviewed-by: Vedang Patel <vedang.patel@intel.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
Leandro Dorileo authored and davem330 committed Apr 11, 2019
1 parent 93e2125 commit 7b9eba7
Showing 1 changed file with 81 additions and 16 deletions.
97 changes: 81 additions & 16 deletions net/sched/sch_taprio.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
#include <net/pkt_cls.h>
#include <net/sch_generic.h>

static LIST_HEAD(taprio_list);
static DEFINE_SPINLOCK(taprio_list_lock);

#define TAPRIO_ALL_GATES_OPEN -1

struct sched_entry {
Expand All @@ -42,9 +45,9 @@ struct taprio_sched {
struct Qdisc *root;
s64 base_time;
int clockid;
int picos_per_byte; /* Using picoseconds because for 10Gbps+
* speeds it's sub-nanoseconds per byte
*/
atomic64_t picos_per_byte; /* Using picoseconds because for 10Gbps+
* speeds it's sub-nanoseconds per byte
*/
size_t num_entries;

/* Protects the update side of the RCU protected current_entry */
Expand All @@ -53,6 +56,7 @@ struct taprio_sched {
struct list_head entries;
ktime_t (*get_time)(void);
struct hrtimer advance_timer;
struct list_head taprio_list;
};

static int taprio_enqueue(struct sk_buff *skb, struct Qdisc *sch,
Expand Down Expand Up @@ -117,7 +121,7 @@ static struct sk_buff *taprio_peek(struct Qdisc *sch)

static inline int length_to_duration(struct taprio_sched *q, int len)
{
return (len * q->picos_per_byte) / 1000;
return (len * atomic64_read(&q->picos_per_byte)) / 1000;
}

static struct sk_buff *taprio_dequeue(struct Qdisc *sch)
Expand All @@ -129,6 +133,11 @@ static struct sk_buff *taprio_dequeue(struct Qdisc *sch)
u32 gate_mask;
int i;

if (atomic64_read(&q->picos_per_byte) == -1) {
WARN_ONCE(1, "taprio: dequeue() called with unknown picos per byte.");
return NULL;
}

rcu_read_lock();
entry = rcu_dereference(q->current_entry);
/* if there's no entry, it means that the schedule didn't
Expand Down Expand Up @@ -233,7 +242,7 @@ static enum hrtimer_restart advance_sched(struct hrtimer *timer)

next->close_time = close_time;
atomic_set(&next->budget,
(next->interval * 1000) / q->picos_per_byte);
(next->interval * 1000) / atomic64_read(&q->picos_per_byte));

first_run:
rcu_assign_pointer(q->current_entry, next);
Expand Down Expand Up @@ -567,24 +576,69 @@ static void taprio_start_sched(struct Qdisc *sch, ktime_t start)

first->close_time = ktime_add_ns(start, first->interval);
atomic_set(&first->budget,
(first->interval * 1000) / q->picos_per_byte);
(first->interval * 1000) /
atomic64_read(&q->picos_per_byte));
rcu_assign_pointer(q->current_entry, NULL);

spin_unlock_irqrestore(&q->current_entry_lock, flags);

hrtimer_start(&q->advance_timer, start, HRTIMER_MODE_ABS);
}

static void taprio_set_picos_per_byte(struct net_device *dev,
struct taprio_sched *q)
{
struct ethtool_link_ksettings ecmd;
int picos_per_byte = -1;

if (!__ethtool_get_link_ksettings(dev, &ecmd) &&
ecmd.base.speed != SPEED_UNKNOWN)
picos_per_byte = div64_s64(NSEC_PER_SEC * 1000LL * 8,
ecmd.base.speed * 1000 * 1000);

atomic64_set(&q->picos_per_byte, picos_per_byte);
netdev_dbg(dev, "taprio: set %s's picos_per_byte to: %lld, linkspeed: %d\n",
dev->name, (long long)atomic64_read(&q->picos_per_byte),
ecmd.base.speed);
}

static int taprio_dev_notifier(struct notifier_block *nb, unsigned long event,
void *ptr)
{
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
struct net_device *qdev;
struct taprio_sched *q;
bool found = false;

ASSERT_RTNL();

if (event != NETDEV_UP && event != NETDEV_CHANGE)
return NOTIFY_DONE;

spin_lock(&taprio_list_lock);
list_for_each_entry(q, &taprio_list, taprio_list) {
qdev = qdisc_dev(q->root);
if (qdev == dev) {
found = true;
break;
}
}
spin_unlock(&taprio_list_lock);

if (found)
taprio_set_picos_per_byte(dev, q);

return NOTIFY_DONE;
}

static int taprio_change(struct Qdisc *sch, struct nlattr *opt,
struct netlink_ext_ack *extack)
{
struct nlattr *tb[TCA_TAPRIO_ATTR_MAX + 1] = { };
struct taprio_sched *q = qdisc_priv(sch);
struct net_device *dev = qdisc_dev(sch);
struct tc_mqprio_qopt *mqprio = NULL;
struct ethtool_link_ksettings ecmd;
int i, err, size;
s64 link_speed;
ktime_t start;

err = nla_parse_nested(tb, TCA_TAPRIO_ATTR_MAX, opt,
Expand Down Expand Up @@ -657,14 +711,7 @@ static int taprio_change(struct Qdisc *sch, struct nlattr *opt,
mqprio->prio_tc_map[i]);
}

if (!__ethtool_get_link_ksettings(dev, &ecmd))
link_speed = ecmd.base.speed;
else
link_speed = SPEED_1000;

q->picos_per_byte = div64_s64(NSEC_PER_SEC * 1000LL * 8,
link_speed * 1000 * 1000);

taprio_set_picos_per_byte(dev, q);
start = taprio_get_start_time(sch);
if (!start)
return 0;
Expand All @@ -681,6 +728,10 @@ static void taprio_destroy(struct Qdisc *sch)
struct sched_entry *entry, *n;
unsigned int i;

spin_lock(&taprio_list_lock);
list_del(&q->taprio_list);
spin_unlock(&taprio_list_lock);

hrtimer_cancel(&q->advance_timer);

if (q->qdiscs) {
Expand Down Expand Up @@ -735,6 +786,10 @@ static int taprio_init(struct Qdisc *sch, struct nlattr *opt,
if (!opt)
return -EINVAL;

spin_lock(&taprio_list_lock);
list_add(&q->taprio_list, &taprio_list);
spin_unlock(&taprio_list_lock);

return taprio_change(sch, opt, extack);
}

Expand Down Expand Up @@ -947,14 +1002,24 @@ static struct Qdisc_ops taprio_qdisc_ops __read_mostly = {
.owner = THIS_MODULE,
};

static struct notifier_block taprio_device_notifier = {
.notifier_call = taprio_dev_notifier,
};

static int __init taprio_module_init(void)
{
int err = register_netdevice_notifier(&taprio_device_notifier);

if (err)
return err;

return register_qdisc(&taprio_qdisc_ops);
}

static void __exit taprio_module_exit(void)
{
unregister_qdisc(&taprio_qdisc_ops);
unregister_netdevice_notifier(&taprio_device_notifier);
}

module_init(taprio_module_init);
Expand Down

0 comments on commit 7b9eba7

Please sign in to comment.