* [PATCH net-next 14/15] net/sched: mq: no longer acquire qdisc spinlocks in dump operations
From: Eric Dumazet @ 2026-04-08 12:56 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Jamal Hadi Salim, Jiri Pirko, Kuniyuki Iwashima,
netdev, eric.dumazet, Eric Dumazet
In-Reply-To: <20260408125611.3592751-1-edumazet@google.com>
Prepare mq_dump_common(), mqprio_dump() and mqprio_dump_class_stats()
for RTNL avoidance.
Use private variables instead of assuming sch->bstats and sch->qstats
can be used when folding stats from children.
This means the children qdisc spinlocks no longer need to be acquired.
Add qdisc_qlen_lockless() helper, and change gnet_stats_add_basic()
prototype.
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
include/net/gen_stats.h | 9 ++++--
include/net/sch_generic.h | 5 ++++
net/core/gen_estimator.c | 24 +++++++--------
net/core/gen_stats.c | 22 ++++++--------
net/sched/sch_mq.c | 28 ++++++++++-------
net/sched/sch_mqprio.c | 63 +++++++++++++++++----------------------
6 files changed, 76 insertions(+), 75 deletions(-)
diff --git a/include/net/gen_stats.h b/include/net/gen_stats.h
index 7aa2b8e1fb298c4f994a745b114fc4da785ddf4b..5484b67298e3fe94fe84f0e929799362d21499df 100644
--- a/include/net/gen_stats.h
+++ b/include/net/gen_stats.h
@@ -21,6 +21,11 @@ struct gnet_stats_basic_sync {
struct u64_stats_sync syncp;
} __aligned(2 * sizeof(u64));
+struct gnet_stats {
+ u64 bytes;
+ u64 packets;
+};
+
struct net_rate_estimator;
struct gnet_dump {
@@ -49,9 +54,9 @@ int gnet_stats_start_copy_compat(struct sk_buff *skb, int type,
int gnet_stats_copy_basic(struct gnet_dump *d,
struct gnet_stats_basic_sync __percpu *cpu,
struct gnet_stats_basic_sync *b, bool running);
-void gnet_stats_add_basic(struct gnet_stats_basic_sync *bstats,
+void gnet_stats_add_basic(struct gnet_stats *bstats,
struct gnet_stats_basic_sync __percpu *cpu,
- struct gnet_stats_basic_sync *b, bool running);
+ const struct gnet_stats_basic_sync *b, bool running);
int gnet_stats_copy_basic_hw(struct gnet_dump *d,
struct gnet_stats_basic_sync __percpu *cpu,
struct gnet_stats_basic_sync *b, bool running);
diff --git a/include/net/sch_generic.h b/include/net/sch_generic.h
index 8e4e12692a1048f5e626b89c4db9e0339b16265d..f85aca5c1a445debd52a4e7b359141cf90cb7b55 100644
--- a/include/net/sch_generic.h
+++ b/include/net/sch_generic.h
@@ -542,6 +542,11 @@ static inline int qdisc_qlen(const struct Qdisc *q)
return q->q.qlen;
}
+static inline int qdisc_qlen_lockless(const struct Qdisc *q)
+{
+ return READ_ONCE(q->q.qlen);
+}
+
static inline void qdisc_qlen_inc(struct Qdisc *q)
{
WRITE_ONCE(q->q.qlen, q->q.qlen + 1);
diff --git a/net/core/gen_estimator.c b/net/core/gen_estimator.c
index c34e58c6c3e666743e72978f9a78cf7f95a360c3..40990aee45590f2c56c070b0d28f856fc82d1f55 100644
--- a/net/core/gen_estimator.c
+++ b/net/core/gen_estimator.c
@@ -60,9 +60,10 @@ struct net_rate_estimator {
};
static void est_fetch_counters(struct net_rate_estimator *e,
- struct gnet_stats_basic_sync *b)
+ struct gnet_stats *b)
{
- gnet_stats_basic_sync_init(b);
+ b->packets = 0;
+ b->bytes = 0;
if (e->stats_lock)
spin_lock(e->stats_lock);
@@ -76,18 +77,15 @@ static void est_fetch_counters(struct net_rate_estimator *e,
static void est_timer(struct timer_list *t)
{
struct net_rate_estimator *est = timer_container_of(est, t, timer);
- struct gnet_stats_basic_sync b;
- u64 b_bytes, b_packets;
+ struct gnet_stats b;
u64 rate, brate;
est_fetch_counters(est, &b);
- b_bytes = u64_stats_read(&b.bytes);
- b_packets = u64_stats_read(&b.packets);
- brate = (b_bytes - est->last_bytes) << (10 - est->intvl_log);
+ brate = (b.bytes - est->last_bytes) << (10 - est->intvl_log);
brate = (brate >> est->ewma_log) - (est->avbps >> est->ewma_log);
- rate = (b_packets - est->last_packets) << (10 - est->intvl_log);
+ rate = (b.packets - est->last_packets) << (10 - est->intvl_log);
rate = (rate >> est->ewma_log) - (est->avpps >> est->ewma_log);
preempt_disable_nested();
@@ -97,8 +95,8 @@ static void est_timer(struct timer_list *t)
write_seqcount_end(&est->seq);
preempt_enable_nested();
- est->last_bytes = b_bytes;
- est->last_packets = b_packets;
+ est->last_bytes = b.bytes;
+ est->last_packets = b.packets;
est->next_jiffies += ((HZ/4) << est->intvl_log);
@@ -138,7 +136,7 @@ int gen_new_estimator(struct gnet_stats_basic_sync *bstats,
{
struct gnet_estimator *parm = nla_data(opt);
struct net_rate_estimator *old, *est;
- struct gnet_stats_basic_sync b;
+ struct gnet_stats b;
int intvl_log;
if (nla_len(opt) < sizeof(*parm))
@@ -172,8 +170,8 @@ int gen_new_estimator(struct gnet_stats_basic_sync *bstats,
est_fetch_counters(est, &b);
if (lock)
local_bh_enable();
- est->last_bytes = u64_stats_read(&b.bytes);
- est->last_packets = u64_stats_read(&b.packets);
+ est->last_bytes = b.bytes;
+ est->last_packets = b.packets;
if (lock)
spin_lock_bh(lock);
diff --git a/net/core/gen_stats.c b/net/core/gen_stats.c
index 1a2380e74272de8eaf3d4ef453e56105a31e9edf..29a178bebcc13d199e71ef0b09d852bd9dbf3378 100644
--- a/net/core/gen_stats.c
+++ b/net/core/gen_stats.c
@@ -123,12 +123,13 @@ void gnet_stats_basic_sync_init(struct gnet_stats_basic_sync *b)
}
EXPORT_SYMBOL(gnet_stats_basic_sync_init);
-static void gnet_stats_add_basic_cpu(struct gnet_stats_basic_sync *bstats,
+static void gnet_stats_add_basic_cpu(struct gnet_stats *bstats,
struct gnet_stats_basic_sync __percpu *cpu)
{
- u64 t_bytes = 0, t_packets = 0;
int i;
+ bstats->bytes = 0;
+ bstats->packets = 0;
for_each_possible_cpu(i) {
struct gnet_stats_basic_sync *bcpu = per_cpu_ptr(cpu, i);
unsigned int start;
@@ -140,19 +141,16 @@ static void gnet_stats_add_basic_cpu(struct gnet_stats_basic_sync *bstats,
packets = u64_stats_read(&bcpu->packets);
} while (u64_stats_fetch_retry(&bcpu->syncp, start));
- t_bytes += bytes;
- t_packets += packets;
+ bstats->bytes += bytes;
+ bstats->packets += packets;
}
- _bstats_update(bstats, t_bytes, t_packets);
}
-void gnet_stats_add_basic(struct gnet_stats_basic_sync *bstats,
+void gnet_stats_add_basic(struct gnet_stats *bstats,
struct gnet_stats_basic_sync __percpu *cpu,
- struct gnet_stats_basic_sync *b, bool running)
+ const struct gnet_stats_basic_sync *b, bool running)
{
unsigned int start;
- u64 bytes = 0;
- u64 packets = 0;
WARN_ON_ONCE((cpu || running) && in_hardirq());
@@ -163,11 +161,9 @@ void gnet_stats_add_basic(struct gnet_stats_basic_sync *bstats,
do {
if (running)
start = u64_stats_fetch_begin(&b->syncp);
- bytes = u64_stats_read(&b->bytes);
- packets = u64_stats_read(&b->packets);
+ bstats->bytes = u64_stats_read(&b->bytes);
+ bstats->packets = u64_stats_read(&b->packets);
} while (running && u64_stats_fetch_retry(&b->syncp, start));
-
- _bstats_update(bstats, bytes, packets);
}
EXPORT_SYMBOL(gnet_stats_add_basic);
diff --git a/net/sched/sch_mq.c b/net/sched/sch_mq.c
index a0133a7b9d3b09a0d2a6064234c8fdef60dbf955..1e4522436620e5ee3a0417996476b37d9abca299 100644
--- a/net/sched/sch_mq.c
+++ b/net/sched/sch_mq.c
@@ -143,13 +143,12 @@ EXPORT_SYMBOL_NS_GPL(mq_attach, "NET_SCHED_INTERNAL");
void mq_dump_common(struct Qdisc *sch, struct sk_buff *skb)
{
struct net_device *dev = qdisc_dev(sch);
- struct Qdisc *qdisc;
+ struct gnet_stats_queue qstats = { 0 };
+ struct gnet_stats bstats = { 0 };
+ const struct Qdisc *qdisc;
+ unsigned int qlen = 0;
unsigned int ntx;
- sch->q.qlen = 0;
- gnet_stats_basic_sync_init(&sch->bstats);
- memset(&sch->qstats, 0, sizeof(sch->qstats));
-
/* MQ supports lockless qdiscs. However, statistics accounting needs
* to account for all, none, or a mix of locked and unlocked child
* qdiscs. Percpu stats are added to counters in-band and locking
@@ -157,16 +156,23 @@ void mq_dump_common(struct Qdisc *sch, struct sk_buff *skb)
*/
for (ntx = 0; ntx < dev->num_tx_queues; ntx++) {
qdisc = rtnl_dereference(netdev_get_tx_queue(dev, ntx)->qdisc_sleeping);
- spin_lock_bh(qdisc_lock(qdisc));
- gnet_stats_add_basic(&sch->bstats, qdisc->cpu_bstats,
+ gnet_stats_add_basic(&bstats, qdisc->cpu_bstats,
&qdisc->bstats, false);
- gnet_stats_add_queue(&sch->qstats, qdisc->cpu_qstats,
+ gnet_stats_add_queue(&qstats, qdisc->cpu_qstats,
&qdisc->qstats);
- sch->q.qlen += qdisc_qlen(qdisc);
-
- spin_unlock_bh(qdisc_lock(qdisc));
+ qlen += qdisc_qlen_lockless(qdisc);
}
+ u64_stats_set(&sch->bstats.bytes, bstats.bytes);
+ u64_stats_set(&sch->bstats.packets, bstats.packets);
+
+ WRITE_ONCE(sch->qstats.qlen, qstats.qlen);
+ WRITE_ONCE(sch->qstats.backlog, qstats.backlog);
+ WRITE_ONCE(sch->qstats.drops, qstats.drops);
+ WRITE_ONCE(sch->qstats.requeues, qstats.requeues);
+ WRITE_ONCE(sch->qstats.overlimits, qstats.overlimits);
+
+ WRITE_ONCE(sch->q.qlen, qlen);
}
EXPORT_SYMBOL_NS_GPL(mq_dump_common, "NET_SCHED_INTERNAL");
diff --git a/net/sched/sch_mqprio.c b/net/sched/sch_mqprio.c
index d35624e5869a4a6a12612886b2cd9cdac7b0b471..ac6d7d0a35309a5375425790f89f587601511cc7 100644
--- a/net/sched/sch_mqprio.c
+++ b/net/sched/sch_mqprio.c
@@ -554,15 +554,13 @@ static int mqprio_dump(struct Qdisc *sch, struct sk_buff *skb)
struct net_device *dev = qdisc_dev(sch);
struct mqprio_sched *priv = qdisc_priv(sch);
struct nlattr *nla = (struct nlattr *)skb_tail_pointer(skb);
+ struct gnet_stats_queue qstats = { 0 };
struct tc_mqprio_qopt opt = { 0 };
+ struct gnet_stats bstats = { 0 };
+ const struct Qdisc *qdisc;
unsigned int qlen = 0;
- struct Qdisc *qdisc;
unsigned int ntx;
- qlen = 0;
- gnet_stats_basic_sync_init(&sch->bstats);
- memset(&sch->qstats, 0, sizeof(sch->qstats));
-
/* MQ supports lockless qdiscs. However, statistics accounting needs
* to account for all, none, or a mix of locked and unlocked child
* qdiscs. Percpu stats are added to counters in-band and locking
@@ -570,16 +568,22 @@ static int mqprio_dump(struct Qdisc *sch, struct sk_buff *skb)
*/
for (ntx = 0; ntx < dev->num_tx_queues; ntx++) {
qdisc = rtnl_dereference(netdev_get_tx_queue(dev, ntx)->qdisc_sleeping);
- spin_lock_bh(qdisc_lock(qdisc));
- gnet_stats_add_basic(&sch->bstats, qdisc->cpu_bstats,
+ gnet_stats_add_basic(&bstats, qdisc->cpu_bstats,
&qdisc->bstats, false);
- gnet_stats_add_queue(&sch->qstats, qdisc->cpu_qstats,
+ gnet_stats_add_queue(&qstats, qdisc->cpu_qstats,
&qdisc->qstats);
qlen += qdisc_qlen(qdisc);
-
- spin_unlock_bh(qdisc_lock(qdisc));
}
+ u64_stats_set(&sch->bstats.bytes, bstats.bytes);
+ u64_stats_set(&sch->bstats.packets, bstats.packets);
+
+ WRITE_ONCE(sch->qstats.qlen, qstats.qlen);
+ WRITE_ONCE(sch->qstats.backlog, qstats.backlog);
+ WRITE_ONCE(sch->qstats.drops, qstats.drops);
+ WRITE_ONCE(sch->qstats.requeues, qstats.requeues);
+ WRITE_ONCE(sch->qstats.overlimits, qstats.overlimits);
+
WRITE_ONCE(sch->q.qlen, qlen);
mqprio_qopt_reconstruct(dev, &opt);
@@ -661,45 +665,32 @@ static int mqprio_dump_class(struct Qdisc *sch, unsigned long cl,
static int mqprio_dump_class_stats(struct Qdisc *sch, unsigned long cl,
struct gnet_dump *d)
- __releases(d->lock)
- __acquires(d->lock)
{
if (cl >= TC_H_MIN_PRIORITY) {
- int i;
- __u32 qlen;
- struct gnet_stats_queue qstats = {0};
- struct gnet_stats_basic_sync bstats;
struct net_device *dev = qdisc_dev(sch);
struct netdev_tc_txq tc = dev->tc_to_txq[cl & TC_BITMASK];
-
- gnet_stats_basic_sync_init(&bstats);
- /* Drop lock here it will be reclaimed before touching
- * statistics this is required because the d->lock we
- * hold here is the look on dev_queue->qdisc_sleeping
- * also acquired below.
- */
- if (d->lock)
- spin_unlock_bh(d->lock);
+ struct gnet_stats_queue qstats = { 0 };
+ struct gnet_stats_basic_sync bstats;
+ struct gnet_stats _bstats = { 0 };
+ u32 qlen = 0;
+ int i;
for (i = tc.offset; i < tc.offset + tc.count; i++) {
struct netdev_queue *q = netdev_get_tx_queue(dev, i);
- struct Qdisc *qdisc = rtnl_dereference(q->qdisc);
+ const struct Qdisc *qdisc = rtnl_dereference(q->qdisc);
- spin_lock_bh(qdisc_lock(qdisc));
-
- gnet_stats_add_basic(&bstats, qdisc->cpu_bstats,
+ gnet_stats_add_basic(&_bstats, qdisc->cpu_bstats,
&qdisc->bstats, false);
gnet_stats_add_queue(&qstats, qdisc->cpu_qstats,
&qdisc->qstats);
- sch->q.qlen += qdisc_qlen(qdisc);
-
- spin_unlock_bh(qdisc_lock(qdisc));
+ qlen += qdisc_qlen_lockless(qdisc);
}
- qlen = qdisc_qlen(sch) + qstats.qlen;
+ u64_stats_init(&bstats.syncp);
+ u64_stats_set(&bstats.bytes, _bstats.bytes);
+ u64_stats_set(&bstats.packets, _bstats.packets);
+
+ qlen = qlen + qstats.qlen;
- /* Reclaim root sleeping lock before completing stats */
- if (d->lock)
- spin_lock_bh(d->lock);
if (gnet_stats_copy_basic(d, NULL, &bstats, false) < 0 ||
gnet_stats_copy_queue(d, NULL, &qstats, qlen) < 0)
return -1;
--
2.53.0.1213.gd9a14994de-goog
^ permalink raw reply related
* [PATCH net-next 15/15] net/sched: convert tc_dump_qdisc() to RCU
From: Eric Dumazet @ 2026-04-08 12:56 UTC (permalink / raw)
To: David S . Miller, Jakub Kicinski, Paolo Abeni
Cc: Simon Horman, Jamal Hadi Salim, Jiri Pirko, Kuniyuki Iwashima,
netdev, eric.dumazet, Eric Dumazet, Stanislav Fomichev
In-Reply-To: <20260408125611.3592751-1-edumazet@google.com>
No longer acquire RTNL in qdisc dumps (tc -s -d qdisc show ...)
Notes:
- Add sch_tree_lock_rcu()/sch_tree_unlock_rcu() for dump methods
which need to acquire qdisc spinlock (fq, fq_codel, fq_pie and gred).
- Removed netdev_lock_ops()/netdev_unlock_ops() because dumps are read-only.
They were are added in commit a0527ee2df3f ("net: hold netdev instance
lock during qdisc ndo_setup_tc").
- for_each_netdev_rcu() can be changed later with
more scalable for_each_netdev_dump().
Signed-off-by: Eric Dumazet <edumazet@google.com>
Cc: Stanislav Fomichev <sdf@fomichev.me>
---
include/net/sch_generic.h | 14 ++++++++++++++
net/sched/sch_api.c | 21 +++++++++------------
net/sched/sch_fq.c | 4 ++--
net/sched/sch_fq_codel.c | 4 ++--
net/sched/sch_fq_pie.c | 4 ++--
net/sched/sch_gred.c | 4 ++--
net/sched/sch_mq.c | 2 +-
net/sched/sch_mqprio.c | 2 +-
net/sched/sch_taprio.c | 16 +++++-----------
9 files changed, 38 insertions(+), 33 deletions(-)
diff --git a/include/net/sch_generic.h b/include/net/sch_generic.h
index f85aca5c1a445debd52a4e7b359141cf90cb7b55..82cb1e639dcc404bffe4b2669bb5aacef605cf6d 100644
--- a/include/net/sch_generic.h
+++ b/include/net/sch_generic.h
@@ -628,6 +628,20 @@ static inline void sch_tree_unlock(struct Qdisc *q)
spin_unlock_bh(qdisc_root_sleeping_lock(q));
}
+static inline void sch_tree_lock_rcu(struct Qdisc *q)
+{
+ if (!(q->flags & TCQ_F_MQROOT))
+ q = qdisc_root_sleeping(q);
+ spin_lock_bh(qdisc_lock(q));
+}
+
+static inline void sch_tree_unlock_rcu(struct Qdisc *q)
+{
+ if (!(q->flags & TCQ_F_MQROOT))
+ q = qdisc_root_sleeping(q);
+ spin_unlock_bh(qdisc_lock(q));
+}
+
extern struct Qdisc noop_qdisc;
extern struct Qdisc_ops noop_qdisc_ops;
extern struct Qdisc_ops pfifo_fast_ops;
diff --git a/net/sched/sch_api.c b/net/sched/sch_api.c
index 292bc8bb7a79922a83865ed54083c04ff72742ff..adc2d499a03d9fb4667dc87ba06aa3abfce30214 100644
--- a/net/sched/sch_api.c
+++ b/net/sched/sch_api.c
@@ -909,7 +909,6 @@ static int tc_fill_qdisc(struct sk_buff *skb, struct Qdisc *q, u32 clid,
u32 block_index;
__u32 qlen;
- cond_resched();
nlh = nlmsg_put(skb, portid, seq, event, sizeof(*tcm), flags);
if (!nlh)
goto out_nlmsg_trim;
@@ -941,7 +940,7 @@ static int tc_fill_qdisc(struct sk_buff *skb, struct Qdisc *q, u32 clid,
goto nla_put_failure;
qlen = qdisc_qlen_sum(q);
- stab = rtnl_dereference(q->stab);
+ stab = rcu_dereference(q->stab);
if (stab && qdisc_dump_stab(skb, stab) < 0)
goto nla_put_failure;
@@ -1888,14 +1887,14 @@ static int tc_dump_qdisc(struct sk_buff *skb, struct netlink_callback *cb)
s_q_idx = q_idx = cb->args[1];
idx = 0;
- ASSERT_RTNL();
err = nlmsg_parse_deprecated(nlh, sizeof(struct tcmsg), tca, TCA_MAX,
rtm_tca_policy, cb->extack);
if (err < 0)
return err;
- for_each_netdev(net, dev) {
+ rcu_read_lock();
+ for_each_netdev_rcu(net, dev) {
struct netdev_queue *dev_queue;
if (idx < s_idx)
@@ -1904,29 +1903,26 @@ static int tc_dump_qdisc(struct sk_buff *skb, struct netlink_callback *cb)
s_q_idx = 0;
q_idx = 0;
- netdev_lock_ops(dev);
- if (tc_dump_qdisc_root(rtnl_dereference(dev->qdisc),
+ if (tc_dump_qdisc_root(rcu_dereference(dev->qdisc),
skb, cb, &q_idx, s_q_idx,
true, tca[TCA_DUMP_INVISIBLE]) < 0) {
- netdev_unlock_ops(dev);
goto done;
}
- dev_queue = dev_ingress_queue(dev);
+ dev_queue = dev_ingress_queue_rcu(dev);
if (dev_queue &&
- tc_dump_qdisc_root(rtnl_dereference(dev_queue->qdisc_sleeping),
+ tc_dump_qdisc_root(rcu_dereference(dev_queue->qdisc_sleeping),
skb, cb, &q_idx, s_q_idx, false,
tca[TCA_DUMP_INVISIBLE]) < 0) {
- netdev_unlock_ops(dev);
goto done;
}
- netdev_unlock_ops(dev);
cont:
idx++;
}
done:
+ rcu_read_unlock();
cb->args[0] = idx;
cb->args[1] = q_idx;
@@ -2487,7 +2483,8 @@ static const struct rtnl_msg_handler psched_rtnl_msg_handlers[] __initconst = {
{.msgtype = RTM_NEWQDISC, .doit = tc_modify_qdisc},
{.msgtype = RTM_DELQDISC, .doit = tc_get_qdisc},
{.msgtype = RTM_GETQDISC, .doit = tc_get_qdisc,
- .dumpit = tc_dump_qdisc},
+ .dumpit = tc_dump_qdisc,
+ .flags = RTNL_FLAG_DUMP_UNLOCKED},
{.msgtype = RTM_NEWTCLASS, .doit = tc_ctl_tclass},
{.msgtype = RTM_DELTCLASS, .doit = tc_ctl_tclass},
{.msgtype = RTM_GETTCLASS, .doit = tc_ctl_tclass,
diff --git a/net/sched/sch_fq.c b/net/sched/sch_fq.c
index dd553d6f3e8e911e161c1440eb6d9ce94f65385a..c13881a5c2b4ecb37b41b906ad96bb5de2653fe8 100644
--- a/net/sched/sch_fq.c
+++ b/net/sched/sch_fq.c
@@ -1286,7 +1286,7 @@ static int fq_dump_stats(struct Qdisc *sch, struct gnet_dump *d)
st.pad = 0;
- sch_tree_lock(sch);
+ sch_tree_lock_rcu(sch);
st.gc_flows = q->stat_gc_flows;
st.highprio_packets = 0;
@@ -1310,7 +1310,7 @@ static int fq_dump_stats(struct Qdisc *sch, struct gnet_dump *d)
st.band_drops[i] = q->stat_band_drops[i];
st.band_pkt_count[i] = q->band_pkt_count[i];
}
- sch_tree_unlock(sch);
+ sch_tree_unlock_rcu(sch);
return gnet_stats_copy_app(d, &st, sizeof(st));
}
diff --git a/net/sched/sch_fq_codel.c b/net/sched/sch_fq_codel.c
index db5cf578cbd63e842381cb147ced145fed4cca3e..e14d014041c2e33b344455f089ab69e93a13feca 100644
--- a/net/sched/sch_fq_codel.c
+++ b/net/sched/sch_fq_codel.c
@@ -586,7 +586,7 @@ static int fq_codel_dump_stats(struct Qdisc *sch, struct gnet_dump *d)
};
struct list_head *pos;
- sch_tree_lock(sch);
+ sch_tree_lock_rcu(sch);
st.qdisc_stats.maxpacket = q->cstats.maxpacket;
st.qdisc_stats.drop_overlimit = q->drop_overlimit;
@@ -601,7 +601,7 @@ static int fq_codel_dump_stats(struct Qdisc *sch, struct gnet_dump *d)
list_for_each(pos, &q->old_flows)
st.qdisc_stats.old_flows_len++;
- sch_tree_unlock(sch);
+ sch_tree_unlock_rcu(sch);
return gnet_stats_copy_app(d, &st, sizeof(st));
}
diff --git a/net/sched/sch_fq_pie.c b/net/sched/sch_fq_pie.c
index 72f48fa4010bebbe6be212938b457db21ff3c5a0..93b2abdb6328f63e2dcbfbee463ddd689a9d733d 100644
--- a/net/sched/sch_fq_pie.c
+++ b/net/sched/sch_fq_pie.c
@@ -512,7 +512,7 @@ static int fq_pie_dump_stats(struct Qdisc *sch, struct gnet_dump *d)
struct tc_fq_pie_xstats st = { 0 };
struct list_head *pos;
- sch_tree_lock(sch);
+ sch_tree_lock_rcu(sch);
st.packets_in = q->stats.packets_in;
st.overlimit = q->stats.overlimit;
@@ -527,7 +527,7 @@ static int fq_pie_dump_stats(struct Qdisc *sch, struct gnet_dump *d)
list_for_each(pos, &q->old_flows)
st.old_flows_len++;
- sch_tree_unlock(sch);
+ sch_tree_unlock_rcu(sch);
return gnet_stats_copy_app(d, &st, sizeof(st));
}
diff --git a/net/sched/sch_gred.c b/net/sched/sch_gred.c
index 36d0cafac2063f3ba1133ad9b8fab08ce4468550..28a99640da95a73117697cbf220109556a253343 100644
--- a/net/sched/sch_gred.c
+++ b/net/sched/sch_gred.c
@@ -377,7 +377,7 @@ static int gred_offload_dump_stats(struct Qdisc *sch)
/* Even if driver returns failure adjust the stats - in case offload
* ended but driver still wants to adjust the values.
*/
- sch_tree_lock(sch);
+ sch_tree_lock_rcu(sch);
for (i = 0; i < MAX_DPs; i++) {
if (!table->tab[i])
continue;
@@ -394,7 +394,7 @@ static int gred_offload_dump_stats(struct Qdisc *sch)
sch->qstats.overlimits += hw_stats->stats.qstats[i].overlimits;
}
_bstats_update(&sch->bstats, bytes, packets);
- sch_tree_unlock(sch);
+ sch_tree_unlock_rcu(sch);
kfree(hw_stats);
return ret;
diff --git a/net/sched/sch_mq.c b/net/sched/sch_mq.c
index 1e4522436620e5ee3a0417996476b37d9abca299..eaffac2453bbd81bcf2655804e22ab9167d0660a 100644
--- a/net/sched/sch_mq.c
+++ b/net/sched/sch_mq.c
@@ -155,7 +155,7 @@ void mq_dump_common(struct Qdisc *sch, struct sk_buff *skb)
* qdisc totals are added at end.
*/
for (ntx = 0; ntx < dev->num_tx_queues; ntx++) {
- qdisc = rtnl_dereference(netdev_get_tx_queue(dev, ntx)->qdisc_sleeping);
+ qdisc = rcu_dereference(netdev_get_tx_queue(dev, ntx)->qdisc_sleeping);
gnet_stats_add_basic(&bstats, qdisc->cpu_bstats,
&qdisc->bstats, false);
diff --git a/net/sched/sch_mqprio.c b/net/sched/sch_mqprio.c
index ac6d7d0a35309a5375425790f89f587601511cc7..b585b94d0cef8e13a416d3c2f969e421bc11cbf5 100644
--- a/net/sched/sch_mqprio.c
+++ b/net/sched/sch_mqprio.c
@@ -567,7 +567,7 @@ static int mqprio_dump(struct Qdisc *sch, struct sk_buff *skb)
* qdisc totals are added at end.
*/
for (ntx = 0; ntx < dev->num_tx_queues; ntx++) {
- qdisc = rtnl_dereference(netdev_get_tx_queue(dev, ntx)->qdisc_sleeping);
+ qdisc = rcu_dereference(netdev_get_tx_queue(dev, ntx)->qdisc_sleeping);
gnet_stats_add_basic(&bstats, qdisc->cpu_bstats,
&qdisc->bstats, false);
diff --git a/net/sched/sch_taprio.c b/net/sched/sch_taprio.c
index 885a9bc859166dfb6d20aa0dfbb8f11194e02ba9..93a366ba793bf92afe8359d2844b7673326ef79c 100644
--- a/net/sched/sch_taprio.c
+++ b/net/sched/sch_taprio.c
@@ -2404,23 +2404,21 @@ static int taprio_dump(struct Qdisc *sch, struct sk_buff *skb)
nla_put_u32(skb, TCA_TAPRIO_ATTR_TXTIME_DELAY, q->txtime_delay))
goto options_error;
- rcu_read_lock();
-
- oper = rtnl_dereference(q->oper_sched);
- admin = rtnl_dereference(q->admin_sched);
+ oper = rcu_dereference(q->oper_sched);
+ admin = rcu_dereference(q->admin_sched);
if (oper && taprio_dump_tc_entries(skb, q, oper))
- goto options_error_rcu;
+ goto options_error;
if (oper && dump_schedule(skb, oper))
- goto options_error_rcu;
+ goto options_error;
if (!admin)
goto done;
sched_nest = nla_nest_start_noflag(skb, TCA_TAPRIO_ATTR_ADMIN_SCHED);
if (!sched_nest)
- goto options_error_rcu;
+ goto options_error;
if (dump_schedule(skb, admin))
goto admin_error;
@@ -2428,15 +2426,11 @@ static int taprio_dump(struct Qdisc *sch, struct sk_buff *skb)
nla_nest_end(skb, sched_nest);
done:
- rcu_read_unlock();
return nla_nest_end(skb, nest);
admin_error:
nla_nest_cancel(skb, sched_nest);
-options_error_rcu:
- rcu_read_unlock();
-
options_error:
nla_nest_cancel(skb, nest);
--
2.53.0.1213.gd9a14994de-goog
^ permalink raw reply related
* Re: [PATCH net-next v2 1/4] dt-bindings: net: dsa: add MT7628 ESW
From: Rob Herring @ 2026-04-08 13:00 UTC (permalink / raw)
To: Joris Vaisvila
Cc: netdev, horms, pabeni, kuba, edumazet, davem, olteanv,
Andrew Lunn, devicetree, Krzysztof Kozlowski, Conor Dooley
In-Reply-To: <20260330184017.766200-2-joey@tinyisr.com>
On Mon, Mar 30, 2026 at 09:40:14PM +0300, Joris Vaisvila wrote:
> Add bindings for MT7628 SoC's Embedded Switch.
>
> Signed-off-by: Joris Vaisvila <joey@tinyisr.com>
> ---
> .../bindings/net/dsa/mediatek,mt7628-esw.yaml | 101 ++++++++++++++++++
> 1 file changed, 101 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/net/dsa/mediatek,mt7628-esw.yaml
>
> diff --git a/Documentation/devicetree/bindings/net/dsa/mediatek,mt7628-esw.yaml b/Documentation/devicetree/bindings/net/dsa/mediatek,mt7628-esw.yaml
> new file mode 100644
> index 000000000000..d6c66ab677d0
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/net/dsa/mediatek,mt7628-esw.yaml
> @@ -0,0 +1,101 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/net/dsa/mediatek,mt7628-esw.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Mediatek MT7628 Embedded Ethernet Switch
> +
> +maintainers:
> + - Joris Vaisvila <joey@tinyisr.com>
> +
> +description:
> + The MT7628 SoC's built-in Ethernet Switch is a five port switch with
> + integrated 10/100 PHYs. The switch registers are directly mapped in the SoC's
> + memory. The switch has an internally connected 1G CPU port and 5 user ports
> + connected to the built-in Fast Ethernet PHYs.
> +
> +unevaluatedProperties: false
> +
> +allOf:
> + - $ref: dsa.yaml#/$defs/ethernet-ports
> +
> +properties:
> + compatible:
> + const: mediatek,mt7628-esw
> +
> + reg:
> + maxItems: 1
> + description: MMIO address of the switch
Drop the description.
> +
> + resets:
> + items:
> + - description: Phandle of system reset controller with ESW reset index
> + - description: Phandle of system reset controller with EPHY reset index
Just describe these in terms of what they reset, not the provider.
> +
> + reset-names:
> + items:
> + - const: esw
> + - const: ephy
> +
> + mdio:
> + $ref: /schemas/net/mdio.yaml#
> + unevaluatedProperties: false
> +
> +required:
> + - compatible
> + - reg
> + - resets
> + - reset-names
Surely 'ethernet-ports' is required?
> +
> +examples:
> + - |
> + switch0: switch@10110000 {
> + reg = <0x10110000 0x8000>;
> +
> + resets = <&sysc 23>, <&sysc 24>;
> + reset-names = "esw", "ephy";
> +
> + compatible = "mediatek,mt7628-esw";
compatible goes before reg.
> +
> + ports {
"ethernet-ports" for new bindings.
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + port@0 {
And ethernet-port
> + reg = <0>;
> + phy-mode = "internal";
> + };
> +
> + port@1 {
> + reg = <1>;
> + phy-mode = "internal";
> + };
> +
> + port@2 {
> + reg = <2>;
> + phy-mode = "internal";
> + };
> +
> + port@3 {
> + reg = <3>;
> + phy-mode = "internal";
> + };
> +
> + port@4 {
> + reg = <4>;
> + phy-mode = "internal";
> + };
> +
> + port@6 {
> + reg = <6>;
> + phy-mode = "internal";
> + ethernet = <ðernet>;
> +
> + fixed-link {
> + speed = <1000>;
> + full-duplex;
> + };
> + };
> + };
> + };
> --
> 2.53.0
>
^ permalink raw reply
* BUG: net-next (7.0-rc6 based and later) fails to boot on Jetson Xavier NX
From: Russell King (Oracle) @ 2026-04-08 13:07 UTC (permalink / raw)
To: netdev, linux-arm-kernel, linux-kernel, iommu, linux-ext4,
Linus Torvalds
Cc: Marek Szyprowski, Robin Murphy, Theodore Ts'o, Andreas Dilger
Hi,
Just a heads-up that current net-next (v7.0-rc6 based) fails to boot on
my nVidia Jetson Xavier platform. v7.0-rc5 and v6.14 based net-next both
boot fine. This is an arm64 platform.
The problem appears to be completely random in terms of its symptoms,
and looks like severe memory corruption - every boot seems to produce
a different problem. The common theme is, although the kernel gets to
userspace, it never gets anywhere close to a login prompt before
failing in some way.
The last net-next+ boot (which is currently v7.0-rc6 based) resulted
in:
tegra-mc 2c00000.memory-controller: xusb_hostw: secure write @0x00000003ffffff00: VPR violation ((null))
...
irq 91: nobody cared (try booting with the "irqpoll" option)
...
depmod: ERROR: could not open directory /lib/modules/7.0.0-rc6-net-next+: No such file or directory
...
Unable to handle kernel paging request at virtual address 0003201fd50320cf
A previous boot of the exact same kernel didn't oops, but was unable
to find the block device to mount for /mnt via block UUID.
A previous boot to that resulted in an oops.
The intersting thing is - the depmod error above is incorrect:
root@tegra-ubuntu:~# ls -ld /lib/modules/7.0.0-rc6-net-next+
drwxrwxr-x 3 root root 4096 Apr 8 10:23 /lib/modules/7.0.0-rc6-net-next+
The directory is definitely there, and is readable - checked after
booting back into net-next based on 7.0-rc5. In some of these boots,
stmmac hasn't probed yet, which rules out my changes.
Rootfs is ext4, and it seems there were a lot of ext4 commits merged
between rc5 and rc6, but nothing for rc7.
My current net-next head is dfecb0c5af3b. Merging rc7 on top also
fails, I suspect also randomly, with that I just got:
EXT4-fs (mmcblk0p1): VFS: Can't find ext4 filesystem
mount: /mnt: wrong fs type, bad option, bad superblock on /dev/mmcblk0p1, missing codepage or helper program, or other error.
mount: /mnt/: can't find PARTUUID=741c0777-391a-4bce-a222-455e180ece2a.
Unable to handle kernel paging request at virtual address f9bf0011ac0fb893
Mem abort info:
ESR = 0x0000000096000004
EC = 0x25: DABT (current EL), IL = 32 bits
SET = 0, FnV = 0
EA = 0, S1PTW = 0
FSC = 0x04: level 0 translation fault
Data abort info:
ISV = 0, ISS = 0x00000004, ISS2 = 0x00000000
CM = 0, WnR = 0, TnD = 0, TagAccess = 0
GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0
[f9bf0011ac0fb893] address between user and kernel address ranges
Internal error: Oops: 0000000096000004 [#1] SMP
Modules linked in:
CPU: 1 UID: 0 PID: 936 Comm: mount Not tainted 7.0.0-rc7-net-next+ #649 PREEMPT
Hardware name: NVIDIA NVIDIA Jetson Xavier NX Developer Kit/Jetson, BIOS 6.0-37391689 08/28/2024
pstate: 20400009 (nzCv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--)
pc : refill_objects+0x298/0x5ec
lr : refill_objects+0x1f0/0x5ec
...
Call trace:
refill_objects+0x298/0x5ec (P)
__pcs_replace_empty_main+0x13c/0x3a8
kmem_cache_alloc_noprof+0x324/0x3a0
alloc_iova+0x3c/0x290
alloc_iova_fast+0x168/0x2d4
iommu_dma_alloc_iova+0x84/0x154
iommu_dma_map_sg+0x2c4/0x538
__dma_map_sg_attrs+0x124/0x2c0
dma_map_sg_attrs+0x10/0x20
sdhci_pre_dma_transfer+0xb8/0x164
sdhci_pre_req+0x38/0x44
mmc_blk_mq_issue_rq+0x3dc/0x920
mmc_mq_queue_rq+0x104/0x2b0
__blk_mq_issue_directly+0x38/0xb0
blk_mq_request_issue_directly+0x54/0xb4
blk_mq_issue_direct+0x84/0x180
blk_mq_dispatch_queue_requests+0x1a8/0x2e0
blk_mq_flush_plug_list+0x60/0x140
__blk_flush_plug+0xe0/0x11c
blk_finish_plug+0x38/0x4c
read_pages+0x158/0x260
page_cache_ra_unbounded+0x158/0x3e0
force_page_cache_ra+0xb0/0xe4
page_cache_sync_ra+0x88/0x480
filemap_get_pages+0xd8/0x850
filemap_read+0xdc/0x3d8
blkdev_read_iter+0x84/0x198
vfs_read+0x208/0x2d8
ksys_read+0x58/0xf4
__arm64_sys_read+0x1c/0x28
invoke_syscall.constprop.0+0x50/0xe0
do_el0_svc+0x40/0xc0
el0_svc+0x48/0x2a0
el0t_64_sync_handler+0xa0/0xe4
el0t_64_sync+0x19c/0x1a0
Code: 54000189 f9000022 aa0203e4 b9402ae3 (f8634840)
---[ end trace 0000000000000000 ]---
Kernel panic - not syncing: Oops: Fatal exception
Looking at the changes between rc5 and rc6, there's one drivers/block
change for zram (which is used on this platform), one change in
drivers/base for regmap, nothing for drivers/mmc, but plenty for
fs/ext4. There are five DMA API changes.
Now building straight -rc7. If that also fails, my plan is to start
bisecting rc5..rc6, which will likely take most of the rest of the
day. So, in the mean time I'm sending this as a heads-up that rc6
and onwards has a problem.
I'll update when I have a potential commit located.
--
RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
FTTP is here! 80Mbps down 10Mbps up. Decent connectivity at last!
^ permalink raw reply
* Re: [PATCH RESEND][next] Bluetooth: hci.h: Avoid a couple -Wflex-array-member-not-at-end warnings
From: patchwork-bot+bluetooth @ 2026-04-08 13:10 UTC (permalink / raw)
To: Gustavo A. R. Silva
Cc: marcel, johan.hedberg, luiz.dentz, davem, edumazet, kuba, pabeni,
horms, linux-bluetooth, netdev, linux-kernel, linux-hardening
In-Reply-To: <adVmLsJCQrBCMDD6@kspp>
Hello:
This patch was applied to bluetooth/bluetooth-next.git (master)
by Luiz Augusto von Dentz <luiz.von.dentz@intel.com>:
On Tue, 7 Apr 2026 14:16:46 -0600 you wrote:
> -Wflex-array-member-not-at-end was introduced in GCC-14, and we are
> getting ready to enable it, globally.
>
> struct hci_std_codecs and struct hci_std_codecs_v2 are flexible
> structures, this is structures that contain a flexible-array member
> (__u8 codec[]; and struct hci_std_codec_v2 codec[];, correspondingly.)
>
> [...]
Here is the summary with links:
- [RESEND,next] Bluetooth: hci.h: Avoid a couple -Wflex-array-member-not-at-end warnings
https://git.kernel.org/bluetooth/bluetooth-next/c/710f92b5b912
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply
* Re: [Intel-wired-lan] [PATCH iwl-next] e1000e: use ktime_get_real_ns() in e1000e_systim_reset()
From: Kohei Enju @ 2026-04-08 13:11 UTC (permalink / raw)
To: Aleksandr Loktionov
Cc: intel-wired-lan, anthony.l.nguyen, netdev, Jacob Keller,
Simon Horman
In-Reply-To: <20260408083644.1621317-1-aleksandr.loktionov@intel.com>
On 04/08 10:36, Aleksandr Loktionov wrote:
> Replace ktime_to_ns(ktime_get_real()) with the direct equivalent
> ktime_get_real_ns() in e1000e_systim_reset(). Using the combined helper
> avoids the unnecessary intermediate ktime_t variable and makes the
> intent clearer.
FYI, e1000e_xtal_tgp_workaround(), introduced in a recent commit, has
the same pattern as well, though that commit only exists in Tony's tree.
Also, igc has the same pattern in igc_ptp_reset(). It may be worth
sending a follow-up patch for that as well?
^ permalink raw reply
* [PATCH iwl-net v2 0/6] ixgbe: six bug fixes
From: Aleksandr Loktionov @ 2026-04-08 13:11 UTC (permalink / raw)
To: intel-wired-lan, anthony.l.nguyen, aleksandr.loktionov; +Cc: netdev
Six fixes for the ixgbe driver, covering a SWFW semaphore timeout
miscalculation, a security-relevant debugfs out-of-bounds, a broken
flow-control NVM-reset path, a false-success return in the cls_u32
nexthdr path, an adaptive-ITR u8 overflow, and wrong bit positions in
the UP-to-TC register normalisation.
Patches 1-3 fix issues that could result in functional regressions
(FW update failures, OOB MMIO, traffic stall after NVM update).
Patches 4-6 fix correctness bugs with user-visible effects.
Patch 1 is a squash of two previously independent submissions:
- "ixgbe: increase SWFW semaphore timeout for X550 FW updates"
(Soumen Karmakar)
- "ixgbe: extend 5 s SWFW semaphore timeout to all X550EM variants"
(Marta Plantykow)
The commit message for patch 1 also fixes an error noted in review:
the previous description stated "200 ms" but the actual default is
1 s (200 iterations x 5 ms per poll). Patch 1 also replaces the
">= X550 && <= x550em_a" range check with three explicit mac.type
comparisons per Tony Nguyen's request.
Patch 3 additionally handles the setup_fc() failure path that was
left unaddressed in the prior posting: fc_enable() is now skipped
when setup_fc() fails so the MDD-triggering stale state is not
committed to hardware.
Changes in v2:
- 1/6: Squash two patches; fix commit msg ("200ms" -> "1s"); three
explicit mac.type == comparisons instead of range check.
- 2/6: Add Fixes: tag; reroute from iwl-next to iwl-net.
- 3/6: Add Fixes: tag; reroute to iwl-net; skip fc_enable() when
setup_fc() fails to avoid committing stale FC state.
- 4/6: Add Fixes: tag; reroute from iwl-next to iwl-net.
- 5/6: Add proper [N/M] patch numbering.
- 6/6: Reroute to iwl-net; swap to (expr >> ..) & MASK operand order.
---
Aleksandr Loktionov (5):
ixgbe: fix SWFW semaphore timeout for X550 family
ixgbe: call ixgbe_setup_fc() before fc_enable() after NVM update
ixgbe: fix cls_u32 nexthdr path returning success when no entry installed
ixgbe: fix ITR value overflow in adaptive interrupt throttling
ixgbe: fix integer overflow and wrong bit position in ixgbe_validate_rtr()
Paul Greenwalt (1):
ixgbe: add bounds check for debugfs register access
drivers/net/ethernet/intel/ixgbe/ixgbe_debugfs.c | 4 ++--
drivers/net/ethernet/intel/ixgbe/ixgbe_main.c | 18 ++++++++++++------
drivers/net/ethernet/intel/ixgbe/ixgbe_x540.c | 8 ++++++++
3 files changed, 22 insertions(+), 8 deletions(-)
--
2.52.0
^ permalink raw reply
* [PATCH iwl-net v2 1/6] ixgbe: fix SWFW semaphore timeout for X550 family
From: Aleksandr Loktionov @ 2026-04-08 13:11 UTC (permalink / raw)
To: intel-wired-lan, anthony.l.nguyen, aleksandr.loktionov; +Cc: netdev
In-Reply-To: <20260408131154.2661818-1-aleksandr.loktionov@intel.com>
According to FW documentation, the most time-consuming FW operation is
Shadow RAM (SR) dump which takes up to 3.2 seconds. For X550 family
devices the module-update FW command can take over 4.5 s. The default
semaphore loop runs 200 iterations with a 5 ms sleep each, giving a
maximum wait of 1 s -- not "200 ms" as previously stated in error.
This is insufficient for X550 family FW update operations and causes
spurious EBUSY failures.
Extend the SW/FW semaphore timeout from 1 s to 5 s (1000 iterations x
5 ms) for all three X550 variants: ixgbe_mac_X550, ixgbe_mac_X550EM_x,
and ixgbe_mac_x550em_a. All three share the same FW and exhibit the
same worst-case latency. Use three explicit mac.type comparisons rather
than a range check so future MAC additions are not inadvertently
captured.
The timeout variable is set immediately before the loop so the intent
is clear, with an inline comment stating the resulting maximum delay.
Suggested-by: Soumen Karmakar <soumen.karmakar@intel.com>
Cc: stable@vger.kernel.org
Suggested-by: Marta Plantykow <marta.a.plantykow@intel.com>
Signed-off-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
---
v1 -> v2:
- Squash with 0015 (X550EM extension); fix commit message ("200ms" was
wrong, actual default is 1 s); replace >= / <= range check with three
explicit mac.type == comparisons per Tony Nguyen.
drivers/net/ethernet/intel/ixgbe/ixgbe_x540.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_x540.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_x540.c
index e67e2fe..a3c8f51 100644
--- a/drivers/net/ethernet/intel/ixgbe/ixgbe_x540.c
+++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_x540.c
@@ -577,6 +577,15 @@ int ixgbe_acquire_swfw_sync_X540(struct ixgbe_hw *hw, u32 mask)
swmask |= swi2c_mask;
fwmask |= swi2c_mask << 2;
+ /* Extend to 5 s (1000 x 5 ms) for X550 family; default is 1 s
+ * (200 x 5 ms). FW SR-dump takes up to 3.2 s; module-update up
+ * to 4.5 s.
+ */
+ if (hw->mac.type == ixgbe_mac_X550 ||
+ hw->mac.type == ixgbe_mac_X550EM_x ||
+ hw->mac.type == ixgbe_mac_x550em_a)
+ timeout = 1000;
+
for (i = 0; i < timeout; i++) {
/* SW NVM semaphore bit is used for access to all
* SW_FW_SYNC bits (not just NVM)
--
2.52.0
^ permalink raw reply related
* [PATCH iwl-net v2 2/6] ixgbe: add bounds check for debugfs register access
From: Aleksandr Loktionov @ 2026-04-08 13:11 UTC (permalink / raw)
To: intel-wired-lan, anthony.l.nguyen, aleksandr.loktionov
Cc: netdev, Paul Greenwalt
In-Reply-To: <20260408131154.2661818-1-aleksandr.loktionov@intel.com>
From: Paul Greenwalt <paul.greenwalt@intel.com>
Prevent out-of-bounds MMIO accesses triggered through user-controlled
register offsets. IXGBE_HFDR (0x15FE8) is the highest valid MMIO
register in the ixgbe register map; any offset beyond it would address
unmapped memory.
Add a defense-in-depth check at two levels:
1. ixgbe_read_reg() -- the noinline register read accessor. A
WARN_ON_ONCE() guard here catches any future code path (including
ioctl extensions) that might inadvertently pass an out-of-range
offset without relying on higher layers to catch it first.
ixgbe_write_reg() is a static inline called from the TX/RX hot path;
adding WARN_ON_ONCE there would inline the check at every call site,
so only the read path gets this guard.
2. ixgbe_dbg_reg_ops_write() -- the debugfs 'reg_ops' interface is the
only current path where a raw, user-supplied offset enters the driver.
Gating it before invoking the register accessors provides a clean,
user-visible failure (silent ignore with no kernel splat) for
deliberately malformed debugfs writes.
Add a reg <= IXGBE_HFDR guard to both the read and write paths in
ixgbe_dbg_reg_ops_write(), and a WARN_ON_ONCE + early-return guard to
ixgbe_read_reg().
Fixes: 91fbd8f081e2 ("ixgbe: added reg_ops file to debugfs")
Signed-off-by: Paul Greenwalt <paul.greenwalt@intel.com>
Cc: stable@vger.kernel.org
Signed-off-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
---
v1 -> v2:
- Add Fixes: tag; reroute from iwl-next to iwl-net (security-relevant
hardening for user-controllable out-of-bounds MMIO).
drivers/net/ethernet/intel/ixgbe/ixgbe_debugfs.c | 6 ++++--
drivers/net/ethernet/intel/ixgbe/ixgbe_main.c | 2 ++
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_debugfs.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_debugfs.c
index 5b1cf49d..a6a19c0 100644
--- a/drivers/net/ethernet/intel/ixgbe/ixgbe_debugfs.c
+++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_debugfs.c
@@ -86,7 +86,8 @@ static ssize_t ixgbe_dbg_reg_ops_write(struct file *filp,
u32 reg, value;
int cnt;
cnt = sscanf(&ixgbe_dbg_reg_ops_buf[5], "%x %x", ®, &value);
- if (cnt == 2) {
+ /* bounds-check register offset */
+ if (cnt == 2 && reg <= IXGBE_HFDR) {
IXGBE_WRITE_REG(&adapter->hw, reg, value);
value = IXGBE_READ_REG(&adapter->hw, reg);
e_dev_info("write: 0x%08x = 0x%08x\n", reg, value);
@@ -97,7 +98,8 @@ static ssize_t ixgbe_dbg_reg_ops_write(struct file *filp,
u32 reg, value;
int cnt;
cnt = sscanf(&ixgbe_dbg_reg_ops_buf[4], "%x", ®);
- if (cnt == 1) {
+ /* bounds-check register offset */
+ if (cnt == 1 && reg <= IXGBE_HFDR) {
value = IXGBE_READ_REG(&adapter->hw, reg);
e_dev_info("read 0x%08x = 0x%08x\n", reg, value);
} else {
diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
index 210c7b9..4a1f3c2 100644
--- a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
+++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
@@ -354,4 +354,6 @@ u32 ixgbe_read_reg(struct ixgbe_hw *hw, u32 reg)
if (ixgbe_removed(reg_addr))
return IXGBE_FAILED_READ_REG;
+ if (WARN_ON_ONCE(reg > IXGBE_HFDR))
+ return IXGBE_FAILED_READ_REG;
if (unlikely(hw->phy.nw_mng_if_sel &
IXGBE_NW_MNG_IF_SEL_SGMII_ENABLE)) {
--
2.52.0
^ permalink raw reply related
* [PATCH iwl-net v2 3/6] ixgbe: call ixgbe_setup_fc() before fc_enable() after NVM update
From: Aleksandr Loktionov @ 2026-04-08 13:11 UTC (permalink / raw)
To: intel-wired-lan, anthony.l.nguyen, aleksandr.loktionov; +Cc: netdev
In-Reply-To: <20260408131154.2661818-1-aleksandr.loktionov@intel.com>
During an NVM update the PHY reset clears the Technology Ability Field
(IEEE 802.3 clause 37 register 7.10) back to hardware defaults. When
the driver subsequently calls only hw->mac.ops.fc_enable() the SRRCTL
register is recalculated from stale autonegotiated capability bits,
which the MDD (Malicious Driver Detect) logic treats as an invalid
change and halts traffic on the PF.
Fix by calling ixgbe_setup_fc() immediately before fc_enable() in
ixgbe_watchdog_update_link() so that flow-control autoneg and the PHY
registers are re-programmed in the correct order after any reset.
Handle the failure path explicitly: if setup_fc() returns an error its
output is invalid and calling fc_enable() on the unchanged hardware
state would repeat the exact MDD-triggering condition the fix is meant
to prevent. Skip fc_enable() in that case while still calling
ixgbe_set_rx_drop_en() which configures the independent RX-drop
behaviour.
Fixes: 93c52dd0033b ("ixgbe: Merge watchdog functionality into service task")
Suggested-by: Radoslaw Tyl <radoslawx.tyl@intel.com>
Cc: stable@vger.kernel.org
Signed-off-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
---
v1 -> v2:
- Add Fixes: tag; reroute to iwl-net; handle setup_fc() failure by
skipping fc_enable() so stale FC state is never committed to hardware.
drivers/net/ethernet/intel/ixgbe/ixgbe_main.c | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
index 210c7b9..fc3bae9 100644
--- a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
+++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
@@ -8029,6 +8029,13 @@ static void ixgbe_watchdog_update_link(struct ixgbe_adapter *adapter)
pfc_en |= !!(adapter->ixgbe_ieee_pfc->pfc_en);
if (link_up && !((adapter->flags & IXGBE_FLAG_DCB_ENABLED) && pfc_en)) {
- hw->mac.ops.fc_enable(hw);
+ /* Re-program flow-control autoneg before applying the result.
+ * If setup_fc() fails its output is invalid; skip fc_enable()
+ * to avoid committing stale capability bits that trigger MDD.
+ */
+ if (hw->mac.ops.setup_fc && hw->mac.ops.setup_fc(hw))
+ e_warn(drv, "setup_fc failed, skipping fc_enable\n");
+ else
+ hw->mac.ops.fc_enable(hw);
ixgbe_set_rx_drop_en(adapter);
}
--
2.52.0
^ permalink raw reply related
* [PATCH iwl-net v2 4/6] ixgbe: fix cls_u32 nexthdr path returning success when no entry installed
From: Aleksandr Loktionov @ 2026-04-08 13:11 UTC (permalink / raw)
To: intel-wired-lan, anthony.l.nguyen, aleksandr.loktionov
Cc: netdev, Marcin Szycik
In-Reply-To: <20260408131154.2661818-1-aleksandr.loktionov@intel.com>
ixgbe_configure_clsu32() returns 0 (success) after the nexthdr loop
even when ixgbe_clsu32_build_input() fails for every candidate entry
and no jump-table slot is actually programmed. Callers that test the
return value would then falsely believe the filter was installed.
The variable 'err' already tracks the last ixgbe_clsu32_build_input()
return value; if the loop completes with a successful break, err is 0.
If all attempts failed, err holds the last failure code. Change the
unconditional 'return 0' to 'return err' so errors are propagated
correctly.
Fixes: 1cdaaf5405ba ("ixgbe: Match on multiple headers for cls_u32 offloads")
Signed-off-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Cc: stable@vger.kernel.org
Reviewed-by: Marcin Szycik <marcin.szycik@linux.intel.com>
---
v1 -> v2:
- Add Fixes: tag; reroute from iwl-next to iwl-net (false-success
return is a user-visible correctness bug, not a cleanup).
drivers/net/ethernet/intel/ixgbe/ixgbe_main.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
index 210c7b9..6e7f8a9 100644
--- a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
+++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
@@ -10311,7 +10311,7 @@ static int ixgbe_configure_clsu32(struct ixgbe_adapter *adapter,
kfree(jump);
}
}
- return 0;
+ return err;
}
input = kzalloc_obj(*input);
--
2.52.0
^ permalink raw reply related
* [PATCH iwl-net v2 5/6] ixgbe: fix ITR value overflow in adaptive interrupt throttling
From: Aleksandr Loktionov @ 2026-04-08 13:11 UTC (permalink / raw)
To: intel-wired-lan, anthony.l.nguyen, aleksandr.loktionov; +Cc: netdev
In-Reply-To: <20260408131154.2661818-1-aleksandr.loktionov@intel.com>
ixgbe_update_itr() packs a mode flag (IXGBE_ITR_ADAPTIVE_LATENCY,
bit 7) and a usecs delay (bits [6:0]) into an unsigned int, then
stores the combined value in ring_container->itr which is declared as
u8. Values above 0xFF wrap on truncation, corrupting both the delay
and the mode flag on the next readback.
Separate the mode bits from the usecs sub-field; clamp only the usecs
portion to [0, IXGBE_ITR_ADAPTIVE_LATENCY - 1] (= 0x7F) using min_t()
so overflow cannot bleed into bit 7.
Fixes: b4ded8327fea ("ixgbe: Update adaptive ITR algorithm")
Cc: stable@vger.kernel.org
Signed-off-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
---
v1 -> v2:
- Add proper [N/M] numbering so patchwork tracks it as part of the set;
no code change.
drivers/net/ethernet/intel/ixgbe/ixgbe_main.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
index 210c7b9..9f3ae21 100644
--- a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
+++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
@@ -2889,8 +2889,9 @@ static void ixgbe_update_itr(struct ixgbe_q_vector *q_vector,
}
clear_counts:
- /* write back value */
- ring_container->itr = itr;
+ ring_container->itr = (itr & IXGBE_ITR_ADAPTIVE_LATENCY) |
+ min_t(unsigned int, itr & ~IXGBE_ITR_ADAPTIVE_LATENCY,
+ IXGBE_ITR_ADAPTIVE_LATENCY - 1);
/* next update should occur within next jiffy */
ring_container->next_update = next_update + 1;
--
2.52.0
^ permalink raw reply related
* [PATCH iwl-net v2 6/6] ixgbe: fix integer overflow and wrong bit position in ixgbe_validate_rtr()
From: Aleksandr Loktionov @ 2026-04-08 13:11 UTC (permalink / raw)
To: intel-wired-lan, anthony.l.nguyen, aleksandr.loktionov; +Cc: netdev
In-Reply-To: <20260408131154.2661818-1-aleksandr.loktionov@intel.com>
Two bugs in the same loop in ixgbe_validate_rtr():
1. The 3-bit traffic-class field was extracted by shifting a u32 and
assigning the result directly to a u8. For user priority 0 this is
harmless; for UP[5..7] the shift leaves bits [15..21] in the u32
which are then silently truncated when stored in u8. Mask with
IXGBE_RTRUP2TC_UP_MASK before the assignment so only the intended
3 bits are kept.
2. When clearing an out-of-bounds entry the mask was always shifted by
the fixed constant IXGBE_RTRUP2TC_UP_SHIFT (== 3), regardless of
which loop iteration was being processed. This means only UP1 (bit
position 3) was ever cleared; UP0,2..7 (positions 0, 6, 9, ..., 21)
were left unreset, so invalid TC mappings persisted in hardware and
could mis-steer received packets to the wrong traffic class.
Use i * IXGBE_RTRUP2TC_UP_SHIFT to target the correct 3-bit field
for each iteration.
Swap the operand order in the mask expression to place the constant
on the right per kernel coding style (noted by David Laight).
Fixes: e7589eab9291 ("ixgbe: consolidate, setup for multiple traffic classes")
Cc: stable@vger.kernel.org
Signed-off-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
---
v1 -> v2:
- Add Fixes: tag; reroute to iwl-net (wrong bit positions cause packet
mis-steering); swap to (reg >> ...) & MASK operand order per David
Laight.
drivers/net/ethernet/intel/ixgbe/ixgbe_main.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
index 210c7b9..c9e4f12 100644
--- a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
+++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
@@ -9772,11 +9772,12 @@ static void ixgbe_validate_rtr(struct ixgbe_adapter *adapter, u8 tc)
rsave = reg;
for (i = 0; i < MAX_TRAFFIC_CLASS; i++) {
- u8 up2tc = reg >> (i * IXGBE_RTRUP2TC_UP_SHIFT);
+ u8 up2tc = (reg >> (i * IXGBE_RTRUP2TC_UP_SHIFT)) &
+ IXGBE_RTRUP2TC_UP_MASK;
/* If up2tc is out of bounds default to zero */
if (up2tc > tc)
- reg &= ~(0x7 << IXGBE_RTRUP2TC_UP_SHIFT);
+ reg &= ~(IXGBE_RTRUP2TC_UP_MASK << (i * IXGBE_RTRUP2TC_UP_SHIFT));
}
if (reg != rsave)
--
2.52.0
^ permalink raw reply related
* [PATCH iwl-next v2 0/8] ixgbe: nits and improvements
From: Aleksandr Loktionov @ 2026-04-08 13:12 UTC (permalink / raw)
To: intel-wired-lan, anthony.l.nguyen, aleksandr.loktionov; +Cc: netdev
Eight cleanups and improvements for the ixgbe driver.
Patches 1-4 split the adaptive-ITR algorithm improvements into
independently reviewable pieces as requested by Simon Horman. Each
patch is self-contained and can be reviewed and reverted independently:
1. Lower IXGBE_ITR_ADAPTIVE_MAX_USECS from 126 to 84 to prevent RX
starvation at minimum bulk-mode rates.
2. Add ixgbe_container_is_rx() helper and refine the RX-specific
latency algorithm (thresholds, no-packet handling, mode tracking).
3. Limit ITR decrease in latency mode to at most 2 us per update so
ACK workloads do not overdrive moderation.
4. Add IXGBE_ITR_ADAPTIVE_MASK_USECS constant to replace the
open-coded ~IXGBE_ITR_ADAPTIVE_LATENCY complement in ixgbe_set_itr.
Patch 5 removes ixgbe_ping_all_vfs() from both ixgbe_watchdog_link_is_up()
and ixgbe_watchdog_link_is_down(). The original submission only removed
the call from the link-up path; this version also removes it from the
link-down path, which carries the same race window against VF mailbox
initialization.
Patch 6 replaces ktime_to_ns(ktime_get_real()) with ktime_get_real_ns().
Patch 7 restructures ixgbe_fcoe_ddp_setup() so that dma_pool_alloc()
is called outside the get_cpu()/put_cpu() preemption-disabled section,
enabling GFP_KERNEL instead of GFP_ATOMIC. The prior submission left
the allocation inside the preempt-off block (GFP_KERNEL can sleep under
direct reclaim, which triggers a BUG() with preemption disabled).
Patch 8 fixes six local variables declared as u32 that are used to
store signed kernel error codes. Reviewed by Simon Horman.
Changes in v2:
- 1-4/8: Split monolithic ITR cleanup into four independent patches
as requested by Simon Horman.
- 5/8: Extend fix to also remove ixgbe_ping_all_vfs() from
ixgbe_watchdog_link_is_down() to cover the identical race.
- 6/8: No code change; add [N/M] numbering.
- 7/8: Move dma_pool_alloc() outside get_cpu()/put_cpu() so
GFP_KERNEL is used safely without sleeping under preempt-off.
- 8/8: Add Reviewed-by: Simon Horman; no code change.
---
Alexander Duyck (4):
ixgbe: lower IXGBE_ITR_ADAPTIVE_MAX_USECS to prevent RX starvation
ixgbe: add ixgbe_container_is_rx() helper and refine RX adaptive ITR
ixgbe: limit ITR decrease in latency mode to prevent ACK overdrive
ixgbe: add IXGBE_ITR_ADAPTIVE_MASK_USECS constant
Aleksandr Loktionov (2):
ixgbe: remove ixgbe_ping_all_vfs() from link state change handlers
ixgbe: use GFP_KERNEL in ixgbe_fcoe_ddp_setup()
Jacob Keller (1):
ixgbe: use ktime_get_real_ns() in ixgbe_ptp_reset()
Aleksandr Loktionov (1):
ixgbe: use int instead of u32 for error code variables
drivers/net/ethernet/intel/ixgbe/ixgbe.h | 5 +-
drivers/net/ethernet/intel/ixgbe/ixgbe_fcoe.c | 50 +++++++----
drivers/net/ethernet/intel/ixgbe/ixgbe_main.c | 97 ++++++++++++------
drivers/net/ethernet/intel/ixgbe/ixgbe_phy.c | 6 +-
drivers/net/ethernet/intel/ixgbe/ixgbe_ptp.c | 2 +-
drivers/net/ethernet/intel/ixgbe/ixgbe_x550.c | 12 +--
6 files changed, 113 insertions(+), 59 deletions(-)
--
2.52.0
^ permalink raw reply
* [PATCH iwl-next v2 1/8] ixgbe: lower IXGBE_ITR_ADAPTIVE_MAX_USECS to prevent RX starvation
From: Aleksandr Loktionov @ 2026-04-08 13:12 UTC (permalink / raw)
To: intel-wired-lan, anthony.l.nguyen, aleksandr.loktionov; +Cc: netdev
In-Reply-To: <20260408131216.2662245-1-aleksandr.loktionov@intel.com>
From: Alexander Duyck <alexander.h.duyck@intel.com>
At the current maximum of 126 us the minimum bulk-mode interrupt rate
is ~7936 interrupts/s. Under sustained full-line-rate bulk RX traffic
this is low enough that descriptor ring starvation can occur before the
next interrupt fires.
Lower IXGBE_ITR_ADAPTIVE_MAX_USECS from 126 to 84 us. This raises the
minimum rate to ~11905 interrupts/s (~12K ints/s), providing enough
headroom to drain the ring before it wraps.
Signed-off-by: Alexander Duyck <alexander.h.duyck@intel.com>
Signed-off-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
---
v1 -> v2:
- Split from monolithic ITR cleanup patch; this patch only lowers
IXGBE_ITR_ADAPTIVE_MAX_USECS from 126 to 84.
drivers/net/ethernet/intel/ixgbe/ixgbe.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe.h b/drivers/net/ethernet/intel/ixgbe/ixgbe.h
index 59a1cee4..b3f4d21 100644
--- a/drivers/net/ethernet/intel/ixgbe/ixgbe.h
+++ b/drivers/net/ethernet/intel/ixgbe/ixgbe.h
@@ -473,6 +473,6 @@ static inline unsigned int ixgbe_rx_pg_order(struct ixgbe_ring *ring)
#define IXGBE_ITR_ADAPTIVE_MIN_INC 2
#define IXGBE_ITR_ADAPTIVE_MIN_USECS 10
-#define IXGBE_ITR_ADAPTIVE_MAX_USECS 126
+#define IXGBE_ITR_ADAPTIVE_MAX_USECS 84
#define IXGBE_ITR_ADAPTIVE_LATENCY 0x80
#define IXGBE_ITR_ADAPTIVE_BULK 0x00
--
2.52.0
^ permalink raw reply related
* [PATCH iwl-next v2 2/8] ixgbe: add ixgbe_container_is_rx() helper and refine RX adaptive ITR
From: Aleksandr Loktionov @ 2026-04-08 13:12 UTC (permalink / raw)
To: intel-wired-lan, anthony.l.nguyen, aleksandr.loktionov; +Cc: netdev
In-Reply-To: <20260408131216.2662245-1-aleksandr.loktionov@intel.com>
From: Alexander Duyck <alexander.h.duyck@intel.com>
Add an ixgbe_container_is_rx() helper to cleanly distinguish RX from TX
ring containers inside ixgbe_update_itr().
Refine the RX-specific latency-detection path:
- Replace the shared "packets < 4 or bytes < 9000" threshold with an
RX-specific check of "1..23 packets and bytes < 12112". When that
condition holds, target 8x the observed byte count in the next
interval by computing avg_wire_size = (bytes + packets * 24) * 2,
clamped to [2560, 12800], and jumping directly to the speed-based
ITR calculation. This provides finer-grained control over low-rate
RX latency workloads without affecting TX.
- Remove the separate "no packets" special-case block. When packets
is 0 it falls into the "< 48" branch. The mode-tracking logic in
that branch is extended: fewer than 8 packets forces latency mode;
8..47 packets preserves the current mode. This replaces the old
unconditional "add LATENCY flag from ring_container->itr" carried
over from the removed block.
- Remove the adjust_by_size label and the associated "halve
avg_wire_size in latency mode" step. The Rx latency path now
pre-calculates avg_wire_size independently and the bulk path no
longer needs the halving to compensate for incorrect thresholds.
Rename the jump target to adjust_for_speed to reflect its purpose.
Signed-off-by: Alexander Duyck <alexander.h.duyck@intel.com>
Signed-off-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
---
v1 -> v2:
- Split from monolithic ITR cleanup; adds ixgbe_container_is_rx(),
refines RX latency thresholds (24 pkts / 12112 B), and removes the
separate no-packet and adjust_by_size code paths.
drivers/net/ethernet/intel/ixgbe/ixgbe_main.c | 72 ++++++++++--------
1 file changed, 41 insertions(+), 31 deletions(-)
diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
index 210c7b9..b3f4a72 100644
--- a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
+++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
@@ -2711,6 +2711,12 @@ static void ixgbe_configure_msix(struct ixgbe_adapter *adapter)
IXGBE_WRITE_REG(&adapter->hw, IXGBE_EIAC, mask);
}
+static bool ixgbe_container_is_rx(struct ixgbe_q_vector *q_vector,
+ struct ixgbe_ring_container *rc)
+{
+ return &q_vector->rx == rc;
+}
+
/**
* ixgbe_update_itr - update the dynamic ITR value based on statistics
* @q_vector: structure containing interrupt and ring information
@@ -2747,35 +2753,24 @@ static void ixgbe_update_itr(struct ixgbe_q_vector *q_vector,
goto clear_counts;
packets = ring_container->total_packets;
-
- /* We have no packets to actually measure against. This means
- * either one of the other queues on this vector is active or
- * we are a Tx queue doing TSO with too high of an interrupt rate.
- *
- * When this occurs just tick up our delay by the minimum value
- * and hope that this extra delay will prevent us from being called
- * without any work on our queue.
- */
- if (!packets) {
- itr = (q_vector->itr >> 2) + IXGBE_ITR_ADAPTIVE_MIN_INC;
- if (itr > IXGBE_ITR_ADAPTIVE_MAX_USECS)
- itr = IXGBE_ITR_ADAPTIVE_MAX_USECS;
- itr += ring_container->itr & IXGBE_ITR_ADAPTIVE_LATENCY;
- goto clear_counts;
- }
-
bytes = ring_container->total_bytes;
- /* If packets are less than 4 or bytes are less than 9000 assume
- * insufficient data to use bulk rate limiting approach. We are
- * likely latency driven.
- */
- if (packets < 4 && bytes < 9000) {
- itr = IXGBE_ITR_ADAPTIVE_LATENCY;
- goto adjust_by_size;
+ if (ixgbe_container_is_rx(q_vector, ring_container)) {
+ /* If Rx and there are 1 to 23 packets and bytes are less than
+ * 12112 assume insufficient data to use bulk rate limiting
+ * approach. Instead we will focus on simply trying to target
+ * receiving 8 times as much data in the next interrupt.
+ */
+ if (packets && packets < 24 && bytes < 12112) {
+ itr = IXGBE_ITR_ADAPTIVE_LATENCY;
+ avg_wire_size = (bytes + packets * 24) * 2;
+ avg_wire_size = clamp_t(unsigned int,
+ avg_wire_size, 2560, 12800);
+ goto adjust_for_speed;
+ }
}
- /* Between 4 and 48 we can assume that our current interrupt delay
+ /* Less than 48 packets we can assume that our current interrupt delay
* is only slightly too low. As such we should increase it by a small
* fixed amount.
*/
@@ -2783,6 +2778,20 @@ static void ixgbe_update_itr(struct ixgbe_q_vector *q_vector,
itr = (q_vector->itr >> 2) + IXGBE_ITR_ADAPTIVE_MIN_INC;
if (itr > IXGBE_ITR_ADAPTIVE_MAX_USECS)
itr = IXGBE_ITR_ADAPTIVE_MAX_USECS;
+
+ /* If sample size is 0 - 7 we should probably switch
+ * to latency mode instead of trying to control
+ * things as though we are in bulk.
+ *
+ * Otherwise if the number of packets is less than 48
+ * we should maintain whatever mode we are currently
+ * in. The range between 8 and 48 is the cross-over
+ * point between latency and bulk traffic.
+ */
+ if (packets < 8)
+ itr += IXGBE_ITR_ADAPTIVE_LATENCY;
+ else
+ itr += ring_container->itr & IXGBE_ITR_ADAPTIVE_LATENCY;
goto clear_counts;
}
@@ -2813,7 +2822,6 @@ static void ixgbe_update_itr(struct ixgbe_q_vector *q_vector,
*/
itr = IXGBE_ITR_ADAPTIVE_BULK;
-adjust_by_size:
/* If packet counts are 256 or greater we can assume we have a gross
* overestimation of what the rate should be. Instead of trying to fine
* tune it just use the formula below to try and dial in an exact value
@@ -2856,12 +2864,7 @@ static void ixgbe_update_itr(struct ixgbe_q_vector *q_vector,
avg_wire_size = 32256;
}
- /* If we are in low latency mode half our delay which doubles the rate
- * to somewhere between 100K to 16K ints/sec
- */
- if (itr & IXGBE_ITR_ADAPTIVE_LATENCY)
- avg_wire_size >>= 1;
-
+adjust_for_speed:
/* Resultant value is 256 times larger than it needs to be. This
* gives us room to adjust the value as needed to either increase
* or decrease the value based on link speeds of 10G, 2.5G, 1G, etc.
--
2.52.0
^ permalink raw reply related
* [PATCH iwl-next v2 3/8] ixgbe: limit ITR decrease in latency mode to prevent ACK overdrive
From: Aleksandr Loktionov @ 2026-04-08 13:12 UTC (permalink / raw)
To: intel-wired-lan, anthony.l.nguyen, aleksandr.loktionov; +Cc: netdev
In-Reply-To: <20260408131216.2662245-1-aleksandr.loktionov@intel.com>
From: Alexander Duyck <alexander.h.duyck@intel.com>
When operating in latency mode and the computed ITR is lower than the
current setting, the algorithm can reduce the interrupt rate too
aggressively in a single step. For a TCP workload this means the ACK
stream (a latency-sensitive, low-packet-rate workload) can drive the
moderation down to very high interrupt rates, starving CPU time from
the sender side.
After the speed-based ITR calculation is complete, check whether the
result is in latency mode and would decrease below the current setting.
If so, limit the decrease to at most IXGBE_ITR_ADAPTIVE_MIN_INC (2 us)
per update. This ensures the number of interrupts grows by no more
than 2x per adjustment step for latency-class workloads, dialling in
smoothly rather than overshooting.
Signed-off-by: Alexander Duyck <alexander.h.duyck@intel.com>
Signed-off-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
---
v1 -> v2:
- Split from monolithic ITR cleanup; this patch only adds the 2 us
per-step decrease limit for latency-mode workloads.
drivers/net/ethernet/intel/ixgbe/ixgbe_main.c | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
index b3f4a72..d7c5e83 100644
--- a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
+++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
@@ -2888,6 +2888,16 @@ static void ixgbe_update_itr(struct ixgbe_q_vector *q_vector,
break;
}
+ /* In the case of a latency specific workload only allow us to
+ * reduce the ITR by at most 2us. By doing this we should dial
+ * in so that our number of interrupts is no more than 2x the number
+ * of packets for the least busy workload. So for example in the case
+ * of a TCP workload the ACK packets being received would set the
+ * interrupt rate as they are a latency specific workload.
+ */
+ if ((itr & IXGBE_ITR_ADAPTIVE_LATENCY) && itr < ring_container->itr)
+ itr = ring_container->itr - IXGBE_ITR_ADAPTIVE_MIN_INC;
+
clear_counts:
/* write back value */
ring_container->itr = itr;
--
2.52.0
^ permalink raw reply related
* [PATCH iwl-next v2 4/8] ixgbe: add IXGBE_ITR_ADAPTIVE_MASK_USECS constant
From: Aleksandr Loktionov @ 2026-04-08 13:12 UTC (permalink / raw)
To: intel-wired-lan, anthony.l.nguyen, aleksandr.loktionov; +Cc: netdev
In-Reply-To: <20260408131216.2662245-1-aleksandr.loktionov@intel.com>
From: Alexander Duyck <alexander.h.duyck@intel.com>
ixgbe_set_itr() clears the mode flag (IXGBE_ITR_ADAPTIVE_LATENCY, bit 7)
with the open-coded complement expression ~IXGBE_ITR_ADAPTIVE_LATENCY.
This is equivalent to keeping only bits [6:0], i.e. the usecs sub-field.
Add IXGBE_ITR_ADAPTIVE_MASK_USECS = IXGBE_ITR_ADAPTIVE_LATENCY - 1 =
0x7F to name this mask explicitly and replace the open-coded AND-NOT
operation with the cleaner AND form. The two expressions are
arithmetically identical; the change improves readability.
Signed-off-by: Alexander Duyck <alexander.h.duyck@intel.com>
Signed-off-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
---
v1 -> v2:
- Split from monolithic ITR cleanup; this patch only adds the
IXGBE_ITR_ADAPTIVE_MASK_USECS constant and updates ixgbe_set_itr().
drivers/net/ethernet/intel/ixgbe/ixgbe.h | 1 +
drivers/net/ethernet/intel/ixgbe/ixgbe_main.c | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe.h b/drivers/net/ethernet/intel/ixgbe/ixgbe.h
index b3f4d21..c704cc6 100644
--- a/drivers/net/ethernet/intel/ixgbe/ixgbe.h
+++ b/drivers/net/ethernet/intel/ixgbe/ixgbe.h
@@ -475,6 +475,7 @@ static inline unsigned int ixgbe_rx_pg_order(struct ixgbe_ring *ring)
#define IXGBE_ITR_ADAPTIVE_MAX_USECS 84
#define IXGBE_ITR_ADAPTIVE_LATENCY 0x80
#define IXGBE_ITR_ADAPTIVE_BULK 0x00
+#define IXGBE_ITR_ADAPTIVE_MASK_USECS (IXGBE_ITR_ADAPTIVE_LATENCY - 1)
struct ixgbe_ring_container {
struct ixgbe_ring *ring; /* pointer to linked list of rings */
diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
index d7c5e83..1885fe8 100644
--- a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
+++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
@@ -2960,7 +2960,7 @@ static void ixgbe_set_itr(struct ixgbe_q_vector *q_vector)
new_itr = min(q_vector->rx.itr, q_vector->tx.itr);
/* Clear latency flag if set, shift into correct position */
- new_itr &= ~IXGBE_ITR_ADAPTIVE_LATENCY;
+ new_itr &= IXGBE_ITR_ADAPTIVE_MASK_USECS;
new_itr <<= 2;
if (new_itr != q_vector->itr) {
--
2.52.0
^ permalink raw reply related
* [PATCH iwl-next v2 5/8] ixgbe: remove ixgbe_ping_all_vfs() from link state change handlers
From: Aleksandr Loktionov @ 2026-04-08 13:12 UTC (permalink / raw)
To: intel-wired-lan, anthony.l.nguyen, aleksandr.loktionov; +Cc: netdev
In-Reply-To: <20260408131216.2662245-1-aleksandr.loktionov@intel.com>
When multiple VFs are brought up simultaneously, a VF can receive a
CTS (Clear To Send) mailbox message instead of the expected RESET |
(N)ACK response, because the watchdog fires a gratuitous ping right as
the VF mailbox transaction is in progress.
Remove the ixgbe_ping_all_vfs() call from both ixgbe_watchdog_link_is_up()
and ixgbe_watchdog_link_is_down(). Both paths contain an identical call
with the same race window during VF initialization: link-state changes
(up or down) can arrive while VFs are mid-initialization, and the
gratuitous ping from either direction collides with the VF mailbox state
machine for the same reason.
Link-state changes are already communicated to VFs through the normal
mailbox protocol; these extra pings are redundant on both paths.
Suggested-by: Sebastian Basierski <sebastianx.basierski@intel.com>
Signed-off-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
---
v1 -> v2:
- Extend fix to also remove ixgbe_ping_all_vfs() from
ixgbe_watchdog_link_is_down(), which carries the same race window;
justifies the asymmetry by eliminating it.
drivers/net/ethernet/intel/ixgbe/ixgbe_main.c | 6 ------
1 file changed, 6 deletions(-)
diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
index 1885fe8..7c3d821 100644
--- a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
+++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
@@ -8151,9 +8151,6 @@ static void ixgbe_watchdog_link_is_up(struct ixgbe_adapter *adapter)
/* update the default user priority for VFs */
ixgbe_update_default_up(adapter);
-
- /* ping all the active vfs to let them know link has changed */
- ixgbe_ping_all_vfs(adapter);
}
/**
@@ -8183,8 +8180,5 @@ static void ixgbe_watchdog_link_is_down(struct ixgbe_adapter *adapter)
e_info(drv, "NIC Link is Down\n");
netif_carrier_off(netdev);
-
- /* ping all the active vfs to let them know link has changed */
- ixgbe_ping_all_vfs(adapter);
}
static bool ixgbe_ring_tx_pending(struct ixgbe_adapter *adapter)
--
2.52.0
^ permalink raw reply related
* [PATCH iwl-next v2 6/8] ixgbe: use ktime_get_real_ns() in ixgbe_ptp_reset()
From: Aleksandr Loktionov @ 2026-04-08 13:12 UTC (permalink / raw)
To: intel-wired-lan, anthony.l.nguyen, aleksandr.loktionov
Cc: netdev, Jacob Keller, Marcin Szycik, Simon Horman
In-Reply-To: <20260408131216.2662245-1-aleksandr.loktionov@intel.com>
From: Jacob Keller <jacob.e.keller@intel.com>
Replace ktime_to_ns(ktime_get_real()) with the direct equivalent
ktime_get_real_ns() in ixgbe_ptp_reset(). Using the combined helper
avoids the unnecessary intermediate ktime_t variable and makes the
intent clearer.
Signed-off-by: Jacob Keller <jacob.e.keller@intel.com>
Signed-off-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
Reviewed-by: Marcin Szycik <marcin.szycik@linux.intel.com>
Reviewed-by: Simon Horman <horms@kernel.org>
---
v1 -> v2:
- Carry Reviewed-by: Simon Horman from reply email; add [N/M]
numbering; no code change.
drivers/net/ethernet/intel/ixgbe/ixgbe_ptp.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_ptp.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_ptp.c
index 6885d23..a7d1635 100644
--- a/drivers/net/ethernet/intel/ixgbe/ixgbe_ptp.c
+++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_ptp.c
@@ -1347,7 +1347,7 @@ void ixgbe_ptp_reset(struct ixgbe_adapter *adapter)
spin_lock_irqsave(&adapter->tmreg_lock, flags);
timecounter_init(&adapter->hw_tc, &adapter->hw_cc,
- ktime_to_ns(ktime_get_real()));
+ ktime_get_real_ns());
spin_unlock_irqrestore(&adapter->tmreg_lock, flags);
adapter->last_overflow_check = jiffies;
--
2.52.0
^ permalink raw reply related
* [PATCH iwl-next v2 7/8] ixgbe: use GFP_KERNEL in ixgbe_fcoe_ddp_setup()
From: Aleksandr Loktionov @ 2026-04-08 13:12 UTC (permalink / raw)
To: intel-wired-lan, anthony.l.nguyen, aleksandr.loktionov; +Cc: netdev
In-Reply-To: <20260408131216.2662245-1-aleksandr.loktionov@intel.com>
ixgbe_fcoe_ddp_setup() is always called from process context (FCoE
offload setup paths) and never from an atomic context. Using GFP_ATOMIC
is therefore unnecessarily restrictive and wastes memory allocator
headroom reserved for genuine atomic callers.
The previous attempt to change this to GFP_KERNEL placed the allocation
inside the get_cpu()/put_cpu() section, which disables preemption.
GFP_KERNEL can sleep under direct reclaim regardless of whether the
caller is in process context, which triggers a BUG() with preemption
disabled.
Restructure the function to split the get_cpu()/put_cpu() usage into
two narrow critical sections:
1. A short initial section that reads the per-CPU pool pointer and
validates it, then immediately calls put_cpu() before any allocation.
The pool pointer is saved in a local variable for use after the pin
is dropped.
2. A second section after the allocation that re-pins the CPU solely to
update per-CPU counters (noddp, noddp_ext_buff) inside the SG loop.
The DMA mapping and pool allocation sit between these two sections with
preemption enabled, making GFP_KERNEL safe. The pool pointer saved
from section 1 remains valid because per-CPU DMA pools are only
destroyed during interface teardown under RTNL, not during normal
operation.
Suggested-by: Sebastian Basierski <sebastianx.basierski@intel.com>
Signed-off-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
---
v1 -> v2:
- Move dma_pool_alloc() outside the get_cpu()/put_cpu() section;
split into two narrow preempt-off regions so GFP_KERNEL is safe.
drivers/net/ethernet/intel/ixgbe/ixgbe_fcoe.c | 51 +++++++++++---------
1 file changed, 35 insertions(+), 18 deletions(-)
diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_fcoe.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_fcoe.c
index 011fda9..064ad17 100644
--- a/drivers/net/ethernet/intel/ixgbe/ixgbe_fcoe.c
+++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_fcoe.c
@@ -139,6 +139,7 @@ static int ixgbe_fcoe_ddp_setup(struct net_device *netdev, u16 xid,
struct ixgbe_fcoe *fcoe;
struct ixgbe_fcoe_ddp *ddp;
struct ixgbe_fcoe_ddp_pool *ddp_pool;
+ struct dma_pool *pool;
struct scatterlist *sg;
unsigned int i, j, dmacount;
unsigned int len;
@@ -179,29 +180,43 @@ static int ixgbe_fcoe_ddp_setup(struct net_device *netdev, u16 xid,
return 0;
}
+ /* Pin to current CPU only to read the per-CPU pool pointer; drop
+ * the pin before any allocations that may sleep under direct reclaim.
+ */
ddp_pool = per_cpu_ptr(fcoe->ddp_pool, get_cpu());
if (!ddp_pool->pool) {
e_warn(drv, "xid=0x%x no ddp pool for fcoe\n", xid);
- goto out_noddp;
+ put_cpu();
+ return 0;
}
+ pool = ddp_pool->pool;
+ put_cpu();
/* setup dma from scsi command sgl */
dmacount = dma_map_sg(&adapter->pdev->dev, sgl, sgc, DMA_FROM_DEVICE);
if (dmacount == 0) {
e_err(drv, "xid 0x%x DMA map error\n", xid);
- goto out_noddp;
+ return 0;
}
- /* alloc the udl from per cpu ddp pool */
- ddp->udl = dma_pool_alloc(ddp_pool->pool, GFP_ATOMIC, &ddp->udp);
+ /* Allocate from per-CPU pool; GFP_KERNEL is safe: preemption is
+ * re-enabled after the put_cpu() above. Per-CPU DMA pools are only
+ * destroyed under RTNL during interface teardown, so the saved pool
+ * pointer remains valid.
+ */
+ ddp->udl = dma_pool_alloc(pool, GFP_KERNEL, &ddp->udp);
if (!ddp->udl) {
e_err(drv, "failed allocated ddp context\n");
- goto out_noddp_unmap;
+ dma_unmap_sg(&adapter->pdev->dev, sgl, sgc, DMA_FROM_DEVICE);
+ return 0;
}
- ddp->pool = ddp_pool->pool;
+ ddp->pool = pool;
ddp->sgl = sgl;
ddp->sgc = sgc;
+ /* Re-pin CPU for per-CPU statistics updates inside the SG loop. */
+ ddp_pool = per_cpu_ptr(fcoe->ddp_pool, get_cpu());
+
j = 0;
for_each_sg(sgl, sg, dmacount, i) {
addr = sg_dma_address(sg);
@@ -210,7 +225,8 @@ static int ixgbe_fcoe_ddp_setup(struct net_device *netdev, u16 xid,
/* max number of buffers allowed in one DDP context */
if (j >= IXGBE_BUFFCNT_MAX) {
ddp_pool->noddp++;
- goto out_noddp_free;
+ put_cpu();
+ goto out_noddp_free_unmap;
}
/* get the offset of length of current buffer */
@@ -220,16 +236,20 @@ static int ixgbe_fcoe_ddp_setup(struct net_device *netdev, u16 xid,
* all but the 1st buffer (j == 0)
* must be aligned on bufflen
*/
- if ((j != 0) && (thisoff))
- goto out_noddp_free;
+ if (j != 0 && thisoff) {
+ put_cpu();
+ goto out_noddp_free_unmap;
+ }
/*
* all but the last buffer
* ((i == (dmacount - 1)) && (thislen == len))
* must end at bufflen
*/
- if (((i != (dmacount - 1)) || (thislen != len))
- && ((thislen + thisoff) != bufflen))
- goto out_noddp_free;
+ if ((i != (dmacount - 1) || thislen != len) &&
+ (thislen + thisoff) != bufflen) {
+ put_cpu();
+ goto out_noddp_free_unmap;
+ }
ddp->udl[j] = (u64)(addr - thisoff);
/* only the first buffer may have none-zero offset */
@@ -250,14 +270,15 @@ static int ixgbe_fcoe_ddp_setup(struct net_device *netdev, u16 xid,
if (lastsize == bufflen) {
if (j >= IXGBE_BUFFCNT_MAX) {
ddp_pool->noddp_ext_buff++;
- goto out_noddp_free;
+ put_cpu();
+ goto out_noddp_free_unmap;
}
ddp->udl[j] = (u64)(fcoe->extra_ddp_buffer_dma);
j++;
lastsize = 1;
}
put_cpu();
fcbuff = (IXGBE_FCBUFF_4KB << IXGBE_FCBUFF_BUFFSIZE_SHIFT);
fcbuff |= ((j & 0xff) << IXGBE_FCBUFF_BUFFCNT_SHIFT);
@@ -316,14 +337,10 @@ static int ixgbe_fcoe_ddp_setup(struct net_device *netdev, u16 xid,
return 1;
-out_noddp_free:
+out_noddp_free_unmap:
dma_pool_free(ddp->pool, ddp->udl, ddp->udp);
ixgbe_fcoe_clear_ddp(ddp);
-
-out_noddp_unmap:
dma_unmap_sg(&adapter->pdev->dev, sgl, sgc, DMA_FROM_DEVICE);
-out_noddp:
- put_cpu();
return 0;
}
--
2.52.0
^ permalink raw reply related
* [PATCH iwl-next v2 8/8] ixgbe: use int instead of u32 for error code variables
From: Aleksandr Loktionov @ 2026-04-08 13:12 UTC (permalink / raw)
To: intel-wired-lan, anthony.l.nguyen, aleksandr.loktionov
Cc: netdev, Simon Horman
In-Reply-To: <20260408131216.2662245-1-aleksandr.loktionov@intel.com>
The variables used to store return values of kernel and driver functions
throughout the ixgbe driver are declared as u32 in several places. Such
functions return negative errno values on error (e.g. -EIO, -EFAULT),
which are sign-extended negative integers. Storing them in an unsigned
u32 silently wraps the value: -EIO (0xFFFFFFF7) stored in u32 becomes a
large positive number, so any "if (status)" truthiness check still works
by accident, but comparisons against specific negative error codes or
propagation up the call stack produce wrong results.
In the Linux kernel, u32 is reserved for fixed-width quantities used in
hardware interfaces or protocol structures. Using it for generic error
codes misleads reviewers into thinking the value is hardware-constrained.
Change all such local variables from u32 to int driver-wide: one in
ixgbe_main.c (ixgbe_resume), three in ixgbe_phy.c
(ixgbe_identify_phy_generic, ixgbe_tn_check_overtemp,
ixgbe_set_copper_phy_power), and six in ixgbe_x550.c
(ixgbe_check_link_t_X550em, ixgbe_get_lasi_ext_t_x550em,
ixgbe_enable_lasi_ext_t_x550em, ixgbe_handle_lasi_ext_t_x550em,
ixgbe_ext_phy_t_x550em_get_link, ixgbe_setup_internal_phy_t_x550em).
No functional change.
Reviewed-by: Simon Horman <horms@kernel.org>
Signed-off-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
---
v1 -> v2:
- Carry Reviewed-by: Simon Horman from the IWL posting; add [N/M]
numbering; no code change.
drivers/net/ethernet/intel/ixgbe/ixgbe_main.c | 2 +-
drivers/net/ethernet/intel/ixgbe/ixgbe_phy.c | 6 +++---
drivers/net/ethernet/intel/ixgbe/ixgbe_x550.c | 12 ++++++------
3 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
index ed5b75b..99cfccd 100644
--- a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
+++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
@@ -7531,7 +7531,7 @@ static int ixgbe_resume(struct device *dev_d)
struct pci_dev *pdev = to_pci_dev(dev_d);
struct ixgbe_adapter *adapter = pci_get_drvdata(pdev);
struct net_device *netdev = adapter->netdev;
- u32 err;
+ int err;
adapter->hw.hw_addr = adapter->io_addr;
diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_phy.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_phy.c
index ab733e7..de8f6c6 100644
--- a/drivers/net/ethernet/intel/ixgbe/ixgbe_phy.c
+++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_phy.c
@@ -262,7 +262,7 @@ static bool ixgbe_probe_phy(struct ixgbe_hw *hw, u16 phy_addr)
**/
int ixgbe_identify_phy_generic(struct ixgbe_hw *hw)
{
- u32 status = -EFAULT;
+ int status = -EFAULT;
u32 phy_addr;
if (!hw->phy.phy_semaphore_mask) {
@@ -2811,7 +2811,7 @@ static void ixgbe_i2c_bus_clear(struct ixgbe_hw *hw)
bool ixgbe_tn_check_overtemp(struct ixgbe_hw *hw)
{
u16 phy_data = 0;
- u32 status;
+ int status;
if (hw->device_id != IXGBE_DEV_ID_82599_T3_LOM)
return false;
@@ -2831,7 +2831,7 @@ bool ixgbe_tn_check_overtemp(struct ixgbe_hw *hw)
**/
int ixgbe_set_copper_phy_power(struct ixgbe_hw *hw, bool on)
{
- u32 status;
+ int status;
u16 reg;
/* Bail if we don't have copper phy */
diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_x550.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_x550.c
index 76d2fa3..9b14f3b 100644
--- a/drivers/net/ethernet/intel/ixgbe/ixgbe_x550.c
+++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_x550.c
@@ -1911,7 +1911,7 @@ static int ixgbe_check_link_t_X550em(struct ixgbe_hw *hw,
bool *link_up,
bool link_up_wait_to_complete)
{
- u32 status;
+ int status;
u16 i, autoneg_status;
if (hw->mac.ops.get_media_type(hw) != ixgbe_media_type_copper)
@@ -2330,7 +2330,7 @@ static int ixgbe_get_link_capabilities_X550em(struct ixgbe_hw *hw,
static int ixgbe_get_lasi_ext_t_x550em(struct ixgbe_hw *hw, bool *lsc,
bool *is_overtemp)
{
- u32 status;
+ int status;
u16 reg;
*is_overtemp = false;
@@ -2418,7 +2418,7 @@ static int ixgbe_get_lasi_ext_t_x550em(struct ixgbe_hw *hw, bool *lsc,
static int ixgbe_enable_lasi_ext_t_x550em(struct ixgbe_hw *hw)
{
bool lsc, overtemp;
- u32 status;
+ int status;
u16 reg;
/* Clear interrupt flags */
@@ -2512,7 +2512,7 @@ static int ixgbe_handle_lasi_ext_t_x550em(struct ixgbe_hw *hw,
{
struct ixgbe_phy_info *phy = &hw->phy;
bool lsc;
- u32 status;
+ int status;
status = ixgbe_get_lasi_ext_t_x550em(hw, &lsc, is_overtemp);
if (status)
@@ -2606,7 +2606,7 @@ static int ixgbe_setup_kr_x550em(struct ixgbe_hw *hw)
**/
static int ixgbe_ext_phy_t_x550em_get_link(struct ixgbe_hw *hw, bool *link_up)
{
- u32 ret;
+ int ret;
u16 autoneg_status;
*link_up = false;
@@ -2642,7 +2642,7 @@ static int ixgbe_setup_internal_phy_t_x550em(struct ixgbe_hw *hw)
{
ixgbe_link_speed force_speed;
bool link_up;
- u32 status;
+ int status;
u16 speed;
if (hw->mac.ops.get_media_type(hw) != ixgbe_media_type_copper)
--
2.52.0
^ permalink raw reply related
* [PATCH net-next v5 1/3] dt-bindings: net: document Microchip PIC64-HPSC/HX MDIO controller
From: Charles Perry @ 2026-04-08 13:18 UTC (permalink / raw)
To: netdev
Cc: MameMaria.Mbaye, Charles Perry, Conor Dooley, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, devicetree,
linux-kernel
In-Reply-To: <20260408131821.1145334-1-charles.perry@microchip.com>
This MDIO hardware is based on a Microsemi design supported in Linux by
mdio-mscc-miim.c. However, The register interface is completely different
with pic64hpsc, hence the need for separate documentation.
The hardware supports C22 and C45.
The documentation recommends an input clock of 156.25MHz and a prescaler
of 39, which yields an MDIO clock of 1.95MHz.
The hardware supports an interrupt pin to signal transaction completion
which is not strictly needed as the software can also poll a "TRIGGER"
bit for this.
Signed-off-by: Charles Perry <charles.perry@microchip.com>
Acked-by: Conor Dooley <conor.dooley@microchip.com>
---
Notes:
Changes in v5:
- Collect Conor's Acked-by
- Remove the "|" in "description: |" (Rob)
- Don't mention how many instances of the MDIO controller there are (Rob)
- Hex addresses are now in lowercase (Rob)
- Drop the phy DT label in the example (Rob)
Changes in v4: none
Changes in v3: none
Changes in v2:
- Make "clocks" and "interrupts" required (Andrew)
- Add a default value to "clock-frequency" (Andrew)
Changes in v5: none
Changes in v4: none
Changes in v3: none
Changes in v2:
- Make "clocks" and "interrupts" required (Andrew)
- Add a default value to "clock-frequency" (Andrew)
.../net/microchip,pic64hpsc-mdio.yaml | 68 +++++++++++++++++++
1 file changed, 68 insertions(+)
create mode 100644 Documentation/devicetree/bindings/net/microchip,pic64hpsc-mdio.yaml
diff --git a/Documentation/devicetree/bindings/net/microchip,pic64hpsc-mdio.yaml b/Documentation/devicetree/bindings/net/microchip,pic64hpsc-mdio.yaml
new file mode 100644
index 000000000000..20f29b71566b
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/microchip,pic64hpsc-mdio.yaml
@@ -0,0 +1,68 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/microchip,pic64hpsc-mdio.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Microchip PIC64-HPSC/HX MDIO controller
+
+maintainers:
+ - Charles Perry <charles.perry@microchip.com>
+
+description:
+ This is the MDIO bus controller present in Microchip PIC64-HPSC/HX SoCs. It
+ supports C22 and C45 register access and is named "MDIO Initiator" in the
+ documentation.
+
+allOf:
+ - $ref: mdio.yaml#
+
+properties:
+ compatible:
+ oneOf:
+ - const: microchip,pic64hpsc-mdio
+ - items:
+ - const: microchip,pic64hx-mdio
+ - const: microchip,pic64hpsc-mdio
+
+ reg:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+
+ clock-frequency:
+ default: 2500000
+
+ interrupts:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - interrupts
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+ bus {
+ #address-cells = <2>;
+ #size-cells = <2>;
+
+ mdio@4000c21e000 {
+ compatible = "microchip,pic64hpsc-mdio";
+ reg = <0x400 0x0c21e000 0x0 0x1000>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ clocks = <&svc_clk>;
+ interrupt-parent = <&saplic0>;
+ interrupts = <168 IRQ_TYPE_LEVEL_HIGH>;
+
+ ethernet-phy@0 {
+ reg = <0>;
+ };
+ };
+ };
--
2.47.3
^ permalink raw reply related
* [PATCH net-next v5 0/3] Add support for PIC64-HPSC/HX MDIO controller
From: Charles Perry @ 2026-04-08 13:18 UTC (permalink / raw)
To: netdev
Cc: MameMaria.Mbaye, Charles Perry, Andrew Lunn, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Heiner Kallweit, Russell King,
devicetree
Hello,
This series adds a driver for the two MDIO controllers of PIC64-HPSC/HX.
The hardware supports C22 and C45 but only C22 is implemented for now.
This MDIO hardware is based on a Microsemi design supported in Linux by
mdio-mscc-miim.c. However, The register interface is completely different
with pic64hpsc, hence the need for a separate driver.
The documentation recommends an input clock of 156.25MHz and a prescaler of
39, which yields an MDIO clock of 1.95MHz.
This was tested on Microchip HB1301 evalkit which has a VSC8574 and a
VSC8541. I've tested with bus frequencies of 0.6, 1.95 and 2.5 MHz.
This series also adds a PHY write barrier when disabling PHY interrupts as
discussed in [1].
Thanks,
Charles
[1]: https://lore.kernel.org/all/acvUqDgepCIScs8M@shell.armlinux.org.uk/
Changes in v5:
- 1/3: Collect Conor's Acked-by
- 1/3: Remove the "|" in "description: |" (Rob)
- 1/3: Don't mention how many instances of the MDIO controller there are
(Rob)
- 1/3: Hex addresses are now in lowercase (Rob)
- 1/3: Drop the phy DT label in the example (Rob)
- 3/3: Support MDIO controllers that only support C45 (Russell, Andrew)
Changes in v4:
- 2/3: return FIELD_GET() directly instead of using "ret" (Russell)
- 3/3: Add the PHY barrier patch (Russell, Andrew)
Changes in v3:
- 2/2: Add a MAINTAINERS entry (Jakub)
Changes in v2:
- 1/2: Make "clocks" and "interrupts" required (Andrew)
- 1/2: Add a default value to "clock-frequency" (Andrew)
- 2/2: Remove #define for unused registers (Maxime)
- 2/2: Add "c22" to clause 22 read/write ops (Maxime)
- 2/2: Remove the call to platform_set_drvdata() (Andrew)
- 2/2: Make the clock mandatory (Andrew)
- 2/2: Use 2.5MHz if no clock-frequency was specified (Andrew)
- 2/2: Change the error message for bad clock-frequency (Andrew)
- 2/2: Fix a use without initialization on bus_freq (Andrew)
CC: Andrew Lunn <andrew+netdev@lunn.ch>
CC: "David S. Miller" <davem@davemloft.net>
CC: Eric Dumazet <edumazet@google.com>
CC: Jakub Kicinski <kuba@kernel.org>
CC: Paolo Abeni <pabeni@redhat.com>
CC: Rob Herring <robh@kernel.org>
CC: Krzysztof Kozlowski <krzk+dt@kernel.org>
CC: Conor Dooley <conor+dt@kernel.org>
CC: Heiner Kallweit <hkallweit1@gmail.com>
CC: Russell King <linux@armlinux.org.uk>
CC: netdev@vger.kernel.org
CC: devicetree@vger.kernel.org
Charles Perry (3):
dt-bindings: net: document Microchip PIC64-HPSC/HX MDIO controller
net: mdio: add a driver for PIC64-HPSC/HX MDIO controller
net: phy: add a PHY write barrier when disabling interrupts
.../net/microchip,pic64hpsc-mdio.yaml | 68 +++++++
MAINTAINERS | 6 +
drivers/net/mdio/Kconfig | 7 +
drivers/net/mdio/Makefile | 1 +
drivers/net/mdio/mdio-pic64hpsc.c | 190 ++++++++++++++++++
drivers/net/phy/phy.c | 25 ++-
6 files changed, 296 insertions(+), 1 deletion(-)
create mode 100644 Documentation/devicetree/bindings/net/microchip,pic64hpsc-mdio.yaml
create mode 100644 drivers/net/mdio/mdio-pic64hpsc.c
--
2.47.3
^ permalink raw reply
* [PATCH net-next v5 2/3] net: mdio: add a driver for PIC64-HPSC/HX MDIO controller
From: Charles Perry @ 2026-04-08 13:18 UTC (permalink / raw)
To: netdev
Cc: MameMaria.Mbaye, Charles Perry, Maxime Chevallier, Andrew Lunn,
Heiner Kallweit, Russell King, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, linux-kernel
In-Reply-To: <20260408131821.1145334-1-charles.perry@microchip.com>
This adds an MDIO driver for PIC64-HPSC/HX. The hardware supports C22
and C45 but only C22 is implemented in this commit.
This MDIO hardware is based on a Microsemi design supported in Linux by
mdio-mscc-miim.c. However, The register interface is completely
different with pic64hpsc, hence the need for a separate driver.
The documentation recommends an input clock of 156.25MHz and a prescaler
of 39, which yields an MDIO clock of 1.95MHz.
The hardware supports an interrupt pin or a "TRIGGER" bit that can be
polled to signal transaction completion. This commit uses polling.
This was tested on Microchip HB1301 evalkit with a VSC8574 and a
VSC8541.
Signed-off-by: Charles Perry <charles.perry@microchip.com>
Reviewed-by: Maxime Chevallier <maxime.chevallier@bootlin.com>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
---
Notes:
Changes in v5: none
Changes in v4:
- read: return FIELD_GET() directly instead of using "ret" (Russell)
Changes in v3:
- Add a MAINTAINERS entry (Jakub)
Changes in v2:
- Remove #define for unused registers (Maxime)
- Add "c22" to clause 22 read/write ops (Maxime)
- Remove the call to platform_set_drvdata() (Andrew)
- Make the clock mandatory (Andrew)
- Use 2.5MHz if no clock-frequency was specified (Andrew)
- Change the error message for bad clock-frequency (Andrew)
- Fix a use without initialization on bus_freq (Andrew)
MAINTAINERS | 6 +
drivers/net/mdio/Kconfig | 7 ++
drivers/net/mdio/Makefile | 1 +
drivers/net/mdio/mdio-pic64hpsc.c | 190 ++++++++++++++++++++++++++++++
4 files changed, 204 insertions(+)
create mode 100644 drivers/net/mdio/mdio-pic64hpsc.c
diff --git a/MAINTAINERS b/MAINTAINERS
index e08767323763..9297c46dba91 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17216,6 +17216,12 @@ L: linux-serial@vger.kernel.org
S: Maintained
F: drivers/tty/serial/8250/8250_pci1xxxx.c
+MICROCHIP PIC64-HPSC/HX DRIVERS
+M: Charles Perry <charles.perry@microchip.com>
+S: Supported
+F: Documentation/devicetree/bindings/net/microchip,pic64hpsc-mdio.yaml
+F: drivers/net/mdio/mdio-pic64hpsc.c
+
MICROCHIP POLARFIRE FPGA DRIVERS
M: Conor Dooley <conor.dooley@microchip.com>
L: linux-fpga@vger.kernel.org
diff --git a/drivers/net/mdio/Kconfig b/drivers/net/mdio/Kconfig
index 44380378911b..7bdba8c3ddef 100644
--- a/drivers/net/mdio/Kconfig
+++ b/drivers/net/mdio/Kconfig
@@ -146,6 +146,13 @@ config MDIO_OCTEON
buses. It is required by the Octeon and ThunderX ethernet device
drivers on some systems.
+config MDIO_PIC64HPSC
+ tristate "PIC64-HPSC/HX MDIO interface support"
+ depends on HAS_IOMEM && OF_MDIO
+ help
+ This driver supports the MDIO interface found on the PIC64-HPSC/HX
+ SoCs.
+
config MDIO_IPQ4019
tristate "Qualcomm IPQ4019 MDIO interface support"
depends on HAS_IOMEM && OF_MDIO
diff --git a/drivers/net/mdio/Makefile b/drivers/net/mdio/Makefile
index fbec636700e7..048586746026 100644
--- a/drivers/net/mdio/Makefile
+++ b/drivers/net/mdio/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_MDIO_MOXART) += mdio-moxart.o
obj-$(CONFIG_MDIO_MSCC_MIIM) += mdio-mscc-miim.o
obj-$(CONFIG_MDIO_MVUSB) += mdio-mvusb.o
obj-$(CONFIG_MDIO_OCTEON) += mdio-octeon.o
+obj-$(CONFIG_MDIO_PIC64HPSC) += mdio-pic64hpsc.o
obj-$(CONFIG_MDIO_REALTEK_RTL9300) += mdio-realtek-rtl9300.o
obj-$(CONFIG_MDIO_REGMAP) += mdio-regmap.o
obj-$(CONFIG_MDIO_SUN4I) += mdio-sun4i.o
diff --git a/drivers/net/mdio/mdio-pic64hpsc.c b/drivers/net/mdio/mdio-pic64hpsc.c
new file mode 100644
index 000000000000..28be77ad6cf7
--- /dev/null
+++ b/drivers/net/mdio/mdio-pic64hpsc.c
@@ -0,0 +1,190 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Microchip PIC64-HPSC/HX MDIO controller driver
+ *
+ * Copyright (c) 2026 Microchip Technology Inc. and its subsidiaries.
+ */
+
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_mdio.h>
+#include <linux/platform_device.h>
+
+#define MDIO_REG_PRESCALER 0x20
+#define MDIO_CFG_PRESCALE_MASK GENMASK(7, 0)
+
+#define MDIO_REG_FRAME_CFG_1 0x24
+#define MDIO_WDATA_MASK GENMASK(15, 0)
+
+#define MDIO_REG_FRAME_CFG_2 0x28
+#define MDIO_TRIGGER_BIT BIT(31)
+#define MDIO_REG_DEV_ADDR_MASK GENMASK(20, 16)
+#define MDIO_PHY_PRT_ADDR_MASK GENMASK(8, 4)
+#define MDIO_OPERATION_MASK GENMASK(3, 2)
+#define MDIO_START_OF_FRAME_MASK GENMASK(1, 0)
+
+/* Possible value of MDIO_OPERATION_MASK */
+#define MDIO_OPERATION_WRITE BIT(0)
+#define MDIO_OPERATION_READ BIT(1)
+
+#define MDIO_REG_FRAME_STATUS 0x2C
+#define MDIO_READOK_BIT BIT(24)
+#define MDIO_RDATA_MASK GENMASK(15, 0)
+
+struct pic64hpsc_mdio_dev {
+ void __iomem *regs;
+};
+
+static int pic64hpsc_mdio_wait_trigger(struct mii_bus *bus)
+{
+ struct pic64hpsc_mdio_dev *priv = bus->priv;
+ u32 val;
+ int ret;
+
+ /* The MDIO_TRIGGER bit returns 0 when a transaction has completed. */
+ ret = readl_poll_timeout(priv->regs + MDIO_REG_FRAME_CFG_2, val,
+ !(val & MDIO_TRIGGER_BIT), 50, 10000);
+
+ if (ret < 0)
+ dev_dbg(&bus->dev, "TRIGGER bit timeout: %x\n", val);
+
+ return ret;
+}
+
+static int pic64hpsc_mdio_c22_read(struct mii_bus *bus, int mii_id, int regnum)
+{
+ struct pic64hpsc_mdio_dev *priv = bus->priv;
+ u32 val;
+ int ret;
+
+ ret = pic64hpsc_mdio_wait_trigger(bus);
+ if (ret)
+ return ret;
+
+ writel(MDIO_TRIGGER_BIT | FIELD_PREP(MDIO_REG_DEV_ADDR_MASK, regnum) |
+ FIELD_PREP(MDIO_PHY_PRT_ADDR_MASK, mii_id) |
+ FIELD_PREP(MDIO_OPERATION_MASK, MDIO_OPERATION_READ) |
+ FIELD_PREP(MDIO_START_OF_FRAME_MASK, 1),
+ priv->regs + MDIO_REG_FRAME_CFG_2);
+
+ ret = pic64hpsc_mdio_wait_trigger(bus);
+ if (ret)
+ return ret;
+
+ val = readl(priv->regs + MDIO_REG_FRAME_STATUS);
+
+ /* The MDIO_READOK is a 1-bit value reflecting the inverse of the MDIO
+ * bus value captured during the 2nd TA cycle. A PHY/Port should drive
+ * the MDIO bus with a logic 0 on the 2nd TA cycle, however, the
+ * PHY/Port could optionally drive a logic 1, to communicate a read
+ * failure. This feature is optional, not defined by the 802.3 standard
+ * and not supported in standard external PHYs.
+ */
+ if (!(bus->phy_ignore_ta_mask & 1 << mii_id) &&
+ !FIELD_GET(MDIO_READOK_BIT, val)) {
+ dev_dbg(&bus->dev, "READOK bit cleared\n");
+ return -EIO;
+ }
+
+ return FIELD_GET(MDIO_RDATA_MASK, val);
+}
+
+static int pic64hpsc_mdio_c22_write(struct mii_bus *bus, int mii_id, int regnum,
+ u16 value)
+{
+ struct pic64hpsc_mdio_dev *priv = bus->priv;
+ int ret;
+
+ ret = pic64hpsc_mdio_wait_trigger(bus);
+ if (ret < 0)
+ return ret;
+
+ writel(FIELD_PREP(MDIO_WDATA_MASK, value),
+ priv->regs + MDIO_REG_FRAME_CFG_1);
+
+ writel(MDIO_TRIGGER_BIT | FIELD_PREP(MDIO_REG_DEV_ADDR_MASK, regnum) |
+ FIELD_PREP(MDIO_PHY_PRT_ADDR_MASK, mii_id) |
+ FIELD_PREP(MDIO_OPERATION_MASK, MDIO_OPERATION_WRITE) |
+ FIELD_PREP(MDIO_START_OF_FRAME_MASK, 1),
+ priv->regs + MDIO_REG_FRAME_CFG_2);
+
+ return 0;
+}
+
+static int pic64hpsc_mdio_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct device *dev = &pdev->dev;
+ struct pic64hpsc_mdio_dev *priv;
+ struct mii_bus *bus;
+ unsigned long rate;
+ struct clk *clk;
+ u32 bus_freq;
+ u32 div;
+ int ret;
+
+ bus = devm_mdiobus_alloc_size(dev, sizeof(*priv));
+ if (!bus)
+ return -ENOMEM;
+
+ priv = bus->priv;
+
+ priv->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->regs))
+ return PTR_ERR(priv->regs);
+
+ bus->name = KBUILD_MODNAME;
+ bus->read = pic64hpsc_mdio_c22_read;
+ bus->write = pic64hpsc_mdio_c22_write;
+ snprintf(bus->id, MII_BUS_ID_SIZE, "%s", dev_name(dev));
+ bus->parent = dev;
+
+ clk = devm_clk_get_enabled(dev, NULL);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ if (of_property_read_u32(np, "clock-frequency", &bus_freq))
+ bus_freq = 2500000;
+
+ rate = clk_get_rate(clk);
+
+ div = DIV_ROUND_UP(rate, 2 * bus_freq) - 1;
+ if (div == 0 || div & ~MDIO_CFG_PRESCALE_MASK) {
+ dev_err(dev, "MDIO clock-frequency out of range\n");
+ return -EINVAL;
+ }
+
+ dev_dbg(dev, "rate=%lu bus_freq=%u real_bus_freq=%lu div=%u\n", rate,
+ bus_freq, rate / (2 * (1 + div)), div);
+ writel(div, priv->regs + MDIO_REG_PRESCALER);
+
+ ret = devm_of_mdiobus_register(dev, bus, np);
+ if (ret) {
+ dev_err(dev, "Cannot register MDIO bus (%d)\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id pic64hpsc_mdio_match[] = {
+ { .compatible = "microchip,pic64hpsc-mdio" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, pic64hpsc_mdio_match);
+
+static struct platform_driver pic64hpsc_mdio_driver = {
+ .probe = pic64hpsc_mdio_probe,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = pic64hpsc_mdio_match,
+ },
+};
+module_platform_driver(pic64hpsc_mdio_driver);
+
+MODULE_AUTHOR("Charles Perry <charles.perry@microchip.com>");
+MODULE_DESCRIPTION("Microchip PIC64-HPSC/HX MDIO driver");
+MODULE_LICENSE("GPL");
--
2.47.3
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox