From 37340ac2c7be914ff76fdc6bb505b204a4140268 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 21 May 2026 18:50:39 -0700 Subject: Input: userio - update maintainer name She's been committing under the name Lyude Paul for a while Signed-off-by: Vicki Pfau Link: https://patch.msgid.link/20260522015040.3953472-1-vi@endrift.com Signed-off-by: Dmitry Torokhov --- include/uapi/linux/userio.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/userio.h b/include/uapi/linux/userio.h index 74c9951d2cd0..98fe7e9089c4 100644 --- a/include/uapi/linux/userio.h +++ b/include/uapi/linux/userio.h @@ -2,7 +2,7 @@ /* * userio: virtual serio device support * Copyright (C) 2015 Red Hat - * Copyright (C) 2015 Lyude (Stephen Chandler Paul) + * Copyright (C) 2015 Lyude Paul * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by the -- cgit v1.2.3 From c6f1363abbff69e5bd1cd1fcf060e1510e8d6d31 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 21 May 2026 18:50:40 -0700 Subject: Input: userio - allow setting other id values Previously, only the type value was settable. The proto value is used internally for choosing the right drivers, so we should expose it. The other values make sense to expose as well. Signed-off-by: Vicki Pfau Link: https://patch.msgid.link/20260522015040.3953472-2-vi@endrift.com Signed-off-by: Dmitry Torokhov --- Documentation/input/userio.rst | 23 +++++++++++++++++++++-- drivers/input/serio/userio.c | 30 ++++++++++++++++++++++++++++++ include/uapi/linux/userio.h | 5 ++++- 3 files changed, 55 insertions(+), 3 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/input/userio.rst b/Documentation/input/userio.rst index 415962152815..7aaaa629bde0 100644 --- a/Documentation/input/userio.rst +++ b/Documentation/input/userio.rst @@ -66,8 +66,27 @@ USERIO_CMD_SET_PORT_TYPE ~~~~~~~~~~~~~~~~~~~~~~~~ Sets the type of port we're emulating, where ``data`` is the port type being -set. Can be any of the macros from . For example: SERIO_8042 -would set the port type to be a normal PS/2 port. +set. Can be any of the serio type macros from . For example: +SERIO_8042 would set the port type to be a normal PS/2 port. + +USERIO_CMD_SET_PORT_PROTO +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Sets the protocol of port we're emulating, where ``data`` is the protocol being +set. Can be any of the serio proto macros from . For example: +SERIO_IFORCE would set the port type to be an I-Force serial joystick. + +USERIO_CMD_SET_PORT_ID +~~~~~~~~~~~~~~~~~~~~~~ + +Sets the ``id`` value on the identification of port we're emulating, where +``data`` is the value being set. + +USERIO_CMD_SET_PORT_EXTRA +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Sets the ``extra`` value on the identification of port we're emulating, where +``data`` is the value being set. USERIO_CMD_SEND_INTERRUPT ~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/drivers/input/serio/userio.c b/drivers/input/serio/userio.c index abca8cb6aca5..8c19975c84bf 100644 --- a/drivers/input/serio/userio.c +++ b/drivers/input/serio/userio.c @@ -206,6 +206,36 @@ static int userio_execute_cmd(struct userio_device *userio, userio->serio->id.type = cmd->data; break; + case USERIO_CMD_SET_PORT_EXTRA: + if (userio->running) { + dev_warn(userio_misc.this_device, + "Can't change port extra on an already running userio instance\n"); + return -EBUSY; + } + + userio->serio->id.extra = cmd->data; + break; + + case USERIO_CMD_SET_PORT_ID: + if (userio->running) { + dev_warn(userio_misc.this_device, + "Can't change port id on an already running userio instance\n"); + return -EBUSY; + } + + userio->serio->id.id = cmd->data; + break; + + case USERIO_CMD_SET_PORT_PROTO: + if (userio->running) { + dev_warn(userio_misc.this_device, + "Can't change port proto on an already running userio instance\n"); + return -EBUSY; + } + + userio->serio->id.proto = cmd->data; + break; + case USERIO_CMD_SEND_INTERRUPT: if (!userio->running) { dev_warn(userio_misc.this_device, diff --git a/include/uapi/linux/userio.h b/include/uapi/linux/userio.h index 98fe7e9089c4..550c7465af1f 100644 --- a/include/uapi/linux/userio.h +++ b/include/uapi/linux/userio.h @@ -27,7 +27,10 @@ enum userio_cmd_type { USERIO_CMD_REGISTER = 0, USERIO_CMD_SET_PORT_TYPE = 1, - USERIO_CMD_SEND_INTERRUPT = 2 + USERIO_CMD_SEND_INTERRUPT = 2, + USERIO_CMD_SET_PORT_EXTRA = 3, + USERIO_CMD_SET_PORT_ID = 4, + USERIO_CMD_SET_PORT_PROTO = 5, }; /* -- cgit v1.2.3 From b8b09dc2bf35a00d4e0556b5d6308c7b917ebda2 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Thu, 18 Jun 2026 13:56:38 +0200 Subject: netfilter: nf_conntrack_expect: use conntrack GC to reap expectations This patch replaces the timer API by GC worker approach for expectations, as it already happened in many other subsystems. Use the existing conntrack GC worker to iterate over the local list of expectations in the master conntrack to reap expired expectations. Check IPS_HELPER_BIT to run GC for expectations, set it on for nft_ct expectation which nevers sets it. Hold the expectation spinlock while iterating over the master conntrack expectation list to synchronize with nf_ct_remove_expectations(). This also performs runtime packet path garbage collection through the expectation insertion and lookup functions while walking over one of the chains of the global expectation hashtables. Unconfirmed conntrack entries are skipped since ct->ext can be reallocated and dying are skipped since those will be gone soon. Set on IPS_HELPER_BIT if the helper ct extension is added, then the new GC worker does not need to bump the ct refcount to check if the ct->ext helper is available. This removes the extra bump on the refcount for expectation timers, this allows to remove several nf_ct_expect_put() calls after the unlink, after this update only refcount remains at 1 while on the expectation hashes. This patch implicitly addresses a race with the existing timer API allowing an expectation to access a stale exp->master pointer which has been already released when expectation removal loses races with an expiring timer, ie. timer_del() reporting false. Add a new NF_CT_EXPECT_DEAD flag to reap this expectation via GC. This is needed by nf_conntrack_unexpect_related() which is called in error paths to invalidate newly created expectations that has been added into the hashes. These expectactions cannot be inmediately released as GC or nf_ct_remove_expectations() could race to make it. On expectation insert, the runtime GC reaps stale expectations before checking the expectation limit set by policy. Set current timestamp in nf_ct_expect_alloc(), then add the expectation policy timeout (or custom timeout specified added on top of this) to specify the expectation lifetime. Fixes: bffcaad9afdf ("netfilter: ctnetlink: ensure safe access to master conntrack") Signed-off-by: Pablo Neira Ayuso --- include/net/netfilter/nf_conntrack_expect.h | 16 ++- include/uapi/linux/netfilter/nf_conntrack_common.h | 1 + net/netfilter/nf_conntrack_core.c | 33 ++++- net/netfilter/nf_conntrack_expect.c | 145 ++++++++++----------- net/netfilter/nf_conntrack_h323_main.c | 4 +- net/netfilter/nf_conntrack_helper.c | 10 +- net/netfilter/nf_conntrack_netlink.c | 22 ++-- net/netfilter/nf_conntrack_sip.c | 13 +- net/netfilter/nft_ct.c | 3 +- 9 files changed, 139 insertions(+), 108 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/net/netfilter/nf_conntrack_expect.h b/include/net/netfilter/nf_conntrack_expect.h index 80f50fd0f7ad..be4a120d549e 100644 --- a/include/net/netfilter/nf_conntrack_expect.h +++ b/include/net/netfilter/nf_conntrack_expect.h @@ -54,8 +54,8 @@ struct nf_conntrack_expect { /* The conntrack of the master connection */ struct nf_conn *master; - /* Timer function; deletes the expectation. */ - struct timer_list timeout; + /* jiffies32 when this expectation expires */ + u32 timeout; #if IS_ENABLED(CONFIG_NF_NAT) union nf_inet_addr saved_addr; @@ -69,6 +69,14 @@ struct nf_conntrack_expect { struct rcu_head rcu; }; +static inline bool nf_ct_exp_is_expired(const struct nf_conntrack_expect *exp) +{ + if (READ_ONCE(exp->flags) & NF_CT_EXPECT_DEAD) + return true; + + return (__s32)(READ_ONCE(exp->timeout) - nfct_time_stamp) <= 0; +} + static inline struct net *nf_ct_exp_net(struct nf_conntrack_expect *exp) { return read_pnet(&exp->net); @@ -130,7 +138,6 @@ static inline void nf_ct_unlink_expect(struct nf_conntrack_expect *exp) void nf_ct_remove_expectations(struct nf_conn *ct); void nf_ct_unexpect_related(struct nf_conntrack_expect *exp); -bool nf_ct_remove_expect(struct nf_conntrack_expect *exp); void nf_ct_expect_iterate_destroy(bool (*iter)(struct nf_conntrack_expect *e, void *data), void *data); void nf_ct_expect_iterate_net(struct net *net, @@ -153,5 +160,8 @@ static inline int nf_ct_expect_related(struct nf_conntrack_expect *expect, return nf_ct_expect_related_report(expect, 0, 0, flags); } +struct nf_conn_help; +void nf_ct_expectation_gc(struct nf_conn_help *master_help); + #endif /*_NF_CONNTRACK_EXPECT_H*/ diff --git a/include/uapi/linux/netfilter/nf_conntrack_common.h b/include/uapi/linux/netfilter/nf_conntrack_common.h index 56b6b60a814f..ee51045ae1d6 100644 --- a/include/uapi/linux/netfilter/nf_conntrack_common.h +++ b/include/uapi/linux/netfilter/nf_conntrack_common.h @@ -160,6 +160,7 @@ enum ip_conntrack_expect_events { #define NF_CT_EXPECT_USERSPACE 0x4 #ifdef __KERNEL__ +#define NF_CT_EXPECT_DEAD 0x8 #define NF_CT_EXPECT_MASK (NF_CT_EXPECT_PERMANENT | NF_CT_EXPECT_INACTIVE | \ NF_CT_EXPECT_USERSPACE) #endif diff --git a/net/netfilter/nf_conntrack_core.c b/net/netfilter/nf_conntrack_core.c index 4fb3a2d18631..784bd1d7a9bf 100644 --- a/net/netfilter/nf_conntrack_core.c +++ b/net/netfilter/nf_conntrack_core.c @@ -1471,6 +1471,31 @@ static bool gc_worker_can_early_drop(const struct nf_conn *ct) return false; } +static void nf_ct_help_gc(struct nf_conn *ct) +{ + struct nf_conn_help *help; + + if (!refcount_inc_not_zero(&ct->ct_general.use)) + return; + + /* load ->status after refcount increase */ + smp_acquire__after_ctrl_dep(); + + if (!nf_ct_is_confirmed(ct) || nf_ct_is_dying(ct)) { + nf_ct_put(ct); + return; + } + + /* re-check helper due to SLAB_TYPESAFE_BY_RCU */ + if (test_bit(IPS_HELPER_BIT, &ct->status)) { + help = nfct_help(ct); + if (help) + nf_ct_expectation_gc(help); + } + + nf_ct_put(ct); +} + static void gc_worker(struct work_struct *work) { unsigned int i, hashsz, nf_conntrack_max95 = 0; @@ -1543,7 +1568,13 @@ static void gc_worker(struct work_struct *work) expires = (expires - (long)next_run) / ++count; next_run += expires; - if (nf_conntrack_max95 == 0 || gc_worker_skip_ct(tmp)) + if (gc_worker_skip_ct(tmp)) + continue; + + if (test_bit(IPS_HELPER_BIT, &tmp->status)) + nf_ct_help_gc(tmp); + + if (nf_conntrack_max95 == 0) continue; net = nf_ct_net(tmp); diff --git a/net/netfilter/nf_conntrack_expect.c b/net/netfilter/nf_conntrack_expect.c index 5c9b17835c28..49e18eda037e 100644 --- a/net/netfilter/nf_conntrack_expect.c +++ b/net/netfilter/nf_conntrack_expect.c @@ -43,6 +43,24 @@ unsigned int nf_ct_expect_max __read_mostly; static struct kmem_cache *nf_ct_expect_cachep __read_mostly; static siphash_aligned_key_t nf_ct_expect_hashrnd; +void nf_ct_expectation_gc(struct nf_conn_help *master_help) +{ + struct nf_conntrack_expect *exp; + struct hlist_node *next; + + if (hlist_empty(&master_help->expectations)) + return; + + spin_lock_bh(&nf_conntrack_expect_lock); + hlist_for_each_entry_safe(exp, next, &master_help->expectations, lnode) { + if (!nf_ct_exp_is_expired(exp)) + continue; + + nf_ct_unlink_expect(exp); + } + spin_unlock_bh(&nf_conntrack_expect_lock); +} + /* nf_conntrack_expect helper functions */ void nf_ct_unlink_expect_report(struct nf_conntrack_expect *exp, u32 portid, int report) @@ -52,7 +70,6 @@ void nf_ct_unlink_expect_report(struct nf_conntrack_expect *exp, struct nf_conntrack_net *cnet; lockdep_nfct_expect_lock_held(); - WARN_ON_ONCE(timer_pending(&exp->timeout)); hlist_del_rcu(&exp->hnode); @@ -70,16 +87,6 @@ void nf_ct_unlink_expect_report(struct nf_conntrack_expect *exp, } EXPORT_SYMBOL_GPL(nf_ct_unlink_expect_report); -static void nf_ct_expectation_timed_out(struct timer_list *t) -{ - struct nf_conntrack_expect *exp = timer_container_of(exp, t, timeout); - - spin_lock_bh(&nf_conntrack_expect_lock); - nf_ct_unlink_expect(exp); - spin_unlock_bh(&nf_conntrack_expect_lock); - nf_ct_expect_put(exp); -} - static unsigned int nf_ct_expect_dst_hash(const struct net *n, const struct nf_conntrack_tuple *tuple) { struct { @@ -117,19 +124,6 @@ nf_ct_exp_equal(const struct nf_conntrack_tuple *tuple, nf_ct_exp_zone_equal_any(i, zone); } -bool nf_ct_remove_expect(struct nf_conntrack_expect *exp) -{ - lockdep_nfct_expect_lock_held(); - - if (timer_delete(&exp->timeout)) { - nf_ct_unlink_expect(exp); - nf_ct_expect_put(exp); - return true; - } - return false; -} -EXPORT_SYMBOL_GPL(nf_ct_remove_expect); - struct nf_conntrack_expect * __nf_ct_expect_find(struct net *net, const struct nf_conntrack_zone *zone, @@ -144,6 +138,8 @@ __nf_ct_expect_find(struct net *net, h = nf_ct_expect_dst_hash(net, tuple); hlist_for_each_entry_rcu(i, &nf_ct_expect_hash[h], hnode) { + if (nf_ct_exp_is_expired(i)) + continue; if (nf_ct_exp_equal(tuple, i, zone, net)) return i; } @@ -178,6 +174,7 @@ nf_ct_find_expectation(struct net *net, { struct nf_conntrack_net *cnet = nf_ct_pernet(net); struct nf_conntrack_expect *i, *exp = NULL; + struct hlist_node *next; unsigned int h; lockdep_nfct_expect_lock_held(); @@ -186,7 +183,11 @@ nf_ct_find_expectation(struct net *net, return NULL; h = nf_ct_expect_dst_hash(net, tuple); - hlist_for_each_entry(i, &nf_ct_expect_hash[h], hnode) { + hlist_for_each_entry_safe(i, next, &nf_ct_expect_hash[h], hnode) { + if (nf_ct_exp_is_expired(i)) { + nf_ct_unlink_expect(i); + continue; + } if (!(i->flags & NF_CT_EXPECT_INACTIVE) && nf_ct_exp_equal(tuple, i, zone, net)) { exp = i; @@ -196,13 +197,16 @@ nf_ct_find_expectation(struct net *net, if (!exp) return NULL; + if (!refcount_inc_not_zero(&exp->use)) + return NULL; + /* If master is not in hash table yet (ie. packet hasn't left this machine yet), how can other end know about expected? Hence these are not the droids you are looking for (if master ct never got confirmed, we'd hold a reference to it and weird things would happen to future packets). */ if (!nf_ct_is_confirmed(exp->master)) - return NULL; + goto err_release_exp; /* Avoid race with other CPUs, that for exp->master ct, is * about to invoke ->destroy(), or nf_ct_delete() via timeout @@ -214,18 +218,17 @@ nf_ct_find_expectation(struct net *net, */ if (unlikely(nf_ct_is_dying(exp->master) || !refcount_inc_not_zero(&exp->master->ct_general.use))) - return NULL; + goto err_release_exp; - if (exp->flags & NF_CT_EXPECT_PERMANENT || !unlink) { - refcount_inc(&exp->use); - return exp; - } else if (timer_delete(&exp->timeout)) { - nf_ct_unlink_expect(exp); + if (exp->flags & NF_CT_EXPECT_PERMANENT || !unlink) return exp; - } - /* Undo exp->master refcnt increase, if timer_delete() failed */ - nf_ct_put(exp->master); + nf_ct_unlink_expect(exp); + + return exp; + +err_release_exp: + nf_ct_expect_put(exp); return NULL; } @@ -241,9 +244,8 @@ void nf_ct_remove_expectations(struct nf_conn *ct) return; spin_lock_bh(&nf_conntrack_expect_lock); - hlist_for_each_entry_safe(exp, next, &help->expectations, lnode) { - nf_ct_remove_expect(exp); - } + hlist_for_each_entry_safe(exp, next, &help->expectations, lnode) + nf_ct_unlink_expect(exp); spin_unlock_bh(&nf_conntrack_expect_lock); } EXPORT_SYMBOL_GPL(nf_ct_remove_expectations); @@ -292,7 +294,7 @@ static bool master_matches(const struct nf_conntrack_expect *a, void nf_ct_unexpect_related(struct nf_conntrack_expect *exp) { spin_lock_bh(&nf_conntrack_expect_lock); - nf_ct_remove_expect(exp); + WRITE_ONCE(exp->flags, exp->flags | NF_CT_EXPECT_DEAD); spin_unlock_bh(&nf_conntrack_expect_lock); } EXPORT_SYMBOL_GPL(nf_ct_unexpect_related); @@ -308,6 +310,7 @@ struct nf_conntrack_expect *nf_ct_expect_alloc(struct nf_conn *me) if (!new) return NULL; + new->timeout = nfct_time_stamp; new->master = me; refcount_set(&new->use, 1); return new; @@ -413,17 +416,12 @@ static void nf_ct_expect_insert(struct nf_conntrack_expect *exp, struct net *net = nf_ct_exp_net(exp); unsigned int h = nf_ct_expect_dst_hash(net, &exp->tuple); - /* two references : one for hash insert, one for the timer */ - refcount_add(2, &exp->use); + refcount_inc(&exp->use); - timer_setup(&exp->timeout, nf_ct_expectation_timed_out, 0); helper = rcu_dereference_protected(master_help->helper, lockdep_is_held(&nf_conntrack_expect_lock)); - if (helper) { - exp->timeout.expires = jiffies + - helper->expect_policy[exp->class].timeout * HZ; - } - add_timer(&exp->timeout); + if (helper) + exp->timeout += helper->expect_policy[exp->class].timeout * HZ; hlist_add_head_rcu(&exp->lnode, &master_help->expectations); master_help->expecting[exp->class]++; @@ -435,19 +433,26 @@ static void nf_ct_expect_insert(struct nf_conntrack_expect *exp, NF_CT_STAT_INC(net, expect_create); } -/* Race with expectations being used means we could have none to find; OK. */ static void evict_oldest_expect(struct nf_conn_help *master_help, - struct nf_conntrack_expect *new) + struct nf_conntrack_expect *new, + const struct nf_conntrack_expect_policy *p) { struct nf_conntrack_expect *exp, *last = NULL; + struct hlist_node *next; - hlist_for_each_entry(exp, &master_help->expectations, lnode) { + hlist_for_each_entry_safe(exp, next, &master_help->expectations, lnode) { + if (nf_ct_exp_is_expired(exp)) { + nf_ct_unlink_expect(exp); + continue; + } if (exp->class == new->class) last = exp; } - if (last) - nf_ct_remove_expect(last); + /* Still worth to evict oldest expectation after garbage collection? */ + if (last && + master_help->expecting[last->class] >= p->max_expected) + nf_ct_unlink_expect(last); } static inline int __nf_ct_expect_check(struct nf_conntrack_expect *expect, @@ -467,14 +472,18 @@ static inline int __nf_ct_expect_check(struct nf_conntrack_expect *expect, h = nf_ct_expect_dst_hash(net, &expect->tuple); hlist_for_each_entry_safe(i, next, &nf_ct_expect_hash[h], hnode) { + if (nf_ct_exp_is_expired(i)) { + nf_ct_unlink_expect(i); + continue; + } if (master_matches(i, expect, flags) && expect_matches(i, expect)) { if (i->class != expect->class || i->master != expect->master) return -EALREADY; - if (nf_ct_remove_expect(i)) - break; + nf_ct_unlink_expect(i); + break; } else if (expect_clash(i, expect)) { ret = -EBUSY; goto out; @@ -486,14 +495,8 @@ static inline int __nf_ct_expect_check(struct nf_conntrack_expect *expect, if (helper) { p = &helper->expect_policy[expect->class]; if (p->max_expected && - master_help->expecting[expect->class] >= p->max_expected) { - evict_oldest_expect(master_help, expect); - if (master_help->expecting[expect->class] - >= p->max_expected) { - ret = -EMFILE; - goto out; - } - } + master_help->expecting[expect->class] >= p->max_expected) + evict_oldest_expect(master_help, expect, p); } cnet = nf_ct_pernet(net); @@ -547,10 +550,8 @@ void nf_ct_expect_iterate_destroy(bool (*iter)(struct nf_conntrack_expect *e, vo hlist_for_each_entry_safe(exp, next, &nf_ct_expect_hash[i], hnode) { - if (iter(exp, data) && timer_delete(&exp->timeout)) { + if (iter(exp, data)) nf_ct_unlink_expect(exp); - nf_ct_expect_put(exp); - } } } @@ -577,10 +578,8 @@ void nf_ct_expect_iterate_net(struct net *net, if (!net_eq(nf_ct_exp_net(exp), net)) continue; - if (iter(exp, data) && timer_delete(&exp->timeout)) { + if (iter(exp, data)) nf_ct_unlink_expect_report(exp, portid, report); - nf_ct_expect_put(exp); - } } } @@ -657,17 +656,17 @@ static int exp_seq_show(struct seq_file *s, void *v) struct net *net = seq_file_net(s); struct hlist_node *n = v; char *delim = ""; + __s32 timeout; expect = hlist_entry(n, struct nf_conntrack_expect, hnode); if (!net_eq(nf_ct_exp_net(expect), net)) return 0; + if (nf_ct_exp_is_expired(expect)) + return 0; - if (expect->timeout.function) - seq_printf(s, "%ld ", timer_pending(&expect->timeout) - ? (long)(expect->timeout.expires - jiffies)/HZ : 0); - else - seq_puts(s, "- "); + timeout = (__s32)(READ_ONCE(expect->timeout) - nfct_time_stamp) / HZ; + seq_printf(s, "%d ", timeout > 0 ? timeout : 0); seq_printf(s, "l3proto = %u proto=%u ", expect->tuple.src.l3num, expect->tuple.dst.protonum); diff --git a/net/netfilter/nf_conntrack_h323_main.c b/net/netfilter/nf_conntrack_h323_main.c index 7f189dceb3c4..24931e379985 100644 --- a/net/netfilter/nf_conntrack_h323_main.c +++ b/net/netfilter/nf_conntrack_h323_main.c @@ -1388,8 +1388,8 @@ static int process_rcf(struct sk_buff *skb, struct nf_conn *ct, "timeout to %u seconds for", info->timeout); nf_ct_dump_tuple(&exp->tuple); - mod_timer_pending(&exp->timeout, - jiffies + info->timeout * HZ); + WRITE_ONCE(exp->timeout, + nfct_time_stamp + (info->timeout * HZ)); } spin_unlock_bh(&nf_conntrack_expect_lock); } diff --git a/net/netfilter/nf_conntrack_helper.c b/net/netfilter/nf_conntrack_helper.c index 2f35bdd0d7d7..8b94001c2430 100644 --- a/net/netfilter/nf_conntrack_helper.c +++ b/net/netfilter/nf_conntrack_helper.c @@ -181,10 +181,10 @@ nf_ct_helper_ext_add(struct nf_conn *ct, gfp_t gfp) struct nf_conn_help *help; help = nf_ct_ext_add(ct, NF_CT_EXT_HELPER, gfp); - if (help) + if (help) { + __set_bit(IPS_HELPER_BIT, &ct->status); INIT_HLIST_HEAD(&help->expectations); - else - pr_debug("failed to add helper extension area"); + } return help; } EXPORT_SYMBOL_GPL(nf_ct_helper_ext_add); @@ -203,10 +203,8 @@ int __nf_ct_try_assign_helper(struct nf_conn *ct, struct nf_conn *tmpl, return 0; help = nfct_help(tmpl); - if (help != NULL) { + if (help) helper = rcu_dereference(help->helper); - set_bit(IPS_HELPER_BIT, &ct->status); - } help = nfct_help(ct); diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c index b429e648f06c..4e78d2482989 100644 --- a/net/netfilter/nf_conntrack_netlink.c +++ b/net/netfilter/nf_conntrack_netlink.c @@ -3014,8 +3014,8 @@ static int ctnetlink_exp_dump_expect(struct sk_buff *skb, const struct nf_conntrack_expect *exp) { + __s32 timeout = (__s32)(READ_ONCE(exp->timeout) - nfct_time_stamp) / HZ; struct nf_conn *master = exp->master; - long timeout = ((long)exp->timeout.expires - (long)jiffies) / HZ; struct nf_conntrack_helper *helper; #if IS_ENABLED(CONFIG_NF_NAT) struct nlattr *nest_parms; @@ -3178,6 +3178,9 @@ ctnetlink_exp_dump_table(struct sk_buff *skb, struct netlink_callback *cb) restart: hlist_for_each_entry_rcu(exp, &nf_ct_expect_hash[cb->args[0]], hnode) { + if (nf_ct_exp_is_expired(exp)) + continue; + if (l3proto && exp->tuple.src.l3num != l3proto) continue; @@ -3456,11 +3459,8 @@ static int ctnetlink_del_expect(struct sk_buff *skb, } /* after list removal, usage count == 1 */ - if (timer_delete(&exp->timeout)) { - nf_ct_unlink_expect_report(exp, NETLINK_CB(skb).portid, - nlmsg_report(info->nlh)); - nf_ct_expect_put(exp); - } + nf_ct_unlink_expect_report(exp, NETLINK_CB(skb).portid, + nlmsg_report(info->nlh)); spin_unlock_bh(&nf_conntrack_expect_lock); /* have to put what we 'get' above. * after this line usage count == 0 */ @@ -3484,14 +3484,10 @@ static int ctnetlink_change_expect(struct nf_conntrack_expect *x, const struct nlattr * const cda[]) { - if (cda[CTA_EXPECT_TIMEOUT]) { - if (!timer_delete(&x->timeout)) - return -ETIME; + if (cda[CTA_EXPECT_TIMEOUT]) + WRITE_ONCE(x->timeout, nfct_time_stamp + + ntohl(nla_get_be32(cda[CTA_EXPECT_TIMEOUT])) * HZ); - x->timeout.expires = jiffies + - ntohl(nla_get_be32(cda[CTA_EXPECT_TIMEOUT])) * HZ; - add_timer(&x->timeout); - } return 0; } diff --git a/net/netfilter/nf_conntrack_sip.c b/net/netfilter/nf_conntrack_sip.c index c606d1f60b58..5ec3a4a4bbd7 100644 --- a/net/netfilter/nf_conntrack_sip.c +++ b/net/netfilter/nf_conntrack_sip.c @@ -897,11 +897,10 @@ static int refresh_signalling_expectation(struct nf_conn *ct, exp->tuple.dst.protonum != proto || exp->tuple.dst.u.udp.port != port) continue; - if (mod_timer_pending(&exp->timeout, jiffies + expires * HZ)) { - exp->flags &= ~NF_CT_EXPECT_INACTIVE; - found = 1; - break; - } + WRITE_ONCE(exp->timeout, nfct_time_stamp + (expires * HZ)); + WRITE_ONCE(exp->flags, exp->flags & ~NF_CT_EXPECT_INACTIVE); + found = 1; + break; } spin_unlock_bh(&nf_conntrack_expect_lock); return found; @@ -920,8 +919,7 @@ static void flush_expectations(struct nf_conn *ct, bool media) hlist_for_each_entry_safe(exp, next, &help->expectations, lnode) { if ((exp->class != SIP_EXPECT_SIGNALLING) ^ media) continue; - if (!nf_ct_remove_expect(exp)) - continue; + nf_ct_unlink_expect(exp); if (!media) break; } @@ -1413,7 +1411,6 @@ static int process_register_request(struct sk_buff *skb, unsigned int protoff, nf_ct_expect_init(exp, SIP_EXPECT_SIGNALLING, nf_ct_l3num(ct), saddr, &daddr, proto, NULL, &port); - exp->timeout.expires = sip_timeout * HZ; rcu_assign_pointer(exp->assign_helper, helper); exp->flags = NF_CT_EXPECT_PERMANENT | NF_CT_EXPECT_INACTIVE; diff --git a/net/netfilter/nft_ct.c b/net/netfilter/nft_ct.c index 25934c6f01fb..958054dd2e2e 100644 --- a/net/netfilter/nft_ct.c +++ b/net/netfilter/nft_ct.c @@ -1145,7 +1145,6 @@ static void nft_ct_helper_obj_eval(struct nft_object *obj, help = nf_ct_helper_ext_add(ct, GFP_ATOMIC); if (help && refcount_inc_not_zero(&to_assign->ct_refcnt)) { rcu_assign_pointer(help->helper, to_assign); - set_bit(IPS_HELPER_BIT, &ct->status); if ((ct->status & IPS_NAT_MASK) && !nfct_seqadj(ct)) if (!nfct_seqadj_ext_add(ct)) @@ -1326,7 +1325,7 @@ static void nft_ct_expect_obj_eval(struct nft_object *obj, &ct->tuplehash[!dir].tuple.src.u3, &ct->tuplehash[!dir].tuple.dst.u3, priv->l4proto, NULL, &priv->dport); - exp->timeout.expires = jiffies + priv->timeout * HZ; + exp->timeout += priv->timeout * HZ; if (nf_ct_expect_related(exp, 0) != 0) regs->verdict.code = NF_DROP; -- cgit v1.2.3 From 509ca545d425512f83ca70093f6d836ec8ab5bd1 Mon Sep 17 00:00:00 2001 From: Jordan Rife Date: Thu, 18 Jun 2026 11:20:32 -0700 Subject: bpf: Support BPF_F_EGRESS with bpf_redirect_peer We have several use cases where a pod injects traffic into the datapath of another so that the traffic appears to have originated from that pod. One such use case is a synthetic flow generator which injects synthetic traffic into a pod's datapath to enable dynamic probing and debugging. Another is a transparent proxy where connections originating from one pod are redirected towards another which proxies that connection. The new connection is bound to the IP of the original pod using IP_TRANSPARENT and its traffic is injected into that pod's datapath and handled as if it had originated there. This can be used for mTLS, etc. We use bpf_redirect(BPF_F_INGRESS) to direct traffic leaving the proxy, flow generator, etc. towards the target pod, ensuring that eBPF programs that are meant to intercept traffic leaving that pod are executed. However, this doesn't work with netkit. With netkit, an ingress redirection from proxy to workload skips eBPF programs that are meant to intercept traffic leaving the pod, since they reside on the netkit peer device. One workaround is to attach the same program to both the netkit peer device and the TCX ingress hook for the netkit pair's primary interface, but a) This seems hacky and we need to be careful not to run the same program twice for the same skb in cases where we want to pass that traffic to the host stack. b) We're trying to keep the proxy redirection / traffic injection systems as modular and separated from Cilium as possible, the system that manages netkit setup and core eBPF programming. It would be handy if instead we could redirect traffic directly from one netkit peer device to another. This patch proposes an extension to bpf_redirect_peer to allow us to do just that. With this patch, the BPF_F_EGRESS flag tells bpf_redirect_peer to emit the skb in the egress direction of the target interface's peer device While the main use case is netkit, I suppose you could also use this mode with veth as well if, e.g., there were some eBPF programs attached to that side of the veth pair that needed to intercept traffic. +---------------------------------------------------------------------+ | +-------------------------+ 6. bpf_redirect_neigh(eth0) | | | pod (10.244.0.10) | ------------------------ | | | | | | | | | +--------+ | | +---------+ | | | | 1. packet -->| | | | | | | | | | leaves ^ | netkit |<===========|======| netkit | | | | | | | peer |=======(eBPF)=====>| primary | | | | | | | | | | | | | | | | | +--------+ | | +---------+ | | | | | | | 2. bpf_redirect v | | +-----------|-------------+ |___________________ +-------| | | | | eth0 | | | 5. bpf_redirect_peer(BPF_F_EGRESS) | +-------| | |________________________ | | | +-------------------------+ | | | | | proxy (10.244.0.11) | | | | | | IP_TRANSPARENT | | | | | | +--------+ | | +---------+ | | | | 3. packet <--| | | | | |<-- | | | enters | netkit |<===========|======| netkit | | | | [proxy] | peer |=======(eBPF)=====>| primary | | | | 4. packet -->| | | | | | | | leaves +--------+ | +---------+ | | | sip=10.244.0.10 | | | +-------------------------+ | +---------------------------------------------------------------------+ Using the proxy use case as an example, in step 5 we would redirect traffic leaving the proxy towards the pod's peer device using bpf_redirect_peer(BPF_F_EGRESS). As a bonus, since the skb doesn't have to go through the backlog queue it can take full advantage of netkit's performance benefits. I set up a test where outgoing iperf3 traffic is injected into the datapath of another pod using either bpf_redirect_peer(BPF_F_EGRESS) or bpf_redirect(BPF_F_INGRESS). I used Cilium's eBPF host routing mode which skips the host stack and uses BPF redirect helpers to do all the routing. (net.ipv4.tcp_congestion_control=cubic,mtu=1500,100GiB link,Cilium eBPF host routing mode) BASELINE [bpf_redirect(BPF_F_INGRESS)] 1. [iperf pod] ==bpf_redirect([pod b], BPF_F_INGRESS)==> [pod b] 2. [pod b] ==bpf_redirect_neigh([eth0])==> eth0 3. eth0 ==over network==> [host b] [ ID] Interval Transfer Bitrate Retr [ 5] 0.00-60.00 sec 231 GBytes 33.0 Gbits/sec 12060 sender [ 5] 0.00-60.00 sec 230 GBytes 33.0 Gbits/sec receiver TEST [bpf_redirect_peer(BPF_F_EGRESS)] 1. [iperf pod] ==bpf_redirect_peer([pod b], BPF_F_EGRESS)==> [pod b] 2. [pod b] ==bpf_redirect_neigh([eth0])==> eth0 3. eth0 ==over network==> [host b] [ ID] Interval Transfer Bitrate Retr [ 5] 0.00-60.00 sec 272 GBytes 38.9 Gbits/sec 0 sender [ 5] 0.00-60.00 sec 272 GBytes 38.9 Gbits/sec receiver In this test, using bpf_redirect_peer(BPF_F_EGRESS) for the hop from [iperf pod] to [pod b] led to ~18% more throughput compared to bpf_redirect(BPF_F_INGRESS). Signed-off-by: Jordan Rife Acked-by: Daniel Borkmann Acked-by: Paul Chaignon Reviewed-by: Jiayuan Chen Link: https://lore.kernel.org/r/20260618182035.43811-2-jordan@jrife.io Signed-off-by: Alexei Starovoitov --- include/uapi/linux/bpf.h | 19 +++++++++++-------- net/core/filter.c | 12 +++++++----- tools/include/uapi/linux/bpf.h | 19 +++++++++++-------- 3 files changed, 29 insertions(+), 21 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index 89b36de5fdbb..c91b5a4bda03 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -5079,17 +5079,19 @@ union bpf_attr { * Description * Redirect the packet to another net device of index *ifindex*. * This helper is somewhat similar to **bpf_redirect**\ (), except - * that the redirection happens to the *ifindex*' peer device and - * the netns switch takes place from ingress to ingress without - * going through the CPU's backlog queue. + * that the redirection happens to the *ifindex*' peer device. If + * *flags* is 0, the netns switch takes place from ingress to + * ingress without going through the CPU's backlog queue. If the + * **BPF_F_EGRESS** flag is provided then redirection happens in + * the egress direction of the peer device. * * *skb*\ **->mark** and *skb*\ **->tstamp** are not cleared during * the netns switch. * - * The *flags* argument is reserved and must be 0. The helper is - * currently only supported for tc BPF program types at the - * ingress hook and for veth and netkit target device types. The - * peer device must reside in a different network namespace. + * If the *flags* argument is 0, the helper is currently only + * supported for tc BPF program types at the ingress hook and for + * veth and netkit target device types. The peer device must reside + * in a different network namespace. * Return * The helper returns **TC_ACT_REDIRECT** on success or * **TC_ACT_SHOT** on error. @@ -6336,9 +6338,10 @@ enum { /* Flags for bpf_redirect and bpf_redirect_map helpers */ enum { BPF_F_INGRESS = (1ULL << 0), /* used for skb path */ + BPF_F_EGRESS = (1ULL << 1), /* used for skb path */ BPF_F_BROADCAST = (1ULL << 3), /* used for XDP path */ BPF_F_EXCLUDE_INGRESS = (1ULL << 4), /* used for XDP path */ -#define BPF_F_REDIRECT_FLAGS (BPF_F_INGRESS | BPF_F_BROADCAST | BPF_F_EXCLUDE_INGRESS) +#define BPF_F_REDIRECT_FLAGS (BPF_F_INGRESS | BPF_F_EGRESS | BPF_F_BROADCAST | BPF_F_EXCLUDE_INGRESS) }; #define __bpf_md_ptr(type, name) \ diff --git a/net/core/filter.c b/net/core/filter.c index b446aa8be5c3..4f5cbcac3e78 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -2529,16 +2529,18 @@ int skb_do_redirect(struct sk_buff *skb) if (unlikely(!dev)) goto out_drop; if (flags & BPF_F_PEER) { - if (unlikely(!skb_at_tc_ingress(skb))) - goto out_drop; dev = skb_get_peer_dev(dev); if (unlikely(!dev || !(dev->flags & IFF_UP) || net_eq(net, dev_net(dev)))) goto out_drop; + skb_scrub_packet(skb, false); + if (flags & BPF_F_EGRESS) + return __bpf_redirect(skb, dev, 0); + if (unlikely(!skb_at_tc_ingress(skb))) + goto out_drop; skb->dev = dev; dev_sw_netstats_rx_add(dev, skb->len); - skb_scrub_packet(skb, false); return -EAGAIN; } return flags & BPF_F_NEIGH ? @@ -2575,10 +2577,10 @@ BPF_CALL_2(bpf_redirect_peer, u32, ifindex, u64, flags) { struct bpf_redirect_info *ri = bpf_net_ctx_get_ri(); - if (unlikely(flags)) + if (unlikely(flags & ~BPF_F_EGRESS)) return TC_ACT_SHOT; - ri->flags = BPF_F_PEER; + ri->flags = BPF_F_PEER | flags; ri->tgt_index = ifindex; return TC_ACT_REDIRECT; diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index 89b36de5fdbb..c91b5a4bda03 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -5079,17 +5079,19 @@ union bpf_attr { * Description * Redirect the packet to another net device of index *ifindex*. * This helper is somewhat similar to **bpf_redirect**\ (), except - * that the redirection happens to the *ifindex*' peer device and - * the netns switch takes place from ingress to ingress without - * going through the CPU's backlog queue. + * that the redirection happens to the *ifindex*' peer device. If + * *flags* is 0, the netns switch takes place from ingress to + * ingress without going through the CPU's backlog queue. If the + * **BPF_F_EGRESS** flag is provided then redirection happens in + * the egress direction of the peer device. * * *skb*\ **->mark** and *skb*\ **->tstamp** are not cleared during * the netns switch. * - * The *flags* argument is reserved and must be 0. The helper is - * currently only supported for tc BPF program types at the - * ingress hook and for veth and netkit target device types. The - * peer device must reside in a different network namespace. + * If the *flags* argument is 0, the helper is currently only + * supported for tc BPF program types at the ingress hook and for + * veth and netkit target device types. The peer device must reside + * in a different network namespace. * Return * The helper returns **TC_ACT_REDIRECT** on success or * **TC_ACT_SHOT** on error. @@ -6336,9 +6338,10 @@ enum { /* Flags for bpf_redirect and bpf_redirect_map helpers */ enum { BPF_F_INGRESS = (1ULL << 0), /* used for skb path */ + BPF_F_EGRESS = (1ULL << 1), /* used for skb path */ BPF_F_BROADCAST = (1ULL << 3), /* used for XDP path */ BPF_F_EXCLUDE_INGRESS = (1ULL << 4), /* used for XDP path */ -#define BPF_F_REDIRECT_FLAGS (BPF_F_INGRESS | BPF_F_BROADCAST | BPF_F_EXCLUDE_INGRESS) +#define BPF_F_REDIRECT_FLAGS (BPF_F_INGRESS | BPF_F_EGRESS | BPF_F_BROADCAST | BPF_F_EXCLUDE_INGRESS) }; #define __bpf_md_ptr(type, name) \ -- cgit v1.2.3 From 969076f31bf63100c8b773cfb85fd5771f5926da Mon Sep 17 00:00:00 2001 From: "Matthew Wilcox (Oracle)" Date: Thu, 18 Jun 2026 22:18:19 +0100 Subject: efs: Remove EFS The kernel EFS code has been unmaintained for over twenty years. It was superseded on IRIX around thirty years ago. I haven't seen an EFS filesystem in the wild since 1999. Userspace tools to read EFS filesystems exist, such as https://github.com/jkbenaim/efsextract There's no benefit to keeping this filesystem in the kernel, and it only increases the maintenance burden for tree-wide changes. Signed-off-by: Matthew Wilcox (Oracle) Link: https://patch.msgid.link/20260618211822.3599089-1-willy@infradead.org Acked-by: Jori Koolstra Signed-off-by: Christian Brauner (Amutable) --- MAINTAINERS | 5 - fs/Kconfig | 1 - fs/Makefile | 1 - fs/efs/Kconfig | 16 -- fs/efs/Makefile | 8 - fs/efs/dir.c | 105 ------------ fs/efs/efs.h | 144 ---------------- fs/efs/file.c | 42 ----- fs/efs/inode.c | 315 ----------------------------------- fs/efs/namei.c | 120 -------------- fs/efs/super.c | 368 ----------------------------------------- fs/efs/symlink.c | 50 ------ include/linux/efs_vh.h | 54 ------ include/uapi/linux/efs_fs_sb.h | 63 ------- 14 files changed, 1292 deletions(-) delete mode 100644 fs/efs/Kconfig delete mode 100644 fs/efs/Makefile delete mode 100644 fs/efs/dir.c delete mode 100644 fs/efs/efs.h delete mode 100644 fs/efs/file.c delete mode 100644 fs/efs/inode.c delete mode 100644 fs/efs/namei.c delete mode 100644 fs/efs/super.c delete mode 100644 fs/efs/symlink.c delete mode 100644 include/linux/efs_vh.h delete mode 100644 include/uapi/linux/efs_fs_sb.h (limited to 'include/uapi/linux') diff --git a/MAINTAINERS b/MAINTAINERS index 15011f5752a9..e2a0d74db4e3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9493,11 +9493,6 @@ L: linux-fbdev@vger.kernel.org S: Maintained F: drivers/video/fbdev/efifb.c -EFS FILESYSTEM -S: Orphan -W: http://aeschi.ch.eu.org/efs/ -F: fs/efs/ - EHEA (IBM pSeries eHEA 10Gb ethernet adapter) DRIVER L: netdev@vger.kernel.org S: Orphan diff --git a/fs/Kconfig b/fs/Kconfig index cf6ae64776e6..64ff193fb42c 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -315,7 +315,6 @@ source "fs/hfs/Kconfig" source "fs/hfsplus/Kconfig" source "fs/befs/Kconfig" source "fs/bfs/Kconfig" -source "fs/efs/Kconfig" source "fs/jffs2/Kconfig" # UBIFS File system configuration source "fs/ubifs/Kconfig" diff --git a/fs/Makefile b/fs/Makefile index 89a8a9d207d1..aa847be93bc6 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -92,7 +92,6 @@ obj-$(CONFIG_HPFS_FS) += hpfs/ obj-$(CONFIG_NTFS_FS) += ntfs/ obj-$(CONFIG_NTFS3_FS) += ntfs3/ obj-$(CONFIG_UFS_FS) += ufs/ -obj-$(CONFIG_EFS_FS) += efs/ obj-$(CONFIG_JFFS2_FS) += jffs2/ obj-$(CONFIG_UBIFS_FS) += ubifs/ obj-$(CONFIG_AFFS_FS) += affs/ diff --git a/fs/efs/Kconfig b/fs/efs/Kconfig deleted file mode 100644 index 0833e533df9d..000000000000 --- a/fs/efs/Kconfig +++ /dev/null @@ -1,16 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -config EFS_FS - tristate "EFS file system support (read only)" - depends on BLOCK - select BUFFER_HEAD - help - EFS is an older file system used for non-ISO9660 CD-ROMs and hard - disk partitions by SGI's IRIX operating system (IRIX 6.0 and newer - uses the XFS file system for hard disk partitions however). - - This implementation only offers read-only access. If you don't know - what all this is about, it's safe to say N. For more information - about EFS see its home page at . - - To compile the EFS file system support as a module, choose M here: the - module will be called efs. diff --git a/fs/efs/Makefile b/fs/efs/Makefile deleted file mode 100644 index 85e5b88f9471..000000000000 --- a/fs/efs/Makefile +++ /dev/null @@ -1,8 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -# -# Makefile for the linux efs-filesystem routines. -# - -obj-$(CONFIG_EFS_FS) += efs.o - -efs-objs := super.o inode.o namei.o dir.o file.o symlink.o diff --git a/fs/efs/dir.c b/fs/efs/dir.c deleted file mode 100644 index 35ad0092c115..000000000000 --- a/fs/efs/dir.c +++ /dev/null @@ -1,105 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * dir.c - * - * Copyright (c) 1999 Al Smith - */ - -#include -#include -#include "efs.h" - -static int efs_readdir(struct file *, struct dir_context *); - -const struct file_operations efs_dir_operations = { - .llseek = generic_file_llseek, - .read = generic_read_dir, - .iterate_shared = efs_readdir, - .setlease = generic_setlease, -}; - -const struct inode_operations efs_dir_inode_operations = { - .lookup = efs_lookup, -}; - -static int efs_readdir(struct file *file, struct dir_context *ctx) -{ - struct inode *inode = file_inode(file); - efs_block_t block; - int slot; - - if (inode->i_size & (EFS_DIRBSIZE-1)) - pr_warn("%s(): directory size not a multiple of EFS_DIRBSIZE\n", - __func__); - - /* work out where this entry can be found */ - block = ctx->pos >> EFS_DIRBSIZE_BITS; - - /* each block contains at most 256 slots */ - slot = ctx->pos & 0xff; - - /* look at all blocks */ - while (block < inode->i_blocks) { - struct efs_dir *dirblock; - struct buffer_head *bh; - - /* read the dir block */ - bh = sb_bread(inode->i_sb, efs_bmap(inode, block)); - - if (!bh) { - pr_err("%s(): failed to read dir block %d\n", - __func__, block); - break; - } - - dirblock = (struct efs_dir *) bh->b_data; - - if (be16_to_cpu(dirblock->magic) != EFS_DIRBLK_MAGIC) { - pr_err("%s(): invalid directory block\n", __func__); - brelse(bh); - break; - } - - for (; slot < dirblock->slots; slot++) { - struct efs_dentry *dirslot; - efs_ino_t inodenum; - const char *nameptr; - int namelen; - - if (dirblock->space[slot] == 0) - continue; - - dirslot = (struct efs_dentry *) (((char *) bh->b_data) + EFS_SLOTAT(dirblock, slot)); - - inodenum = be32_to_cpu(dirslot->inode); - namelen = dirslot->namelen; - nameptr = dirslot->name; - pr_debug("%s(): block %d slot %d/%d: inode %u, name \"%s\", namelen %u\n", - __func__, block, slot, dirblock->slots-1, - inodenum, nameptr, namelen); - if (!namelen) - continue; - /* found the next entry */ - ctx->pos = (block << EFS_DIRBSIZE_BITS) | slot; - - /* sanity check */ - if (nameptr - (char *) dirblock + namelen > EFS_DIRBSIZE) { - pr_warn("directory entry %d exceeds directory block\n", - slot); - continue; - } - - /* copy filename and data in dirslot */ - if (!dir_emit(ctx, nameptr, namelen, inodenum, DT_UNKNOWN)) { - brelse(bh); - return 0; - } - } - brelse(bh); - - slot = 0; - block++; - } - ctx->pos = (block << EFS_DIRBSIZE_BITS) | slot; - return 0; -} diff --git a/fs/efs/efs.h b/fs/efs/efs.h deleted file mode 100644 index 918d2b9abb76..000000000000 --- a/fs/efs/efs.h +++ /dev/null @@ -1,144 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * Copyright (c) 1999 Al Smith, - * - * Portions derived from work (c) 1995,1996 Christian Vogelgsang. - * Portions derived from IRIX header files (c) 1988 Silicon Graphics - */ -#ifndef _EFS_EFS_H_ -#define _EFS_EFS_H_ - -#ifdef pr_fmt -#undef pr_fmt -#endif - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include - -#define EFS_VERSION "1.0a" - -/* 1 block is 512 bytes */ -#define EFS_BLOCKSIZE_BITS 9 -#define EFS_BLOCKSIZE (1 << EFS_BLOCKSIZE_BITS) - -typedef int32_t efs_block_t; -typedef uint32_t efs_ino_t; - -#define EFS_DIRECTEXTENTS 12 - -/* - * layout of an extent, in memory and on disk. 8 bytes exactly. - */ -typedef union extent_u { - unsigned char raw[8]; - struct extent_s { - unsigned int ex_magic:8; /* magic # (zero) */ - unsigned int ex_bn:24; /* basic block */ - unsigned int ex_length:8; /* numblocks in this extent */ - unsigned int ex_offset:24; /* logical offset into file */ - } cooked; -} efs_extent; - -typedef struct edevs { - __be16 odev; - __be32 ndev; -} efs_devs; - -/* - * extent based filesystem inode as it appears on disk. The efs inode - * is exactly 128 bytes long. - */ -struct efs_dinode { - __be16 di_mode; /* mode and type of file */ - __be16 di_nlink; /* number of links to file */ - __be16 di_uid; /* owner's user id */ - __be16 di_gid; /* owner's group id */ - __be32 di_size; /* number of bytes in file */ - __be32 di_atime; /* time last accessed */ - __be32 di_mtime; /* time last modified */ - __be32 di_ctime; /* time created */ - __be32 di_gen; /* generation number */ - __be16 di_numextents; /* # of extents */ - u_char di_version; /* version of inode */ - u_char di_spare; /* spare - used by AFS */ - union di_addr { - efs_extent di_extents[EFS_DIRECTEXTENTS]; - efs_devs di_dev; /* device for IFCHR/IFBLK */ - } di_u; -}; - -/* efs inode storage in memory */ -struct efs_inode_info { - int numextents; - int lastextent; - - efs_extent extents[EFS_DIRECTEXTENTS]; - struct inode vfs_inode; -}; - -#include - -#define EFS_DIRBSIZE_BITS EFS_BLOCKSIZE_BITS -#define EFS_DIRBSIZE (1 << EFS_DIRBSIZE_BITS) - -struct efs_dentry { - __be32 inode; - unsigned char namelen; - char name[3]; -}; - -#define EFS_DENTSIZE (sizeof(struct efs_dentry) - 3 + 1) -#define EFS_MAXNAMELEN ((1 << (sizeof(char) * 8)) - 1) - -#define EFS_DIRBLK_HEADERSIZE 4 -#define EFS_DIRBLK_MAGIC 0xbeef /* moo */ - -struct efs_dir { - __be16 magic; - unsigned char firstused; - unsigned char slots; - - unsigned char space[EFS_DIRBSIZE - EFS_DIRBLK_HEADERSIZE]; -}; - -#define EFS_MAXENTS \ - ((EFS_DIRBSIZE - EFS_DIRBLK_HEADERSIZE) / \ - (EFS_DENTSIZE + sizeof(char))) - -#define EFS_SLOTAT(dir, slot) EFS_REALOFF((dir)->space[slot]) - -#define EFS_REALOFF(offset) ((offset << 1)) - - -static inline struct efs_inode_info *INODE_INFO(struct inode *inode) -{ - return container_of(inode, struct efs_inode_info, vfs_inode); -} - -static inline struct efs_sb_info *SUPER_INFO(struct super_block *sb) -{ - return sb->s_fs_info; -} - -struct statfs; -struct fid; - -extern const struct inode_operations efs_dir_inode_operations; -extern const struct file_operations efs_dir_operations; -extern const struct address_space_operations efs_symlink_aops; - -extern struct inode *efs_iget(struct super_block *, unsigned long); -extern efs_block_t efs_map_block(struct inode *, efs_block_t); -extern int efs_get_block(struct inode *, sector_t, struct buffer_head *, int); - -extern struct dentry *efs_lookup(struct inode *, struct dentry *, unsigned int); -extern struct dentry *efs_fh_to_dentry(struct super_block *sb, struct fid *fid, - int fh_len, int fh_type); -extern struct dentry *efs_fh_to_parent(struct super_block *sb, struct fid *fid, - int fh_len, int fh_type); -extern struct dentry *efs_get_parent(struct dentry *); -extern int efs_bmap(struct inode *, int); - -#endif /* _EFS_EFS_H_ */ diff --git a/fs/efs/file.c b/fs/efs/file.c deleted file mode 100644 index 9153dfe79bbc..000000000000 --- a/fs/efs/file.c +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * file.c - * - * Copyright (c) 1999 Al Smith - * - * Portions derived from work (c) 1995,1996 Christian Vogelgsang. - */ - -#include -#include "efs.h" - -int efs_get_block(struct inode *inode, sector_t iblock, - struct buffer_head *bh_result, int create) -{ - int error = -EROFS; - long phys; - - if (create) - return error; - if (iblock >= inode->i_blocks) - return 0; - - phys = efs_map_block(inode, iblock); - if (phys) - map_bh(bh_result, inode->i_sb, phys); - return 0; -} - -int efs_bmap(struct inode *inode, efs_block_t block) { - - if (block < 0) { - pr_warn("%s(): block < 0\n", __func__); - return 0; - } - - /* are we about to read past the end of a file ? */ - if (!(block < inode->i_blocks)) - return 0; - - return efs_map_block(inode, block); -} diff --git a/fs/efs/inode.c b/fs/efs/inode.c deleted file mode 100644 index 4b132729e638..000000000000 --- a/fs/efs/inode.c +++ /dev/null @@ -1,315 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * inode.c - * - * Copyright (c) 1999 Al Smith - * - * Portions derived from work (c) 1995,1996 Christian Vogelgsang, - * and from work (c) 1998 Mike Shaver. - */ - -#include -#include -#include -#include "efs.h" -#include - -static int efs_read_folio(struct file *file, struct folio *folio) -{ - return block_read_full_folio(folio, efs_get_block); -} - -static sector_t _efs_bmap(struct address_space *mapping, sector_t block) -{ - return generic_block_bmap(mapping,block,efs_get_block); -} - -static const struct address_space_operations efs_aops = { - .read_folio = efs_read_folio, - .bmap = _efs_bmap -}; - -static inline void extent_copy(efs_extent *src, efs_extent *dst) { - /* - * this is slightly evil. it doesn't just copy - * efs_extent from src to dst, it also mangles - * the bits so that dst ends up in cpu byte-order. - */ - - dst->cooked.ex_magic = (unsigned int) src->raw[0]; - dst->cooked.ex_bn = ((unsigned int) src->raw[1] << 16) | - ((unsigned int) src->raw[2] << 8) | - ((unsigned int) src->raw[3] << 0); - dst->cooked.ex_length = (unsigned int) src->raw[4]; - dst->cooked.ex_offset = ((unsigned int) src->raw[5] << 16) | - ((unsigned int) src->raw[6] << 8) | - ((unsigned int) src->raw[7] << 0); - return; -} - -struct inode *efs_iget(struct super_block *super, unsigned long ino) -{ - int i, inode_index; - dev_t device; - u32 rdev; - struct buffer_head *bh; - struct efs_sb_info *sb = SUPER_INFO(super); - struct efs_inode_info *in; - efs_block_t block, offset; - struct efs_dinode *efs_inode; - struct inode *inode; - - inode = iget_locked(super, ino); - if (!inode) - return ERR_PTR(-ENOMEM); - if (!(inode_state_read_once(inode) & I_NEW)) - return inode; - - in = INODE_INFO(inode); - - /* - ** EFS layout: - ** - ** | cylinder group | cylinder group | cylinder group ..etc - ** |inodes|data |inodes|data |inodes|data ..etc - ** - ** work out the inode block index, (considering initially that the - ** inodes are stored as consecutive blocks). then work out the block - ** number of that inode given the above layout, and finally the - ** offset of the inode within that block. - */ - - inode_index = inode->i_ino / - (EFS_BLOCKSIZE / sizeof(struct efs_dinode)); - - block = sb->fs_start + sb->first_block + - (sb->group_size * (inode_index / sb->inode_blocks)) + - (inode_index % sb->inode_blocks); - - offset = (inode->i_ino % - (EFS_BLOCKSIZE / sizeof(struct efs_dinode))) * - sizeof(struct efs_dinode); - - bh = sb_bread(inode->i_sb, block); - if (!bh) { - pr_warn("%s() failed at block %d\n", __func__, block); - goto read_inode_error; - } - - efs_inode = (struct efs_dinode *) (bh->b_data + offset); - - inode->i_mode = be16_to_cpu(efs_inode->di_mode); - set_nlink(inode, be16_to_cpu(efs_inode->di_nlink)); - i_uid_write(inode, (uid_t)be16_to_cpu(efs_inode->di_uid)); - i_gid_write(inode, (gid_t)be16_to_cpu(efs_inode->di_gid)); - inode->i_size = be32_to_cpu(efs_inode->di_size); - inode_set_atime(inode, be32_to_cpu(efs_inode->di_atime), 0); - inode_set_mtime(inode, be32_to_cpu(efs_inode->di_mtime), 0); - inode_set_ctime(inode, be32_to_cpu(efs_inode->di_ctime), 0); - - /* this is the number of blocks in the file */ - if (inode->i_size == 0) { - inode->i_blocks = 0; - } else { - inode->i_blocks = ((inode->i_size - 1) >> EFS_BLOCKSIZE_BITS) + 1; - } - - rdev = be16_to_cpu(efs_inode->di_u.di_dev.odev); - if (rdev == 0xffff) { - rdev = be32_to_cpu(efs_inode->di_u.di_dev.ndev); - if (sysv_major(rdev) > 0xfff) - device = 0; - else - device = MKDEV(sysv_major(rdev), sysv_minor(rdev)); - } else - device = old_decode_dev(rdev); - - /* get the number of extents for this object */ - in->numextents = be16_to_cpu(efs_inode->di_numextents); - in->lastextent = 0; - - /* copy the extents contained within the inode to memory */ - for(i = 0; i < EFS_DIRECTEXTENTS; i++) { - extent_copy(&(efs_inode->di_u.di_extents[i]), &(in->extents[i])); - if (i < in->numextents && in->extents[i].cooked.ex_magic != 0) { - pr_warn("extent %d has bad magic number in inode %llu\n", - i, inode->i_ino); - brelse(bh); - goto read_inode_error; - } - } - - brelse(bh); - pr_debug("efs_iget(): inode %llu, extents %d, mode %o\n", - inode->i_ino, in->numextents, inode->i_mode); - switch (inode->i_mode & S_IFMT) { - case S_IFDIR: - inode->i_op = &efs_dir_inode_operations; - inode->i_fop = &efs_dir_operations; - break; - case S_IFREG: - inode->i_fop = &generic_ro_fops; - inode->i_data.a_ops = &efs_aops; - break; - case S_IFLNK: - inode->i_op = &page_symlink_inode_operations; - inode_nohighmem(inode); - inode->i_data.a_ops = &efs_symlink_aops; - break; - case S_IFCHR: - case S_IFBLK: - case S_IFIFO: - init_special_inode(inode, inode->i_mode, device); - break; - default: - pr_warn("unsupported inode mode %o\n", inode->i_mode); - goto read_inode_error; - break; - } - - unlock_new_inode(inode); - return inode; - -read_inode_error: - pr_warn("failed to read inode %llu\n", inode->i_ino); - iget_failed(inode); - return ERR_PTR(-EIO); -} - -static inline efs_block_t -efs_extent_check(efs_extent *ptr, efs_block_t block, struct efs_sb_info *sb) { - efs_block_t start; - efs_block_t length; - efs_block_t offset; - - /* - * given an extent and a logical block within a file, - * can this block be found within this extent ? - */ - start = ptr->cooked.ex_bn; - length = ptr->cooked.ex_length; - offset = ptr->cooked.ex_offset; - - if ((block >= offset) && (block < offset+length)) { - return(sb->fs_start + start + block - offset); - } else { - return 0; - } -} - -efs_block_t efs_map_block(struct inode *inode, efs_block_t block) { - struct efs_sb_info *sb = SUPER_INFO(inode->i_sb); - struct efs_inode_info *in = INODE_INFO(inode); - struct buffer_head *bh = NULL; - - int cur, last, first = 1; - int ibase, ioffset, dirext, direxts, indext, indexts; - efs_block_t iblock, result = 0, lastblock = 0; - efs_extent ext, *exts; - - last = in->lastextent; - - if (in->numextents <= EFS_DIRECTEXTENTS) { - /* first check the last extent we returned */ - if ((result = efs_extent_check(&in->extents[last], block, sb))) - return result; - - /* if we only have one extent then nothing can be found */ - if (in->numextents == 1) { - pr_err("%s() failed to map (1 extent)\n", __func__); - return 0; - } - - direxts = in->numextents; - - /* - * check the stored extents in the inode - * start with next extent and check forwards - */ - for(dirext = 1; dirext < direxts; dirext++) { - cur = (last + dirext) % in->numextents; - if ((result = efs_extent_check(&in->extents[cur], block, sb))) { - in->lastextent = cur; - return result; - } - } - - pr_err("%s() failed to map block %u (dir)\n", __func__, block); - return 0; - } - - pr_debug("%s(): indirect search for logical block %u\n", - __func__, block); - direxts = in->extents[0].cooked.ex_offset; - indexts = in->numextents; - - for(indext = 0; indext < indexts; indext++) { - cur = (last + indext) % indexts; - - /* - * work out which direct extent contains `cur'. - * - * also compute ibase: i.e. the number of the first - * indirect extent contained within direct extent `cur'. - * - */ - ibase = 0; - for(dirext = 0; cur < ibase && dirext < direxts; dirext++) { - ibase += in->extents[dirext].cooked.ex_length * - (EFS_BLOCKSIZE / sizeof(efs_extent)); - } - - if (dirext == direxts) { - /* should never happen */ - pr_err("couldn't find direct extent for indirect extent %d (block %u)\n", - cur, block); - if (bh) brelse(bh); - return 0; - } - - /* work out block number and offset of this indirect extent */ - iblock = sb->fs_start + in->extents[dirext].cooked.ex_bn + - (cur - ibase) / - (EFS_BLOCKSIZE / sizeof(efs_extent)); - ioffset = (cur - ibase) % - (EFS_BLOCKSIZE / sizeof(efs_extent)); - - if (first || lastblock != iblock) { - if (bh) brelse(bh); - - bh = sb_bread(inode->i_sb, iblock); - if (!bh) { - pr_err("%s() failed at block %d\n", - __func__, iblock); - return 0; - } - pr_debug("%s(): read indirect extent block %d\n", - __func__, iblock); - first = 0; - lastblock = iblock; - } - - exts = (efs_extent *) bh->b_data; - - extent_copy(&(exts[ioffset]), &ext); - - if (ext.cooked.ex_magic != 0) { - pr_err("extent %d has bad magic number in block %d\n", - cur, iblock); - if (bh) brelse(bh); - return 0; - } - - if ((result = efs_extent_check(&ext, block, sb))) { - if (bh) brelse(bh); - in->lastextent = cur; - return result; - } - } - if (bh) brelse(bh); - pr_err("%s() failed to map block %u (indir)\n", __func__, block); - return 0; -} - -MODULE_DESCRIPTION("Extent File System (efs)"); -MODULE_LICENSE("GPL"); diff --git a/fs/efs/namei.c b/fs/efs/namei.c deleted file mode 100644 index 38961ee1d1af..000000000000 --- a/fs/efs/namei.c +++ /dev/null @@ -1,120 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * namei.c - * - * Copyright (c) 1999 Al Smith - * - * Portions derived from work (c) 1995,1996 Christian Vogelgsang. - */ - -#include -#include -#include -#include "efs.h" - - -static efs_ino_t efs_find_entry(struct inode *inode, const char *name, int len) -{ - struct buffer_head *bh; - - int slot, namelen; - char *nameptr; - struct efs_dir *dirblock; - struct efs_dentry *dirslot; - efs_ino_t inodenum; - efs_block_t block; - - if (inode->i_size & (EFS_DIRBSIZE-1)) - pr_warn("%s(): directory size not a multiple of EFS_DIRBSIZE\n", - __func__); - - for(block = 0; block < inode->i_blocks; block++) { - - bh = sb_bread(inode->i_sb, efs_bmap(inode, block)); - if (!bh) { - pr_err("%s(): failed to read dir block %d\n", - __func__, block); - return 0; - } - - dirblock = (struct efs_dir *) bh->b_data; - - if (be16_to_cpu(dirblock->magic) != EFS_DIRBLK_MAGIC) { - pr_err("%s(): invalid directory block\n", __func__); - brelse(bh); - return 0; - } - - for (slot = 0; slot < dirblock->slots; slot++) { - dirslot = (struct efs_dentry *) (((char *) bh->b_data) + EFS_SLOTAT(dirblock, slot)); - - namelen = dirslot->namelen; - nameptr = dirslot->name; - - if ((namelen == len) && (!memcmp(name, nameptr, len))) { - inodenum = be32_to_cpu(dirslot->inode); - brelse(bh); - return inodenum; - } - } - brelse(bh); - } - return 0; -} - -struct dentry *efs_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags) -{ - efs_ino_t inodenum; - struct inode *inode = NULL; - - inodenum = efs_find_entry(dir, dentry->d_name.name, dentry->d_name.len); - if (inodenum) - inode = efs_iget(dir->i_sb, inodenum); - - return d_splice_alias(inode, dentry); -} - -static struct inode *efs_nfs_get_inode(struct super_block *sb, u64 ino, - u32 generation) -{ - struct inode *inode; - - if (ino == 0) - return ERR_PTR(-ESTALE); - inode = efs_iget(sb, ino); - if (IS_ERR(inode)) - return ERR_CAST(inode); - - if (generation && inode->i_generation != generation) { - iput(inode); - return ERR_PTR(-ESTALE); - } - - return inode; -} - -struct dentry *efs_fh_to_dentry(struct super_block *sb, struct fid *fid, - int fh_len, int fh_type) -{ - return generic_fh_to_dentry(sb, fid, fh_len, fh_type, - efs_nfs_get_inode); -} - -struct dentry *efs_fh_to_parent(struct super_block *sb, struct fid *fid, - int fh_len, int fh_type) -{ - return generic_fh_to_parent(sb, fid, fh_len, fh_type, - efs_nfs_get_inode); -} - -struct dentry *efs_get_parent(struct dentry *child) -{ - struct dentry *parent = ERR_PTR(-ENOENT); - efs_ino_t ino; - - ino = efs_find_entry(d_inode(child), "..", 2); - if (ino) - parent = d_obtain_alias(efs_iget(child->d_sb, ino)); - - return parent; -} diff --git a/fs/efs/super.c b/fs/efs/super.c deleted file mode 100644 index 11fea3bbce7c..000000000000 --- a/fs/efs/super.c +++ /dev/null @@ -1,368 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * super.c - * - * Copyright (c) 1999 Al Smith - * - * Portions derived from work (c) 1995,1996 Christian Vogelgsang. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include "efs.h" -#include -#include - -static int efs_statfs(struct dentry *dentry, struct kstatfs *buf); -static int efs_init_fs_context(struct fs_context *fc); - -static void efs_kill_sb(struct super_block *s) -{ - struct efs_sb_info *sbi = SUPER_INFO(s); - kill_block_super(s); - kfree(sbi); -} - -static struct pt_types sgi_pt_types[] = { - {0x00, "SGI vh"}, - {0x01, "SGI trkrepl"}, - {0x02, "SGI secrepl"}, - {0x03, "SGI raw"}, - {0x04, "SGI bsd"}, - {SGI_SYSV, "SGI sysv"}, - {0x06, "SGI vol"}, - {SGI_EFS, "SGI efs"}, - {0x08, "SGI lv"}, - {0x09, "SGI rlv"}, - {0x0A, "SGI xfs"}, - {0x0B, "SGI xfslog"}, - {0x0C, "SGI xlv"}, - {0x82, "Linux swap"}, - {0x83, "Linux native"}, - {0, NULL} -}; - -/* - * File system definition and registration. - */ -static struct file_system_type efs_fs_type = { - .owner = THIS_MODULE, - .name = "efs", - .kill_sb = efs_kill_sb, - .fs_flags = FS_REQUIRES_DEV, - .init_fs_context = efs_init_fs_context, -}; -MODULE_ALIAS_FS("efs"); - -static struct kmem_cache * efs_inode_cachep; - -static struct inode *efs_alloc_inode(struct super_block *sb) -{ - struct efs_inode_info *ei; - ei = alloc_inode_sb(sb, efs_inode_cachep, GFP_KERNEL); - if (!ei) - return NULL; - return &ei->vfs_inode; -} - -static void efs_free_inode(struct inode *inode) -{ - kmem_cache_free(efs_inode_cachep, INODE_INFO(inode)); -} - -static void init_once(void *foo) -{ - struct efs_inode_info *ei = (struct efs_inode_info *) foo; - - inode_init_once(&ei->vfs_inode); -} - -static int __init init_inodecache(void) -{ - efs_inode_cachep = kmem_cache_create("efs_inode_cache", - sizeof(struct efs_inode_info), 0, - SLAB_RECLAIM_ACCOUNT|SLAB_ACCOUNT, - init_once); - if (efs_inode_cachep == NULL) - return -ENOMEM; - return 0; -} - -static void destroy_inodecache(void) -{ - /* - * Make sure all delayed rcu free inodes are flushed before we - * destroy cache. - */ - rcu_barrier(); - kmem_cache_destroy(efs_inode_cachep); -} - -static const struct super_operations efs_superblock_operations = { - .alloc_inode = efs_alloc_inode, - .free_inode = efs_free_inode, - .statfs = efs_statfs, -}; - -static const struct export_operations efs_export_ops = { - .encode_fh = generic_encode_ino32_fh, - .fh_to_dentry = efs_fh_to_dentry, - .fh_to_parent = efs_fh_to_parent, - .get_parent = efs_get_parent, -}; - -static int __init init_efs_fs(void) { - int err; - pr_info(EFS_VERSION" - http://aeschi.ch.eu.org/efs/\n"); - err = init_inodecache(); - if (err) - goto out1; - err = register_filesystem(&efs_fs_type); - if (err) - goto out; - return 0; -out: - destroy_inodecache(); -out1: - return err; -} - -static void __exit exit_efs_fs(void) { - unregister_filesystem(&efs_fs_type); - destroy_inodecache(); -} - -module_init(init_efs_fs) -module_exit(exit_efs_fs) - -static efs_block_t efs_validate_vh(struct volume_header *vh) { - int i; - __be32 cs, *ui; - int csum; - efs_block_t sblock = 0; /* shuts up gcc */ - struct pt_types *pt_entry; - int pt_type, slice = -1; - - if (be32_to_cpu(vh->vh_magic) != VHMAGIC) { - /* - * assume that we're dealing with a partition and allow - * read_super() to try and detect a valid superblock - * on the next block. - */ - return 0; - } - - ui = ((__be32 *) (vh + 1)) - 1; - for(csum = 0; ui >= ((__be32 *) vh);) { - cs = *ui--; - csum += be32_to_cpu(cs); - } - if (csum) { - pr_warn("SGI disklabel: checksum bad, label corrupted\n"); - return 0; - } - -#ifdef DEBUG - pr_debug("bf: \"%16s\"\n", vh->vh_bootfile); - - for(i = 0; i < NVDIR; i++) { - int j; - char name[VDNAMESIZE+1]; - - for(j = 0; j < VDNAMESIZE; j++) { - name[j] = vh->vh_vd[i].vd_name[j]; - } - name[j] = (char) 0; - - if (name[0]) { - pr_debug("vh: %8s block: 0x%08x size: 0x%08x\n", - name, (int) be32_to_cpu(vh->vh_vd[i].vd_lbn), - (int) be32_to_cpu(vh->vh_vd[i].vd_nbytes)); - } - } -#endif - - for(i = 0; i < NPARTAB; i++) { - pt_type = (int) be32_to_cpu(vh->vh_pt[i].pt_type); - for(pt_entry = sgi_pt_types; pt_entry->pt_name; pt_entry++) { - if (pt_type == pt_entry->pt_type) break; - } -#ifdef DEBUG - if (be32_to_cpu(vh->vh_pt[i].pt_nblks)) { - pr_debug("pt %2d: start: %08d size: %08d type: 0x%02x (%s)\n", - i, (int)be32_to_cpu(vh->vh_pt[i].pt_firstlbn), - (int)be32_to_cpu(vh->vh_pt[i].pt_nblks), - pt_type, (pt_entry->pt_name) ? - pt_entry->pt_name : "unknown"); - } -#endif - if (IS_EFS(pt_type)) { - sblock = be32_to_cpu(vh->vh_pt[i].pt_firstlbn); - slice = i; - } - } - - if (slice == -1) { - pr_notice("partition table contained no EFS partitions\n"); -#ifdef DEBUG - } else { - pr_info("using slice %d (type %s, offset 0x%x)\n", slice, - (pt_entry->pt_name) ? pt_entry->pt_name : "unknown", - sblock); -#endif - } - return sblock; -} - -static int efs_validate_super(struct efs_sb_info *sb, struct efs_super *super) { - - if (!IS_EFS_MAGIC(be32_to_cpu(super->fs_magic))) - return -1; - - sb->fs_magic = be32_to_cpu(super->fs_magic); - sb->total_blocks = be32_to_cpu(super->fs_size); - sb->first_block = be32_to_cpu(super->fs_firstcg); - sb->group_size = be32_to_cpu(super->fs_cgfsize); - sb->data_free = be32_to_cpu(super->fs_tfree); - sb->inode_free = be32_to_cpu(super->fs_tinode); - sb->inode_blocks = be16_to_cpu(super->fs_cgisize); - sb->total_groups = be16_to_cpu(super->fs_ncg); - - return 0; -} - -static int efs_fill_super(struct super_block *s, struct fs_context *fc) -{ - struct efs_sb_info *sb; - struct buffer_head *bh; - struct inode *root; - - sb = kzalloc_obj(struct efs_sb_info); - if (!sb) - return -ENOMEM; - s->s_fs_info = sb; - s->s_time_min = 0; - s->s_time_max = U32_MAX; - - s->s_magic = EFS_SUPER_MAGIC; - if (!sb_set_blocksize(s, EFS_BLOCKSIZE)) { - pr_err("device does not support %d byte blocks\n", - EFS_BLOCKSIZE); - return invalf(fc, "device does not support %d byte blocks\n", - EFS_BLOCKSIZE); - } - - /* read the vh (volume header) block */ - bh = sb_bread(s, 0); - - if (!bh) { - pr_err("cannot read volume header\n"); - return -EIO; - } - - /* - * if this returns zero then we didn't find any partition table. - * this isn't (yet) an error - just assume for the moment that - * the device is valid and go on to search for a superblock. - */ - sb->fs_start = efs_validate_vh((struct volume_header *) bh->b_data); - brelse(bh); - - if (sb->fs_start == -1) { - return -EINVAL; - } - - bh = sb_bread(s, sb->fs_start + EFS_SUPER); - if (!bh) { - pr_err("cannot read superblock\n"); - return -EIO; - } - - if (efs_validate_super(sb, (struct efs_super *) bh->b_data)) { -#ifdef DEBUG - pr_warn("invalid superblock at block %u\n", - sb->fs_start + EFS_SUPER); -#endif - brelse(bh); - return -EINVAL; - } - brelse(bh); - - if (!sb_rdonly(s)) { -#ifdef DEBUG - pr_info("forcing read-only mode\n"); -#endif - s->s_flags |= SB_RDONLY; - } - s->s_op = &efs_superblock_operations; - s->s_export_op = &efs_export_ops; - root = efs_iget(s, EFS_ROOTINODE); - if (IS_ERR(root)) { - pr_err("get root inode failed\n"); - return PTR_ERR(root); - } - - s->s_root = d_make_root(root); - if (!(s->s_root)) { - pr_err("get root dentry failed\n"); - return -ENOMEM; - } - - return 0; -} - -static int efs_get_tree(struct fs_context *fc) -{ - return get_tree_bdev(fc, efs_fill_super); -} - -static int efs_reconfigure(struct fs_context *fc) -{ - sync_filesystem(fc->root->d_sb); - fc->sb_flags |= SB_RDONLY; - - return 0; -} - -static const struct fs_context_operations efs_context_opts = { - .get_tree = efs_get_tree, - .reconfigure = efs_reconfigure, -}; - -/* - * Set up the filesystem mount context. - */ -static int efs_init_fs_context(struct fs_context *fc) -{ - fc->ops = &efs_context_opts; - - return 0; -} - -static int efs_statfs(struct dentry *dentry, struct kstatfs *buf) { - struct super_block *sb = dentry->d_sb; - struct efs_sb_info *sbi = SUPER_INFO(sb); - u64 id = huge_encode_dev(sb->s_bdev->bd_dev); - - buf->f_type = EFS_SUPER_MAGIC; /* efs magic number */ - buf->f_bsize = EFS_BLOCKSIZE; /* blocksize */ - buf->f_blocks = sbi->total_groups * /* total data blocks */ - (sbi->group_size - sbi->inode_blocks); - buf->f_bfree = sbi->data_free; /* free data blocks */ - buf->f_bavail = sbi->data_free; /* free blocks for non-root */ - buf->f_files = sbi->total_groups * /* total inodes */ - sbi->inode_blocks * - (EFS_BLOCKSIZE / sizeof(struct efs_dinode)); - buf->f_ffree = sbi->inode_free; /* free inodes */ - buf->f_fsid = u64_to_fsid(id); - buf->f_namelen = EFS_MAXNAMELEN; /* max filename length */ - - return 0; -} - diff --git a/fs/efs/symlink.c b/fs/efs/symlink.c deleted file mode 100644 index 7749feded722..000000000000 --- a/fs/efs/symlink.c +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * symlink.c - * - * Copyright (c) 1999 Al Smith - * - * Portions derived from work (c) 1995,1996 Christian Vogelgsang. - */ - -#include -#include -#include -#include "efs.h" - -static int efs_symlink_read_folio(struct file *file, struct folio *folio) -{ - char *link = folio_address(folio); - struct buffer_head *bh; - struct inode *inode = folio->mapping->host; - efs_block_t size = inode->i_size; - int err; - - err = -ENAMETOOLONG; - if (size > 2 * EFS_BLOCKSIZE) - goto fail; - - /* read first 512 bytes of link target */ - err = -EIO; - bh = sb_bread(inode->i_sb, efs_bmap(inode, 0)); - if (!bh) - goto fail; - memcpy(link, bh->b_data, (size > EFS_BLOCKSIZE) ? EFS_BLOCKSIZE : size); - brelse(bh); - if (size > EFS_BLOCKSIZE) { - bh = sb_bread(inode->i_sb, efs_bmap(inode, 1)); - if (!bh) - goto fail; - memcpy(link + EFS_BLOCKSIZE, bh->b_data, size - EFS_BLOCKSIZE); - brelse(bh); - } - link[size] = '\0'; - err = 0; -fail: - folio_end_read(folio, err == 0); - return err; -} - -const struct address_space_operations efs_symlink_aops = { - .read_folio = efs_symlink_read_folio -}; diff --git a/include/linux/efs_vh.h b/include/linux/efs_vh.h deleted file mode 100644 index 206c5270f7b8..000000000000 --- a/include/linux/efs_vh.h +++ /dev/null @@ -1,54 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * efs_vh.h - * - * Copyright (c) 1999 Al Smith - * - * Portions derived from IRIX header files (c) 1985 MIPS Computer Systems, Inc. - */ - -#ifndef __EFS_VH_H__ -#define __EFS_VH_H__ - -#define VHMAGIC 0xbe5a941 /* volume header magic number */ -#define NPARTAB 16 /* 16 unix partitions */ -#define NVDIR 15 /* max of 15 directory entries */ -#define BFNAMESIZE 16 /* max 16 chars in boot file name */ -#define VDNAMESIZE 8 - -struct volume_directory { - char vd_name[VDNAMESIZE]; /* name */ - __be32 vd_lbn; /* logical block number */ - __be32 vd_nbytes; /* file length in bytes */ -}; - -struct partition_table { /* one per logical partition */ - __be32 pt_nblks; /* # of logical blks in partition */ - __be32 pt_firstlbn; /* first lbn of partition */ - __be32 pt_type; /* use of partition */ -}; - -struct volume_header { - __be32 vh_magic; /* identifies volume header */ - __be16 vh_rootpt; /* root partition number */ - __be16 vh_swappt; /* swap partition number */ - char vh_bootfile[BFNAMESIZE]; /* name of file to boot */ - char pad[48]; /* device param space */ - struct volume_directory vh_vd[NVDIR]; /* other vol hdr contents */ - struct partition_table vh_pt[NPARTAB]; /* device partition layout */ - __be32 vh_csum; /* volume header checksum */ - __be32 vh_fill; /* fill out to 512 bytes */ -}; - -/* partition type sysv is used for EFS format CD-ROM partitions */ -#define SGI_SYSV 0x05 -#define SGI_EFS 0x07 -#define IS_EFS(x) (((x) == SGI_EFS) || ((x) == SGI_SYSV)) - -struct pt_types { - int pt_type; - char *pt_name; -}; - -#endif /* __EFS_VH_H__ */ - diff --git a/include/uapi/linux/efs_fs_sb.h b/include/uapi/linux/efs_fs_sb.h deleted file mode 100644 index 6bad29a10faa..000000000000 --- a/include/uapi/linux/efs_fs_sb.h +++ /dev/null @@ -1,63 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ -/* - * efs_fs_sb.h - * - * Copyright (c) 1999 Al Smith - * - * Portions derived from IRIX header files (c) 1988 Silicon Graphics - */ - -#ifndef __EFS_FS_SB_H__ -#define __EFS_FS_SB_H__ - -#include -#include - -/* EFS superblock magic numbers */ -#define EFS_MAGIC 0x072959 -#define EFS_NEWMAGIC 0x07295a - -#define IS_EFS_MAGIC(x) ((x == EFS_MAGIC) || (x == EFS_NEWMAGIC)) - -#define EFS_SUPER 1 -#define EFS_ROOTINODE 2 - -/* efs superblock on disk */ -struct efs_super { - __be32 fs_size; /* size of filesystem, in sectors */ - __be32 fs_firstcg; /* bb offset to first cg */ - __be32 fs_cgfsize; /* size of cylinder group in bb's */ - __be16 fs_cgisize; /* bb's of inodes per cylinder group */ - __be16 fs_sectors; /* sectors per track */ - __be16 fs_heads; /* heads per cylinder */ - __be16 fs_ncg; /* # of cylinder groups in filesystem */ - __be16 fs_dirty; /* fs needs to be fsck'd */ - __be32 fs_time; /* last super-block update */ - __be32 fs_magic; /* magic number */ - char fs_fname[6]; /* file system name */ - char fs_fpack[6]; /* file system pack name */ - __be32 fs_bmsize; /* size of bitmap in bytes */ - __be32 fs_tfree; /* total free data blocks */ - __be32 fs_tinode; /* total free inodes */ - __be32 fs_bmblock; /* bitmap location. */ - __be32 fs_replsb; /* Location of replicated superblock. */ - __be32 fs_lastialloc; /* last allocated inode */ - char fs_spare[20]; /* space for expansion - MUST BE ZERO */ - __be32 fs_checksum; /* checksum of volume portion of fs */ -}; - -/* efs superblock information in memory */ -struct efs_sb_info { - __u32 fs_magic; /* superblock magic number */ - __u32 fs_start; /* first block of filesystem */ - __u32 first_block; /* first data block in filesystem */ - __u32 total_blocks; /* total number of blocks in filesystem */ - __u32 group_size; /* # of blocks a group consists of */ - __u32 data_free; /* # of free data blocks */ - __u32 inode_free; /* # of free inodes */ - __u16 inode_blocks; /* # of blocks used for inodes in every grp */ - __u16 total_groups; /* # of groups */ -}; - -#endif /* __EFS_FS_SB_H__ */ - -- cgit v1.2.3 From 859055e07697c46f6964109981aa1cd23d6bde47 Mon Sep 17 00:00:00 2001 From: Jiri Olsa Date: Mon, 29 Jun 2026 23:22:06 +0200 Subject: bpf: Add tracing_multi link info support Adding BPF_OBJ_GET_INFO_BY_FD support for tracing_multi links. We expose following tracing_multi link data: - attach_type of the program - number of ids - array of BTF ids - array of its related kernel addresses - array of cookies The change follows the kprobe_multi and uprobe_multi link-info convention of optional output arrays with an in/out count, On top of standard tracing link data we also expose addresses, because they are useful info for user (especially when the attachment was done via pattern). This data is hidden when kallsyms does not allow exposing kernel pointer values. Assisted-by: Codex:GPT-5 Signed-off-by: Jiri Olsa Signed-off-by: Andrii Nakryiko Acked-by: Leon Hwang Acked-by: Andrii Nakryiko Link: https://lore.kernel.org/bpf/20260629212208.895962-2-jolsa@kernel.org --- include/uapi/linux/bpf.h | 9 +++++++ kernel/trace/bpf_trace.c | 55 ++++++++++++++++++++++++++++++++++++++++++ tools/include/uapi/linux/bpf.h | 9 +++++++ 3 files changed, 73 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index c91b5a4bda03..2f1d24fef857 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -6842,6 +6842,15 @@ struct bpf_link_info { __u32 flags; __u32 pid; } uprobe_multi; + struct { + __u32 attach_type; + __u32 count; /* in/out: tracing_multi target count */ + __u32 btf_obj_id; + __u32 :32; + __aligned_u64 ids; + __aligned_u64 addrs; + __aligned_u64 cookies; + } tracing_multi; struct { __u32 type; /* enum bpf_perf_event_type */ __u32 :32; diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c index 75495a5c3507..76ab51deaa6b 100644 --- a/kernel/trace/bpf_trace.c +++ b/kernel/trace/bpf_trace.c @@ -3700,6 +3700,60 @@ static void bpf_tracing_multi_link_dealloc(struct bpf_link *link) kvfree(tr_link); } +static int bpf_tracing_multi_link_fill_link_info(const struct bpf_link *link, + struct bpf_link_info *info) +{ + u64 __user *ucookies = u64_to_user_ptr(info->tracing_multi.cookies); + u64 __user *uaddrs = u64_to_user_ptr(info->tracing_multi.addrs); + u32 __user *uids = u64_to_user_ptr(info->tracing_multi.ids); + struct bpf_tracing_multi_link *tr_link; + u32 ucount = info->tracing_multi.count; + bool has_cookies, show_addrs; + int err = 0; + + if ((uids || ucookies || uaddrs) && !ucount) + return -EINVAL; + + tr_link = container_of(link, struct bpf_tracing_multi_link, link); + + info->tracing_multi.attach_type = tr_link->link.attach_type; + info->tracing_multi.count = tr_link->nodes_cnt; + info->tracing_multi.btf_obj_id = btf_obj_id(tr_link->link.prog->aux->attach_btf); + + if (!uids && !ucookies && !uaddrs) + return 0; + + if (ucount < tr_link->nodes_cnt) + err = -ENOSPC; + else + ucount = tr_link->nodes_cnt; + + has_cookies = !!tr_link->cookies; + show_addrs = kallsyms_show_value(current_cred()); + + for (int i = 0; i < ucount; i++) { + struct bpf_tracing_multi_node *mnode = &tr_link->nodes[i]; + u64 addr, cookie; + u32 id; + + bpf_trampoline_unpack_key(mnode->trampoline->key, NULL, &id); + + addr = show_addrs ? mnode->trampoline->ip : 0; + cookie = has_cookies ? tr_link->cookies[i] : 0; + + if (uids && put_user(id, uids + i)) + return -EFAULT; + if (uaddrs && put_user(addr, uaddrs + i)) + return -EFAULT; + if (ucookies && put_user(cookie, ucookies + i)) + return -EFAULT; + + cond_resched(); + } + + return err; +} + #ifdef CONFIG_PROC_FS static void bpf_tracing_multi_show_fdinfo(const struct bpf_link *link, struct seq_file *seq) @@ -3730,6 +3784,7 @@ static void bpf_tracing_multi_show_fdinfo(const struct bpf_link *link, static const struct bpf_link_ops bpf_tracing_multi_link_lops = { .release = bpf_tracing_multi_link_release, .dealloc_deferred = bpf_tracing_multi_link_dealloc, + .fill_link_info = bpf_tracing_multi_link_fill_link_info, #ifdef CONFIG_PROC_FS .show_fdinfo = bpf_tracing_multi_show_fdinfo, #endif diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index c91b5a4bda03..2f1d24fef857 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -6842,6 +6842,15 @@ struct bpf_link_info { __u32 flags; __u32 pid; } uprobe_multi; + struct { + __u32 attach_type; + __u32 count; /* in/out: tracing_multi target count */ + __u32 btf_obj_id; + __u32 :32; + __aligned_u64 ids; + __aligned_u64 addrs; + __aligned_u64 cookies; + } tracing_multi; struct { __u32 type; /* enum bpf_perf_event_type */ __u32 :32; -- cgit v1.2.3 From 7cb8198761e627ff3a3b4770c8f147e75c4e649d Mon Sep 17 00:00:00 2001 From: Yuyang Huang Date: Tue, 30 Jun 2026 20:02:05 +0900 Subject: net: ipv4: report multicast group user count RTM_GETMULTICAST has been part of the rtnetlink ABI for a long time and already reports IPv4 multicast group membership through IFA_MULTICAST and IFA_CACHEINFO. It does not report how many consumers hold each membership, so userspace still has to parse /proc/net/igmp to get the Users column. Add IFA_MC_USERS as a u32 attribute carrying ip_mc_list::users in RTM_GETMULTICAST replies and entry-lifecycle notifications. This gives iproute2 enough information to migrate the IPv4 part of "ip maddr show" from procfs parsing to rtnetlink. Signed-off-by: Yuyang Huang Reviewed-by: Vadim Fedorenko Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260630110207.37841-2-sigefriedhyy@gmail.com Signed-off-by: Paolo Abeni --- Documentation/netlink/specs/rt-addr.yaml | 4 ++++ include/uapi/linux/if_addr.h | 1 + net/ipv4/igmp.c | 2 ++ 3 files changed, 7 insertions(+) (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/rt-addr.yaml b/Documentation/netlink/specs/rt-addr.yaml index 163a106c41bb..0ecbd24c890c 100644 --- a/Documentation/netlink/specs/rt-addr.yaml +++ b/Documentation/netlink/specs/rt-addr.yaml @@ -123,6 +123,9 @@ attribute-sets: - name: proto type: u8 + - + name: mc-users + type: u32 operations: @@ -176,6 +179,7 @@ operations: value: 58 attributes: &mcaddr-attrs - multicast + - mc-users - cacheinfo dump: request: diff --git a/include/uapi/linux/if_addr.h b/include/uapi/linux/if_addr.h index aa7958b4e41d..7fb630b7fe31 100644 --- a/include/uapi/linux/if_addr.h +++ b/include/uapi/linux/if_addr.h @@ -36,6 +36,7 @@ enum { IFA_RT_PRIORITY, /* u32, priority/metric for prefix route */ IFA_TARGET_NETNSID, IFA_PROTO, /* u8, address protocol */ + IFA_MC_USERS, /* u32, multicast group users */ __IFA_MAX, }; diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c index b6337a47c141..116ce7cec80e 100644 --- a/net/ipv4/igmp.c +++ b/net/ipv4/igmp.c @@ -1473,6 +1473,7 @@ int inet_fill_ifmcaddr(struct sk_buff *skb, struct net_device *dev, ci.ifa_valid = INFINITY_LIFE_TIME; if (nla_put_in_addr(skb, IFA_MULTICAST, im->multiaddr) < 0 || + nla_put_u32(skb, IFA_MC_USERS, READ_ONCE(im->users)) < 0 || nla_put(skb, IFA_CACHEINFO, sizeof(ci), &ci) < 0) { nlmsg_cancel(skb, nlh); return -EMSGSIZE; @@ -1494,6 +1495,7 @@ static void inet_ifmcaddr_notify(struct net_device *dev, skb = nlmsg_new(NLMSG_ALIGN(sizeof(struct ifaddrmsg)) + nla_total_size(sizeof(__be32)) + + nla_total_size(sizeof(u32)) + nla_total_size(sizeof(struct ifa_cacheinfo)), GFP_KERNEL); if (!skb) -- cgit v1.2.3 From 4ab9b637b94a3821acfcd6d6037dffe33669245a Mon Sep 17 00:00:00 2001 From: Priyansha Tiwari Date: Thu, 11 Jun 2026 11:52:22 +0530 Subject: wifi: nl80211/cfg80211: rename probe_client to probe_peer Rename NL80211_CMD_PROBE_CLIENT to NL80211_CMD_PROBE_PEER in the UAPI enum and retain NL80211_CMD_PROBE_CLIENT as a compatibility alias. Rename the .probe_client cfg80211_ops callback to .probe_peer and update all in-tree users (wil6210, mwifiex) and mac80211 so the tree continues to build after this change. Signed-off-by: Priyansha Tiwari Link: https://patch.msgid.link/20260611062225.2144241-2-pritiwa@qti.qualcomm.com Signed-off-by: Johannes Berg --- drivers/net/wireless/ath/wil6210/cfg80211.c | 8 ++++---- drivers/net/wireless/marvell/mwifiex/cfg80211.c | 8 ++++---- include/net/cfg80211.h | 6 +++--- include/uapi/linux/nl80211.h | 5 +++-- net/mac80211/cfg.c | 6 +++--- net/wireless/nl80211.c | 17 ++++++++--------- net/wireless/rdev-ops.h | 10 +++++----- net/wireless/trace.h | 2 +- 8 files changed, 31 insertions(+), 31 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/net/wireless/ath/wil6210/cfg80211.c b/drivers/net/wireless/ath/wil6210/cfg80211.c index d6ef92cfcbaf..a85ff2a4316b 100644 --- a/drivers/net/wireless/ath/wil6210/cfg80211.c +++ b/drivers/net/wireless/ath/wil6210/cfg80211.c @@ -2379,9 +2379,9 @@ void wil_probe_client_flush(struct wil6210_vif *vif) mutex_unlock(&vif->probe_client_mutex); } -static int wil_cfg80211_probe_client(struct wiphy *wiphy, - struct net_device *dev, - const u8 *peer, u64 *cookie) +static int wil_cfg80211_probe_peer(struct wiphy *wiphy, + struct net_device *dev, + const u8 *peer, u64 *cookie) { struct wil6210_priv *wil = wiphy_to_wil(wiphy); struct wil6210_vif *vif = ndev_to_vif(dev); @@ -2660,7 +2660,7 @@ static const struct cfg80211_ops wil_cfg80211_ops = { .add_station = wil_cfg80211_add_station, .del_station = wil_cfg80211_del_station, .change_station = wil_cfg80211_change_station, - .probe_client = wil_cfg80211_probe_client, + .probe_peer = wil_cfg80211_probe_peer, .change_bss = wil_cfg80211_change_bss, /* P2P device */ .start_p2p_device = wil_cfg80211_start_p2p_device, diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c index c9daf893472f..99d96088e364 100644 --- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c +++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c @@ -4558,9 +4558,9 @@ mwifiex_cfg80211_disassociate(struct wiphy *wiphy, } static int -mwifiex_cfg80211_probe_client(struct wiphy *wiphy, - struct net_device *dev, const u8 *peer, - u64 *cookie) +mwifiex_cfg80211_probe_peer(struct wiphy *wiphy, + struct net_device *dev, const u8 *peer, + u64 *cookie) { /* hostapd looks for NL80211_CMD_PROBE_CLIENT support; otherwise, * it requires monitor-mode support (which mwifiex doesn't support). @@ -4726,7 +4726,7 @@ int mwifiex_register_cfg80211(struct mwifiex_adapter *adapter) ops->disassoc = mwifiex_cfg80211_disassociate; ops->disconnect = NULL; ops->connect = NULL; - ops->probe_client = mwifiex_cfg80211_probe_client; + ops->probe_peer = mwifiex_cfg80211_probe_peer; } wiphy->max_scan_ssids = MWIFIEX_MAX_SSID_LIST_LENGTH; wiphy->max_scan_ie_len = MWIFIEX_MAX_VSIE_LEN; diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 8188ad200de5..549b2214e833 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -5086,7 +5086,7 @@ struct mgmt_frame_regs { * @tdls_mgmt: Transmit a TDLS management frame. * @tdls_oper: Perform a high-level TDLS operation (e.g. TDLS link setup). * - * @probe_client: probe an associated client, must return a cookie that it + * @probe_peer: probe an associated client, must return a cookie that it * later passes to cfg80211_probe_status(). * * @set_noack_map: Set the NoAck Map for the TIDs. @@ -5488,8 +5488,8 @@ struct cfg80211_ops { int (*tdls_oper)(struct wiphy *wiphy, struct net_device *dev, const u8 *peer, enum nl80211_tdls_operation oper); - int (*probe_client)(struct wiphy *wiphy, struct net_device *dev, - const u8 *peer, u64 *cookie); + int (*probe_peer)(struct wiphy *wiphy, struct net_device *dev, + const u8 *peer, u64 *cookie); int (*set_noack_map)(struct wiphy *wiphy, struct net_device *dev, diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 9998f6c0a665..d1907dd12a80 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -922,7 +922,7 @@ * and wasn't already in a 4-addr VLAN. The event will be sent similarly * to the %NL80211_CMD_UNEXPECTED_FRAME event, to the same listener. * - * @NL80211_CMD_PROBE_CLIENT: Probe an associated station on an AP interface + * @NL80211_CMD_PROBE_PEER: Probe an associated station on an AP interface * by sending a null data frame to it and reporting when the frame is * acknowledged. This is used to allow timing out inactive clients. Uses * %NL80211_ATTR_IFINDEX and %NL80211_ATTR_MAC. The command returns a @@ -1558,7 +1558,7 @@ enum nl80211_commands { NL80211_CMD_UNEXPECTED_FRAME, - NL80211_CMD_PROBE_CLIENT, + NL80211_CMD_PROBE_PEER, NL80211_CMD_REGISTER_BEACONS, @@ -1729,6 +1729,7 @@ enum nl80211_commands { #define NL80211_CMD_GET_MESH_PARAMS NL80211_CMD_GET_MESH_CONFIG #define NL80211_CMD_SET_MESH_PARAMS NL80211_CMD_SET_MESH_CONFIG #define NL80211_MESH_SETUP_VENDOR_PATH_SEL_IE NL80211_MESH_SETUP_IE +#define NL80211_CMD_PROBE_CLIENT NL80211_CMD_PROBE_PEER /** * enum nl80211_attrs - nl80211 netlink attributes diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 3b58af59f7e4..9c311c8290f7 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -4949,8 +4949,8 @@ static int ieee80211_set_rekey_data(struct wiphy *wiphy, return 0; } -static int ieee80211_probe_client(struct wiphy *wiphy, struct net_device *dev, - const u8 *peer, u64 *cookie) +static int ieee80211_probe_peer(struct wiphy *wiphy, struct net_device *dev, + const u8 *peer, u64 *cookie) { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_local *local = sdata->local; @@ -6060,7 +6060,7 @@ const struct cfg80211_ops mac80211_config_ops = { .tdls_mgmt = ieee80211_tdls_mgmt, .tdls_channel_switch = ieee80211_tdls_channel_switch, .tdls_cancel_channel_switch = ieee80211_tdls_cancel_channel_switch, - .probe_client = ieee80211_probe_client, + .probe_peer = ieee80211_probe_peer, .set_noack_map = ieee80211_set_noack_map, #ifdef CONFIG_PM .set_wakeup = ieee80211_set_wakeup, diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 53b4b3f76697..0d651a46b9d6 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -2444,7 +2444,7 @@ static int nl80211_add_commands_unsplit(struct cfg80211_registered_device *rdev, } if (rdev->wiphy.max_sched_scan_reqs) CMD(sched_scan_start, START_SCHED_SCAN); - CMD(probe_client, PROBE_CLIENT); + CMD(probe_peer, PROBE_PEER); CMD(set_noack_map, SET_NOACK_MAP); if (rdev->wiphy.flags & WIPHY_FLAG_REPORTS_OBSS) { i++; @@ -16150,8 +16150,7 @@ static int nl80211_register_unexpected_frame(struct sk_buff *skb, return 0; } -static int nl80211_probe_client(struct sk_buff *skb, - struct genl_info *info) +static int nl80211_probe_peer(struct sk_buff *skb, struct genl_info *info) { struct cfg80211_registered_device *rdev = info->user_ptr[0]; struct net_device *dev = info->user_ptr[1]; @@ -16169,7 +16168,7 @@ static int nl80211_probe_client(struct sk_buff *skb, if (!info->attrs[NL80211_ATTR_MAC]) return -EINVAL; - if (!rdev->ops->probe_client) + if (!rdev->ops->probe_peer) return -EOPNOTSUPP; msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); @@ -16177,7 +16176,7 @@ static int nl80211_probe_client(struct sk_buff *skb, return -ENOMEM; hdr = nl80211hdr_put(msg, info->snd_portid, info->snd_seq, 0, - NL80211_CMD_PROBE_CLIENT); + NL80211_CMD_PROBE_PEER); if (!hdr) { err = -ENOBUFS; goto free_msg; @@ -16185,7 +16184,7 @@ static int nl80211_probe_client(struct sk_buff *skb, addr = nla_data(info->attrs[NL80211_ATTR_MAC]); - err = rdev_probe_client(rdev, dev, addr, &cookie); + err = rdev_probe_peer(rdev, dev, addr, &cookie); if (err) goto free_msg; @@ -20042,9 +20041,9 @@ static const struct genl_small_ops nl80211_small_ops[] = { .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV), }, { - .cmd = NL80211_CMD_PROBE_CLIENT, + .cmd = NL80211_CMD_PROBE_PEER, .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, - .doit = nl80211_probe_client, + .doit = nl80211_probe_peer, .flags = GENL_UNS_ADMIN_PERM, .internal_flags = IFLAGS(NL80211_FLAG_NEED_NETDEV_UP), }, @@ -22614,7 +22613,7 @@ void cfg80211_probe_status(struct net_device *dev, const u8 *addr, if (!msg) return; - hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_PROBE_CLIENT); + hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_PROBE_PEER); if (!hdr) { nlmsg_free(msg); return; diff --git a/net/wireless/rdev-ops.h b/net/wireless/rdev-ops.h index 63c26e8b1139..6c3bad8b2d6f 100644 --- a/net/wireless/rdev-ops.h +++ b/net/wireless/rdev-ops.h @@ -948,13 +948,13 @@ static inline int rdev_tdls_oper(struct cfg80211_registered_device *rdev, return ret; } -static inline int rdev_probe_client(struct cfg80211_registered_device *rdev, - struct net_device *dev, const u8 *peer, - u64 *cookie) +static inline int rdev_probe_peer(struct cfg80211_registered_device *rdev, + struct net_device *dev, const u8 *peer, + u64 *cookie) { int ret; - trace_rdev_probe_client(&rdev->wiphy, dev, peer); - ret = rdev->ops->probe_client(&rdev->wiphy, dev, peer, cookie); + trace_rdev_probe_peer(&rdev->wiphy, dev, peer); + ret = rdev->ops->probe_peer(&rdev->wiphy, dev, peer, cookie); trace_rdev_return_int_cookie(&rdev->wiphy, ret, *cookie); return ret; } diff --git a/net/wireless/trace.h b/net/wireless/trace.h index 94944f2a39a4..8c2a91b85c39 100644 --- a/net/wireless/trace.h +++ b/net/wireless/trace.h @@ -2132,7 +2132,7 @@ DECLARE_EVENT_CLASS(rdev_pmksa, WIPHY_PR_ARG, NETDEV_PR_ARG, __entry->bssid) ); -TRACE_EVENT(rdev_probe_client, +TRACE_EVENT(rdev_probe_peer, TP_PROTO(struct wiphy *wiphy, struct net_device *netdev, const u8 *peer), TP_ARGS(wiphy, netdev, peer), -- cgit v1.2.3 From 010e955c203e435d5bdba228fdc1556297d3d7ff Mon Sep 17 00:00:00 2001 From: Priyansha Tiwari Date: Thu, 11 Jun 2026 11:52:23 +0530 Subject: wifi: cfg80211/nl80211: add STA-mode peer probing Add NL80211_EXT_FEATURE_PROBE_AP to allow drivers to advertise support for probing the associated AP from STA/P2P-client mode. Extend nl80211_probe_peer() to accept STA/P2P-client interfaces when the driver advertises NL80211_EXT_FEATURE_PROBE_AP; in that case the MAC attribute must be omitted (the peer is implied by the association). Update cfg80211_probe_status() to accept an optional peer address and a link_id parameter (-1 for non-MLO), and include NL80211_ATTR_MLO_LINK_ID in the event when link_id >= 0. Update all callers. Signed-off-by: Priyansha Tiwari Link: https://patch.msgid.link/20260611062225.2144241-3-pritiwa@qti.qualcomm.com Signed-off-by: Johannes Berg --- drivers/net/wireless/ath/wil6210/cfg80211.c | 2 +- include/net/cfg80211.h | 14 ++++---- include/uapi/linux/nl80211.h | 20 +++++++---- net/mac80211/status.c | 2 +- net/wireless/nl80211.c | 52 ++++++++++++++++++++--------- 5 files changed, 59 insertions(+), 31 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/net/wireless/ath/wil6210/cfg80211.c b/drivers/net/wireless/ath/wil6210/cfg80211.c index a85ff2a4316b..5f2bd9a31faf 100644 --- a/drivers/net/wireless/ath/wil6210/cfg80211.c +++ b/drivers/net/wireless/ath/wil6210/cfg80211.c @@ -2326,7 +2326,7 @@ static void wil_probe_client_handle(struct wil6210_priv *wil, */ bool alive = (sta->status == wil_sta_connected); - cfg80211_probe_status(ndev, sta->addr, req->cookie, alive, + cfg80211_probe_status(ndev, sta->addr, req->cookie, -1, alive, 0, false, GFP_KERNEL); } diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 549b2214e833..ddefe5acc5ae 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -5086,8 +5086,8 @@ struct mgmt_frame_regs { * @tdls_mgmt: Transmit a TDLS management frame. * @tdls_oper: Perform a high-level TDLS operation (e.g. TDLS link setup). * - * @probe_peer: probe an associated client, must return a cookie that it - * later passes to cfg80211_probe_status(). + * @probe_peer: probe a connected peer (AP: STA MAC required; STA: no MAC), + * must return a cookie that is later passed to cfg80211_probe_status(). * * @set_noack_map: Set the NoAck Map for the TIDs. * @@ -9846,15 +9846,17 @@ bool cfg80211_rx_unexpected_4addr_frame(struct net_device *dev, const u8 *addr, /** * cfg80211_probe_status - notify userspace about probe status * @dev: the device the probe was sent on - * @addr: the address of the peer - * @cookie: the cookie filled in @probe_client previously + * @peer: The peer MAC address (or MLD address for MLO) or %NULL if not + * applicable (e.g. for STA/P2P-client) + * @cookie: the cookie filled in @probe_peer previously + * @link_id: The link ID on which the probe was sent (or -1 for non-MLO) * @acked: indicates whether probe was acked or not * @ack_signal: signal strength (in dBm) of the ACK frame. * @is_valid_ack_signal: indicates the ack_signal is valid or not. * @gfp: allocation flags */ -void cfg80211_probe_status(struct net_device *dev, const u8 *addr, - u64 cookie, bool acked, s32 ack_signal, +void cfg80211_probe_status(struct net_device *dev, const u8 *peer, u64 cookie, + int link_id, bool acked, s32 ack_signal, bool is_valid_ack_signal, gfp_t gfp); /** diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index d1907dd12a80..6b8071606e6f 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -922,13 +922,15 @@ * and wasn't already in a 4-addr VLAN. The event will be sent similarly * to the %NL80211_CMD_UNEXPECTED_FRAME event, to the same listener. * - * @NL80211_CMD_PROBE_PEER: Probe an associated station on an AP interface - * by sending a null data frame to it and reporting when the frame is - * acknowledged. This is used to allow timing out inactive clients. Uses - * %NL80211_ATTR_IFINDEX and %NL80211_ATTR_MAC. The command returns a - * direct reply with an %NL80211_ATTR_COOKIE that is later used to match - * up the event with the request. The event includes the same data and - * has %NL80211_ATTR_ACK set if the frame was ACKed. + * @NL80211_CMD_PROBE_PEER: Probe a connected peer by sending a null data + * frame and reporting when the frame is acknowledged. + * In AP/GO mode, %NL80211_ATTR_MAC is required to identify the client. + * In STA/P2P-client mode, %NL80211_ATTR_MAC must be omitted (the AP is + * implied); the driver must advertise %NL80211_EXT_FEATURE_PROBE_AP. + * The command returns a direct reply with an %NL80211_ATTR_COOKIE that + * is later used to match up the event with the request. The event + * includes the same data and has %NL80211_ATTR_ACK set if the frame + * was ACKed. * * @NL80211_CMD_REGISTER_BEACONS: Register this socket to receive beacons from * other BSSes when any interfaces are in AP mode. This helps implement @@ -7086,6 +7088,9 @@ enum nl80211_feature_flags { * LTF key seed via %NL80211_KEY_LTF_SEED. The seed is used to generate * secure LTF keys for secure LTF measurement sessions. * + * @NL80211_EXT_FEATURE_PROBE_AP: Driver supports probing the associated AP + * in STA mode using @NL80211_CMD_PROBE_PEER. + * * @NUM_NL80211_EXT_FEATURES: number of extended features. * @MAX_NL80211_EXT_FEATURES: highest extended feature index. */ @@ -7167,6 +7172,7 @@ enum nl80211_ext_feature_index { NL80211_EXT_FEATURE_IEEE8021X_AUTH, NL80211_EXT_FEATURE_ROC_ADDR_FILTER, NL80211_EXT_FEATURE_SET_KEY_LTF_SEED, + NL80211_EXT_FEATURE_PROBE_AP, /* add new features before the definition below */ NUM_NL80211_EXT_FEATURES, diff --git a/net/mac80211/status.c b/net/mac80211/status.c index dd1dbba06838..c3d29aed93fe 100644 --- a/net/mac80211/status.c +++ b/net/mac80211/status.c @@ -655,7 +655,7 @@ static void ieee80211_report_ack_skb(struct ieee80211_local *local, GFP_ATOMIC); else if (ieee80211_is_any_nullfunc(hdr->frame_control)) cfg80211_probe_status(sdata->dev, hdr->addr1, - cookie, acked, + cookie, -1, acked, info->status.ack_signal, is_valid_ack_signal, GFP_ATOMIC); diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 0d651a46b9d6..a62e319f6aec 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -16157,16 +16157,32 @@ static int nl80211_probe_peer(struct sk_buff *skb, struct genl_info *info) struct wireless_dev *wdev = dev->ieee80211_ptr; struct sk_buff *msg; void *hdr; - const u8 *addr; + const u8 *addr = NULL; u64 cookie; int err; - if (wdev->iftype != NL80211_IFTYPE_AP && - wdev->iftype != NL80211_IFTYPE_P2P_GO) + /* Allow in AP, STA, and their P2P counterparts */ + switch (wdev->iftype) { + case NL80211_IFTYPE_AP: + case NL80211_IFTYPE_P2P_GO: + if (!info->attrs[NL80211_ATTR_MAC]) + return -EINVAL; + addr = nla_data(info->attrs[NL80211_ATTR_MAC]); + break; + case NL80211_IFTYPE_STATION: + case NL80211_IFTYPE_P2P_CLIENT: + if (!wiphy_ext_feature_isset(&rdev->wiphy, + NL80211_EXT_FEATURE_PROBE_AP)) + return -EOPNOTSUPP; + if (!wdev->connected) + return -ENOLINK; + /* STA/P2P-client probes the currently associated AP/GO. */ + if (info->attrs[NL80211_ATTR_MAC]) + return -EINVAL; + break; + default: return -EOPNOTSUPP; - - if (!info->attrs[NL80211_ATTR_MAC]) - return -EINVAL; + } if (!rdev->ops->probe_peer) return -EOPNOTSUPP; @@ -16182,8 +16198,6 @@ static int nl80211_probe_peer(struct sk_buff *skb, struct genl_info *info) goto free_msg; } - addr = nla_data(info->attrs[NL80211_ATTR_MAC]); - err = rdev_probe_peer(rdev, dev, addr, &cookie); if (err) goto free_msg; @@ -22597,8 +22611,8 @@ nla_put_failure: } EXPORT_SYMBOL(cfg80211_sta_opmode_change_notify); -void cfg80211_probe_status(struct net_device *dev, const u8 *addr, - u64 cookie, bool acked, s32 ack_signal, +void cfg80211_probe_status(struct net_device *dev, const u8 *peer, u64 cookie, + int link_id, bool acked, s32 ack_signal, bool is_valid_ack_signal, gfp_t gfp) { struct wireless_dev *wdev = dev->ieee80211_ptr; @@ -22606,7 +22620,7 @@ void cfg80211_probe_status(struct net_device *dev, const u8 *addr, struct sk_buff *msg; void *hdr; - trace_cfg80211_probe_status(dev, addr, cookie, acked); + trace_cfg80211_probe_status(dev, peer, cookie, acked); msg = nlmsg_new(NLMSG_DEFAULT_SIZE, gfp); @@ -22621,12 +22635,18 @@ void cfg80211_probe_status(struct net_device *dev, const u8 *addr, if (nla_put_u32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx) || nla_put_u32(msg, NL80211_ATTR_IFINDEX, dev->ifindex) || - nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, addr) || + (peer && nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, peer)) || nla_put_u64_64bit(msg, NL80211_ATTR_COOKIE, cookie, - NL80211_ATTR_PAD) || - (acked && nla_put_flag(msg, NL80211_ATTR_ACK)) || - (is_valid_ack_signal && nla_put_s32(msg, NL80211_ATTR_ACK_SIGNAL, - ack_signal))) + NL80211_ATTR_PAD)) + goto nla_put_failure; + + if (link_id >= 0 && + nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID, link_id)) + goto nla_put_failure; + + if ((acked && nla_put_flag(msg, NL80211_ATTR_ACK)) || + (is_valid_ack_signal && + nla_put_s32(msg, NL80211_ATTR_ACK_SIGNAL, ack_signal))) goto nla_put_failure; genlmsg_end(msg, hdr); -- cgit v1.2.3 From f83378e6ce497eda7e9a7490de4e1a46459febb2 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Mon, 15 Jun 2026 09:39:48 +0200 Subject: wifi: nl80211: clarify NL80211_BAND_IFTYPE_ATTR_HE_6GHZ_CAPA content This is currently __le16, but really the whole content of the corresponding 802.11 element, which is even extensible and could, in theory, be increased in size. Clarify the docs. Link: https://patch.msgid.link/20260615093948.0f730833a6d5.I1c8c5c09dfe16b0b1dcb10d54fc030f6b1d4fc8c@changeid Signed-off-by: Johannes Berg --- include/uapi/linux/nl80211.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 6b8071606e6f..d9a8c693457f 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -4477,8 +4477,8 @@ enum nl80211_mpath_info { * capabilities IE * @NL80211_BAND_IFTYPE_ATTR_HE_CAP_PPE: HE PPE thresholds information as * defined in HE capabilities IE - * @NL80211_BAND_IFTYPE_ATTR_HE_6GHZ_CAPA: HE 6GHz band capabilities (__le16), - * given for all 6 GHz band channels + * @NL80211_BAND_IFTYPE_ATTR_HE_6GHZ_CAPA: HE 6GHz band capabilities, + * given for all 6 GHz band channels (binary, element content) * @NL80211_BAND_IFTYPE_ATTR_VENDOR_ELEMS: vendor element capabilities that are * advertised on this band/for this iftype (binary) * @NL80211_BAND_IFTYPE_ATTR_EHT_CAP_MAC: EHT MAC capabilities as in EHT -- cgit v1.2.3 From b1d0c412088e3908821ef2ec52e2c0e5e7f5a535 Mon Sep 17 00:00:00 2001 From: Ivan Vecera Date: Tue, 30 Jun 2026 14:55:32 +0200 Subject: dpll: add STATE_CONNECTED_OVERRIDE pin capability Add DPLL_PIN_CAPABILITIES_STATE_CONNECTED_OVERRIDE capability flag that indicates a pin can be set to connected regardless of the current DPLL device mode, overriding the active input selection. This is useful for automatic-only DPLL devices where mode cannot be switched to manual, allowing userspace to directly connect such pin from automatic mode. The capability requires STATE_CAN_CHANGE to be set as well; dpll_pin_register() warns if a driver violates this. Document the new capability in the Pin selection section of Documentation/driver-api/dpll.rst. Signed-off-by: Ivan Vecera Link: https://patch.msgid.link/20260630125536.720717-2-ivecera@redhat.com Signed-off-by: Paolo Abeni --- Documentation/driver-api/dpll.rst | 7 +++++++ Documentation/netlink/specs/dpll.yaml | 6 ++++++ drivers/dpll/dpll_core.c | 6 +++++- include/uapi/linux/dpll.h | 4 ++++ 4 files changed, 22 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/Documentation/driver-api/dpll.rst b/Documentation/driver-api/dpll.rst index bae14766d4f7..f83150917814 100644 --- a/Documentation/driver-api/dpll.rst +++ b/Documentation/driver-api/dpll.rst @@ -91,6 +91,13 @@ following pin states: - ``DPLL_PIN_STATE_DISCONNECTED`` - the pin shall be not considered as a valid input for automatic selection algorithm +Pins that have the ``DPLL_PIN_CAPABILITIES_STATE_CONNECTED_OVERRIDE`` +capability can additionally be set to ``DPLL_PIN_STATE_CONNECTED`` in +automatic mode, overriding the active input selection. This is useful +for automatic-only DPLL devices where mode cannot be switched to manual. +When such a pin is disconnected, the device returns to automatic input +selection. + The actual hardware status of a pin is reported via the operational state (``DPLL_A_PIN_OPERSTATE``) attribute nested under the parent device: diff --git a/Documentation/netlink/specs/dpll.yaml b/Documentation/netlink/specs/dpll.yaml index 2bf83f6732ab..526a5b2df2bd 100644 --- a/Documentation/netlink/specs/dpll.yaml +++ b/Documentation/netlink/specs/dpll.yaml @@ -252,6 +252,12 @@ definitions: - name: state-can-change doc: pin state can be changed + - + name: state-connected-override + doc: | + pin state can be set to connected regardless of current + DPLL device mode, overriding the active input selection. + Requires state-can-change to be set as well. - type: const name: phase-offset-divider diff --git a/drivers/dpll/dpll_core.c b/drivers/dpll/dpll_core.c index 2e8690cb3c16..bb1e8650c9d5 100644 --- a/drivers/dpll/dpll_core.c +++ b/drivers/dpll/dpll_core.c @@ -884,7 +884,11 @@ dpll_pin_register(struct dpll_device *dpll, struct dpll_pin *pin, WARN_ON(ops->measured_freq_get && (!dpll_device_ops(dpll)->freq_monitor_get || !dpll_device_ops(dpll)->freq_monitor_set)) || - WARN_ON(ops->supported_ffo && !ops->ffo_get)) + WARN_ON(ops->supported_ffo && !ops->ffo_get) || + WARN_ON((pin->prop.capabilities & + DPLL_PIN_CAPABILITIES_STATE_CONNECTED_OVERRIDE) && + !(pin->prop.capabilities & + DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE))) return -EINVAL; mutex_lock(&dpll_lock); diff --git a/include/uapi/linux/dpll.h b/include/uapi/linux/dpll.h index 55eaa82f5f98..5d7ca6a413cd 100644 --- a/include/uapi/linux/dpll.h +++ b/include/uapi/linux/dpll.h @@ -208,11 +208,15 @@ enum dpll_pin_operstate { * @DPLL_PIN_CAPABILITIES_DIRECTION_CAN_CHANGE: pin direction can be changed * @DPLL_PIN_CAPABILITIES_PRIORITY_CAN_CHANGE: pin priority can be changed * @DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE: pin state can be changed + * @DPLL_PIN_CAPABILITIES_STATE_CONNECTED_OVERRIDE: pin state can be set to + * connected regardless of current DPLL device mode, overriding the active + * input selection. Requires state-can-change to be set as well. */ enum dpll_pin_capabilities { DPLL_PIN_CAPABILITIES_DIRECTION_CAN_CHANGE = 1, DPLL_PIN_CAPABILITIES_PRIORITY_CAN_CHANGE = 2, DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE = 4, + DPLL_PIN_CAPABILITIES_STATE_CONNECTED_OVERRIDE = 8, }; #define DPLL_PHASE_OFFSET_DIVIDER 1000 -- cgit v1.2.3 From 0cc8348a9786727e3622f833f442e8b45e2d363b Mon Sep 17 00:00:00 2001 From: Ivan Vecera Date: Tue, 30 Jun 2026 14:55:33 +0200 Subject: dpll: add DPLL_PIN_TYPE_INT_NCO pin type Add DPLL_PIN_TYPE_INT_NCO pin type for virtual pins representing the NCO mode of a DPLL. When connected as a DPLL input, the DPLL enters NCO mode where the output frequency is adjusted by the host via the PTP clock interface. Update the fractional-frequency-offset and fractional-frequency- offset-ppt attribute documentation to note that for INT_NCO pins these attributes represent the DPLL's current output frequency offset from its nominal frequency. Reviewed-by: Jiri Pirko Signed-off-by: Ivan Vecera Link: https://patch.msgid.link/20260630125536.720717-3-ivecera@redhat.com Signed-off-by: Paolo Abeni --- Documentation/netlink/specs/dpll.yaml | 13 +++++++++++++ drivers/dpll/dpll_nl.c | 2 +- include/uapi/linux/dpll.h | 4 ++++ 3 files changed, 18 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/dpll.yaml b/Documentation/netlink/specs/dpll.yaml index 526a5b2df2bd..cdc8c7b456df 100644 --- a/Documentation/netlink/specs/dpll.yaml +++ b/Documentation/netlink/specs/dpll.yaml @@ -165,6 +165,13 @@ definitions: - name: gnss doc: GNSS recovered clock + - + name: int-nco + doc: | + Device internal numerically controlled oscillator. + When connected as a DPLL input, the DPLL enters NCO mode + where the output frequency is adjusted by the host via + the PTP clock interface. render-max: true - type: enum @@ -462,6 +469,9 @@ attribute-sets: offset on the media associated with the pin. Inside the pin-parent-device nest it represents the frequency offset between the pin and its parent DPLL device. + For pins of type PIN_TYPE_INT_NCO this represents + the DPLL's current output frequency offset from its + nominal frequency. Value is in PPM (parts per million). This is a lower-precision version of fractional-frequency-offset-ppt. @@ -508,6 +518,9 @@ attribute-sets: offset on the media associated with the pin. Inside the pin-parent-device nest it represents the frequency offset between the pin and its parent DPLL device. + For pins of type PIN_TYPE_INT_NCO this represents + the DPLL's current output frequency offset from its + nominal frequency. Value is in PPT (parts per trillion, 10^-12). This is a higher-precision version of fractional-frequency-offset. diff --git a/drivers/dpll/dpll_nl.c b/drivers/dpll/dpll_nl.c index ed3bbe9841ea..b1ba490e72b0 100644 --- a/drivers/dpll/dpll_nl.c +++ b/drivers/dpll/dpll_nl.c @@ -61,7 +61,7 @@ static const struct nla_policy dpll_pin_id_get_nl_policy[DPLL_A_PIN_TYPE + 1] = [DPLL_A_PIN_BOARD_LABEL] = { .type = NLA_NUL_STRING, }, [DPLL_A_PIN_PANEL_LABEL] = { .type = NLA_NUL_STRING, }, [DPLL_A_PIN_PACKAGE_LABEL] = { .type = NLA_NUL_STRING, }, - [DPLL_A_PIN_TYPE] = NLA_POLICY_RANGE(NLA_U32, 1, 5), + [DPLL_A_PIN_TYPE] = NLA_POLICY_RANGE(NLA_U32, 1, 6), }; /* DPLL_CMD_PIN_GET - do */ diff --git a/include/uapi/linux/dpll.h b/include/uapi/linux/dpll.h index 5d7ca6a413cd..85b898b1db5e 100644 --- a/include/uapi/linux/dpll.h +++ b/include/uapi/linux/dpll.h @@ -129,6 +129,9 @@ enum dpll_type { * @DPLL_PIN_TYPE_SYNCE_ETH_PORT: ethernet port PHY's recovered clock * @DPLL_PIN_TYPE_INT_OSCILLATOR: device internal oscillator * @DPLL_PIN_TYPE_GNSS: GNSS recovered clock + * @DPLL_PIN_TYPE_INT_NCO: Device internal numerically controlled oscillator. + * When connected as a DPLL input, the DPLL enters NCO mode where the output + * frequency is adjusted by the host via the PTP clock interface. */ enum dpll_pin_type { DPLL_PIN_TYPE_MUX = 1, @@ -136,6 +139,7 @@ enum dpll_pin_type { DPLL_PIN_TYPE_SYNCE_ETH_PORT, DPLL_PIN_TYPE_INT_OSCILLATOR, DPLL_PIN_TYPE_GNSS, + DPLL_PIN_TYPE_INT_NCO, /* private: */ __DPLL_PIN_TYPE_MAX, -- cgit v1.2.3 From dd21d1844aa096216e1be551d1ae5e57c27c837c Mon Sep 17 00:00:00 2001 From: P Praneesh Date: Sun, 14 Jun 2026 10:47:34 +0530 Subject: wifi: cfg80211: Fragment per-link station stats in nl80211_dump_station() In MLO scenarios, stations may have multiple links, each with distinct statistics. When userspace tools like iw or hostapd request station dumps, attempting to pack all per-link stats into a single netlink message can easily exceed the default 4KB buffer limit, especially when more than two links are active. This results in -EMSGSIZE errors and incomplete data delivery. To address this, fragment per-link station statistics across multiple netlink messages to ensure reliable delivery of complete MLO station information. Extend the stateful context with a two-phase dump mechanism: phase 0 (AGGREGATED) sends combined MLO-level statistics and phase 1 (PER_LINK) sends individual per-link statistics for each active link. The dump loop is structured to produce exactly one netlink message per iteration, with a common header (ifindex, wdev, mac, generation) built once and phase-specific payload added via a switch statement. This keeps header construction in one place and makes the EMSGSIZE bail-out uniform. Add a new request flag attribute, NL80211_ATTR_STA_DUMP_LINK_STATS (NLA_FLAG), for NL80211_CMD_GET_STATION dump. Userspace can set this flag to request per-link station statistics for MLO stations. Extract this flag during the first dump invocation by passing an attrbuf to nl80211_prepare_wdev_dump(); use __free(kfree) to avoid scattered manual kfree() calls. Cache the boolean in the dump context to avoid repeated parsing on subsequent invocations. Per-link messages carry a single NL80211_ATTR_MLO_LINKS nest with the link ID, link-specific MAC, and per-link NL80211_ATTR_STA_INFO payload. The link-specific validity (is_valid_ether_addr) and null pointer guard are checked in nl80211_put_link_station_payload() before any message construction begins. Also fix all nla_nest_start_noflag() calls in nl80211_fill_link_station() for nested attribute types (STA_INFO, BSS_PARAM, TID_STATS, per-tid) to use nla_nest_start() so the NLA_F_NESTED flag is set correctly. Propagate the actual return value from nl80211_put_sta_info_common() in the AGGREGATED phase rather than returning skb->len. Returning skb->len signals netlink to re-invoke the dump with the same sta_idx, causing an infinite loop when the aggregated payload is too large to fit; returning the real error code (-EMSGSIZE or otherwise) terminates the dump cleanly. Backward compatibility is seamlessly preserved for non-MLO stations. Signed-off-by: P Praneesh Link: https://patch.msgid.link/20260614051739.3979947-5-praneesh.p@oss.qualcomm.com Signed-off-by: Johannes Berg --- include/uapi/linux/nl80211.h | 19 +++++ net/wireless/nl80211.c | 170 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 157 insertions(+), 32 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index d9a8c693457f..020387d76412 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -3168,6 +3168,23 @@ enum nl80211_commands { * @NL80211_ATTR_NPCA_PRIMARY_FREQ: NPCA primary channel (u32) * @NL80211_ATTR_NPCA_PUNCT_BITMAP: NPCA puncturing bitmap (u32) * + * @NL80211_ATTR_STA_DUMP_LINK_STATS: Request flag for %NL80211_CMD_GET_STATION + * (dump mode only). When set on an MLD station, the dump produces two + * %NL80211_CMD_NEW_STATION messages per station per dump call: + * + * 1. An aggregated-stats message whose top-level %NL80211_ATTR_STA_INFO + * contains MLO-combined statistics (same content as a dump without + * this flag). + * + * 2. For each active link, a per-link message containing + * %NL80211_ATTR_MLO_LINKS with a single link entry. Each entry holds + * %NL80211_ATTR_MLO_LINK_ID, the link-specific %NL80211_ATTR_MAC, + * and %NL80211_ATTR_STA_INFO with per-link statistics (see + * &enum nl80211_sta_info). + * + * The aggregated message always precedes the per-link messages for the + * same station within a dump sequence. + * * @NUM_NL80211_ATTR: total number of nl80211_attrs available * @NL80211_ATTR_MAX: highest attribute number currently defined * @__NL80211_ATTR_AFTER_LAST: internal use @@ -3766,6 +3783,8 @@ enum nl80211_attrs { NL80211_ATTR_NPCA_PRIMARY_FREQ, NL80211_ATTR_NPCA_PUNCT_BITMAP, + NL80211_ATTR_STA_DUMP_LINK_STATS, + /* add attributes here, update the policy in nl80211.c */ __NL80211_ATTR_AFTER_LAST, diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index ccc4341a0aea..be16a40132ff 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -1093,6 +1093,7 @@ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = { [NL80211_ATTR_NPCA_PRIMARY_FREQ] = { .type = NLA_U32 }, [NL80211_ATTR_NPCA_PUNCT_BITMAP] = NLA_POLICY_FULL_RANGE(NLA_U32, &nl80211_punct_bitmap_range), + [NL80211_ATTR_STA_DUMP_LINK_STATS] = { .type = NLA_FLAG }, }; /* policy for the key attributes */ @@ -7870,7 +7871,7 @@ static int nl80211_fill_link_station(struct sk_buff *msg, goto nla_put_failure; \ } while (0) - link_sinfoattr = nla_nest_start_noflag(msg, NL80211_ATTR_STA_INFO); + link_sinfoattr = nla_nest_start(msg, NL80211_ATTR_STA_INFO); if (!link_sinfoattr) goto nla_put_failure; @@ -7936,8 +7937,8 @@ static int nl80211_fill_link_station(struct sk_buff *msg, PUT_LINK_SINFO(BEACON_LOSS, beacon_loss_count, u32); if (link_sinfo->filled & BIT_ULL(NL80211_STA_INFO_BSS_PARAM)) { - bss_param = nla_nest_start_noflag(msg, - NL80211_STA_INFO_BSS_PARAM); + bss_param = nla_nest_start(msg, + NL80211_STA_INFO_BSS_PARAM); if (!bss_param) goto nla_put_failure; @@ -7979,8 +7980,7 @@ static int nl80211_fill_link_station(struct sk_buff *msg, struct nlattr *tidsattr; int tid; - tidsattr = nla_nest_start_noflag(msg, - NL80211_STA_INFO_TID_STATS); + tidsattr = nla_nest_start(msg, NL80211_STA_INFO_TID_STATS); if (!tidsattr) goto nla_put_failure; @@ -7993,7 +7993,7 @@ static int nl80211_fill_link_station(struct sk_buff *msg, if (!tidstats->filled) continue; - tidattr = nla_nest_start_noflag(msg, tid + 1); + tidattr = nla_nest_start(msg, tid + 1); if (!tidattr) goto nla_put_failure; @@ -8464,21 +8464,74 @@ static void cfg80211_sta_set_mld_sinfo(struct station_info *sinfo) sinfo->filled &= ~BIT_ULL(NL80211_STA_INFO_CHAIN_SIGNAL_AVG); } +enum nl80211_dump_station_phase { + NL80211_DUMP_STA_PHASE_AGGREGATED = 0, + NL80211_DUMP_STA_PHASE_PER_LINK = 1, +}; + struct nl80211_dump_station_ctx { int sta_idx; + int link_idx; + enum nl80211_dump_station_phase phase; + bool dump_link_stats; u8 mac_addr[ETH_ALEN]; struct station_info sinfo; }; +static int nl80211_put_link_station_payload(struct sk_buff *msg, + struct cfg80211_registered_device *rdev, + struct station_info *sinfo, + int link_idx) +{ + struct link_station_info *link_sinfo = sinfo->links[link_idx]; + struct nlattr *links, *link; + + if (WARN_ON_ONCE(!link_sinfo)) + return -ENOENT; + + if (!is_valid_ether_addr(link_sinfo->addr)) + return -EADDRNOTAVAIL; + + links = nla_nest_start(msg, NL80211_ATTR_MLO_LINKS); + if (!links) + return -EMSGSIZE; + + link = nla_nest_start(msg, link_idx + 1); + if (!link) + goto nla_put_failure; + + if (nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID, link_idx) || + nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, link_sinfo->addr)) + goto nla_put_failure; + + if (nl80211_fill_link_station(msg, rdev, link_sinfo)) + goto nla_put_failure; + + nla_nest_end(msg, link); + nla_nest_end(msg, links); + return 0; + +nla_put_failure: + nla_nest_cancel(msg, links); + return -EMSGSIZE; +} + static int nl80211_dump_station(struct sk_buff *skb, struct netlink_callback *cb) { struct cfg80211_registered_device *rdev; struct wireless_dev *wdev; struct nl80211_dump_station_ctx *ctx = (void *)cb->args[2]; + struct nlattr **attrbuf __free(kfree) = NULL; int err; - err = nl80211_prepare_wdev_dump(cb, &rdev, &wdev, NULL); + if (!ctx) { + attrbuf = kzalloc_objs(*attrbuf, NUM_NL80211_ATTR); + if (!attrbuf) + return -ENOMEM; + } + + err = nl80211_prepare_wdev_dump(cb, &rdev, &wdev, attrbuf); if (err) return err; /* nl80211_prepare_wdev_dump acquired it in the successful case */ @@ -8490,6 +8543,9 @@ static int nl80211_dump_station(struct sk_buff *skb, err = -ENOMEM; goto out_err; } + ctx->phase = NL80211_DUMP_STA_PHASE_AGGREGATED; + ctx->dump_link_stats = + !!attrbuf[NL80211_ATTR_STA_DUMP_LINK_STATS]; cb->args[2] = (long)ctx; } @@ -8505,34 +8561,53 @@ static int nl80211_dump_station(struct sk_buff *skb, while (true) { void *hdr; + int ret; - memset(&ctx->sinfo, 0, sizeof(ctx->sinfo)); - for (int i = 0; i < IEEE80211_MLD_MAX_NUM_LINKS; i++) { - ctx->sinfo.links[i] = - kzalloc_obj(*ctx->sinfo.links[0]); - if (!ctx->sinfo.links[i]) { - err = -ENOMEM; + /* AGGREGATED phase: fetch sinfo from driver once per station */ + if (ctx->phase == NL80211_DUMP_STA_PHASE_AGGREGATED) { + memset(&ctx->sinfo, 0, sizeof(ctx->sinfo)); + for (int i = 0; i < IEEE80211_MLD_MAX_NUM_LINKS; i++) { + ctx->sinfo.links[i] = + kzalloc_obj(*ctx->sinfo.links[0]); + if (!ctx->sinfo.links[i]) { + err = -ENOMEM; + goto out_err_release; + } + } + + err = rdev_dump_station(rdev, wdev, ctx->sta_idx, + ctx->mac_addr, &ctx->sinfo); + if (err == -ENOENT) { + err = skb->len; goto out_err_release; } - } + if (err) + goto out_err_release; - err = rdev_dump_station(rdev, wdev, ctx->sta_idx, - ctx->mac_addr, &ctx->sinfo); - if (err == -ENOENT) { - err = skb->len; - goto out_err_release; + if (ctx->sinfo.valid_links) + cfg80211_sta_set_mld_sinfo(&ctx->sinfo); + } else { + /* PER_LINK phase: advance to next valid link */ + while (ctx->link_idx < IEEE80211_MLD_MAX_NUM_LINKS && + !(ctx->sinfo.valid_links & BIT(ctx->link_idx))) + ctx->link_idx++; + + if (ctx->link_idx >= IEEE80211_MLD_MAX_NUM_LINKS) { + cfg80211_sinfo_release_content(&ctx->sinfo); + ctx->sta_idx++; + ctx->phase = NL80211_DUMP_STA_PHASE_AGGREGATED; + continue; + } } - if (err) - goto out_err_release; - - if (ctx->sinfo.valid_links) - cfg80211_sta_set_mld_sinfo(&ctx->sinfo); + /* Build common header for both phases */ hdr = nl80211hdr_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq, NLM_F_MULTI, NL80211_CMD_NEW_STATION); if (!hdr) { err = skb->len; + if (ctx->phase == NL80211_DUMP_STA_PHASE_PER_LINK) + goto out_err; goto out_err_release; } @@ -8546,18 +8621,49 @@ static int nl80211_dump_station(struct sk_buff *skb, ctx->sinfo.generation)) { genlmsg_cancel(skb, hdr); err = skb->len; + if (ctx->phase == NL80211_DUMP_STA_PHASE_PER_LINK) + goto out_err; goto out_err_release; } - if (nl80211_put_sta_info_common(skb, rdev, &ctx->sinfo)) { - genlmsg_cancel(skb, hdr); - err = skb->len; - goto out_err_release; - } + switch (ctx->phase) { + case NL80211_DUMP_STA_PHASE_AGGREGATED: + ret = nl80211_put_sta_info_common(skb, rdev, &ctx->sinfo); + if (ret) { + genlmsg_cancel(skb, hdr); + err = ret; + goto out_err_release; + } + genlmsg_end(skb, hdr); + + if (ctx->dump_link_stats && ctx->sinfo.valid_links) { + ctx->phase = NL80211_DUMP_STA_PHASE_PER_LINK; + ctx->link_idx = 0; + } else { + cfg80211_sinfo_release_content(&ctx->sinfo); + ctx->sta_idx++; + } + break; - genlmsg_end(skb, hdr); - cfg80211_sinfo_release_content(&ctx->sinfo); - ctx->sta_idx++; + case NL80211_DUMP_STA_PHASE_PER_LINK: + ret = nl80211_put_link_station_payload(skb, rdev, + &ctx->sinfo, + ctx->link_idx); + if (ret == -EMSGSIZE) { + genlmsg_cancel(skb, hdr); + err = skb->len; + goto out_err; + } + if (ret) { + /* skip invalid link, do not abort the dump */ + genlmsg_cancel(skb, hdr); + ctx->link_idx++; + continue; + } + genlmsg_end(skb, hdr); + ctx->link_idx++; + break; + } } out_err_release: -- cgit v1.2.3 From b5f90fd4580ce71aa24ac9afcf5c9b4fa8121518 Mon Sep 17 00:00:00 2001 From: Cosmin Ratiu Date: Wed, 1 Jul 2026 10:32:45 +0300 Subject: devlink: Add parent dev to devlink API Upcoming changes to the rate commands need the parent devlink specified. This change adds a nested 'parent-dev' attribute to the API and helpers to obtain and put a reference to the parent devlink instance in info->ctx. To avoid deadlocks, the parent devlink is unlocked before obtaining the main devlink instance that is the target of the request. A reference to the parent is kept until the end of the request to avoid it suddenly disappearing. This means that this reference is of limited use without additional protection. Signed-off-by: Cosmin Ratiu Reviewed-by: Carolina Jubran Reviewed-by: Jiri Pirko Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260701073254.754518-6-tariqt@nvidia.com Signed-off-by: Paolo Abeni --- Documentation/netlink/specs/devlink.yaml | 20 ++++++++++++++++++ include/uapi/linux/devlink.h | 2 ++ net/devlink/devl_internal.h | 3 +++ net/devlink/netlink.c | 36 +++++++++++++++++++++++++++----- 4 files changed, 56 insertions(+), 5 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/devlink.yaml b/Documentation/netlink/specs/devlink.yaml index 52ad1e7805d1..13d960b3abb1 100644 --- a/Documentation/netlink/specs/devlink.yaml +++ b/Documentation/netlink/specs/devlink.yaml @@ -895,6 +895,16 @@ attribute-sets: resource-dump response. Bit 0 (dev) selects device-level resources; bit 1 (port) selects port-level resources. When absent all classes are returned. + - + name: parent-dev + type: nest + nested-attributes: dl-parent-dev + doc: | + Identifies the devlink instance which owns the parent rate node. + Used with rate-set and rate-new to parent a rate object to a node on + a different devlink instance, enabling cross-device rate scheduling. + When absent, the parent node is resolved on the same instance. + - name: dl-dev-stats subset-of: devlink @@ -1317,6 +1327,16 @@ attribute-sets: Specifies the bandwidth share assigned to the Traffic Class. The bandwidth for the traffic class is determined in proportion to the sum of the shares of all configured classes. + - + name: dl-parent-dev + subset-of: devlink + attributes: + - + name: bus-name + - + name: dev-name + - + name: index operations: enum-model: directional diff --git a/include/uapi/linux/devlink.h b/include/uapi/linux/devlink.h index ca713bcc47b9..a6801feb7744 100644 --- a/include/uapi/linux/devlink.h +++ b/include/uapi/linux/devlink.h @@ -648,6 +648,8 @@ enum devlink_attr { DEVLINK_ATTR_INDEX, /* uint */ DEVLINK_ATTR_RESOURCE_SCOPE_MASK, /* u32 */ + DEVLINK_ATTR_PARENT_DEV, /* nested */ + /* Add new attributes above here, update the spec in * Documentation/netlink/specs/devlink.yaml and re-generate * net/devlink/netlink_gen.c. diff --git a/net/devlink/devl_internal.h b/net/devlink/devl_internal.h index 52c8bf359dd4..cdf894ba5a9d 100644 --- a/net/devlink/devl_internal.h +++ b/net/devlink/devl_internal.h @@ -154,6 +154,7 @@ int devlink_rel_devlink_handle_put(struct sk_buff *msg, struct devlink *devlink, struct devlink_nl_ctx { struct devlink *devlink; struct devlink_port *devlink_port; + struct devlink *parent_devlink; }; static inline struct devlink_nl_ctx * @@ -197,6 +198,8 @@ typedef int devlink_nl_dump_one_func_t(struct sk_buff *msg, struct devlink * devlink_get_from_attrs_lock(struct net *net, struct nlattr **attrs, bool dev_lock); +struct devlink * +devlink_get_parent_from_attrs_lock(struct net *net, struct nlattr **attrs); int devlink_nl_dumpit(struct sk_buff *msg, struct netlink_callback *cb, devlink_nl_dump_one_func_t *dump_one); diff --git a/net/devlink/netlink.c b/net/devlink/netlink.c index f0a857e286bc..5a057dc86b0f 100644 --- a/net/devlink/netlink.c +++ b/net/devlink/netlink.c @@ -12,6 +12,7 @@ #define DEVLINK_NL_FLAG_NEED_PORT BIT(0) #define DEVLINK_NL_FLAG_NEED_DEVLINK_OR_PORT BIT(1) #define DEVLINK_NL_FLAG_NEED_DEV_LOCK BIT(2) +#define DEVLINK_NL_FLAG_OPTIONAL_PARENT_DEV BIT(3) static const struct genl_multicast_group devlink_nl_mcgrps[] = { [DEVLINK_MCGRP_CONFIG] = { .name = DEVLINK_GENL_MCGRP_CONFIG_NAME }, @@ -239,19 +240,39 @@ found: return ERR_PTR(-ENODEV); } +struct devlink * +devlink_get_parent_from_attrs_lock(struct net *net, struct nlattr **attrs) +{ + return ERR_PTR(-EOPNOTSUPP); +} + static int __devlink_nl_pre_doit(struct sk_buff *skb, struct genl_info *info, u8 flags) { + bool parent_dev = flags & DEVLINK_NL_FLAG_OPTIONAL_PARENT_DEV; bool dev_lock = flags & DEVLINK_NL_FLAG_NEED_DEV_LOCK; + struct devlink *devlink, *parent_devlink = NULL; + struct net *net = genl_info_net(info); + struct nlattr **attrs = info->attrs; struct devlink_port *devlink_port; - struct devlink *devlink; int err; - devlink = devlink_get_from_attrs_lock(genl_info_net(info), info->attrs, - dev_lock); - if (IS_ERR(devlink)) - return PTR_ERR(devlink); + if (parent_dev && attrs[DEVLINK_ATTR_PARENT_DEV]) { + parent_devlink = devlink_get_parent_from_attrs_lock(net, attrs); + if (IS_ERR(parent_devlink)) + return PTR_ERR(parent_devlink); + devlink_nl_ctx(info)->parent_devlink = parent_devlink; + /* Drop the parent devlink lock but don't release the reference. + * This will keep it alive until the end of the request. + */ + devl_unlock(parent_devlink); + } + devlink = devlink_get_from_attrs_lock(net, attrs, dev_lock); + if (IS_ERR(devlink)) { + err = PTR_ERR(devlink); + goto parent_put; + } devlink_nl_ctx(info)->devlink = devlink; if (flags & DEVLINK_NL_FLAG_NEED_PORT) { devlink_port = devlink_port_get_from_info(devlink, info); @@ -270,6 +291,9 @@ static int __devlink_nl_pre_doit(struct sk_buff *skb, struct genl_info *info, unlock: devl_dev_unlock(devlink, dev_lock); devlink_put(devlink); +parent_put: + if (parent_dev && parent_devlink) + devlink_put(parent_devlink); return err; } @@ -307,6 +331,8 @@ static void __devlink_nl_post_doit(struct sk_buff *skb, struct genl_info *info, devlink = devlink_nl_ctx(info)->devlink; devl_dev_unlock(devlink, dev_lock); devlink_put(devlink); + if (devlink_nl_ctx(info)->parent_devlink) + devlink_put(devlink_nl_ctx(info)->parent_devlink); } void devlink_nl_post_doit(const struct genl_split_ops *ops, -- cgit v1.2.3 From 79ced850e549e8c86b772a79ea417a1425b5c04b Mon Sep 17 00:00:00 2001 From: Thomas Weißschuh Date: Mon, 4 May 2026 08:32:26 +0200 Subject: y2038: uapi: Use 64-bit __kernel_old_timespec::tv_nsec on x32 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 'struct __kernel_old_timespec' represents the 'native' time ABI of the kernel. On 32-bit systems it uses 32-bit fields and on 64-bit systems it uses 64-bit fields. However the x86 x32 ABI uses the 64-bit time ABI natively. This is correctly handled for the 'tv_sec' fields, through the typedefs of '__kernel_old_time_t' -> '__kernel_long_t' -> 'long long'. The same treatment was missed for 'tv_nsec'. In practice this might not make much of a difference as the value of 'tv_nsec' will always fit into 32 bits and the missing bits fall into the padding of the structure. When introspecting the structure however, a difference can be observed. Switch to 64-bit tv_nsec on x32. No other architectures or ABIs are affected. While this could be interpreted as violating the POSIX requirement of 'timespec::tv_nsec' being 'long': * __kernel_old_timespec is not actually the POSIX timespec type * the requirement is gone in newer versions of POSIX * this matches glibc Fixes: 94c467ddb273 ("y2038: add __kernel_old_timespec and __kernel_old_time_t") Signed-off-by: Thomas Weißschuh Signed-off-by: Thomas Gleixner Link: https://patch.msgid.link/20260504-timespec-x32-v2-1-0739c9047fc4@linutronix.de --- include/uapi/linux/time_types.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/time_types.h b/include/uapi/linux/time_types.h index bcc0002115d3..03a0d8aaadca 100644 --- a/include/uapi/linux/time_types.h +++ b/include/uapi/linux/time_types.h @@ -30,7 +30,7 @@ struct __kernel_old_timeval { struct __kernel_old_timespec { __kernel_old_time_t tv_sec; /* seconds */ - long tv_nsec; /* nanoseconds */ + __kernel_long_t tv_nsec; /* nanoseconds */ }; struct __kernel_old_itimerval { -- cgit v1.2.3 From 98432eec31803ae8707a9fd593ba64cd2648aa74 Mon Sep 17 00:00:00 2001 From: Shyam Sundar S K Date: Tue, 9 Jun 2026 13:40:38 +0530 Subject: platform/x86/amd/pmf: Add util layer and userspace character device interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a util layer to AMD PMF that exposes a minimal userspace interface via a character device for metrics monitoring and feature discovery. This creates /dev/amdpmf_interface with basic ioctl support to retrieve PMF metrics such as: * Power source and power slider position * Platform type, lid state, and user presence * Skin temperature and ambient light * BIOS input parameters (1-10) * Graphics workload metrics * CPU C-state residency (average and maximum) * Socket power consumption * Auto Mode: Automatic power profile switching based on system activity * Static Power Slider: User-selectable power profiles * Policy Builder (Smart PC): Action based policy management * Dynamic Power Slider AC: Adaptive power profiles when on AC power * Dynamic Power Slider DC: Adaptive power profiles when on battery The interface enables smoother integration with userspace tools such as AMD SystemDeck [1], which is widely used for monitoring and controlling power and thermal behavior on AMD platforms. These tools help designers keep major components within thermal limits to ensure proper operation and enhance overall system stability and reliability. The feature is gated behind the CONFIG_AMD_PMF_UTIL_SUPPORT Kconfig option, allowing it to be disabled if not needed. The implementation uses existing PMF infrastructure to populate data from the TA (Trusted Application) shared memory buffer. Link: https://docs.amd.com/v/u/en-US/68773_0.50 [1] Co-developed-by: Sanket Goswami Signed-off-by: Sanket Goswami Signed-off-by: Shyam Sundar S K Link: https://patch.msgid.link/20260609081044.2416731-2-Shyam-sundar.S-k@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmf/Kconfig | 10 +++++ drivers/platform/x86/amd/pmf/Makefile | 2 + drivers/platform/x86/amd/pmf/core.c | 5 +++ drivers/platform/x86/amd/pmf/pmf.h | 9 +++++ drivers/platform/x86/amd/pmf/util.c | 68 +++++++++++++++++++++++++++++++++ include/uapi/linux/amd-pmf.h | 71 +++++++++++++++++++++++++++++++++++ 6 files changed, 165 insertions(+) create mode 100644 drivers/platform/x86/amd/pmf/util.c create mode 100644 include/uapi/linux/amd-pmf.h (limited to 'include/uapi/linux') diff --git a/drivers/platform/x86/amd/pmf/Kconfig b/drivers/platform/x86/amd/pmf/Kconfig index 25b8f7ae3abd..ad4faf18de47 100644 --- a/drivers/platform/x86/amd/pmf/Kconfig +++ b/drivers/platform/x86/amd/pmf/Kconfig @@ -30,3 +30,13 @@ config AMD_PMF_DEBUG in the PMF config store. Say Y here to enable more debug logs and Say N here if you are not sure. + +config AMD_PMF_UTIL_SUPPORT + bool "AMD PMF Util layer support" + depends on AMD_PMF + help + Enabling this option provides a character device for userspace to capture + PMF features (Smart PC Builder, Auto Mode, Static Power Slider, Dynamic + Power Slider AC/DC) along with PMF metrics from the AMD PMF driver. + + Say Y here to enable it and Say N here if you are not sure. diff --git a/drivers/platform/x86/amd/pmf/Makefile b/drivers/platform/x86/amd/pmf/Makefile index 5978464e0eb7..bf7aad80b9e9 100644 --- a/drivers/platform/x86/amd/pmf/Makefile +++ b/drivers/platform/x86/amd/pmf/Makefile @@ -8,3 +8,5 @@ obj-$(CONFIG_AMD_PMF) += amd-pmf.o amd-pmf-y := core.o acpi.o sps.o \ auto-mode.o cnqf.o \ tee-if.o spc.o +# Build util.c only when AMD_PMF_UTIL_SUPPORT is enabled +amd-pmf-$(CONFIG_AMD_PMF_UTIL_SUPPORT) += util.o diff --git a/drivers/platform/x86/amd/pmf/core.c b/drivers/platform/x86/amd/pmf/core.c index b9e5a2cf3aae..58d86b4c2828 100644 --- a/drivers/platform/x86/amd/pmf/core.c +++ b/drivers/platform/x86/amd/pmf/core.c @@ -634,6 +634,10 @@ static int amd_pmf_probe(struct platform_device *pdev) pmf_device = dev->dev; + err = amd_pmf_cdev_register(dev); + if (err) + dev_warn(dev->dev, "failed to register util interface: %d\n", err); + dev_info(dev->dev, "registered PMF device successfully\n"); return 0; @@ -643,6 +647,7 @@ static void amd_pmf_remove(struct platform_device *pdev) { struct amd_pmf_dev *dev = platform_get_drvdata(pdev); + amd_pmf_cdev_unregister(); amd_pmf_deinit_features(dev); if (is_apmf_func_supported(dev, APMF_FUNC_SBIOS_HEARTBEAT_V2)) amd_pmf_notify_sbios_heartbeat_event_v2(dev, ON_UNLOAD); diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h index 69fef7448744..6f61076a9386 100644 --- a/drivers/platform/x86/amd/pmf/pmf.h +++ b/drivers/platform/x86/amd/pmf/pmf.h @@ -928,4 +928,13 @@ int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid); void amd_pmf_tee_deinit(struct amd_pmf_dev *dev); int amd_pmf_start_policy_engine(struct amd_pmf_dev *dev); +/* Util Layer */ +#if IS_ENABLED(CONFIG_AMD_PMF_UTIL_SUPPORT) +int amd_pmf_cdev_register(struct amd_pmf_dev *dev); +void amd_pmf_cdev_unregister(void); +#else +static inline int amd_pmf_cdev_register(struct amd_pmf_dev *dev) { return 0; } +static inline void amd_pmf_cdev_unregister(void) {} +#endif + #endif /* PMF_H */ diff --git a/drivers/platform/x86/amd/pmf/util.c b/drivers/platform/x86/amd/pmf/util.c new file mode 100644 index 000000000000..f8a283192ffe --- /dev/null +++ b/drivers/platform/x86/amd/pmf/util.c @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * AMD Platform Management Framework Util Layer + * + * Copyright (c) 2026, Advanced Micro Devices, Inc. + * All Rights Reserved. + * + * Authors: Shyam Sundar S K + * Sanket Goswami + */ + +#include +#include +#include +#include + +#include "pmf.h" + +static struct amd_pmf_dev *pmf_dev_handle; +static DEFINE_MUTEX(pmf_util_lock); + +static long amd_pmf_set_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + return -ENOTTY; +} + +static int amd_pmf_open(struct inode *inode, struct file *filp) +{ + guard(mutex)(&pmf_util_lock); + if (!pmf_dev_handle) + return -ENODEV; + + filp->private_data = pmf_dev_handle; + return 0; +} + +static const struct file_operations pmf_if_ops = { + .owner = THIS_MODULE, + .open = amd_pmf_open, + .unlocked_ioctl = amd_pmf_set_ioctl, +}; + +static struct miscdevice amd_pmf_util_if = { + .minor = MISC_DYNAMIC_MINOR, + .name = "amdpmf_interface", + .fops = &pmf_if_ops, +}; + +int amd_pmf_cdev_register(struct amd_pmf_dev *dev) +{ + int ret; + + guard(mutex)(&pmf_util_lock); + pmf_dev_handle = dev; + ret = misc_register(&amd_pmf_util_if); + if (ret) + pmf_dev_handle = NULL; + + return ret; +} + +void amd_pmf_cdev_unregister(void) +{ + guard(mutex)(&pmf_util_lock); + if (pmf_dev_handle) + misc_deregister(&amd_pmf_util_if); + pmf_dev_handle = NULL; +} diff --git a/include/uapi/linux/amd-pmf.h b/include/uapi/linux/amd-pmf.h new file mode 100644 index 000000000000..bbbd39b2ce15 --- /dev/null +++ b/include/uapi/linux/amd-pmf.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later WITH Linux-syscall-note */ +/* + * AMD Platform Management Framework (PMF) UAPI Header + * + * Copyright (c) 2026, Advanced Micro Devices, Inc. + * All Rights Reserved. + * + * This file defines the user-space API for interacting with the AMD PMF + * driver. It provides ioctl interfaces to query platform-specific metrics + * such as power source, slider position, platform type, laptop placement, + * and various BIOS input/output parameters. + */ + +#ifndef _UAPI_LINUX_AMD_PMF_H +#define _UAPI_LINUX_AMD_PMF_H + +#include +#include +#include + +/** + * AMD_PMF_IOC_MAGIC - Magic number for AMD PMF ioctl commands + * + * This magic number uniquely identifies AMD PMF ioctl operations. + */ +#define AMD_PMF_IOC_MAGIC 'p' + +/** + * IOCTL_AMD_PMF_POPULATE_DATA - ioctl command to retrieve PMF metrics data + * + * This ioctl command is used to populate the amd_pmf_info structure + * with the requested PMF metrics information. + */ +#define IOCTL_AMD_PMF_POPULATE_DATA _IOWR(AMD_PMF_IOC_MAGIC, 0x00, __u64) + +#define AMD_PMF_BIOS_PARAMS_MAX 10 + +/* AMD PMF feature flags - bitmask indicating supported features */ +#define AMD_PMF_FEAT_AUTO_MODE BIT(0) +#define AMD_PMF_FEAT_STATIC_POWER_SLIDER BIT(1) +#define AMD_PMF_FEAT_POLICY_BUILDER BIT(2) +#define AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_AC BIT(3) +#define AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_DC BIT(4) + +struct amd_pmf_info { + __u64 size; + + /* Feature info */ + __u32 features_supported; + + /* Power and state info */ + __u32 platform_type; + __u32 power_source; + __u32 laptop_placement; + __u32 lid_state; + __u32 user_presence; + __u32 slider_position; + + /* Thermal and power metrics */ + __s32 skin_temp; + __u32 gfx_busy; + __s32 ambient_light; + __u32 avg_c0_residency; + __u32 max_c0_residency; + __u32 socket_power; + + /* BIOS parameters */ + __u32 bios_input[AMD_PMF_BIOS_PARAMS_MAX]; +}; + +#endif /* _UAPI_LINUX_AMD_PMF_H */ -- cgit v1.2.3 From 1983d7dee5db2bd17bf99ac22bddeebbc5f3e38c Mon Sep 17 00:00:00 2001 From: Shyam Sundar S K Date: Tue, 9 Jun 2026 13:40:39 +0530 Subject: platform/x86/amd/pmf: store BIOS output values for user-space metrics via util IOCTL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a bios_output[] to amd_pmf_dev struct and store the latest values for BIOS output policies when applying PMF policies. This enables the AMD PMF util layer to expose these BIOS outputs alongside selected thermal and power metrics to user space via /dev/amdpmf_interface and a new IOCTL, supporting real-time monitoring tools such as SystemDeck. Co-developed-by: Sanket Goswami Signed-off-by: Sanket Goswami Signed-off-by: Shyam Sundar S K Link: https://patch.msgid.link/20260609081044.2416731-3-Shyam-sundar.S-k@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmf/pmf.h | 2 ++ drivers/platform/x86/amd/pmf/tee-if.c | 13 +++++++++++-- include/uapi/linux/amd-pmf.h | 1 + 3 files changed, 14 insertions(+), 2 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h index 6f61076a9386..ffe74ebc46f8 100644 --- a/drivers/platform/x86/amd/pmf/pmf.h +++ b/drivers/platform/x86/amd/pmf/pmf.h @@ -130,6 +130,7 @@ struct cookie_header { #define GET_CMD true #define METRICS_TABLE_ID 7 +#define BIOS_OUTPUT_MAX 10 typedef void (*apmf_event_handler_t)(acpi_handle handle, u32 event, void *data); @@ -442,6 +443,7 @@ struct amd_pmf_dev { struct pmf_cbi_ring_buffer cbi_buf; struct mutex cbi_mutex; /* Protects ring buffer access */ struct mutex metrics_mutex; + u32 bios_output[BIOS_OUTPUT_MAX]; }; struct apmf_sps_prop_granular_v2 { diff --git a/drivers/platform/x86/amd/pmf/tee-if.c b/drivers/platform/x86/amd/pmf/tee-if.c index 7ccd93f506b2..3fe79f0f37c5 100644 --- a/drivers/platform/x86/amd/pmf/tee-if.c +++ b/drivers/platform/x86/amd/pmf/tee-if.c @@ -8,7 +8,9 @@ * Author: Shyam Sundar S K */ +#include #include +#include #include #include #include "pmf.h" @@ -97,11 +99,18 @@ static int amd_pmf_get_bios_output_idx(u32 action_idx) static void amd_pmf_update_bios_output(struct amd_pmf_dev *pdev, struct ta_pmf_action *action) { - u32 bios_idx; + int bios_idx; + int ret; bios_idx = amd_pmf_get_bios_output_idx(action->action_index); + if (bios_idx < 0 || bios_idx >= ARRAY_SIZE(pdev->bios_output)) { + dev_warn(pdev->dev, "BIOS output index %d out of bounds\n", bios_idx); + return; + } - amd_pmf_smartpc_apply_bios_output(pdev, action->value, BIT(bios_idx), bios_idx); + ret = amd_pmf_smartpc_apply_bios_output(pdev, action->value, BIT(bios_idx), bios_idx); + if (!ret) + pdev->bios_output[bios_idx] = action->value; } static void amd_pmf_apply_policies(struct amd_pmf_dev *dev, struct ta_pmf_enact_result *out) diff --git a/include/uapi/linux/amd-pmf.h b/include/uapi/linux/amd-pmf.h index bbbd39b2ce15..90bc2970665d 100644 --- a/include/uapi/linux/amd-pmf.h +++ b/include/uapi/linux/amd-pmf.h @@ -66,6 +66,7 @@ struct amd_pmf_info { /* BIOS parameters */ __u32 bios_input[AMD_PMF_BIOS_PARAMS_MAX]; + __u32 bios_output[AMD_PMF_BIOS_PARAMS_MAX]; }; #endif /* _UAPI_LINUX_AMD_PMF_H */ -- cgit v1.2.3 From 08f3df928fc8cbab2cae09296f568ed294b3a81a Mon Sep 17 00:00:00 2001 From: Shyam Sundar S K Date: Tue, 9 Jun 2026 13:40:40 +0530 Subject: platform/x86/amd/pmf: Store commonly used enums in the header file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Relocate commonly used enums from multiple source files into a shared header file to simplify code structure, improve readability, and enhance maintainability. Also, remove the initialization of the first enum member, since it is not needed. Add the AMD_PMF_ prefix to the laptop_placement and platform_type enums since these names are overly generic for inclusion in a UAPI header Co-developed-by: Sanket Goswami Signed-off-by: Sanket Goswami Signed-off-by: Shyam Sundar S K Link: https://patch.msgid.link/20260609081044.2416731-4-Shyam-sundar.S-k@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c | 13 ++-- drivers/platform/x86/amd/pmf/pmf.h | 22 ------- drivers/platform/x86/amd/pmf/spc.c | 37 +++++------ include/linux/amd-pmf-io.h | 9 --- include/uapi/linux/amd-pmf.h | 74 ++++++++++++++++++++++ 5 files changed, 100 insertions(+), 55 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c index 837d59e7a661..8ac44368a37d 100644 --- a/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c +++ b/drivers/hid/amd-sfh-hid/sfh1_1/amd_sfh_interface.c @@ -7,6 +7,7 @@ * * Author: Basavaraj Natikar */ +#include #include #include #include @@ -102,20 +103,20 @@ static int amd_sfh_mode_info(u32 *platform_type, u32 *laptop_placement) *platform_type = mode.op_mode.devicemode; if (mode.op_mode.ontablestate == 1) { - *laptop_placement = ON_TABLE; + *laptop_placement = AMD_PMF_ON_TABLE; } else if (mode.op_mode.ontablestate == 2) { - *laptop_placement = ON_LAP_MOTION; + *laptop_placement = AMD_PMF_ON_LAP_MOTION; } else if (mode.op_mode.inbagstate == 1) { - *laptop_placement = IN_BAG; + *laptop_placement = AMD_PMF_IN_BAG; } else if (mode.op_mode.outbagstate == 1) { - *laptop_placement = OUT_OF_BAG; + *laptop_placement = AMD_PMF_OUT_OF_BAG; } else if (mode.op_mode.ontablestate == 0 || mode.op_mode.inbagstate == 0 || mode.op_mode.outbagstate == 0) { - *laptop_placement = LP_UNKNOWN; + *laptop_placement = AMD_PMF_LP_UNKNOWN; pr_warn_once("Unknown laptop placement\n"); } else if (mode.op_mode.ontablestate == 3 || mode.op_mode.inbagstate == 3 || mode.op_mode.outbagstate == 3) { - *laptop_placement = LP_UNDEFINED; + *laptop_placement = AMD_PMF_LP_UNDEFINED; pr_warn_once("Undefined laptop placement\n"); } diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h index ffe74ebc46f8..269c0a4b1cae 100644 --- a/drivers/platform/x86/amd/pmf/pmf.h +++ b/drivers/platform/x86/amd/pmf/pmf.h @@ -682,14 +682,6 @@ enum system_state { SYSTEM_STATE_MAX, }; -enum ta_slider { - TA_BEST_BATTERY, - TA_BETTER_BATTERY, - TA_BETTER_PERFORMANCE, - TA_BEST_PERFORMANCE, - TA_MAX, -}; - struct amd_pmf_pb_bitmap { const char *name; u32 bit_mask; @@ -721,20 +713,6 @@ static const struct amd_pmf_pb_bitmap custom_bios_inputs_v1[] __used = { {"NOTIFY_CUSTOM_BIOS_INPUT10", BIT(16)}, }; -enum platform_type { - PTYPE_UNKNOWN = 0, - LID_CLOSE, - CLAMSHELL, - FLAT, - TENT, - STAND, - TABLET, - BOOK, - PRESENTATION, - PULL_FWD, - PTYPE_INVALID = 0xf, -}; - /* Command ids for TA communication */ enum ta_pmf_command { TA_PMF_COMMAND_POLICY_BUILDER_INITIALIZE, diff --git a/drivers/platform/x86/amd/pmf/spc.c b/drivers/platform/x86/amd/pmf/spc.c index f48678a23cc7..05998946d1bd 100644 --- a/drivers/platform/x86/amd/pmf/spc.c +++ b/drivers/platform/x86/amd/pmf/spc.c @@ -10,6 +10,7 @@ */ #include +#include #include #include #include @@ -20,21 +21,21 @@ static const char *platform_type_as_str(u16 platform_type) { switch (platform_type) { - case CLAMSHELL: + case AMD_PMF_CLAMSHELL: return "CLAMSHELL"; - case FLAT: + case AMD_PMF_FLAT: return "FLAT"; - case TENT: + case AMD_PMF_TENT: return "TENT"; - case STAND: + case AMD_PMF_STAND: return "STAND"; - case TABLET: + case AMD_PMF_TABLET: return "TABLET"; - case BOOK: + case AMD_PMF_BOOK: return "BOOK"; - case PRESENTATION: + case AMD_PMF_PRESENTATION: return "PRESENTATION"; - case PULL_FWD: + case AMD_PMF_PULL_FWD: return "PULL_FWD"; default: return "UNKNOWN"; @@ -44,13 +45,13 @@ static const char *platform_type_as_str(u16 platform_type) static const char *laptop_placement_as_str(u16 device_state) { switch (device_state) { - case ON_TABLE: + case AMD_PMF_ON_TABLE: return "ON_TABLE"; - case ON_LAP_MOTION: + case AMD_PMF_ON_LAP_MOTION: return "ON_LAP_MOTION"; - case IN_BAG: + case AMD_PMF_IN_BAG: return "IN_BAG"; - case OUT_OF_BAG: + case AMD_PMF_OUT_OF_BAG: return "OUT_OF_BAG"; default: return "UNKNOWN"; @@ -60,11 +61,11 @@ static const char *laptop_placement_as_str(u16 device_state) static const char *ta_slider_as_str(unsigned int state) { switch (state) { - case TA_BEST_PERFORMANCE: + case AMD_PMF_TA_BEST_PERFORMANCE: return "PERFORMANCE"; - case TA_BETTER_PERFORMANCE: + case AMD_PMF_TA_BETTER_PERFORMANCE: return "BALANCED"; - case TA_BEST_BATTERY: + case AMD_PMF_TA_BEST_BATTERY: return "POWER_SAVER"; default: return "Unknown TA Slider State"; @@ -287,14 +288,14 @@ static int amd_pmf_get_slider_info(struct amd_pmf_dev *dev, struct ta_pmf_enact_ switch (dev->current_profile) { case PLATFORM_PROFILE_PERFORMANCE: case PLATFORM_PROFILE_BALANCED_PERFORMANCE: - val = TA_BEST_PERFORMANCE; + val = AMD_PMF_TA_BEST_PERFORMANCE; break; case PLATFORM_PROFILE_BALANCED: - val = TA_BETTER_PERFORMANCE; + val = AMD_PMF_TA_BETTER_PERFORMANCE; break; case PLATFORM_PROFILE_LOW_POWER: case PLATFORM_PROFILE_QUIET: - val = TA_BEST_BATTERY; + val = AMD_PMF_TA_BEST_BATTERY; break; default: dev_err(dev->dev, "Unknown Platform Profile.\n"); diff --git a/include/linux/amd-pmf-io.h b/include/linux/amd-pmf-io.h index 55198d2875cc..e014d4ce5a20 100644 --- a/include/linux/amd-pmf-io.h +++ b/include/linux/amd-pmf-io.h @@ -52,15 +52,6 @@ struct amd_sfh_info { u32 laptop_placement; }; -enum laptop_placement { - LP_UNKNOWN = 0, - ON_TABLE, - ON_LAP_MOTION, - IN_BAG, - OUT_OF_BAG, - LP_UNDEFINED, -}; - /** * struct amd_pmf_npu_metrics: Get NPU metrics data from PMF driver * @npuclk_freq: NPU clock frequency [MHz] diff --git a/include/uapi/linux/amd-pmf.h b/include/uapi/linux/amd-pmf.h index 90bc2970665d..bd802f25a717 100644 --- a/include/uapi/linux/amd-pmf.h +++ b/include/uapi/linux/amd-pmf.h @@ -42,6 +42,80 @@ #define AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_AC BIT(3) #define AMD_PMF_FEAT_DYNAMIC_POWER_SLIDER_DC BIT(4) +/** + * enum amd_pmf_laptop_placement - Describes the physical placement of the laptop + * @AMD_PMF_LP_UNKNOWN: Placement cannot be determined + * @AMD_PMF_ON_TABLE: Laptop is placed on a stable surface like a table or desk + * @AMD_PMF_ON_LAP_MOTION: Laptop is on a lap with detected motion + * @AMD_PMF_IN_BAG: Laptop is detected to be inside a bag or case + * @AMD_PMF_OUT_OF_BAG: Laptop has been removed from bag or case + * @AMD_PMF_LP_UNDEFINED: Placement state is undefined + * + * This enumeration represents the physical placement state of the laptop + * as detected by platform sensors. Used for adaptive power management + * and thermal policies. + */ +enum amd_pmf_laptop_placement { + AMD_PMF_LP_UNKNOWN, + AMD_PMF_ON_TABLE, + AMD_PMF_ON_LAP_MOTION, + AMD_PMF_IN_BAG, + AMD_PMF_OUT_OF_BAG, + AMD_PMF_LP_UNDEFINED, +}; + +/** + * enum amd_pmf_ta_slider - Trusted Application power slider positions + * @AMD_PMF_TA_BEST_BATTERY: Maximum battery savings, minimal performance + * @AMD_PMF_TA_BETTER_BATTERY: Balanced towards battery life + * @AMD_PMF_TA_BETTER_PERFORMANCE: Balanced towards performance + * @AMD_PMF_TA_BEST_PERFORMANCE: Maximum performance, higher power consumption + * @AMD_PMF_TA_MAX: Sentinel value indicating maximum enum value + * + * This enumeration defines the power slider positions used by the + * AMD PMF Trusted Application for dynamic power management decisions. + * These correspond to the Windows power slider UI positions. + */ +enum amd_pmf_ta_slider { + AMD_PMF_TA_BEST_BATTERY, + AMD_PMF_TA_BETTER_BATTERY, + AMD_PMF_TA_BETTER_PERFORMANCE, + AMD_PMF_TA_BEST_PERFORMANCE, + AMD_PMF_TA_MAX, +}; + +/** + * enum amd_pmf_platform_type - Describes the physical form factor orientation + * @AMD_PMF_PTYPE_UNKNOWN: Platform type cannot be determined + * @AMD_PMF_LID_CLOSE: Laptop lid is closed + * @AMD_PMF_CLAMSHELL: Traditional laptop mode with keyboard and screen + * @AMD_PMF_FLAT: Device is lying flat on a surface + * @AMD_PMF_TENT: Device is in tent mode (keyboard folded back, standing) + * @AMD_PMF_STAND: Device is propped up in stand orientation + * @AMD_PMF_TABLET: Device is in tablet mode with keyboard hidden + * @AMD_PMF_BOOK: Device is in book reading orientation + * @AMD_PMF_PRESENTATION: Device is in presentation mode + * @AMD_PMF_PULL_FWD: Screen is pulled forward towards user + * @AMD_PMF_PTYPE_INVALID: Invalid platform type marker + * + * This enumeration describes the current physical orientation or form + * factor of convertible/2-in-1 devices. Used for optimizing power and + * thermal management based on device posture. + */ +enum amd_pmf_platform_type { + AMD_PMF_PTYPE_UNKNOWN, + AMD_PMF_LID_CLOSE, + AMD_PMF_CLAMSHELL, + AMD_PMF_FLAT, + AMD_PMF_TENT, + AMD_PMF_STAND, + AMD_PMF_TABLET, + AMD_PMF_BOOK, + AMD_PMF_PRESENTATION, + AMD_PMF_PULL_FWD, + AMD_PMF_PTYPE_INVALID = 0xf, +}; + struct amd_pmf_info { __u64 size; -- cgit v1.2.3 From c7bdedfcb8bab6dc5074623462bddd3241f0f311 Mon Sep 17 00:00:00 2001 From: Shyam Sundar S K Date: Tue, 9 Jun 2026 13:40:41 +0530 Subject: platform/x86/amd/pmf: Move debug helper functions to UAPI header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These debug helper functions convert enumerated values to appropriate message and can be useful for userspace tools and other kernel components that need to interpret AMD PMF state values. By making them inline functions in the UAPI header, they become available to both kernel and userspace without code duplication. Also, prefix the function names with amd_pmf_. Also, include a case to cover unused enum entries. Co-developed-by: Sanket Goswami Signed-off-by: Sanket Goswami Signed-off-by: Shyam Sundar S K Link: https://patch.msgid.link/20260609081044.2416731-5-Shyam-sundar.S-k@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/pmf/spc.c | 62 +++----------------------------------- include/uapi/linux/amd-pmf.h | 58 +++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 57 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/platform/x86/amd/pmf/spc.c b/drivers/platform/x86/amd/pmf/spc.c index 05998946d1bd..6e33824ccadc 100644 --- a/drivers/platform/x86/amd/pmf/spc.c +++ b/drivers/platform/x86/amd/pmf/spc.c @@ -18,60 +18,6 @@ #include "pmf.h" #ifdef CONFIG_AMD_PMF_DEBUG -static const char *platform_type_as_str(u16 platform_type) -{ - switch (platform_type) { - case AMD_PMF_CLAMSHELL: - return "CLAMSHELL"; - case AMD_PMF_FLAT: - return "FLAT"; - case AMD_PMF_TENT: - return "TENT"; - case AMD_PMF_STAND: - return "STAND"; - case AMD_PMF_TABLET: - return "TABLET"; - case AMD_PMF_BOOK: - return "BOOK"; - case AMD_PMF_PRESENTATION: - return "PRESENTATION"; - case AMD_PMF_PULL_FWD: - return "PULL_FWD"; - default: - return "UNKNOWN"; - } -} - -static const char *laptop_placement_as_str(u16 device_state) -{ - switch (device_state) { - case AMD_PMF_ON_TABLE: - return "ON_TABLE"; - case AMD_PMF_ON_LAP_MOTION: - return "ON_LAP_MOTION"; - case AMD_PMF_IN_BAG: - return "IN_BAG"; - case AMD_PMF_OUT_OF_BAG: - return "OUT_OF_BAG"; - default: - return "UNKNOWN"; - } -} - -static const char *ta_slider_as_str(unsigned int state) -{ - switch (state) { - case AMD_PMF_TA_BEST_PERFORMANCE: - return "PERFORMANCE"; - case AMD_PMF_TA_BETTER_PERFORMANCE: - return "BALANCED"; - case AMD_PMF_TA_BEST_BATTERY: - return "POWER_SAVER"; - default: - return "Unknown TA Slider State"; - } -} - static u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index) { switch (index) { @@ -89,7 +35,8 @@ void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table * int i; dev_dbg(dev->dev, "==== TA inputs START ====\n"); - dev_dbg(dev->dev, "Slider State: %s\n", ta_slider_as_str(in->ev_info.power_slider)); + dev_dbg(dev->dev, "Slider State: %s\n", + amd_pmf_get_slider_position(in->ev_info.power_slider)); dev_dbg(dev->dev, "Power Source: %s\n", amd_pmf_source_as_str(in->ev_info.power_source)); dev_dbg(dev->dev, "Battery Percentage: %u\n", in->ev_info.bat_percentage); dev_dbg(dev->dev, "Designed Battery Capacity: %u\n", in->ev_info.bat_design); @@ -103,9 +50,10 @@ void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table * dev_dbg(dev->dev, "LID State: %s\n", in->ev_info.lid_state ? "close" : "open"); dev_dbg(dev->dev, "User Presence: %s\n", in->ev_info.user_present ? "Present" : "Away"); dev_dbg(dev->dev, "Ambient Light: %d\n", in->ev_info.ambient_light); - dev_dbg(dev->dev, "Platform type: %s\n", platform_type_as_str(in->ev_info.platform_type)); + dev_dbg(dev->dev, "Platform type: %s\n", + amd_pmf_get_platform_type(in->ev_info.platform_type)); dev_dbg(dev->dev, "Laptop placement: %s\n", - laptop_placement_as_str(in->ev_info.device_state)); + amd_pmf_get_laptop_placement(in->ev_info.device_state)); for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++) dev_dbg(dev->dev, "Custom BIOS input%d: %u\n", i + 1, amd_pmf_get_ta_custom_bios_inputs(in, i)); diff --git a/include/uapi/linux/amd-pmf.h b/include/uapi/linux/amd-pmf.h index bd802f25a717..24cf9926dc59 100644 --- a/include/uapi/linux/amd-pmf.h +++ b/include/uapi/linux/amd-pmf.h @@ -116,6 +116,64 @@ enum amd_pmf_platform_type { AMD_PMF_PTYPE_INVALID = 0xf, }; +static inline const char *amd_pmf_get_platform_type(unsigned int platform_type) +{ + switch (platform_type) { + case AMD_PMF_CLAMSHELL: + return "CLAMSHELL"; + case AMD_PMF_LID_CLOSE: + return "LID_CLOSE"; + case AMD_PMF_FLAT: + return "FLAT"; + case AMD_PMF_TENT: + return "TENT"; + case AMD_PMF_STAND: + return "STAND"; + case AMD_PMF_TABLET: + return "TABLET"; + case AMD_PMF_BOOK: + return "BOOK"; + case AMD_PMF_PRESENTATION: + return "PRESENTATION"; + case AMD_PMF_PULL_FWD: + return "PULL_FWD"; + default: + return "UNKNOWN"; + } +} + +static inline const char *amd_pmf_get_laptop_placement(unsigned int device_state) +{ + switch (device_state) { + case AMD_PMF_ON_TABLE: + return "ON_TABLE"; + case AMD_PMF_ON_LAP_MOTION: + return "ON_LAP_MOTION"; + case AMD_PMF_IN_BAG: + return "IN_BAG"; + case AMD_PMF_OUT_OF_BAG: + return "OUT_OF_BAG"; + default: + return "UNKNOWN"; + } +} + +static inline const char *amd_pmf_get_slider_position(unsigned int state) +{ + switch (state) { + case AMD_PMF_TA_BEST_PERFORMANCE: + return "PERFORMANCE"; + case AMD_PMF_TA_BETTER_PERFORMANCE: + return "BALANCED"; + case AMD_PMF_TA_BEST_BATTERY: + return "POWER_SAVER"; + case AMD_PMF_TA_BETTER_BATTERY: + return "BALANCED_BATTERY"; + default: + return "Unknown TA Slider State"; + } +} + struct amd_pmf_info { __u64 size; -- cgit v1.2.3 From 44b374622a3f93f2f9ce0a782f3fedb7cd758b6b Mon Sep 17 00:00:00 2001 From: Hugo Villeneuve Date: Thu, 21 May 2026 14:16:53 -0400 Subject: serial: core: add new I/O type for SPI and I2C bus devices I2C/SPI serial drivers don't use the following struct uart_port variables: port->membase port->mapbase port->iobase However, they are forced to set membase to a non-zero value so that uart_configure_port() will succeed because of the following check: /* If there isn't a port here, don't do anything further. */ if (!port->iobase && !port->mapbase && !port->membase) return; Add a new I/O type for SPI and I2C bus devices to remove the need to implement the kind of above-mentioned ambiguous workarounds to make them work. Now that UART report functions are using uart_iotype_*() functions, no more irrelevant I/O information are being printed for UPIO_BUS iotypes. Signed-off-by: Hugo Villeneuve Link: https://patch.msgid.link/20260521-tty-upio-v3-7-bf74567994a0@dimonoff.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/serial_core.c | 11 ++++++----- include/linux/serial_core.h | 1 + include/uapi/linux/serial.h | 1 + 3 files changed, 8 insertions(+), 5 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c index 9b16480b374a..06d7f44643d6 100644 --- a/drivers/tty/serial/serial_core.c +++ b/drivers/tty/serial/serial_core.c @@ -2542,11 +2542,10 @@ uart_configure_port(struct uart_driver *drv, struct uart_state *state, { unsigned int flags; - /* - * If there isn't a port here, don't do anything further. - */ - if (!port->iobase && !port->mapbase && !port->membase) - return; + /* If there isn't a port here, don't do anything further. */ + if (uart_iotype_mmio(port->iotype) || uart_iotype_io(port->iotype)) + if (!port->iobase && !port->mapbase && !port->membase) + return; /* * Now do the auto configuration stuff. Note that config_port @@ -3249,6 +3248,8 @@ bool uart_match_port(const struct uart_port *port1, return hub6_match_port(port1, port2); else if (uart_iotype_mmio(port1->iotype)) return port1->mapbase == port2->mapbase; + else if (port1->iotype == UPIO_BUS) + return true; else return false; } diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h index 66b1459d53fc..c4cc4f66af4b 100644 --- a/include/linux/serial_core.h +++ b/include/linux/serial_core.h @@ -437,6 +437,7 @@ enum uart_iotype { UPIO_TSI = SERIAL_IO_TSI, /* Tsi108/109 type IO */ UPIO_MEM32BE = SERIAL_IO_MEM32BE, /* 32b big endian */ UPIO_MEM16 = SERIAL_IO_MEM16, /* 16b little endian */ + UPIO_BUS = SERIAL_IO_BUS, /* Serial bus I/O access (ex: SPI, I2C) */ }; struct uart_port { diff --git a/include/uapi/linux/serial.h b/include/uapi/linux/serial.h index de9b4733607e..e6f61538fc28 100644 --- a/include/uapi/linux/serial.h +++ b/include/uapi/linux/serial.h @@ -72,6 +72,7 @@ struct serial_struct { #define SERIAL_IO_TSI 5 #define SERIAL_IO_MEM32BE 6 #define SERIAL_IO_MEM16 7 +#define SERIAL_IO_BUS 8 #define UART_CLEAR_FIFO 0x01 #define UART_USE_FIFO 0x02 -- cgit v1.2.3 From c3249c462b30e003712cd80f05c26ae3ee29040c Mon Sep 17 00:00:00 2001 From: Neill Kapron Date: Fri, 19 Jun 2026 04:06:05 +0000 Subject: usb: gadget: f_fs: Add zero-length packet ioctl When transferring data from a USB gadget to a host, a transfer is considered complete when the host receives a short packet (a packet smaller than wMaxPacketSize). If the total transfer length is an exact multiple of wMaxPacketSize, a Zero-Length Packet (ZLP) must be appended to signal the end of the transfer. FunctionFS currently provides no mechanism for userspace to instruct the kernel to set the `req->zero` flag on transfers. Userspace workarounds, such as manually submitting separate 0-byte requests, may not be available for legacy protocols which must maintain write behavior compatibility when moved to functionfs implementations. To resolve this, introduce a new ioctl, FUNCTIONFS_ENDPOINT_ENABLE_ZLP, which takes a pointer to a __u32 flag. When enabled, all subsequent transfers on that endpoint will have the `req->zero` flag set, allowing the underlying USB Device Controller (UDC) hardware to automatically append a ZLP only when mathematically required. For logical transfers chunked across multiple requests, userspace can dynamically toggle this flag, enabling it only prior to submitting the final chunk. The flag defaults to false to maintain backward compatibility. Once enabled, the state is persistent for the lifetime of the endpoint and will not be reset by opening or closing the endpoint file descriptors. Assisted-by: Gemini-CLI:gemini-3.1-pro-preview Signed-off-by: Neill Kapron Link: https://patch.msgid.link/20260619040609.4010746-4-nkapron@google.com Signed-off-by: Greg Kroah-Hartman --- Documentation/usb/functionfs.rst | 24 ++++++++++++++++++++++++ drivers/usb/gadget/function/f_fs.c | 25 ++++++++++++++++++++++++- include/uapi/linux/usb/functionfs.h | 23 +++++++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/Documentation/usb/functionfs.rst b/Documentation/usb/functionfs.rst index f7487b0d8057..582e53549d5b 100644 --- a/Documentation/usb/functionfs.rst +++ b/Documentation/usb/functionfs.rst @@ -72,6 +72,30 @@ have been written to their ep0's. Conversely, the gadget is unregistered after the first USB function closes its endpoints. +Endpoint IOCTLs +=============== + +FunctionFS supports additional IOCTLs that can be performed on data endpoints +(ie. not ep0). For a full list of these IOCTLs, please refer to the documentation +in ``include/uapi/linux/usb/functionfs.h``. + +One such IOCTL is: + + ``FUNCTIONFS_ENDPOINT_ENABLE_ZLP(__u32 *)`` + Enable or disable automatic zero-length packet (ZLP) appending for the + endpoint. The argument is a pointer to a __u32: 0 to disable, non-zero to + enable. When enabled, the kernel will automatically append a ZLP at the end + of a transfer if the payload length is an exact multiple of the endpoint's + max packet size. This is useful for compatibility with legacy protocols + which require automatic ZLP appending to data written from userspace. This + IOCTL can only be used on IN endpoints. It can be called at any time after + the FunctionFS instance is active, even before the host has connected or + enabled the endpoint. Returns zero on success, or a negative errno value on + error: + + * ``-ENODEV``: The FunctionFS instance is not active. + * ``-EINVAL``: The endpoint is not an IN endpoint. + * ``-EFAULT``: Invalid user space pointer for the argument. DMABUF interface ================ diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index ac8c53789ec2..70e7c18ce062 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -224,7 +224,7 @@ struct ffs_epfile { unsigned char in; /* P: ffs->eps_lock */ unsigned char isoc; /* P: ffs->eps_lock */ - unsigned char _pad; + u8 zlp_enabled; /* P: ffs->eps_lock */ /* Protects dmabufs */ struct mutex dmabufs_mutex; @@ -1114,6 +1114,8 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) req->buf = data; req->num_sgs = 0; } + + req->zero = epfile->zlp_enabled; req->length = data_len; io_data->buf = data; @@ -1165,6 +1167,8 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) req->buf = data; req->num_sgs = 0; } + + req->zero = epfile->zlp_enabled; req->length = data_len; io_data->buf = data; @@ -1708,6 +1712,7 @@ static int ffs_dmabuf_transfer(struct file *file, /* Now that the dma_fence is in place, queue the transfer. */ + usb_req->zero = epfile->zlp_enabled; usb_req->length = req->length; usb_req->buf = NULL; usb_req->sg = priv->sgt->sgl; @@ -1755,6 +1760,7 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, struct ffs_epfile *epfile = file->private_data; struct ffs_ep *ep; int ret; + __u32 enable_zlp = 0; if (WARN_ON(epfile->ffs->state != FFS_ACTIVE)) return -ENODEV; @@ -1787,6 +1793,23 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, return ffs_dmabuf_transfer(file, &req); } + /* + * We handle this IOCTL before ffs_epfile_wait_ep() to allow userspace + * to configure ZLP behavior immediately without blocking indefinitely + * while waiting for the USB host to connect and enable the endpoint. + */ + case FUNCTIONFS_ENDPOINT_ENABLE_ZLP: + if (!epfile->in) + return -EINVAL; + + if (copy_from_user(&enable_zlp, (void __user *)value, sizeof(enable_zlp))) + return -EFAULT; + + spin_lock_irq(&epfile->ffs->eps_lock); + epfile->zlp_enabled = !!enable_zlp; + spin_unlock_irq(&epfile->ffs->eps_lock); + + return 0; default: break; } diff --git a/include/uapi/linux/usb/functionfs.h b/include/uapi/linux/usb/functionfs.h index beef1752e36e..06134dca4e8a 100644 --- a/include/uapi/linux/usb/functionfs.h +++ b/include/uapi/linux/usb/functionfs.h @@ -414,4 +414,27 @@ struct usb_functionfs_event { #define FUNCTIONFS_DMABUF_TRANSFER _IOW('g', 133, \ struct usb_ffs_dmabuf_transfer_req) +/* + * Enable or disable automatic zero-length packet (ZLP) appending for the + * endpoint. The argument is a pointer to a __u32: 0 to disable, non-zero to + * enable. + * + * When enabled, the kernel will automatically append a ZLP at the end of + * a transfer if the payload length is an exact multiple of the endpoint's + * max packet size. + * + * This is useful for compatibility with legacy protocols which require + * automatic ZLP appending to data written from userspace. + * + * This ioctl can only be used on IN endpoints. It can be called at any time + * after the FunctionFS instance is active, even before the host has connected + * or enabled the endpoint. + * + * Returns zero on success, or a negative errno value on error: + * -ENODEV: The FunctionFS instance is not active. + * -EINVAL: The endpoint is not an IN endpoint. + * -EFAULT: Invalid user space pointer for the argument. + */ +#define FUNCTIONFS_ENDPOINT_ENABLE_ZLP _IOW('g', 134, __u32) + #endif /* _UAPI__LINUX_FUNCTIONFS_H__ */ -- cgit v1.2.3 From 0f0ea552cd215a01baca1e99eec02e2f8df0c897 Mon Sep 17 00:00:00 2001 From: Neill Kapron Date: Fri, 19 Jun 2026 04:06:06 +0000 Subject: usb: gadget: f_fs: Introduce rw_proxy file descriptors Currently, FunctionFS exposes each USB endpoint as a separate, unidirectional file descriptor (e.g., `ep1` for IN, `ep2` for OUT). While this mirrors the underlying hardware structure, it forces userspace daemons implementing bidirectional protocols to manage multiple file descriptors. When dealing with legacy protocols which require exposing a single, bi-directional fd to userspace, this becomes problematic. This patch introduces the `FUNCTIONFS_RW_PROXY_EPS` UAPI flag. When passed in the descriptor header during initialization, FunctionFS provisions a "rw_proxy" bidirectional file descriptor (e.g., `ep1_rw`) alongside every pair of IN/OUT endpoints. Implementation details: - RW proxy files act as a pure VFS alias, proxying operations directly to the base ffs_epfile instances. A `read()` proxies to the OUT endpoint's file, and a `write()` proxies to the IN file. - Because operations are proxied natively, they reuse the underlying base endpoint's lock (`epfile->mutex`) and tracking state. This serializes concurrent I/O, preventing buffer corruption or races even if userspace mixes transfers across both the rw_proxy and base files while allowing full-duplex synchronous operations to occur concurrently without serializing on a single lock. - Control operations (like IOCTLs) and intentional stalls (via reverse-direction I/O) must still be issued on the base endpoints, as the rw_proxy returns `-ENOTTY` for IOCTLs and cannot trigger stalls. Assisted-by: Antigravity:gemini-3.1-pro Signed-off-by: Neill Kapron Link: https://patch.msgid.link/20260619040609.4010746-5-nkapron@google.com Signed-off-by: Greg Kroah-Hartman --- Documentation/usb/functionfs.rst | 56 ++++++++++++++++++++++++ drivers/usb/gadget/function/f_fs.c | 87 ++++++++++++++++++++++++++++++------- drivers/usb/gadget/function/u_fs.h | 8 +++- include/uapi/linux/usb/functionfs.h | 1 + 4 files changed, 136 insertions(+), 16 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/usb/functionfs.rst b/Documentation/usb/functionfs.rst index 582e53549d5b..b189cf5626ba 100644 --- a/Documentation/usb/functionfs.rst +++ b/Documentation/usb/functionfs.rst @@ -96,6 +96,58 @@ One such IOCTL is: * ``-ENODEV``: The FunctionFS instance is not active. * ``-EINVAL``: The endpoint is not an IN endpoint. * ``-EFAULT``: Invalid user space pointer for the argument. + +RW Proxy Endpoints +================== + +If the ``FUNCTIONFS_RW_PROXY_EPS`` flag is passed in the descriptor header +(requires ``FUNCTIONFS_DESCRIPTORS_MAGIC_V2``), FunctionFS will provision a +bidirectional rw_proxy file descriptor (e.g., "ep1_rw") alongside each pair +of IN and OUT endpoints. The rw_proxy file aliases the underlying hardware +endpoints, allowing userspace to use a single file descriptor for both reading +(OUT) and writing (IN). + +This flag requires the total number of hardware endpoints to be an even number. +FunctionFS will automatically walk the provided endpoints and group them into +adjacent pairs (e.g., ep1 and ep2 form the first pair, ep3 and ep4 form the +second pair). Each pair must consist of exactly one IN endpoint and one OUT +endpoint. + +For each valid pair, a rw_proxy file is created and named after the first +endpoint in the pair with a "_rw" suffix. For example, if ep1 and ep2 are +paired, a rw_proxy file named "ep1_rw" is created. If ep3 and ep4 are paired, +"ep3_rw" is created. + +If the ``FUNCTIONFS_VIRTUAL_ADDR`` flag is also enabled, the endpoints will be +named using their physical endpoint address in hexadecimal instead of their +index. RW proxy files will inherit this naming convention. For example, if the +first endpoint of a pair maps to address 0x02, the rw_proxy file will be +named "ep02_rw". + +When this flag is enabled, userspace has the choice of performing data transfers +via the single rw_proxy file descriptor or the two base file descriptors. The +rw_proxy file descriptor acts as a pure VFS alias that proxies all operations +directly to the underlying base file descriptors. + +Because it is a pure proxy, there are no data races or buffer corruptions if +userspace uses both the rw_proxy endpoint and the base endpoints concurrently. +The native mutexes of the base endpoints perfectly serialize all concurrent +transfers. However, userspace should generally pick one method and stick to it +to avoid interleaving its own data stream. + +- **IOCTLs (Clear Halt, etc.):** RW proxy endpoints do not support IOCTLs and + will return ``-ENOTTY``. To clear a host-initiated halt, userspace must issue + the ``FUNCTIONFS_CLEAR_HALT`` ioctl directly on the corresponding base + endpoint file descriptor. +- **Intentional Stalls:** The traditional mechanism for intentionally halting an + endpoint by issuing a reverse-direction data operation (e.g., attempting to + read from an IN endpoint) continues to work, but it must be issued on the + base endpoint. RW proxy endpoints cannot be used to trigger a stall because + they are fully bidirectional. + +Note that DMABUF data transfers (``FUNCTIONFS_DMABUF_TRANSFER``) are unsupported +via the rw_proxy endpoint because it does not support IOCTLs. If DMABUF +transfers are required, users must use the standard base endpoints. DMABUF interface ================ @@ -103,6 +155,10 @@ FunctionFS additionally supports a DMABUF based interface, where the userspace can attach DMABUF objects (externally created) to an endpoint, and subsequently use them for data transfers. +Note: The DMABUF interface is unsupported on rw_proxy endpoints. See +the RW Proxy Endpoints section for details on using DMABUF alongside +the ``FUNCTIONFS_RW_PROXY_EPS`` flag. + A userspace application can then use this interface to share DMABUF objects between several interfaces, allowing it to transfer data in a zero-copy fashion, for instance between IIO and the USB stack. diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index 70e7c18ce062..d7ae4d75d5f5 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -159,7 +159,9 @@ struct ffs_epfile { struct mutex mutex; struct ffs_data *ffs; - struct ffs_ep *ep; /* P: ffs->eps_lock */ + struct ffs_ep *ep; /* P: ffs->eps_lock */ + struct ffs_epfile *epfile_in; /* P: ffs->eps_lock */ + struct ffs_epfile *epfile_out; /* P: ffs->eps_lock */ /* * Buffer for holding data from partial reads which may happen since @@ -219,12 +221,13 @@ struct ffs_epfile { struct ffs_buffer *read_buffer; #define READ_BUFFER_DROP ((struct ffs_buffer *)ERR_PTR(-ESHUTDOWN)) - char name[5]; + char name[8]; unsigned char in; /* P: ffs->eps_lock */ unsigned char isoc; /* P: ffs->eps_lock */ u8 zlp_enabled; /* P: ffs->eps_lock */ + bool is_rw_proxy; /* Protects dmabufs */ struct mutex dmabufs_mutex; @@ -978,9 +981,8 @@ static ssize_t __ffs_epfile_read_data(struct ffs_epfile *epfile, return ret; } -static struct ffs_ep *ffs_epfile_wait_ep(struct file *file) +static struct ffs_ep *ffs_epfile_wait_ep(struct ffs_epfile *epfile, struct file *file) { - struct ffs_epfile *epfile = file->private_data; struct ffs_ep *ep; int ret; @@ -1007,17 +1009,22 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) char *data = NULL; ssize_t ret, data_len = -EINVAL; int halt; + bool is_rw_proxy = epfile->is_rw_proxy; /* Are we still active? */ if (WARN_ON(epfile->ffs->state != FFS_ACTIVE)) return -ENODEV; - ep = ffs_epfile_wait_ep(file); + /* Proxy to base endpoint if rw_proxy */ + if (is_rw_proxy) + epfile = io_data->read ? epfile->epfile_out : epfile->epfile_in; + + ep = ffs_epfile_wait_ep(epfile, file); if (IS_ERR(ep)) return PTR_ERR(ep); /* Do we halt? */ - halt = (!io_data->read == !epfile->in); + halt = is_rw_proxy ? 0 : (!io_data->read == !epfile->in); if (halt && epfile->isoc) return -EINVAL; @@ -1115,7 +1122,7 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) req->num_sgs = 0; } - req->zero = epfile->zlp_enabled; + req->zero = !io_data->read ? epfile->zlp_enabled : 0; req->length = data_len; io_data->buf = data; @@ -1168,7 +1175,7 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) req->num_sgs = 0; } - req->zero = epfile->zlp_enabled; + req->zero = !io_data->read ? epfile->zlp_enabled : 0; req->length = data_len; io_data->buf = data; @@ -1647,7 +1654,7 @@ static int ffs_dmabuf_transfer(struct file *file, priv = attach->importer_priv; - ep = ffs_epfile_wait_ep(file); + ep = ffs_epfile_wait_ep(epfile, file); if (IS_ERR(ep)) { ret = PTR_ERR(ep); goto err_attachment_put; @@ -1765,6 +1772,9 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, if (WARN_ON(epfile->ffs->state != FFS_ACTIVE)) return -ENODEV; + if (epfile->is_rw_proxy) + return -ENOTTY; + switch (code) { case FUNCTIONFS_DMABUF_ATTACH: { @@ -1815,7 +1825,7 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code, } /* Wait for endpoint to be enabled */ - ep = ffs_epfile_wait_ep(file); + ep = ffs_epfile_wait_ep(epfile, file); if (IS_ERR(ep)) return PTR_ERR(ep); @@ -2213,7 +2223,7 @@ static void ffs_data_closed(struct ffs_data *ffs) if (epfiles) ffs_epfiles_destroy(ffs->sb, epfiles, - ffs->eps_count); + ffs->epfiles_count); if (ffs->setup_state == FFS_SETUP_PENDING) __ffs_ep0_stall(ffs); @@ -2271,7 +2281,7 @@ static void ffs_data_clear(struct ffs_data *ffs) * copy of epfile will save us from use-after-free. */ if (epfiles) { - ffs_epfiles_destroy(ffs->sb, epfiles, ffs->eps_count); + ffs_epfiles_destroy(ffs->sb, epfiles, ffs->epfiles_count); ffs->epfiles = NULL; } @@ -2369,11 +2379,16 @@ static void functionfs_unbind(struct ffs_data *ffs) static int ffs_epfiles_create(struct ffs_data *ffs) { struct ffs_epfile *epfile, *epfiles; - unsigned i, count; + unsigned int i, count, epfiles_count; int err; count = ffs->eps_count; - epfiles = kzalloc_objs(*epfiles, count); + epfiles_count = count; + if (ffs->user_flags & FUNCTIONFS_RW_PROXY_EPS) + epfiles_count += count / 2; + ffs->epfiles_count = epfiles_count; + + epfiles = kzalloc_objs(*epfiles, epfiles_count); if (!epfiles) return -ENOMEM; @@ -2395,6 +2410,32 @@ static int ffs_epfiles_create(struct ffs_data *ffs) } } + if (ffs->user_flags & FUNCTIONFS_RW_PROXY_EPS) { + struct ffs_epfile *comp = epfiles + count; + + for (i = 0; i < count; i += 2, ++comp) { + struct ffs_epfile *ep1 = &epfiles[i]; + struct ffs_epfile *ep2 = &epfiles[i + 1]; + bool ep1_in = ffs->eps_addrmap[i + 1] & USB_ENDPOINT_DIR_MASK; + + comp->ffs = ffs; + comp->is_rw_proxy = true; + comp->epfile_in = ep1_in ? ep1 : ep2; + comp->epfile_out = ep1_in ? ep2 : ep1; + mutex_init(&comp->mutex); + mutex_init(&comp->dmabufs_mutex); + INIT_LIST_HEAD(&comp->dmabufs); + snprintf(comp->name, sizeof(comp->name), "%s_rw", + epfiles[i].name); + err = ffs_sb_create_file(ffs->sb, comp->name, + comp, &ffs_epfile_operations); + if (err) { + ffs_epfiles_destroy(ffs->sb, epfiles, count + (i / 2)); + return err; + } + } + } + ffs->epfiles = epfiles; return 0; } @@ -2972,7 +3013,8 @@ static int __ffs_data_got_descs(struct ffs_data *ffs, FUNCTIONFS_VIRTUAL_ADDR | FUNCTIONFS_EVENTFD | FUNCTIONFS_ALL_CTRL_RECIP | - FUNCTIONFS_CONFIG0_SETUP)) { + FUNCTIONFS_CONFIG0_SETUP | + FUNCTIONFS_RW_PROXY_EPS)) { ret = -ENOSYS; goto error; } @@ -3060,6 +3102,21 @@ static int __ffs_data_got_descs(struct ffs_data *ffs, goto error; } + if (ffs->user_flags & FUNCTIONFS_RW_PROXY_EPS) { + if (ffs->eps_count % 2) { + ret = -EINVAL; + goto error; + } + + for (i = 1; i < ffs->eps_count; i += 2) { + if ((ffs->eps_addrmap[i] & USB_ENDPOINT_DIR_MASK) == + (ffs->eps_addrmap[i + 1] & USB_ENDPOINT_DIR_MASK)) { + ret = -EINVAL; + goto error; + } + } + } + ffs->raw_descs_data = _data; ffs->raw_descs = raw_descs; ffs->raw_descs_length = data - raw_descs; diff --git a/drivers/usb/gadget/function/u_fs.h b/drivers/usb/gadget/function/u_fs.h index 6a80182aadd7..c280c495fbd2 100644 --- a/drivers/usb/gadget/function/u_fs.h +++ b/drivers/usb/gadget/function/u_fs.h @@ -252,8 +252,14 @@ struct ffs_data { unsigned short strings_count; unsigned short interfaces_count; + + /* + * eps_count tracks the number of underlying hardware endpoints. + * epfiles_count tracks the total number of VFS endpoint files. + * When companion endpoints are active, epfiles_count > eps_count. + */ unsigned short eps_count; - unsigned short _pad1; + unsigned short epfiles_count; /* filled by __ffs_data_got_strings() */ /* ids in stringtabs are set in functionfs_bind() */ diff --git a/include/uapi/linux/usb/functionfs.h b/include/uapi/linux/usb/functionfs.h index 06134dca4e8a..290308c9dd92 100644 --- a/include/uapi/linux/usb/functionfs.h +++ b/include/uapi/linux/usb/functionfs.h @@ -25,6 +25,7 @@ enum functionfs_flags { FUNCTIONFS_EVENTFD = 32, FUNCTIONFS_ALL_CTRL_RECIP = 64, FUNCTIONFS_CONFIG0_SETUP = 128, + FUNCTIONFS_RW_PROXY_EPS = 256, }; /* Descriptor of an non-audio endpoint */ -- cgit v1.2.3 From b95181f3929ff98949fa9460ca93eccebbf2d7fc Mon Sep 17 00:00:00 2001 From: You-Kai Zheng Date: Tue, 16 Jun 2026 18:39:07 +0800 Subject: btrfs: declare btrfs_ioctl_search_args_v2::buf as __u8 The variable-sized buffer buf in struct btrfs_ioctl_search_args_v2 is declared as __u64[], but it holds a packed byte stream of search results, where all offsets into the buffer are in bytes. Declaring buf as __u64[] makes it easy for user space to write incorrect pointer arithmetic: adding a byte offset directly to a __u64 pointer scales the offset by 8, landing at byte position offset*8 instead of offset. This recently caused an infinite loop in btrfs-progs: the accessor read all-zero data from misaddressed items, which fed zeroed search keys back into the ioctl loop and spun forever. The issue was worked around at the time by disabling TREE_SEARCH_V2 entirely in btrfs-progs (d73e69824854: "btrfs-progs: temporarily disable usage of v2 of search tree ioctl"). The kernel side already treats buf as a byte buffer, so change the declaration to __u8[] to match the actual semantics and prevent similar misuse in user space. The change is ABI compatible: both the structure size and alignment are unchanged. Fixes: cc68a8a5a433 ("btrfs: new ioctl TREE_SEARCH_V2") Reviewed-by: Qu Wenruo Signed-off-by: You-Kai Zheng Reviewed-by: David Sterba Signed-off-by: David Sterba --- include/uapi/linux/btrfs.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/btrfs.h b/include/uapi/linux/btrfs.h index 9b576603b3f1..0a13baf3d8d1 100644 --- a/include/uapi/linux/btrfs.h +++ b/include/uapi/linux/btrfs.h @@ -598,7 +598,7 @@ struct btrfs_ioctl_search_args_v2 { __u64 buf_size; /* in - size of buffer * out - on EOVERFLOW: needed size * to store item */ - __u64 buf[]; /* out - found items */ + __u8 buf[]; /* out - found items */ }; /* With a @src_length of zero, the range from @src_offset->EOF is cloned! */ -- cgit v1.2.3 From 60decab325a439b1f6108be276c08a11e80c5529 Mon Sep 17 00:00:00 2001 From: Jacob Pan Date: Mon, 6 Jul 2026 11:48:32 -0700 Subject: iommufd: Add an ioctl to query PA from IOVA for noiommu mode To support no-IOMMU mode where userspace drivers perform unsafe DMA using physical addresses, introduce a new API to retrieve the physical address of a user-allocated DMA buffer that has been mapped to an IOVA via IOMMU_IOAS_MAP. The mapping is backed by SW-only I/O page tables maintained by the GENERIC_PT framework. Link: https://patch.msgid.link/r/a60a601509688e8552c75e668deb548c93974a3b.1783360051.git.jacob.pan@linux.microsoft.com Reviewed-by: Lu Baolu Reviewed-by: Kevin Tian Reviewed-by: Pranjal Shrivastava Reviewed-by: Yi Liu Suggested-by: Jason Gunthorpe Co-developed-by: Jason Gunthorpe Signed-off-by: Jacob Pan Fixes: 2c6cf6ab1564 ("iommufd: Allow binding to a noiommu device") Signed-off-by: Jason Gunthorpe --- drivers/iommu/iommufd/io_pagetable.c | 78 +++++++++++++++++++++++++++++++++ drivers/iommu/iommufd/ioas.c | 36 +++++++++++++++ drivers/iommu/iommufd/iommufd_private.h | 18 ++++++++ drivers/iommu/iommufd/main.c | 3 ++ include/uapi/linux/iommufd.h | 28 ++++++++++++ 5 files changed, 163 insertions(+) (limited to 'include/uapi/linux') diff --git a/drivers/iommu/iommufd/io_pagetable.c b/drivers/iommu/iommufd/io_pagetable.c index ad15d6d2db62..4e447ce74cf6 100644 --- a/drivers/iommu/iommufd/io_pagetable.c +++ b/drivers/iommu/iommufd/io_pagetable.c @@ -859,6 +859,84 @@ int iopt_unmap_iova(struct io_pagetable *iopt, unsigned long iova, return iopt_unmap_iova_range(iopt, iova, iova_last, unmapped); } +#ifdef CONFIG_IOMMUFD_NOIOMMU +int iopt_get_phys(struct io_pagetable *iopt, unsigned long iova, u64 *paddr, + u64 *length) +{ + struct iopt_area *area; + struct iopt_pages *pages; + u64 max_length = *length; + u64 tmp_length = 0; + u64 tmp_paddr = 0; + int rc = 0; + + if (!max_length) + return -EINVAL; + + down_read(&iopt->iova_rwsem); + area = iopt_area_iter_first(iopt, iova, iova); + if (!area || !area->pages) { + rc = -ENOENT; + goto unlock_exit; + } + + pages = area->pages; + mutex_lock(&pages->mutex); + if (iopt_dmabuf_revoked(pages)) { + rc = -EINVAL; + goto unlock_pages; + } + + if (!area->storage_domain || + area->storage_domain->owner != &iommufd_noiommu_ops) { + rc = -EOPNOTSUPP; + goto unlock_pages; + } + + *paddr = iommu_iova_to_phys(area->storage_domain, iova); + tmp_length = min_t(u64, PAGE_SIZE - offset_in_page(iova), + iopt_area_last_iova(area) - iova + 1); + tmp_paddr = *paddr; + /* + * Return the physically contiguous length, capped by the caller + * supplied range length. + */ + while (iova < iopt_area_last_iova(area)) { + unsigned long next_iova; + u64 next_paddr; + + if (tmp_length >= max_length) + break; + + if (check_add_overflow(iova, PAGE_SIZE, &next_iova)) + break; + + if (next_iova > iopt_area_last_iova(area)) + break; + + next_paddr = iommu_iova_to_phys(area->storage_domain, next_iova); + + if (!next_paddr || next_paddr != tmp_paddr + PAGE_SIZE) + break; + + iova = next_iova; + tmp_paddr += PAGE_SIZE; + tmp_length += PAGE_SIZE; + } + + if (tmp_length > max_length) + tmp_length = max_length; + *length = tmp_length; + +unlock_pages: + mutex_unlock(&pages->mutex); +unlock_exit: + up_read(&iopt->iova_rwsem); + + return rc; +} +#endif + int iopt_unmap_all(struct io_pagetable *iopt, unsigned long *unmapped) { /* If the IOVAs are empty then unmap all succeeds */ diff --git a/drivers/iommu/iommufd/ioas.c b/drivers/iommu/iommufd/ioas.c index fed06c2b728e..1a18cee7456b 100644 --- a/drivers/iommu/iommufd/ioas.c +++ b/drivers/iommu/iommufd/ioas.c @@ -375,6 +375,42 @@ out_put: return rc; } +#ifdef CONFIG_IOMMUFD_NOIOMMU +int iommufd_ioas_noiommu_get_pa(struct iommufd_ucmd *ucmd) +{ + struct iommu_ioas_noiommu_get_pa *cmd = ucmd->cmd; + struct iommufd_ioas *ioas; + int rc; + + if (!capable(CAP_SYS_RAWIO)) + return -EPERM; + + if (cmd->flags || cmd->__reserved) + return -EOPNOTSUPP; + + if (!cmd->length) + return -EINVAL; + + if (cmd->iova >= ULONG_MAX) + return -EOVERFLOW; + + ioas = iommufd_get_ioas(ucmd->ictx, cmd->ioas_id); + if (IS_ERR(ioas)) + return PTR_ERR(ioas); + + rc = iopt_get_phys(&ioas->iopt, cmd->iova, &cmd->out_phys, + &cmd->length); + if (rc) + goto out_put; + + rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd)); +out_put: + iommufd_put_object(ucmd->ictx, &ioas->obj); + + return rc; +} +#endif + static void iommufd_release_all_iova_rwsem(struct iommufd_ctx *ictx, struct xarray *ioas_list) { diff --git a/drivers/iommu/iommufd/iommufd_private.h b/drivers/iommu/iommufd/iommufd_private.h index 4b15db1ca83b..eb2e85b27e42 100644 --- a/drivers/iommu/iommufd/iommufd_private.h +++ b/drivers/iommu/iommufd/iommufd_private.h @@ -118,6 +118,16 @@ int iopt_map_pages(struct io_pagetable *iopt, struct list_head *pages_list, int iopt_unmap_iova(struct io_pagetable *iopt, unsigned long iova, unsigned long length, unsigned long *unmapped); int iopt_unmap_all(struct io_pagetable *iopt, unsigned long *unmapped); +#ifdef CONFIG_IOMMUFD_NOIOMMU +int iopt_get_phys(struct io_pagetable *iopt, unsigned long iova, u64 *paddr, + u64 *length); +#else +static inline int iopt_get_phys(struct io_pagetable *iopt, unsigned long iova, + u64 *paddr, u64 *length) +{ + return -EOPNOTSUPP; +} +#endif int iopt_read_and_clear_dirty_data(struct io_pagetable *iopt, struct iommu_domain *domain, @@ -347,6 +357,14 @@ int iommufd_ioas_map_file(struct iommufd_ucmd *ucmd); int iommufd_ioas_change_process(struct iommufd_ucmd *ucmd); int iommufd_ioas_copy(struct iommufd_ucmd *ucmd); int iommufd_ioas_unmap(struct iommufd_ucmd *ucmd); +#ifdef CONFIG_IOMMUFD_NOIOMMU +int iommufd_ioas_noiommu_get_pa(struct iommufd_ucmd *ucmd); +#else +static inline int iommufd_ioas_noiommu_get_pa(struct iommufd_ucmd *ucmd) +{ + return -EOPNOTSUPP; +} +#endif int iommufd_ioas_option(struct iommufd_ucmd *ucmd); int iommufd_option_rlimit_mode(struct iommu_option *cmd, struct iommufd_ctx *ictx); diff --git a/drivers/iommu/iommufd/main.c b/drivers/iommu/iommufd/main.c index 0d7fa10c70cc..9a921b153162 100644 --- a/drivers/iommu/iommufd/main.c +++ b/drivers/iommu/iommufd/main.c @@ -444,6 +444,7 @@ union ucmd_buffer { struct iommu_ioas_alloc alloc; struct iommu_ioas_allow_iovas allow_iovas; struct iommu_ioas_copy ioas_copy; + struct iommu_ioas_noiommu_get_pa noiommu_get_pa; struct iommu_ioas_iova_ranges iova_ranges; struct iommu_ioas_map map; struct iommu_ioas_unmap unmap; @@ -502,6 +503,8 @@ static const struct iommufd_ioctl_op iommufd_ioctl_ops[] = { IOCTL_OP(IOMMU_IOAS_MAP, iommufd_ioas_map, struct iommu_ioas_map, iova), IOCTL_OP(IOMMU_IOAS_MAP_FILE, iommufd_ioas_map_file, struct iommu_ioas_map_file, iova), + IOCTL_OP(IOMMU_IOAS_NOIOMMU_GET_PA, iommufd_ioas_noiommu_get_pa, struct iommu_ioas_noiommu_get_pa, + out_phys), IOCTL_OP(IOMMU_IOAS_UNMAP, iommufd_ioas_unmap, struct iommu_ioas_unmap, length), IOCTL_OP(IOMMU_OPTION, iommufd_option, struct iommu_option, val64), diff --git a/include/uapi/linux/iommufd.h b/include/uapi/linux/iommufd.h index 0425d452d41e..fddaf14cfdd2 100644 --- a/include/uapi/linux/iommufd.h +++ b/include/uapi/linux/iommufd.h @@ -57,6 +57,7 @@ enum { IOMMUFD_CMD_IOAS_CHANGE_PROCESS = 0x92, IOMMUFD_CMD_VEVENTQ_ALLOC = 0x93, IOMMUFD_CMD_HW_QUEUE_ALLOC = 0x94, + IOMMUFD_CMD_IOAS_NOIOMMU_GET_PA = 0x95, }; /** @@ -219,6 +220,33 @@ struct iommu_ioas_map { }; #define IOMMU_IOAS_MAP _IO(IOMMUFD_TYPE, IOMMUFD_CMD_IOAS_MAP) +/** + * struct iommu_ioas_noiommu_get_pa - ioctl(IOMMU_IOAS_NOIOMMU_GET_PA) + * @size: sizeof(struct iommu_ioas_noiommu_get_pa) + * @flags: Reserved, must be 0 for now + * @ioas_id: IOAS ID to query IOVA to PA mapping from + * @__reserved: Must be 0 + * @iova: IOVA to query + * @length: On input, non-zero maximum number of bytes to query starting from + * @iova. On output, number of physically contiguous bytes starting + * from @out_phys, capped by the input length. + * @out_phys: Output physical address the IOVA maps to + * + * Query the physical address backing an IOVA range. The beginning of the + * range must be mapped already and length must be non-zero. For noiommu + * devices doing unsafe DMA only. + */ +struct iommu_ioas_noiommu_get_pa { + __u32 size; + __u32 flags; + __u32 ioas_id; + __u32 __reserved; + __aligned_u64 iova; + __aligned_u64 length; + __aligned_u64 out_phys; +}; +#define IOMMU_IOAS_NOIOMMU_GET_PA _IO(IOMMUFD_TYPE, IOMMUFD_CMD_IOAS_NOIOMMU_GET_PA) + /** * struct iommu_ioas_map_file - ioctl(IOMMU_IOAS_MAP_FILE) * @size: sizeof(struct iommu_ioas_map_file) -- cgit v1.2.3 From 35dac1daeb3c8208515047d32f22c4a162e8de5f Mon Sep 17 00:00:00 2001 From: Avinash Duduskar Date: Mon, 13 Jul 2026 21:53:03 +0530 Subject: bpf: Add BPF_FIB_LOOKUP_VLAN flag to bpf_fib_lookup() helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bpf_fib_lookup() returns the FIB-resolved egress ifindex straight from the fib result. When the egress is a VLAN device, the returned ifindex is the VLAN netdev's, which has no XDP xmit handler; XDP programs that want to forward the frame (e.g. xdp-forward) must instead target the underlying physical device and push the VLAN tag themselves. Today the program has no way to learn either the underlying ifindex or the VLAN tag without maintaining its own VLAN-to-ifindex map in userspace and refreshing it on netlink events. Add BPF_FIB_LOOKUP_VLAN. When the caller sets this flag and the fib result is a VLAN device whose immediate parent is a real (non-VLAN) device in the same network namespace, populate the existing output fields params->h_vlan_proto and params->h_vlan_TCI from the VLAN device and replace params->ifindex with the parent's ifindex. params->h_vlan_TCI carries the VID only, with PCP and DEI bits zero; a consumer wanting to set egress priority writes PCP itself. params->smac is the VLAN device's own address, which can differ from the parent's. Only the immediate parent is resolved, via vlan_dev_priv(dev)->real_dev and not vlan_dev_real_dev(), which walks to the bottom of a stack. When the immediate parent is not a real device in the same namespace, the lookup returns BPF_FIB_LKUP_RET_VLAN_FAILURE and leaves params->ifindex at the input. This covers a stacked VLAN (QinQ), where the immediate parent is itself a VLAN device and one h_vlan_proto/h_vlan_TCI pair cannot describe two tags, and a parent in another network namespace (a VLAN device can be moved while its parent stays), whose ifindex would be meaningless in the caller's namespace. A program that wants the VLAN device's own ifindex re-issues the lookup, with a re-initialized params, without BPF_FIB_LOOKUP_VLAN, so the unreducible case stays distinct from a physical egress. That distinction matters for XDP: a program cannot xmit on a VLAN device, so a success carrying the VLAN ifindex would make it redirect to a device with no ndo_xdp_xmit and drop the frame at xdp_do_flush(). The swap and the vlan fields are written only on the reduce path; other output fields keep their existing behaviour, so a frag-needed result still reports the route mtu in params->mtu_result. BPF_FIB_LOOKUP_VLAN is only useful to XDP, which cannot redirect to a VLAN device. A tc program can redirect to the VLAN device directly, so bpf_skb_fib_lookup() rejects the flag with -EINVAL; bpf_xdp_fib_lookup() accepts it. When the flag is not set, behaviour is unchanged: h_vlan_proto and h_vlan_TCI are zeroed and ifindex is left at the FIB result. The new block is compiled only under CONFIG_VLAN_8021Q since vlan_dev_priv() is not defined otherwise; without that config is_vlan_dev() is constant false and the flag is accepted but never acts. That is safe because no VLAN device can exist there, so every egress is already physical. This lets an XDP redirect target the physical device and learn the tag to push in a single lookup, which xdp-forward's optional VLAN mode (xdp-project/xdp-tools#504) wants from the kernel side. The helper's input semantics are unchanged; the reverse direction (supplying a tag as lookup input) is added in the following patch. Suggested-by: Toke Høiland-Jørgensen Signed-off-by: Avinash Duduskar Reviewed-by: Toke Høiland-Jørgensen Reviewed-by: Emil Tsalapatis Acked-by: David Ahern Link: https://lore.kernel.org/bpf/20260713162305.1237211-2-avinash.duduskar@gmail.com Signed-off-by: Kumar Kartikeya Dwivedi --- include/uapi/linux/bpf.h | 33 ++++++++++++++++++++++++++++++++- net/core/filter.c | 33 +++++++++++++++++++++++++++++---- tools/include/uapi/linux/bpf.h | 33 ++++++++++++++++++++++++++++++++- 3 files changed, 93 insertions(+), 6 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index 2f1d24fef857..8b41c365ddf9 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -3532,6 +3532,31 @@ union bpf_attr { * Use the mark present in *params*->mark for the fib lookup. * This option should not be used with BPF_FIB_LOOKUP_DIRECT, * as it only has meaning for full lookups. + * **BPF_FIB_LOOKUP_VLAN** + * If the fib lookup resolves to a VLAN device whose + * parent is a real (non-VLAN) device, set + * *params*->h_vlan_proto and *params*->h_vlan_TCI from + * the VLAN device and replace *params*->ifindex with the + * parent's ifindex. *params*->h_vlan_TCI carries the VID + * only, with PCP and DEI bits zero; a consumer wanting to + * set egress priority writes PCP itself. *params*->smac is + * the VLAN device's own address, which can differ from the + * parent's. Only the immediate parent is resolved; if it + * is itself a VLAN device (QinQ) or in another namespace, + * the egress cannot be reduced to a physical device plus + * one tag and the lookup returns + * **BPF_FIB_LKUP_RET_VLAN_FAILURE** with *params*->ifindex + * left at the input. To obtain the VLAN device's own + * ifindex, repeat the lookup without + * **BPF_FIB_LOOKUP_VLAN**, re-initializing *params* + * first: output fields overwrite the inputs they share + * storage with. The swap and the vlan fields + * are written only on success; other output fields keep + * the helper's existing behaviour, so a frag-needed result + * still reports the route mtu in *params*->mtu_result. + * This flag is only valid for XDP programs; tc programs + * receive -EINVAL since they can redirect to the VLAN + * device directly. * * *ctx* is either **struct xdp_md** for XDP programs or * **struct sk_buff** tc cls_act programs. @@ -7339,6 +7364,7 @@ enum { BPF_FIB_LOOKUP_TBID = (1U << 3), BPF_FIB_LOOKUP_SRC = (1U << 4), BPF_FIB_LOOKUP_MARK = (1U << 5), + BPF_FIB_LOOKUP_VLAN = (1U << 6), }; enum { @@ -7352,6 +7378,7 @@ enum { BPF_FIB_LKUP_RET_NO_NEIGH, /* no neighbor entry for nh */ BPF_FIB_LKUP_RET_FRAG_NEEDED, /* fragmentation required to fwd */ BPF_FIB_LKUP_RET_NO_SRC_ADDR, /* failed to derive IP src addr */ + BPF_FIB_LKUP_RET_VLAN_FAILURE, /* VLAN egress, parent unresolvable */ }; struct bpf_fib_lookup { @@ -7405,7 +7432,11 @@ struct bpf_fib_lookup { union { struct { - /* output */ + /* + * output with BPF_FIB_LOOKUP_VLAN: set from the + * resolved egress VLAN device (see the flag); zeroed + * on other successful lookups. + */ __be16 h_vlan_proto; __be16 h_vlan_TCI; }; diff --git a/net/core/filter.c b/net/core/filter.c index 056deb9b3fc3..b8f2595ff582 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -6206,10 +6206,29 @@ static const struct bpf_func_proto bpf_skb_get_xfrm_state_proto = { #endif #if IS_ENABLED(CONFIG_INET) || IS_ENABLED(CONFIG_IPV6) -static int bpf_fib_set_fwd_params(struct bpf_fib_lookup *params, u32 mtu) +static int bpf_fib_set_fwd_params(struct net_device *dev, + struct bpf_fib_lookup *params, + u32 flags, u32 mtu, u32 in_ifindex) { params->h_vlan_TCI = 0; params->h_vlan_proto = 0; + +#if IS_ENABLED(CONFIG_VLAN_8021Q) + if ((flags & BPF_FIB_LOOKUP_VLAN) && is_vlan_dev(dev)) { + struct net_device *real_dev = vlan_dev_priv(dev)->real_dev; + + if (!is_vlan_dev(real_dev) && + net_eq(dev_net(real_dev), dev_net(dev))) { + params->h_vlan_proto = vlan_dev_vlan_proto(dev); + params->h_vlan_TCI = htons(vlan_dev_vlan_id(dev)); + params->ifindex = real_dev->ifindex; + } else { + params->ifindex = in_ifindex; + return BPF_FIB_LKUP_RET_VLAN_FAILURE; + } + } +#endif + if (mtu) params->mtu_result = mtu; /* union with tot_len */ @@ -6221,6 +6240,7 @@ static int bpf_fib_set_fwd_params(struct bpf_fib_lookup *params, u32 mtu) static int bpf_ipv4_fib_lookup(struct net *net, struct bpf_fib_lookup *params, u32 flags, bool check_mtu) { + u32 in_ifindex = params->ifindex; struct neighbour *neigh = NULL; struct fib_nh_common *nhc; struct in_device *in_dev; @@ -6352,7 +6372,7 @@ static int bpf_ipv4_fib_lookup(struct net *net, struct bpf_fib_lookup *params, memcpy(params->smac, dev->dev_addr, ETH_ALEN); set_fwd_params: - return bpf_fib_set_fwd_params(params, mtu); + return bpf_fib_set_fwd_params(dev, params, flags, mtu, in_ifindex); } #endif @@ -6362,6 +6382,7 @@ static int bpf_ipv6_fib_lookup(struct net *net, struct bpf_fib_lookup *params, { struct in6_addr *src = (struct in6_addr *) params->ipv6_src; struct in6_addr *dst = (struct in6_addr *) params->ipv6_dst; + u32 in_ifindex = params->ifindex; struct fib6_result res = {}; struct neighbour *neigh; struct net_device *dev; @@ -6491,13 +6512,14 @@ static int bpf_ipv6_fib_lookup(struct net *net, struct bpf_fib_lookup *params, memcpy(params->smac, dev->dev_addr, ETH_ALEN); set_fwd_params: - return bpf_fib_set_fwd_params(params, mtu); + return bpf_fib_set_fwd_params(dev, params, flags, mtu, in_ifindex); } #endif #define BPF_FIB_LOOKUP_MASK (BPF_FIB_LOOKUP_DIRECT | BPF_FIB_LOOKUP_OUTPUT | \ BPF_FIB_LOOKUP_SKIP_NEIGH | BPF_FIB_LOOKUP_TBID | \ - BPF_FIB_LOOKUP_SRC | BPF_FIB_LOOKUP_MARK) + BPF_FIB_LOOKUP_SRC | BPF_FIB_LOOKUP_MARK | \ + BPF_FIB_LOOKUP_VLAN) BPF_CALL_4(bpf_xdp_fib_lookup, struct xdp_buff *, ctx, struct bpf_fib_lookup *, params, int, plen, u32, flags) @@ -6546,6 +6568,9 @@ BPF_CALL_4(bpf_skb_fib_lookup, struct sk_buff *, skb, if (flags & ~BPF_FIB_LOOKUP_MASK) return -EINVAL; + if (flags & BPF_FIB_LOOKUP_VLAN) + return -EINVAL; + if (params->tot_len) check_mtu = true; diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index 2f1d24fef857..8b41c365ddf9 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -3532,6 +3532,31 @@ union bpf_attr { * Use the mark present in *params*->mark for the fib lookup. * This option should not be used with BPF_FIB_LOOKUP_DIRECT, * as it only has meaning for full lookups. + * **BPF_FIB_LOOKUP_VLAN** + * If the fib lookup resolves to a VLAN device whose + * parent is a real (non-VLAN) device, set + * *params*->h_vlan_proto and *params*->h_vlan_TCI from + * the VLAN device and replace *params*->ifindex with the + * parent's ifindex. *params*->h_vlan_TCI carries the VID + * only, with PCP and DEI bits zero; a consumer wanting to + * set egress priority writes PCP itself. *params*->smac is + * the VLAN device's own address, which can differ from the + * parent's. Only the immediate parent is resolved; if it + * is itself a VLAN device (QinQ) or in another namespace, + * the egress cannot be reduced to a physical device plus + * one tag and the lookup returns + * **BPF_FIB_LKUP_RET_VLAN_FAILURE** with *params*->ifindex + * left at the input. To obtain the VLAN device's own + * ifindex, repeat the lookup without + * **BPF_FIB_LOOKUP_VLAN**, re-initializing *params* + * first: output fields overwrite the inputs they share + * storage with. The swap and the vlan fields + * are written only on success; other output fields keep + * the helper's existing behaviour, so a frag-needed result + * still reports the route mtu in *params*->mtu_result. + * This flag is only valid for XDP programs; tc programs + * receive -EINVAL since they can redirect to the VLAN + * device directly. * * *ctx* is either **struct xdp_md** for XDP programs or * **struct sk_buff** tc cls_act programs. @@ -7339,6 +7364,7 @@ enum { BPF_FIB_LOOKUP_TBID = (1U << 3), BPF_FIB_LOOKUP_SRC = (1U << 4), BPF_FIB_LOOKUP_MARK = (1U << 5), + BPF_FIB_LOOKUP_VLAN = (1U << 6), }; enum { @@ -7352,6 +7378,7 @@ enum { BPF_FIB_LKUP_RET_NO_NEIGH, /* no neighbor entry for nh */ BPF_FIB_LKUP_RET_FRAG_NEEDED, /* fragmentation required to fwd */ BPF_FIB_LKUP_RET_NO_SRC_ADDR, /* failed to derive IP src addr */ + BPF_FIB_LKUP_RET_VLAN_FAILURE, /* VLAN egress, parent unresolvable */ }; struct bpf_fib_lookup { @@ -7405,7 +7432,11 @@ struct bpf_fib_lookup { union { struct { - /* output */ + /* + * output with BPF_FIB_LOOKUP_VLAN: set from the + * resolved egress VLAN device (see the flag); zeroed + * on other successful lookups. + */ __be16 h_vlan_proto; __be16 h_vlan_TCI; }; -- cgit v1.2.3 From 217828aad80d091fa1d840587a3d9b6187ee170f Mon Sep 17 00:00:00 2001 From: Avinash Duduskar Date: Mon, 13 Jul 2026 21:53:04 +0530 Subject: bpf: Add BPF_FIB_LOOKUP_VLAN_INPUT flag to bpf_fib_lookup() helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BPF_FIB_LOOKUP_VLAN resolves a VLAN egress. The reverse is also useful: an XDP program receiving a VLAN-tagged frame on a physical device wants the lookup to behave as if the packet had arrived on the corresponding VLAN subinterface, so iif-based policy routing and VRF table selection use the right ingress. Add BPF_FIB_LOOKUP_VLAN_INPUT. When set, params->h_vlan_proto and params->h_vlan_TCI are read as an input VLAN tag and the matching VLAN device of params->ifindex is resolved with __vlan_find_dev_deep_rcu(). The device must be up and in the same network namespace as params->ifindex (a VLAN device can be moved to another netns while registered on its parent; receive would deliver into that other namespace, which a lookup here cannot represent). If params->ifindex is itself a VLAN device, its inner (QinQ) subinterface is matched. For a bond or team, a tag on a port matches no device and returns NOT_FWDED; pass the master's ifindex. The lookup then runs with the resolved device as the ingress; params->ifindex itself is not modified on the input side. When the resolved device is enslaved to a VRF, both the full lookup (via the l3mdev rule) and BPF_FIB_LOOKUP_DIRECT (via l3mdev_fib_table_rcu()) select the VRF's table from the resolved ingress. That follows from feeding the resolved device to the flow as the ingress (fl4.flowi4_iif = dev->ifindex), which is what makes l3mdev resolve the VRF master from the subinterface rather than from params->ifindex. The two failure classes get different treatment on purpose. A h_vlan_proto other than 802.1Q/802.1ad is API misuse and returns -EINVAL, since it would otherwise reach the WARN in vlan_proto_idx() with a program-controlled value. An unmatched VID, a device that is down, or one in another namespace is a data outcome and returns BPF_FIB_LKUP_RET_NOT_FWDED, matching the DIRECT path when fib_get_table() finds no table and mirroring real ingress, where the receive path drops such frames. A VID of 0 (a priority tag) is looked up literally and normally fails the same way; receive instead processes such frames untagged, so callers should not set the flag for priority tags. Proceeding on the physical device for any of these would be fail-open for the policy-routing cases above. The h_vlan fields share a union with tbid, so the flag cannot be combined with BPF_FIB_LOOKUP_TBID. It describes ingress, so it also cannot be combined with BPF_FIB_LOOKUP_OUTPUT. Both combinations return -EINVAL; restricting now keeps a later relaxation backward compatible. Combining with BPF_FIB_LOOKUP_VLAN is allowed: the tag is consumed on the ingress side and the egress tag is written on success. Under !CONFIG_VLAN_8021Q the __vlan_find_dev_deep_rcu() stub returns NULL, so every lookup with a valid proto returns NOT_FWDED, which is correct since no VLAN device can exist. Suggested-by: Toke Høiland-Jørgensen Signed-off-by: Avinash Duduskar Reviewed-by: Toke Høiland-Jørgensen Reviewed-by: Emil Tsalapatis Link: https://lore.kernel.org/bpf/20260713162305.1237211-3-avinash.duduskar@gmail.com Signed-off-by: Kumar Kartikeya Dwivedi --- include/uapi/linux/bpf.h | 21 +++++++++++++- net/core/filter.c | 66 ++++++++++++++++++++++++++++++++++++++---- tools/include/uapi/linux/bpf.h | 21 +++++++++++++- 3 files changed, 101 insertions(+), 7 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index 8b41c365ddf9..005038fbeea4 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -3557,6 +3557,22 @@ union bpf_attr { * This flag is only valid for XDP programs; tc programs * receive -EINVAL since they can redirect to the VLAN * device directly. + * **BPF_FIB_LOOKUP_VLAN_INPUT** + * Treat *params*->h_vlan_proto and *params*->h_vlan_TCI + * as an input VLAN tag and run the lookup as if ingress + * had happened on the VLAN subinterface carrying that tag + * on *params*->ifindex. The VID is the low 12 bits of + * *params*->h_vlan_TCI; *params*->h_vlan_proto must be + * ETH_P_8021Q or ETH_P_8021AD in network byte order, else + * **-EINVAL**. If *params*->ifindex is itself a VLAN + * device, its inner (QinQ) subinterface is matched; for a + * bond or team, pass the master's ifindex. An unmatched + * tag, a down device, or one in another namespace returns + * **BPF_FIB_LKUP_RET_NOT_FWDED**, mirroring real ingress. + * A VID of 0 is looked up literally, so do not set this + * flag for priority-tagged frames. Cannot be combined with + * **BPF_FIB_LOOKUP_TBID** or **BPF_FIB_LOOKUP_OUTPUT** + * (returns **-EINVAL**). * * *ctx* is either **struct xdp_md** for XDP programs or * **struct sk_buff** tc cls_act programs. @@ -7365,6 +7381,7 @@ enum { BPF_FIB_LOOKUP_SRC = (1U << 4), BPF_FIB_LOOKUP_MARK = (1U << 5), BPF_FIB_LOOKUP_VLAN = (1U << 6), + BPF_FIB_LOOKUP_VLAN_INPUT = (1U << 7), }; enum { @@ -7435,7 +7452,9 @@ struct bpf_fib_lookup { /* * output with BPF_FIB_LOOKUP_VLAN: set from the * resolved egress VLAN device (see the flag); zeroed - * on other successful lookups. + * on other successful lookups. input with + * BPF_FIB_LOOKUP_VLAN_INPUT: the VLAN tag to scope + * the lookup by. */ __be16 h_vlan_proto; __be16 h_vlan_TCI; diff --git a/net/core/filter.c b/net/core/filter.c index b8f2595ff582..0b7afdd0ae47 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -6234,6 +6234,25 @@ static int bpf_fib_set_fwd_params(struct net_device *dev, return 0; } + +static struct net_device *bpf_fib_vlan_input_dev(struct net_device *dev, + const struct bpf_fib_lookup *params) +{ + __be16 proto = params->h_vlan_proto; + struct net_device *vlan_dev; + u16 vid; + + if (proto != htons(ETH_P_8021Q) && proto != htons(ETH_P_8021AD)) + return ERR_PTR(-EINVAL); + + vid = ntohs(params->h_vlan_TCI) & VLAN_VID_MASK; + vlan_dev = __vlan_find_dev_deep_rcu(dev, proto, vid); + if (!vlan_dev || !(vlan_dev->flags & IFF_UP) || + !net_eq(dev_net(vlan_dev), dev_net(dev))) + return NULL; + + return vlan_dev; +} #endif #if IS_ENABLED(CONFIG_INET) @@ -6254,6 +6273,14 @@ static int bpf_ipv4_fib_lookup(struct net *net, struct bpf_fib_lookup *params, if (unlikely(!dev)) return -ENODEV; + if (flags & BPF_FIB_LOOKUP_VLAN_INPUT) { + dev = bpf_fib_vlan_input_dev(dev, params); + if (IS_ERR(dev)) + return PTR_ERR(dev); + if (!dev) + return BPF_FIB_LKUP_RET_NOT_FWDED; + } + /* verify forwarding is enabled on this interface */ in_dev = __in_dev_get_rcu(dev); if (unlikely(!in_dev || !IN_DEV_FORWARD(in_dev))) @@ -6263,7 +6290,11 @@ static int bpf_ipv4_fib_lookup(struct net *net, struct bpf_fib_lookup *params, fl4.flowi4_iif = 1; fl4.flowi4_oif = params->ifindex; } else { - fl4.flowi4_iif = params->ifindex; + /* + * dev->ifindex, not params->ifindex: VLAN_INPUT may have + * resolved dev to a subinterface above. + */ + fl4.flowi4_iif = dev->ifindex; fl4.flowi4_oif = 0; } fl4.flowi4_dscp = inet_dsfield_to_dscp(params->tos); @@ -6400,6 +6431,14 @@ static int bpf_ipv6_fib_lookup(struct net *net, struct bpf_fib_lookup *params, if (unlikely(!dev)) return -ENODEV; + if (flags & BPF_FIB_LOOKUP_VLAN_INPUT) { + dev = bpf_fib_vlan_input_dev(dev, params); + if (IS_ERR(dev)) + return PTR_ERR(dev); + if (!dev) + return BPF_FIB_LKUP_RET_NOT_FWDED; + } + idev = __in6_dev_get_safely(dev); if (unlikely(!idev || !READ_ONCE(idev->cnf.forwarding))) return BPF_FIB_LKUP_RET_FWD_DISABLED; @@ -6408,7 +6447,12 @@ static int bpf_ipv6_fib_lookup(struct net *net, struct bpf_fib_lookup *params, fl6.flowi6_iif = 1; oif = fl6.flowi6_oif = params->ifindex; } else { - oif = fl6.flowi6_iif = params->ifindex; + /* + * dev->ifindex, not params->ifindex: VLAN_INPUT may have + * resolved dev to a subinterface above. + */ + oif = dev->ifindex; + fl6.flowi6_iif = oif; fl6.flowi6_oif = 0; strict = RT6_LOOKUP_F_HAS_SADDR; } @@ -6519,7 +6563,19 @@ set_fwd_params: #define BPF_FIB_LOOKUP_MASK (BPF_FIB_LOOKUP_DIRECT | BPF_FIB_LOOKUP_OUTPUT | \ BPF_FIB_LOOKUP_SKIP_NEIGH | BPF_FIB_LOOKUP_TBID | \ BPF_FIB_LOOKUP_SRC | BPF_FIB_LOOKUP_MARK | \ - BPF_FIB_LOOKUP_VLAN) + BPF_FIB_LOOKUP_VLAN | BPF_FIB_LOOKUP_VLAN_INPUT) + +static bool bpf_fib_lookup_flags_ok(u32 flags) +{ + if (flags & ~BPF_FIB_LOOKUP_MASK) + return false; + + if ((flags & BPF_FIB_LOOKUP_VLAN_INPUT) && + (flags & (BPF_FIB_LOOKUP_TBID | BPF_FIB_LOOKUP_OUTPUT))) + return false; + + return true; +} BPF_CALL_4(bpf_xdp_fib_lookup, struct xdp_buff *, ctx, struct bpf_fib_lookup *, params, int, plen, u32, flags) @@ -6527,7 +6583,7 @@ BPF_CALL_4(bpf_xdp_fib_lookup, struct xdp_buff *, ctx, if (plen < sizeof(*params)) return -EINVAL; - if (flags & ~BPF_FIB_LOOKUP_MASK) + if (!bpf_fib_lookup_flags_ok(flags)) return -EINVAL; switch (params->family) { @@ -6565,7 +6621,7 @@ BPF_CALL_4(bpf_skb_fib_lookup, struct sk_buff *, skb, if (plen < sizeof(*params)) return -EINVAL; - if (flags & ~BPF_FIB_LOOKUP_MASK) + if (!bpf_fib_lookup_flags_ok(flags)) return -EINVAL; if (flags & BPF_FIB_LOOKUP_VLAN) diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index 8b41c365ddf9..005038fbeea4 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -3557,6 +3557,22 @@ union bpf_attr { * This flag is only valid for XDP programs; tc programs * receive -EINVAL since they can redirect to the VLAN * device directly. + * **BPF_FIB_LOOKUP_VLAN_INPUT** + * Treat *params*->h_vlan_proto and *params*->h_vlan_TCI + * as an input VLAN tag and run the lookup as if ingress + * had happened on the VLAN subinterface carrying that tag + * on *params*->ifindex. The VID is the low 12 bits of + * *params*->h_vlan_TCI; *params*->h_vlan_proto must be + * ETH_P_8021Q or ETH_P_8021AD in network byte order, else + * **-EINVAL**. If *params*->ifindex is itself a VLAN + * device, its inner (QinQ) subinterface is matched; for a + * bond or team, pass the master's ifindex. An unmatched + * tag, a down device, or one in another namespace returns + * **BPF_FIB_LKUP_RET_NOT_FWDED**, mirroring real ingress. + * A VID of 0 is looked up literally, so do not set this + * flag for priority-tagged frames. Cannot be combined with + * **BPF_FIB_LOOKUP_TBID** or **BPF_FIB_LOOKUP_OUTPUT** + * (returns **-EINVAL**). * * *ctx* is either **struct xdp_md** for XDP programs or * **struct sk_buff** tc cls_act programs. @@ -7365,6 +7381,7 @@ enum { BPF_FIB_LOOKUP_SRC = (1U << 4), BPF_FIB_LOOKUP_MARK = (1U << 5), BPF_FIB_LOOKUP_VLAN = (1U << 6), + BPF_FIB_LOOKUP_VLAN_INPUT = (1U << 7), }; enum { @@ -7435,7 +7452,9 @@ struct bpf_fib_lookup { /* * output with BPF_FIB_LOOKUP_VLAN: set from the * resolved egress VLAN device (see the flag); zeroed - * on other successful lookups. + * on other successful lookups. input with + * BPF_FIB_LOOKUP_VLAN_INPUT: the VLAN tag to scope + * the lookup by. */ __be16 h_vlan_proto; __be16 h_vlan_TCI; -- cgit v1.2.3 From f68df52fbad0928f4686f734827e5fb6b20386ad Mon Sep 17 00:00:00 2001 From: Jianlin Shi Date: Mon, 13 Jul 2026 10:49:01 +0800 Subject: docs: bpf: Document BPF_RB_OVERWRITE_POS in bpf_ringbuf_query BPF_RB_OVERWRITE_POS is supported by bpf_ringbuf_query() but was missing from the helper documentation. Add it to the flags list in both the kernel UAPI header and its tools/ mirror. Signed-off-by: Jianlin Shi Acked-by: Xu Kuohai Link: https://lore.kernel.org/bpf/tencent_22134645443B75ED907D2A85A47AD554A709@qq.com Signed-off-by: Kumar Kartikeya Dwivedi --- include/uapi/linux/bpf.h | 1 + tools/include/uapi/linux/bpf.h | 1 + 2 files changed, 2 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index 005038fbeea4..ffd96e8b920b 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -4735,6 +4735,7 @@ union bpf_attr { * * **BPF_RB_RING_SIZE**: The size of ring buffer. * * **BPF_RB_CONS_POS**: Consumer position (can wrap around). * * **BPF_RB_PROD_POS**: Producer(s) position (can wrap around). + * * **BPF_RB_OVERWRITE_POS**: Overwrite position (can wrap around). * * Data returned is just a momentary snapshot of actual values * and could be inaccurate, so this facility should be used to diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index 005038fbeea4..ffd96e8b920b 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -4735,6 +4735,7 @@ union bpf_attr { * * **BPF_RB_RING_SIZE**: The size of ring buffer. * * **BPF_RB_CONS_POS**: Consumer position (can wrap around). * * **BPF_RB_PROD_POS**: Producer(s) position (can wrap around). + * * **BPF_RB_OVERWRITE_POS**: Overwrite position (can wrap around). * * Data returned is just a momentary snapshot of actual values * and could be inaccurate, so this facility should be used to -- cgit v1.2.3 From cba172820898140dc1fd4c2223ddf3ded978d2b2 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Fri, 10 Jul 2026 13:07:34 +0200 Subject: media: cec/core: add a new CEC_LOG_ADDRS_FL_CONFIG_FAILED flag If claiming a logical address fails, then set the CEC_LOG_ADDRS_FL_CONFIG_FAILED flag. This makes it possible for userspace to detect this corner case. Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- Documentation/userspace-api/media/cec/cec-ioc-adap-g-log-addrs.rst | 7 +++++++ drivers/media/cec/core/cec-adap.c | 3 +++ include/uapi/linux/cec.h | 2 ++ 3 files changed, 12 insertions(+) (limited to 'include/uapi/linux') diff --git a/Documentation/userspace-api/media/cec/cec-ioc-adap-g-log-addrs.rst b/Documentation/userspace-api/media/cec/cec-ioc-adap-g-log-addrs.rst index f3293a589dd6..8397bf573798 100644 --- a/Documentation/userspace-api/media/cec/cec-ioc-adap-g-log-addrs.rst +++ b/Documentation/userspace-api/media/cec/cec-ioc-adap-g-log-addrs.rst @@ -185,6 +185,13 @@ logical address types are already defined will return with error ``EBUSY``. are CEC devices that can only handle CDC messages. All other messages are ignored. + * .. _`CEC-LOG-ADDRS-FL-CONFIG-FAILED`: + + - ``CEC_LOG_ADDRS_FL_CONFIG_FAILED`` + - 8 + - If this flag is set, then the CEC device failed to claim a free logical + address and is in the unconfigured state. This can never happen if + ``CEC_LOG_ADDRS_FL_ALLOW_UNREG_FALLBACK`` was set as well. .. tabularcolumns:: |p{7.8cm}|p{1.0cm}|p{8.5cm}| diff --git a/drivers/media/cec/core/cec-adap.c b/drivers/media/cec/core/cec-adap.c index 4d14186bfef6..774bf9099183 100644 --- a/drivers/media/cec/core/cec-adap.c +++ b/drivers/media/cec/core/cec-adap.c @@ -1482,6 +1482,7 @@ static int cec_config_thread_func(void *arg) dprintk(1, "physical address: %x.%x.%x.%x, claim %d logical addresses\n", cec_phys_addr_exp(adap->phys_addr), las->num_log_addrs); las->log_addr_mask = 0; + las->flags &= ~CEC_LOG_ADDRS_FL_CONFIG_FAILED; if (las->log_addr_type[0] == CEC_LOG_ADDR_TYPE_UNREGISTERED) goto configured; @@ -1614,6 +1615,8 @@ configured: unconfigure: for (i = 0; i < las->num_log_addrs; i++) las->log_addr[i] = CEC_LOG_ADDR_INVALID; + if (adap->phys_addr != CEC_PHYS_ADDR_INVALID) + las->flags |= CEC_LOG_ADDRS_FL_CONFIG_FAILED; cec_adap_unconfigure(adap); adap->is_configuring = false; adap->must_reconfigure = false; diff --git a/include/uapi/linux/cec.h b/include/uapi/linux/cec.h index 81a05c9c0706..fdfc97a6e4ec 100644 --- a/include/uapi/linux/cec.h +++ b/include/uapi/linux/cec.h @@ -403,6 +403,8 @@ struct cec_log_addrs { #define CEC_LOG_ADDRS_FL_ALLOW_RC_PASSTHRU (1 << 1) /* CDC-Only device: supports only CDC messages */ #define CEC_LOG_ADDRS_FL_CDC_ONLY (1 << 2) +/* Configuration failed */ +#define CEC_LOG_ADDRS_FL_CONFIG_FAILED (1 << 3) /** * struct cec_drm_connector_info - tells which drm connector is -- cgit v1.2.3 From 008f965fb40f88c47f5fb852e1fef5becb0fa3b2 Mon Sep 17 00:00:00 2001 From: Siddaraju DH Date: Fri, 3 Jul 2026 15:35:37 +0530 Subject: ethtool: link 10000baseCR to SFF-8431, Appendix-E SFP+ DA Add comment to clarify the physical media 10000baseCR follows. 10000baseCR does not correspond to any IEEE 802.3 *base-CR PMD. It has no autonegotiation, no link training, and no mandatory FEC. The industry standard for this media type is SFF-8431 Appendix-E Direct Attach cable, also known as 10G_SFI_DA. Link: https://lore.kernel.org/r/SN7PR11MB69003D33489DB1D6B17EF72A9AF52@SN7PR11MB6900.namprd11.prod.outlook.com Signed-off-by: Siddaraju DH Reviewed-by: Maxime Chevallier Link: https://patch.msgid.link/20260703100537.1109838-1-siddaraju.dh@intel.com Signed-off-by: Paolo Abeni --- include/uapi/linux/ethtool.h | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/ethtool.h b/include/uapi/linux/ethtool.h index a2091d4e00f3..986d70caec33 100644 --- a/include/uapi/linux/ethtool.h +++ b/include/uapi/linux/ethtool.h @@ -2013,7 +2013,13 @@ enum ethtool_link_mode_bit_indices { ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT = 39, ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT = 40, ETHTOOL_LINK_MODE_1000baseX_Full_BIT = 41, + + /* Despite the "baseCR" in 10000baseCR, this is not an IEEE 802.3 baseCR + * It represents SFF-8431 Appendix-E SFP+ Direct Attach (10G-SFI-DA). + * The name is kept as-is for uAPI backward compatibility. + */ ETHTOOL_LINK_MODE_10000baseCR_Full_BIT = 42, + ETHTOOL_LINK_MODE_10000baseSR_Full_BIT = 43, ETHTOOL_LINK_MODE_10000baseLR_Full_BIT = 44, ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT = 45, -- cgit v1.2.3 From 65f0ecb9ec85cb3a2e2372a6427dcc100691fc57 Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Wed, 13 May 2026 17:18:42 +0300 Subject: issei: initial driver skeleton The ISSEI (Intel Silicon Security Engine Interface) subsystem provides a communication channel between the host and the Silicon Security Engine. Prepare basic driver functions and character device for user-space communication. Add DMA access routines for ISSEI HECI devices. Add of DMA-related structures and implementation of routines for setting up DMA, as well as reading and writing DMA buffers. Reviewed-by: Karol Wachowski Co-developed-by: Vitaly Lubart Signed-off-by: Vitaly Lubart Signed-off-by: Alexander Usyskin Link: https://patch.msgid.link/20260513-issei-for-upstream-v1-1-f590038678f9@intel.com Signed-off-by: Greg Kroah-Hartman --- Documentation/driver-api/index.rst | 1 + Documentation/driver-api/issei/index.rst | 16 +++ Documentation/driver-api/issei/issei.rst | 135 +++++++++++++++++++ MAINTAINERS | 7 + drivers/misc/Kconfig | 1 + drivers/misc/Makefile | 1 + drivers/misc/issei/Kconfig | 13 ++ drivers/misc/issei/Makefile | 7 + drivers/misc/issei/cdev.c | 219 +++++++++++++++++++++++++++++++ drivers/misc/issei/cdev.h | 16 +++ drivers/misc/issei/dma.c | 154 ++++++++++++++++++++++ drivers/misc/issei/dma.h | 69 ++++++++++ drivers/misc/issei/hw_msg.h | 163 +++++++++++++++++++++++ drivers/misc/issei/issei_dev.h | 160 ++++++++++++++++++++++ include/uapi/linux/issei.h | 69 ++++++++++ 15 files changed, 1031 insertions(+) create mode 100644 Documentation/driver-api/issei/index.rst create mode 100644 Documentation/driver-api/issei/issei.rst create mode 100644 drivers/misc/issei/Kconfig create mode 100644 drivers/misc/issei/Makefile create mode 100644 drivers/misc/issei/cdev.c create mode 100644 drivers/misc/issei/cdev.h create mode 100644 drivers/misc/issei/dma.c create mode 100644 drivers/misc/issei/dma.h create mode 100644 drivers/misc/issei/hw_msg.h create mode 100644 drivers/misc/issei/issei_dev.h create mode 100644 include/uapi/linux/issei.h (limited to 'include/uapi/linux') diff --git a/Documentation/driver-api/index.rst b/Documentation/driver-api/index.rst index eaf7161ff957..6601a258690f 100644 --- a/Documentation/driver-api/index.rst +++ b/Documentation/driver-api/index.rst @@ -105,6 +105,7 @@ Subsystem-specific APIs interconnect ipmb ipmi + issei/index libata mailbox md/index diff --git a/Documentation/driver-api/issei/index.rst b/Documentation/driver-api/issei/index.rst new file mode 100644 index 000000000000..604267463fd4 --- /dev/null +++ b/Documentation/driver-api/issei/index.rst @@ -0,0 +1,16 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: + +========================================================= +The Intel Silicon Security Engine Interface (Intel SSEI) +========================================================= + +**Copyright** |copy| 2026 Intel Corporation + + +.. toctree:: + :caption: Table of Contents + :maxdepth: 3 + + issei diff --git a/Documentation/driver-api/issei/issei.rst b/Documentation/driver-api/issei/issei.rst new file mode 100644 index 000000000000..a5e99e92e095 --- /dev/null +++ b/Documentation/driver-api/issei/issei.rst @@ -0,0 +1,135 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Introduction +============ + +The Intel Silicon Security Engine (Intel SSE) is an isolated and +protected computing resource (Co-processor) residing inside +Intel client chipsets released in 2024 (Lunar Lake) or later. +The Intel SSE provide security support and platform boot orchestration. +The actual feature set depends on the Intel chipset SKU. + +The Intel Silicon Security Engine Interface (Intel SSEI) +is the interface between the Host and Intel SSE. +This interface is exposed to the host as one or more PCI devices. +The Intel SSEI Driver is in charge of the communication channel between +a host application and the Intel SSE features. + +Each Intel SSE feature, or Intel SSE Client is addressed by a unique UUID and +each client has its own protocol. The protocol is message-based with a +header and payload up to maximal number of bytes advertised by the client, +upon connection. + +Intel SSEI Driver +================= + +The driver exposes a character device with device nodes /dev/isseiX. + +An application maintains communication with an Intel SSE feature while +/dev/isseiX is open. The binding to a specific feature is performed by calling +:c:macro:`IOCTL_ISSEI_CONNECT_CLIENT`, which passes the desired UUID. +The number of instances of an Intel SSE feature that can be opened +at the same time is limited to single instance. + +The driver is transparent to data that are passed between firmware feature +and host application. + +Because some of the Intel SSE features can change the system +configuration, the driver by default allows only a privileged +user to access it. + +The connection termination is performed by calling +:c:macro:`IOCTL_ISSEI_DISCONNECT_CLIENT`. + +The session is terminated calling :c:expr:`close(fd)`. + +A code snippet for an application communicating with SPDM client: + +.. code-block:: C + + struct issei_connect_client_data data = {.in_client_uuid = + {0xe8, 0x51, 0x49, 0xdf, 0x94, 0x47, 0x4C, + 0x9A, 0x83, 0x67, 0xC4, 0xE3, 0x34, 0x64, 0xF1, 0xB4}}; + __u8 req_data[] = {0x10, 0x84, 0x00, 0x00}; /* SPDM Get Version */ + size_t req_data_len = sizeof(req_data); + __u8 res_data[256]; + size_t res_data_len = sizeof(res_data); + int fd = open("/dev/issei0", O_RDWR); + + ioctl(fd, IOCTL_ISSEI_CONNECT_CLIENT, &data); + + printf("Ver=%d, MaxLen=%u, Flags=0x%08X\n", + data.out_client_properties.protocol_version, + data.out_client_properties.max_msg_length, + data.out_client_properties.flags); + + [...] + + write(fd, req_data, req_data_len); + + [...] + + read(fd, res_data, res_data_len); + + printf("SPDM version count %u, version[0]=%02X%02X\n", + res_data[5], res_data[6], res_data[7]); + + [...] + + ioctl(fd, IOCTL_ISSEI_DISCONNECT_CLIENT, &data); + + [...] + + close(fd); + + +User space API ioctl +==================== + +The Intel SSEI Driver supports the following ioctl commands: + +IOCTL_ISSEI_CONNECT_CLIENT +-------------------------- +Connect to firmware Feature/Client. + +.. code-block:: none + + Usage: + + struct issei_connect_client_data client_data; + + ioctl(fd, IOCTL_ISSEI_CONNECT_CLIENT, &client_data); + + struct issei_connect_client_data - contain the following + Inputs: + in_client_uuid - UUID of the FW Feature that needs to connect to. + Outputs: + out_client_properties - Client Properties: MTU, Protocol Version and Flags. + + Error returns: + ENOTTY No such client (i.e. wrong UUID) or connection is not allowed. + EINVAL Wrong IOCTL Number + ENODEV Device or Connection is not initialized or ready. + ENOMEM Unable to allocate memory to client internal data. + EFAULT Fatal Error (e.g. Unable to access user input data) + EBUSY Connection Already Open + +:Note: + max_msg_length (MTU) in client properties describes the maximum + data that can be sent or received. (e.g. with MTU=2K, can send + requests up to bytes 2k and received responses up to 2k bytes). + +IOCTL_ISSEI_DISCONNECT_CLIENT +----------------------------- +Disconnect from firmware Feature/Client. + +.. code-block:: none + + Usage: + + ioctl(fd, IOCTL_ISSEI_DISCONNECT_CLIENT, NULL); + + Error returns: + EINVAL Wrong IOCTL Number + ENODEV Device or Connection is not initialized or ready. + ENOTCONN Feature/Client is not connected. diff --git a/MAINTAINERS b/MAINTAINERS index be0af1665945..b6bf86f9f8ab 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13360,6 +13360,13 @@ F: drivers/platform/x86/intel/sdsi.c F: tools/arch/x86/intel_sdsi/ F: tools/testing/selftests/drivers/sdsi/ +INTEL SILICON SECURITY ENGINE INTERFACE (ISSEI) +M: Alexander Usyskin +S: Supported +F: Documentation/driver-api/issei/issei.rst +F: drivers/misc/issei/ +F: include/uapi/linux/issei.h + INTEL SGX M: Jarkko Sakkinen R: Dave Hansen diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 390256ed91f4..e594edf86941 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -651,4 +651,5 @@ source "drivers/misc/mchp_pci1xxxx/Kconfig" source "drivers/misc/keba/Kconfig" source "drivers/misc/amd-sbi/Kconfig" source "drivers/misc/rp1/Kconfig" +source "drivers/misc/issei/Kconfig" endmenu diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index fed47c7672b9..086ac3f75935 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -74,3 +74,4 @@ obj-$(CONFIG_MCHP_LAN966X_PCI) += lan966x-pci.o obj-y += keba/ obj-y += amd-sbi/ obj-$(CONFIG_MISC_RP1) += rp1/ +obj-$(CONFIG_INTEL_SSEI) += issei/ diff --git a/drivers/misc/issei/Kconfig b/drivers/misc/issei/Kconfig new file mode 100644 index 000000000000..d98ac7925ce6 --- /dev/null +++ b/drivers/misc/issei/Kconfig @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2023-2026 Intel Corporation +config INTEL_SSEI + tristate "Intel Silicon Security Engine Interface" + help + The ISSEI (Intel Silicon Security Engine Interface) + subsystem provides a communication channel between the host and the + Silicon Security Engine. + Enable this driver to get SPDM and other features on Intel client CPUs + released in 2024 (Lunar Lake) or later. + + If selected, the /dev/isseiX device will be created. + If in doubt, select N. diff --git a/drivers/misc/issei/Makefile b/drivers/misc/issei/Makefile new file mode 100644 index 000000000000..f13bcf3e1699 --- /dev/null +++ b/drivers/misc/issei/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2023-2026 Intel Corporation +ccflags-y += -DDEFAULT_SYMBOL_NAMESPACE='"INTEL_SSEI"' + +obj-$(CONFIG_INTEL_SSEI) += issei.o +issei-objs += cdev.o +issei-objs += dma.o diff --git a/drivers/misc/issei/cdev.c b/drivers/misc/issei/cdev.c new file mode 100644 index 000000000000..d3d53dad088e --- /dev/null +++ b/drivers/misc/issei/cdev.c @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2023-2026 Intel Corporation */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "issei_dev.h" +#include "cdev.h" + +struct class *issei_class; +static dev_t issei_devt; + +#define ISSEI_MAX_DEVS MINORMASK + +static DEFINE_XARRAY_ALLOC(issei_minor_xa); + +static ssize_t fw_ver_show(struct device *device, + struct device_attribute *attr, char *buf) +{ + struct issei_device *idev = dev_get_drvdata(device); + + return sysfs_emit(buf, "%u.%u.%u.%u\n", idev->fw_version[0], idev->fw_version[1], + idev->fw_version[2], idev->fw_version[3]); +} +static DEVICE_ATTR_RO(fw_ver); + +static struct attribute *issei_attrs[] = { + &dev_attr_fw_ver.attr, + NULL +}; +ATTRIBUTE_GROUPS(issei); + +static const struct file_operations issei_fops = { + .owner = THIS_MODULE, +}; + +static void issei_device_release(struct device *dev) +{ + kfree(dev_get_drvdata(dev)); +} + +static void issei_device_init(struct issei_device *idev, struct device *parent, + const struct issei_dma_length *dma_length, + const struct issei_hw_ops *ops) +{ + idev->parent = parent; + idev->power_down = false; + init_waitqueue_head(&idev->wait_has_data); + idev->has_data = false; + init_waitqueue_head(&idev->wait_rst_state); + idev->rst_state = ISSEI_RST_STATE_INIT; + + mutex_init(&idev->client_lock); + INIT_LIST_HEAD(&idev->host_client_list); + idev->host_client_last_id = 0; + idev->host_client_count = 0; + INIT_LIST_HEAD(&idev->fw_client_list); + INIT_LIST_HEAD(&idev->write_queue); + idev->last_write_ts = 0; + + idev->dma.length = *dma_length; + + idev->ops = ops; +} + +/** + * issei_register: register issei character device + * @hw_size: size of the hardware structure to allocate + * @parent: parent device + * @dma_length: structure with DMA sizes + * @ops: hardware-related operations + * + * Return: pointer allocated to issei_device structure, error on failure + */ +struct issei_device *issei_register(size_t hw_size, struct device *parent, + const struct issei_dma_length *dma_length, + const struct issei_hw_ops *ops) +{ + struct issei_device *idev; + u32 minor; + int ret, devno; + + idev = kzalloc(sizeof(*idev) + hw_size, GFP_KERNEL); + if (!idev) + return ERR_PTR(-ENOMEM); + + issei_device_init(idev, parent, dma_length, ops); + + ret = xa_alloc(&issei_minor_xa, &minor, idev, XA_LIMIT(0, ISSEI_MAX_DEVS), GFP_KERNEL); + if (ret < 0) { + dev_err(&idev->dev, "Failed to allocate minor. ret = %d\n", ret); + kfree(idev); + return ERR_PTR(ret); + } + + idev->minor = minor; + devno = MKDEV(MAJOR(issei_devt), idev->minor); + + device_initialize(&idev->dev); + idev->dev.devt = devno; + idev->dev.class = issei_class; + idev->dev.parent = parent; + idev->dev.groups = issei_groups; + idev->dev.release = issei_device_release; + dev_set_drvdata(&idev->dev, idev); + + idev->cdev = cdev_alloc(); + if (!idev->cdev) { + ret = -ENOMEM; + goto err; + } + idev->cdev->ops = &issei_fops; + if (parent->driver) + idev->cdev->owner = parent->driver->owner; + cdev_set_parent(idev->cdev, &idev->dev.kobj); + + ret = cdev_add(idev->cdev, devno, 1); + if (ret) { + dev_err(parent, "unable to add device %d:%u ret = %d\n", + MAJOR(issei_devt), idev->minor, ret); + goto err_del_cdev; + } + + ret = dev_set_name(&idev->dev, "issei%u", idev->minor); + if (ret) { + dev_err(parent, "unable to set name to device %d:%u ret = %d\n", + MAJOR(issei_devt), idev->minor, ret); + goto err_del_cdev; + } + + ret = device_add(&idev->dev); + if (ret) { + dev_err(parent, "unable to add device %d:%u ret = %d\n", + MAJOR(issei_devt), idev->minor, ret); + goto err_del_cdev; + } + + idev->fw_clients = kset_create_and_add("fw_clients", NULL, &idev->dev.kobj); + if (!idev->fw_clients) { + ret = -ENOMEM; + goto err_del_dev; + } + + return idev; + +err_del_dev: + device_del(&idev->dev); +err_del_cdev: + cdev_del(idev->cdev); +err: + put_device(&idev->dev); + xa_erase(&issei_minor_xa, minor); + + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(issei_register); + +/** + * issei_deregister: remove issei character device + * @idev: the device structure + */ +void issei_deregister(struct issei_device *idev) +{ + u32 minor = idev->minor; + + cdev_del(idev->cdev); + + kset_unregister(idev->fw_clients); + + device_del(&idev->dev); + + put_device(&idev->dev); + + xa_erase(&issei_minor_xa, minor); +} +EXPORT_SYMBOL_GPL(issei_deregister); + +static int __init issei_cdev_init(void) +{ + int ret; + + issei_class = class_create("issei"); + if (IS_ERR(issei_class)) { + pr_err("couldn't create class\n"); + return PTR_ERR(issei_class); + } + + ret = alloc_chrdev_region(&issei_devt, 0, ISSEI_MAX_DEVS, "issei"); + if (ret < 0) { + pr_err("unable to allocate char dev region\n"); + class_destroy(issei_class); + return ret; + } + + return 0; +} + +static void __exit issei_cdev_exit(void) +{ + unregister_chrdev_region(issei_devt, ISSEI_MAX_DEVS); + class_destroy(issei_class); +} + +module_init(issei_cdev_init); +module_exit(issei_cdev_exit); + +MODULE_DESCRIPTION("Intel(R) Silicon Security Engine Interface"); +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/issei/cdev.h b/drivers/misc/issei/cdev.h new file mode 100644 index 000000000000..30075a624f2d --- /dev/null +++ b/drivers/misc/issei/cdev.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2023-2026 Intel Corporation */ +#ifndef _ISSEI_CDEV_H_ +#define _ISSEI_CDEV_H_ + +struct device; +struct issei_device; +struct issei_dma_length; +struct issei_hw_ops; + +struct issei_device *issei_register(size_t hw_size, struct device *parent, + const struct issei_dma_length *dma_length, + const struct issei_hw_ops *ops); +void issei_deregister(struct issei_device *idev); + +#endif /* _ISSEI_CDEV_H_ */ diff --git a/drivers/misc/issei/dma.c b/drivers/misc/issei/dma.c new file mode 100644 index 000000000000..457d28f31c06 --- /dev/null +++ b/drivers/misc/issei/dma.c @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2023-2026 Intel Corporation */ +#include +#include +#include +#include +#include +#include + +#include "issei_dev.h" +#include "hw_msg.h" + +static inline size_t __issei_dma_size(const struct issei_dma *dma) +{ + return dma->length.h2f + dma->length.f2h + dma->length.ctl; +} + +/** + * issei_dmam_setup - setup DMA buffer and clean it + * @idev: issei device object + * + * Return: 0 on success, <0 on failures + */ +int issei_dmam_setup(struct issei_device *idev) +{ + struct issei_dma *dma = &idev->dma; + size_t size; + + size = __issei_dma_size(dma); + if (!size) + return -EINVAL; + + if (!dma->vaddr) + dma->vaddr = dmam_alloc_coherent(idev->parent, size, &dma->daddr, + GFP_KERNEL | __GFP_ZERO); + if (dma->vaddr) + memset(dma->vaddr, 0, size); + return dma->vaddr ? 0 : -ENOMEM; +} + +static inline struct control_buffer *__dma_get_ctl_buf(struct issei_dma *dma) +{ + return dma->vaddr + dma->length.h2f + dma->length.f2h; +} + +static bool __issei_dma_is_read_busy(struct issei_dma *dma) +{ + struct control_buffer *ctl = __dma_get_ctl_buf(dma); + + return ctl->f2h_counter_wr != ctl->f2h_counter_rd; +} + +static bool __issei_dma_is_write_busy(struct issei_dma *dma) +{ + struct control_buffer *ctl = __dma_get_ctl_buf(dma); + + return ctl->h2f_counter_wr != ctl->h2f_counter_rd; +} + +static void __issei_dma_read_finalize(struct issei_device *idev) +{ + struct control_buffer *ctl = __dma_get_ctl_buf(&idev->dma); + + dev_dbg(&idev->dev, "ctl->f2h_counter_rd %u\n", ctl->f2h_counter_rd); + /* No need to check overflow - the firmware counters overflow the same way */ + ctl->f2h_counter_rd++; +} + +static void __issei_dma_write_finalize(struct issei_device *idev) +{ + struct control_buffer *ctl = __dma_get_ctl_buf(&idev->dma); + + dev_dbg(&idev->dev, "ctl->h2f_counter_wr %u\n", ctl->h2f_counter_wr); + /* No need to check overflow - the firmware counters overflow the same way */ + ctl->h2f_counter_wr++; +} + +/** + * issei_dma_write - write data package to DMA + * @idev: issei device object + * @data: data atructure + * + * Return: 0 on success, <0 on failures + */ +int issei_dma_write(struct issei_device *idev, const struct issei_dma_data *data) +{ + u8 *write_buf = idev->dma.vaddr; + struct ham_message_header *hdr = (struct ham_message_header *)write_buf; + + if (data->length > idev->dma.length.h2f - sizeof(*hdr)) { + dev_err(&idev->dev, "Message is too big\n"); + return -EMSGSIZE; + } + + if (__issei_dma_is_write_busy(&idev->dma)) { + if (ktime_ms_delta(ktime_get(), idev->last_write_ts) > ISSEI_WRITE_TIMEOUT_MSEC) { + dev_err(&idev->dev, "Write stuck in queue\n"); + return -EIO; + } + dev_info(&idev->dev, "Write is busy\n"); + return -EBUSY; + } + + hdr->length = data->length; + hdr->fw_id = data->fw_id; + hdr->host_id = data->host_id; + hdr->flags = data->flags; + hdr->status = data->status; + hdr->reserved = 0; + + memcpy(write_buf + sizeof(*hdr), data->buf, data->length); + + __issei_dma_write_finalize(idev); + idev->last_write_ts = ktime_get(); + return 0; +} + +/** + * issei_dma_read - read data package from DMA + * @idev: issei device object + * @data: data atructure + * + * Return: %0 on success, <0 on failures + */ +int issei_dma_read(struct issei_device *idev, struct issei_dma_data *data) +{ + u8 *read_buf = idev->dma.vaddr + idev->dma.length.h2f; + struct ham_message_header *hdr = (struct ham_message_header *)read_buf; + + if (!__issei_dma_is_read_busy(&idev->dma)) { + dev_dbg(&idev->dev, "Nothing to read\n"); + return -ENODATA; + } + + dev_dbg(&idev->dev, "Reading header\n"); + data->length = hdr->length; + data->fw_id = hdr->fw_id; + data->host_id = hdr->host_id; + data->flags = hdr->flags; + data->status = hdr->status; + + if (data->length > idev->dma.length.f2h - sizeof(*hdr)) { + dev_err(&idev->dev, "Message length %u is bigger than buffer %zu\n", + data->length, idev->dma.length.f2h - sizeof(*hdr)); + return -EIO; + } + + dev_dbg(&idev->dev, "Reading data (size %u)\n", data->length); + data->buf = kmemdup(read_buf + sizeof(*hdr), data->length, GFP_KERNEL); + if (!data->buf) + return -ENOMEM; + __issei_dma_read_finalize(idev); + return 0; +} diff --git a/drivers/misc/issei/dma.h b/drivers/misc/issei/dma.h new file mode 100644 index 000000000000..e6b7aeb50ae6 --- /dev/null +++ b/drivers/misc/issei/dma.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2023-2026 Intel Corporation */ +#ifndef _ISSEI_DMA_H_ +#define _ISSEI_DMA_H_ + +#include + +struct issei_device; + +/** + * struct issei_dma_length - sizes of DMA memory portions + * @h2f: host to firmware buffer size + * @f2h: firmware to host buffer size + * @ctl: control buffer size + */ +struct issei_dma_length { + size_t h2f; + size_t f2h; + size_t ctl; +}; + +/** + * struct issei_dma - DMA memory structure + * @vaddr: virtual address + * @daddr: physical address + * @length: memory sizes structure + */ +struct issei_dma { + void *vaddr; + dma_addr_t daddr; + struct issei_dma_length length; +}; + +/* Operation statuses */ +#define HAMS_SUCCESS 0x00 +#define HAMS_PROTOCOL_NOT_SUPPORTED 0x01 +#define HAMS_DEPRECATED_BUS_MSG 0x02 +#define HAMS_CLIENT_NOT_EXISTS 0x03 +#define HAMS_MSG_TOO_BIG 0x04 +#define HAMS_MSG_NOT_CONSUMED 0x05 +#define HAMS_CORRUPTED_BUS_MSG 0x06 +#define HAMS_CORRUPTED_HEADER 0x07 +#define HAMS_INVALID_LENGTH 0x08 +#define HAMS_SHARED_MEMORY_SIZE_UNSUPPORTED 0x09 +#define HAMS_GENERAL_FATAL_ERROR 0xff + +/** + * struct issei_dma_data - data passed through channel + * @fw_id: firmware client id + * @host_id: host client id + * @flags: flags bitmap + * @status: operation status + * @length: data length + * @buf: pointer to data buffer + */ +struct issei_dma_data { + u16 fw_id; + u16 host_id; + u32 flags; + u32 status; + u32 length; + void *buf; +}; + +int issei_dmam_setup(struct issei_device *idev); +int issei_dma_write(struct issei_device *idev, const struct issei_dma_data *data); +int issei_dma_read(struct issei_device *idev, struct issei_dma_data *data); + +#endif /*_ISSEI_DMA_H_*/ diff --git a/drivers/misc/issei/hw_msg.h b/drivers/misc/issei/hw_msg.h new file mode 100644 index 000000000000..28fd3775f64c --- /dev/null +++ b/drivers/misc/issei/hw_msg.h @@ -0,0 +1,163 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2023-2026 Intel Corporation */ +#ifndef _ISSEI_HW_MSG_H_ +#define _ISSEI_HW_MSG_H_ + +#include +#include + +#define HAM_CB_MESSAGE_ID_REQ 0x8086cafe +#define HAM_CB_MESSAGE_ID_RES 0xcafe8086 +#define HAM_CB_MESSAGE_VER 0x1 + +/** + * struct ham_setup_shared_memory_req - shared memory setup request + * @msg_id: message id, should be %HAM_CB_MESSAGE_ID_REQ + * @ver: message version (%HAM_CB_MESSAGE_VER) + * @reserved: reserved + * @buffer_physical_address: physical address of DMA buffer + * @host_to_fw_section_length: memory size for host to fw communication + * @fw_to_host_section_length: memory size for fw to host communication + * @control_length: memory size for control buffer + */ +struct ham_setup_shared_memory_req { + u32 msg_id; + u16 ver; + u16 reserved; + u64 buffer_physical_address; + u32 host_to_fw_section_length; + u32 fw_to_host_section_length; + u32 control_length; +} __packed __aligned(4); + +/** + * struct ham_setup_shared_memory_res - shared memory setup response + * @msg_id: message id, should be %HAM_CB_MESSAGE_ID_RES + * @status: operation status + */ +struct ham_setup_shared_memory_res { + u32 msg_id; + u32 status; +}; + +/** + * struct control_buffer - control buffer structure + * @h2f_counter_wr: write counter host to fw + * @h2f_counter_rd: read counter host to fw + * @f2h_counter_wr: write counter fw to host + * @f2h_counter_rd: read counter fw to host + */ +struct control_buffer { + u32 h2f_counter_wr; + u32 h2f_counter_rd; + u32 f2h_counter_wr; + u32 f2h_counter_rd; +}; + +/* HAM messages over DMA */ + +/** + * struct ham_message_header - message header over DMA + * @length: message length (payload only, not including header) + * @fw_id: firmware client id (0 means Bus Message) + * @host_id: host client id (0 means Bus Message) + * @flags: message flags + * @status: operation status + * @reserved: reserved + */ +struct ham_message_header { + u32 length; + u16 fw_id; + u16 host_id; + u32 flags; + u32 status; + u32 reserved; +}; + +/* Bus Commands */ +#define HAM_BUS_CMD_START_REQ 0x00 +#define HAM_BUS_CMD_START_RSP 0x80 +#define HAM_BUS_CMD_CLIENT_REQ 0x01 +#define HAM_BUS_CMD_CLIENT_RSP 0x81 + +/** + * struct ham_bus_message - bus message header + * @cmd: command code + */ +struct ham_bus_message { + u32 cmd; +}; + +#define HAM_SUPPORTED_VERSION 0x01 + +/** + * struct ham_start_message_req - start message + * @header: bus message header (%HAM_BUS_CMD_START_REQ) + * @supported_version: supported protocol version + * @heci_capabilities_length: protocol capabilities length in bytes + * @heci_capabilities: protocol capabilities data + */ +struct ham_start_message_req { + struct ham_bus_message header; + u16 supported_version; + u8 heci_capabilities_length; + u8 heci_capabilities[] __counted_by(heci_capabilities_length); +} __packed; + +/** + * struct ham_start_message_res - start message response + * @header: bus message header (%HAM_BUS_CMD_START_RSP) + * @fw_version: firmware version (four u16 blocks) + * @supported_version: supported protocol version + * @heci_capabilities_length: protocol capabilities length in bytes + * @heci_capabilities: protocol capabilities data + */ +struct ham_start_message_res { + struct ham_bus_message header; + u16 fw_version[4]; + u16 supported_version; + u8 heci_capabilities_length; + u8 heci_capabilities[] __counted_by(heci_capabilities_length); +} __packed; + +/** + * struct ham_get_clients_req - clients list request + * @header: bus message header (%HAM_BUS_CMD_CLIENT_REQ) + */ +struct ham_get_clients_req { + struct ham_bus_message header; +}; + +/** + * struct ham_client_properties - single client properties + * @client_number: client id in firmware + * @protocol_ver: client protocol version + * @reserved: reserved + * @client_uuid: protocol name (UUID) + * @client_mtu: max message length supported by client + * @flags: client flags + */ +struct ham_client_properties { + u16 client_number; + u8 protocol_ver; + u8 reserved; + uuid_t client_uuid; + u32 client_mtu; + u32 flags; +}; + +/** + * struct ham_get_clients_res - client properties response + * @header: bus message header (%HAM_BUS_CMD_CLIENT_RSP) + * @client_count: number of clients in firmware + * @reserved: reserved + * @clients_props: list of client properties + */ +struct ham_get_clients_res { + struct ham_bus_message header; + u16 client_count; + u16 reserved; + struct ham_client_properties clients_props[] __counted_by(client_count); +}; + +#endif /* _ISSEI_HW_MSG_H_ */ diff --git a/drivers/misc/issei/issei_dev.h b/drivers/misc/issei/issei_dev.h new file mode 100644 index 000000000000..c742e7fe6cb6 --- /dev/null +++ b/drivers/misc/issei/issei_dev.h @@ -0,0 +1,160 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2023-2026 Intel Corporation */ +#ifndef _ISSEI_DEV_H_ +#define _ISSEI_DEV_H_ + +#include +#include +#include +#include +#include +#include + +#include "dma.h" + +struct cdev; +struct kset; + +struct issei_device; +struct issei_host_client; + +extern struct class *issei_class; + +#define ISSEI_HOST_CLIENTS_MAX 255 + +#define ISSEI_SUPPORTED_PROTOCOL_VER 1 + +#define ISSEI_MAX_CONSEC_RESET 3 + +#define ISSEI_RST_HW_READY_TIMEOUT_MSEC (2 * MSEC_PER_SEC) +#define ISSEI_RST_STEP_TIMEOUT_MSEC (2 * MSEC_PER_SEC) +#define ISSEI_STOP_TIMEOUT_MSEC 500 +#define ISSEI_WRITE_TIMEOUT_MSEC (MSEC_PER_SEC) + +/** + * struct issei_write_buf - write buffer object + * @list: linked list pointer + * @cl: host client that requested this write + * @data: data to write + * @data_size: data size + */ +struct issei_write_buf { + struct list_head list; + struct issei_host_client *cl; + const u8 *data; + size_t data_size; +}; + +/** + * struct issei_hw_ops - callbacks for hardware operations + * @irq_clear: clear irq + * @irq_enable: enable irq + * @irq_disable: disable irq + * @irq_sync: sync irq + * @hw_reset: initiate hardware reset + * @hw_config: initial hardware config + * @hw_is_ready: check if hardware is ready + * @hw_reset_release: release hardware from reset + * @host_set_ready: set host ready indicator + * @setup_message_send: send setup message + * @setup_message_recv: receive setup message + * @irq_write_generate: generate interrupt on write complete + */ +struct issei_hw_ops { + void (*irq_clear)(struct issei_device *idev); + void (*irq_enable)(struct issei_device *idev); + void (*irq_disable)(struct issei_device *idev); + void (*irq_sync)(struct issei_device *idev); + int (*hw_reset)(struct issei_device *idev, bool enable); + int (*hw_config)(struct issei_device *idev); + bool (*hw_is_ready)(struct issei_device *idev); + void (*hw_reset_release)(struct issei_device *idev); + void (*host_set_ready)(struct issei_device *idev); + int (*setup_message_send)(struct issei_device *idev); + int (*setup_message_recv)(struct issei_device *idev); + int (*irq_write_generate)(struct issei_device *idev); +}; + +/** + * enum issei_rst_state: driver reset flow states + * @ISSEI_RST_STATE_INIT: initial state + * @ISSEI_RST_STATE_HW_READY: waiting for HW to be ready + * @ISSEI_RST_STATE_SETUP: waiting for channel setup completion + * @ISSEI_RST_STATE_START: waiting for start handshake completion + * @ISSEI_RST_STATE_CLIENT_ENUM: waiting for client enumeration + * @ISSEI_RST_STATE_DONE: reset flow is done + * @ISSEI_RST_STATE_DISABLED: flow is disabled + */ +enum issei_rst_state { + ISSEI_RST_STATE_INIT, + ISSEI_RST_STATE_HW_READY, + ISSEI_RST_STATE_SETUP, + ISSEI_RST_STATE_START, + ISSEI_RST_STATE_CLIENT_ENUM, + ISSEI_RST_STATE_DONE, + ISSEI_RST_STATE_DISABLED, +}; + +/** + * struct issei_device - issei device + * @parent: parent device object + * @dev: associated device object + * @cdev: character device + * @minor: allocated minor number + * @wait_has_data: wait queue for data + * @has_data: there are data to process + * @power_down: device is powering down + * @wait_rst_state: waitqueue for reset state processing + * @rst_state: reset state + * @fw_protocol_ver: protocol version + * @fw_version: firmware version + * @process_thread: worker thread + * @reset_count: number of consecutive link reset attempts + * @all_reset_count: cumilative number of link reset attempts + * @client_lock: mutex to protect client lists and write queue + * @host_client_list: host clients list + * @host_client_last_id: last allocated host client id + * @host_client_count: number of active host clients + * @fw_client_list: firmware clients list + * @write_queue: write queue + * @last_write_ts: last write timestamp + * @dma: DMA memory configuration + * @ops: hardware operations + * @hw: hw-specific data + */ +struct issei_device { + struct device *parent; + struct device dev; + struct cdev *cdev; + u32 minor; + wait_queue_head_t wait_has_data; + bool has_data; + bool power_down; + wait_queue_head_t wait_rst_state; + enum issei_rst_state rst_state; + u16 fw_protocol_ver; + u16 fw_version[4]; + /* reset flow */ + struct task_struct *process_thread; + u8 reset_count; + u8 all_reset_count; + /* clients */ + struct mutex client_lock; + struct list_head host_client_list; + u16 host_client_last_id; + u8 host_client_count; + struct kset *fw_clients; + struct list_head fw_client_list; + struct list_head write_queue; + ktime_t last_write_ts; + struct issei_dma dma; + const struct issei_hw_ops *ops; + char hw[]; +}; + +static inline void issei_poke_process_thread(struct issei_device *idev) +{ + WRITE_ONCE(idev->has_data, true); + wake_up_interruptible(&idev->wait_has_data); +} +#endif /* _ISSEI_DEV_H_ */ diff --git a/include/uapi/linux/issei.h b/include/uapi/linux/issei.h new file mode 100644 index 000000000000..3bfb89330265 --- /dev/null +++ b/include/uapi/linux/issei.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Copyright (C) 2023-2026 Intel Corporation + * Intel Silicon Security Engine Interface (ISSEI) Linux driver: + * ISSEI Interface Header + */ +#ifndef _LINUX_ISSEI_H +#define _LINUX_ISSEI_H + +#include +#include + +/* + * This ioctl is used to associate the current file descriptor with a + * FW Client (given by UUID). This opens a communication channel + * between a host client and a FW client. From this point every read and write + * will communicate with the associated FW client. + * The communication between the clients can be terminated by + * IOCTL_ISSEI_DISCONNECT_CLIENT IOCTL or by + * closing the file descriptor (file_operation release()). + * + * The ioctl argument is a struct with a union that contains + * the input parameter and the output parameter for this ioctl. + * + * The input parameter is UUID of the FW Client. + * The output parameter is the properties of the FW client + * (FW protocol version, max message size and client flags). + */ +#define IOCTL_ISSEI_CONNECT_CLIENT \ + _IOWR('H', 0x01, struct issei_connect_client_data) + +/** + * struct issei_client - ISSEI client information structure + * @max_msg_length: maximum message length supported by the firmware client (in bytes) + * @protocol_version: protocol version reported by the firmware client + * @reserved1: reserved + * @flags: flag bitmask reported by the firmware client + * @reserved2: reserved + */ +struct issei_client { + __u32 max_msg_length; + __u8 protocol_version; + __u8 reserved1[3]; + __u32 flags; + __u32 reserved2; +}; + +#define ISSEI_IOCTL_UUID_LEN 16 + +/** + * struct issei_connect_client_data - ioctl Connect Client Data structure + * @in_client_uuid: unique id of the firmware client to connect to (from user space to kernel) + * @out_client_properties: connected firmware client properties (from kernel to user space) + */ +struct issei_connect_client_data { + union { + __u8 in_client_uuid[ISSEI_IOCTL_UUID_LEN]; + struct issei_client out_client_properties; + }; +}; + +/* + * This ioctl is used to terminate association between + * the host client and the FW client. + */ +#define IOCTL_ISSEI_DISCONNECT_CLIENT \ + _IO('H', 0x02) + +#endif /* _LINUX_ISSEI_H */ -- cgit v1.2.3 From fa1517bc997ef89124d5cb63b2ced305b08784ad Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Thu, 18 Jun 2026 16:14:04 -0700 Subject: fscrypt: Remove FSCRYPT_MODE_MAX Now that the arrays of per-mode keys in struct fscrypt_master_key have been replaced by a linked list, the definition of FSCRYPT_MODE_MAX doesn't do anything useful. (Previously it was used to size these arrays.) Remove it. Link: https://patch.msgid.link/20260618231404.132829-1-ebiggers@kernel.org Signed-off-by: Eric Biggers --- fs/crypto/fscrypt_private.h | 3 --- fs/crypto/keysetup.c | 5 ----- include/uapi/linux/fscrypt.h | 1 - tools/include/uapi/linux/fscrypt.h | 1 - 4 files changed, 10 deletions(-) (limited to 'include/uapi/linux') diff --git a/fs/crypto/fscrypt_private.h b/fs/crypto/fscrypt_private.h index 0053b5c45412..45307f7fa540 100644 --- a/fs/crypto/fscrypt_private.h +++ b/fs/crypto/fscrypt_private.h @@ -66,9 +66,6 @@ #define FSCRYPT_CONTEXT_V1 1 #define FSCRYPT_CONTEXT_V2 2 -/* Keep this in sync with include/uapi/linux/fscrypt.h */ -#define FSCRYPT_MODE_MAX FSCRYPT_MODE_AES_256_HCTR2 - struct fscrypt_context_v1 { u8 version; /* FSCRYPT_CONTEXT_V1 */ u8 contents_encryption_mode; diff --git a/fs/crypto/keysetup.c b/fs/crypto/keysetup.c index 281b5a8b0bcd..cfd348e2252e 100644 --- a/fs/crypto/keysetup.c +++ b/fs/crypto/keysetup.c @@ -83,8 +83,6 @@ static struct fscrypt_mode * select_encryption_mode(const union fscrypt_policy *policy, const struct inode *inode) { - BUILD_BUG_ON(ARRAY_SIZE(fscrypt_modes) != FSCRYPT_MODE_MAX + 1); - if (S_ISREG(inode->i_mode)) return &fscrypt_modes[fscrypt_policy_contents_mode(policy)]; @@ -229,9 +227,6 @@ static int setup_per_mode_enc_key(struct fscrypt_inode_info *ci, bool use_hw_wrapped_key = false; int err; - if (WARN_ON_ONCE(mode_num > FSCRYPT_MODE_MAX)) - return -EINVAL; - if (mk->mk_secret.is_hw_wrapped && S_ISREG(inode->i_mode)) { /* Using a hardware-wrapped key for file contents encryption */ if (!fscrypt_using_inline_encryption(ci)) { diff --git a/include/uapi/linux/fscrypt.h b/include/uapi/linux/fscrypt.h index 3aff99f2696a..84507280b3ea 100644 --- a/include/uapi/linux/fscrypt.h +++ b/include/uapi/linux/fscrypt.h @@ -30,7 +30,6 @@ #define FSCRYPT_MODE_SM4_CTS 8 #define FSCRYPT_MODE_ADIANTUM 9 #define FSCRYPT_MODE_AES_256_HCTR2 10 -/* If adding a mode number > 10, update FSCRYPT_MODE_MAX in fscrypt_private.h */ /* * Legacy policy version; ad-hoc KDF and no key verification. diff --git a/tools/include/uapi/linux/fscrypt.h b/tools/include/uapi/linux/fscrypt.h index 3aff99f2696a..84507280b3ea 100644 --- a/tools/include/uapi/linux/fscrypt.h +++ b/tools/include/uapi/linux/fscrypt.h @@ -30,7 +30,6 @@ #define FSCRYPT_MODE_SM4_CTS 8 #define FSCRYPT_MODE_ADIANTUM 9 #define FSCRYPT_MODE_AES_256_HCTR2 10 -/* If adding a mode number > 10, update FSCRYPT_MODE_MAX in fscrypt_private.h */ /* * Legacy policy version; ad-hoc KDF and no key verification. -- cgit v1.2.3 From ffdc1ee2d6d6f7554426f9d11cbcb0a32f324173 Mon Sep 17 00:00:00 2001 From: Andrea Mayer Date: Sat, 11 Jul 2026 18:29:06 +0200 Subject: seg6: add FIB table attribute for post-encap SID route lookup After SRv6 encapsulation the kernel looks up the route for the first SID, that is the outer IPv6 destination of the encapsulated packet. This post-encap SID route lookup uses the FIB table of the current routing context. When the encap route is installed in a VRF, the VRF's table may not have a route matching the SID. In that case another table should handle it, e.g. one configured for underlay connectivity. Add an optional SEG6_IPTUNNEL_TABLE attribute that selects the FIB table used for this lookup. When set by the user, the attribute is honored on both the input path (forwarded traffic) and the output path (locally originated traffic). SRv6 encap routes that do not set the attribute use the current routing context, as before. For example: # SID route installed in the underlay table 500 ip -6 route add fc00::100/128 via fd00::1 dev veth0 table 500 # encap route in vrf-100; the first SID is looked up in table 500 ip -6 route add cafe::1/128 vrf vrf-100 \ encap seg6 mode encap segs fc00::100 lookup 500 dev veth0 # or look up the SID in the main table ip -6 route add cafe::1/128 vrf vrf-100 \ encap seg6 mode encap segs fc00::100 lookup main dev veth0 Suggested-by: Nicolas Dichtel Signed-off-by: Andrea Mayer Reviewed-by: Nicolas Dichtel Acked-by: David Ahern Link: https://patch.msgid.link/20260711162907.6521-2-andrea.mayer@uniroma2.it Signed-off-by: Jakub Kicinski --- include/uapi/linux/seg6_iptunnel.h | 1 + net/ipv6/seg6_iptunnel.c | 132 ++++++++++++++++++++++++++++++++----- 2 files changed, 117 insertions(+), 16 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/seg6_iptunnel.h b/include/uapi/linux/seg6_iptunnel.h index 485889b19900..e1964b3a4bb0 100644 --- a/include/uapi/linux/seg6_iptunnel.h +++ b/include/uapi/linux/seg6_iptunnel.h @@ -21,6 +21,7 @@ enum { SEG6_IPTUNNEL_UNSPEC, SEG6_IPTUNNEL_SRH, SEG6_IPTUNNEL_SRC, /* struct in6_addr */ + SEG6_IPTUNNEL_TABLE, /* __u32 FIB table for post-encap SID lookup */ __SEG6_IPTUNNEL_MAX, }; #define SEG6_IPTUNNEL_MAX (__SEG6_IPTUNNEL_MAX - 1) diff --git a/net/ipv6/seg6_iptunnel.c b/net/ipv6/seg6_iptunnel.c index 4c45c0a77d75..61c6a27bf202 100644 --- a/net/ipv6/seg6_iptunnel.c +++ b/net/ipv6/seg6_iptunnel.c @@ -51,6 +51,7 @@ struct seg6_lwt { struct dst_cache cache_input; struct dst_cache cache_output; struct in6_addr tunsrc; + u32 table; struct seg6_iptunnel_encap tuninfo[]; }; @@ -68,6 +69,7 @@ seg6_encap_lwtunnel(struct lwtunnel_state *lwt) static const struct nla_policy seg6_iptunnel_policy[SEG6_IPTUNNEL_MAX + 1] = { [SEG6_IPTUNNEL_SRH] = { .type = NLA_BINARY }, [SEG6_IPTUNNEL_SRC] = NLA_POLICY_EXACT_LEN(sizeof(struct in6_addr)), + [SEG6_IPTUNNEL_TABLE] = { .type = NLA_U32 }, }; static int nla_put_srh(struct sk_buff *skb, int attrtype, @@ -479,6 +481,73 @@ int seg6_do_srh_inline(struct sk_buff *skb, struct ipv6_sr_hdr *osrh) } EXPORT_SYMBOL_GPL(seg6_do_srh_inline); +/* look up a route in a specific FIB table. + * Returns a refcounted dst, or NULL if the table does not exist. + */ +static struct dst_entry *seg6_table_lookup(struct net *net, + struct sk_buff *skb, + struct flowi6 *fl6, u32 tbl_id) +{ + struct fib6_table *table; + struct rt6_info *rt; + + table = fib6_get_table(net, tbl_id); + if (!table) + return NULL; + + rt = ip6_pol_route(net, table, 0, fl6, skb, RT6_LOOKUP_F_HAS_SADDR); + return &rt->dst; +} + +static void seg6_init_flowi6(struct sk_buff *skb, struct ipv6hdr *hdr, + struct flowi6 *fl6) +{ + memset(fl6, 0, sizeof(*fl6)); + + fl6->daddr = hdr->daddr; + fl6->saddr = hdr->saddr; + fl6->flowlabel = ip6_flowinfo(hdr); + fl6->flowi6_mark = skb->mark; + fl6->flowi6_proto = hdr->nexthdr; +} + +/* look up the route for the first SID on the input path and set it on the skb. + * Returns the refcounted dst, or NULL if a reference could not be safely taken. + */ +static struct dst_entry *seg6_input_route(struct net *net, + struct sk_buff *skb, + struct seg6_lwt *slwt) +{ + u32 table = slwt->table; + + if (table) { + struct ipv6hdr *hdr = ipv6_hdr(skb); + struct dst_entry *dst; + struct flowi6 fl6; + + seg6_init_flowi6(skb, hdr, &fl6); + fl6.flowi6_iif = skb->dev->ifindex; + + dst = seg6_table_lookup(net, skb, &fl6, table); + if (!dst) { + dst = &net->ipv6.ip6_blk_hole_entry->dst; + dst_hold(dst); + } + + skb_dst_drop(skb); + skb_dst_set(skb, dst); + } else { + ip6_route_input(skb); + + /* ip6_route_input() sets a NOREF dst; force a refcount on it + * before caching or further use. + */ + skb_dst_force(skb); + } + + return skb_dst(skb); +} + static int seg6_input_finish(struct net *net, struct sock *sk, struct sk_buff *skb) { @@ -513,15 +582,9 @@ static int seg6_input_core(struct net *net, struct sock *sk, goto drop; } - if (!dst) { - ip6_route_input(skb); - - /* ip6_route_input() sets a NOREF dst; force a refcount on it - * before caching or further use. - */ - skb_dst_force(skb); - dst = skb_dst(skb); - if (unlikely(!dst)) { + if (unlikely(!dst)) { + dst = seg6_input_route(net, skb, slwt); + if (!dst) { err = -ENETUNREACH; goto drop; } @@ -578,6 +641,29 @@ static int seg6_input(struct sk_buff *skb) return seg6_input_core(dev_net(skb->dev), NULL, skb); } +/* look up the route for the first SID on the output path. Always returns a + * refcounted dst. + */ +static struct dst_entry *seg6_output_dst_lookup(struct net *net, + struct sk_buff *skb, + struct flowi6 *fl6, + struct seg6_lwt *slwt) +{ + struct dst_entry *dst; + + if (slwt->table) { + dst = seg6_table_lookup(net, skb, fl6, slwt->table); + if (!dst) { + dst = &net->ipv6.ip6_blk_hole_entry->dst; + dst_hold(dst); + } + } else { + dst = ip6_route_output(net, NULL, fl6); + } + + return dst; +} + static int seg6_output_core(struct net *net, struct sock *sk, struct sk_buff *skb) { @@ -600,14 +686,9 @@ static int seg6_output_core(struct net *net, struct sock *sk, struct ipv6hdr *hdr = ipv6_hdr(skb); struct flowi6 fl6; - memset(&fl6, 0, sizeof(fl6)); - fl6.daddr = hdr->daddr; - fl6.saddr = hdr->saddr; - fl6.flowlabel = ip6_flowinfo(hdr); - fl6.flowi6_mark = skb->mark; - fl6.flowi6_proto = hdr->nexthdr; + seg6_init_flowi6(skb, hdr, &fl6); - dst = ip6_route_output(net, NULL, &fl6); + dst = seg6_output_dst_lookup(net, skb, &fl6, slwt); if (dst->error) { err = dst->error; goto drop; @@ -752,6 +833,15 @@ static int seg6_build_state(struct net *net, struct nlattr *nla, } } + if (tb[SEG6_IPTUNNEL_TABLE]) { + slwt->table = nla_get_u32(tb[SEG6_IPTUNNEL_TABLE]); + if (!slwt->table) { + NL_SET_ERR_MSG(extack, "invalid lookup table"); + err = -EINVAL; + goto err_destroy_output; + } + } + newts->type = LWTUNNEL_ENCAP_SEG6; newts->flags |= LWTUNNEL_STATE_INPUT_REDIRECT; @@ -795,6 +885,10 @@ static int seg6_fill_encap_info(struct sk_buff *skb, nla_put_in6_addr(skb, SEG6_IPTUNNEL_SRC, &slwt->tunsrc)) return -EMSGSIZE; + if (slwt->table && + nla_put_u32(skb, SEG6_IPTUNNEL_TABLE, slwt->table)) + return -EMSGSIZE; + return 0; } @@ -809,6 +903,9 @@ static int seg6_encap_nlsize(struct lwtunnel_state *lwtstate) if (!ipv6_addr_any(&slwt->tunsrc)) nlsize += nla_total_size(sizeof(slwt->tunsrc)); + if (slwt->table) + nlsize += nla_total_size(sizeof(u32)); + return nlsize; } @@ -826,6 +923,9 @@ static int seg6_encap_cmp(struct lwtunnel_state *a, struct lwtunnel_state *b) if (!ipv6_addr_equal(&a_slwt->tunsrc, &b_slwt->tunsrc)) return 1; + if (a_slwt->table != b_slwt->table) + return 1; + return memcmp(a_hdr, b_hdr, len); } -- cgit v1.2.3 From af78c0020480aba3132fb9fa4db0fd1fb949feaa Mon Sep 17 00:00:00 2001 From: Pavel Begunkov Date: Wed, 22 Jul 2026 14:42:15 +0100 Subject: io_uring/zcrx: drop "notif" from stats struct names Keep zcrx statistics generic and don't stick "notif" to its uapi definitions. Stats dosn't need to be bound to notification details, it makes it cleaner and more readable. Signed-off-by: Pavel Begunkov Link: https://patch.msgid.link/6a39676b6f71b67d3f89c6ebab7a3739873834a3.1784726895.git.asml.silence@gmail.com Signed-off-by: Jens Axboe --- include/uapi/linux/io_uring/query.h | 6 +++--- include/uapi/linux/io_uring/zcrx.h | 4 ++-- io_uring/query.c | 4 ++-- io_uring/zcrx.c | 4 ++-- io_uring/zcrx.h | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/io_uring/query.h b/include/uapi/linux/io_uring/query.h index 1a68eca7c6b4..708bcdd585dd 100644 --- a/include/uapi/linux/io_uring/query.h +++ b/include/uapi/linux/io_uring/query.h @@ -66,10 +66,10 @@ struct io_uring_query_zcrx { struct io_uring_query_zcrx_notif { /* Bitmask of supported ZCRX_NOTIF_* flags */ __u32 notif_flags; - /* Size of io_uring_zcrx_notif_stats */ - __u32 notif_stats_size; + /* Size of zcrx_stats */ + __u32 stats_size; /* Required alignment for the stats struct within the region (ie stats_offset) */ - __u32 notif_stats_off_alignment; + __u32 stats_off_alignment; __u32 __resv1; __u64 __resv2[4]; }; diff --git a/include/uapi/linux/io_uring/zcrx.h b/include/uapi/linux/io_uring/zcrx.h index 15c05c45ce36..99e56ec26ead 100644 --- a/include/uapi/linux/io_uring/zcrx.h +++ b/include/uapi/linux/io_uring/zcrx.h @@ -76,11 +76,11 @@ enum zcrx_notification_type { }; enum zcrx_notification_desc_flags { - /* If set, stats_offset holds a valid offset to a notif_stats struct */ + /* If set, stats_offset holds a valid offset to a zcrx_stats struct */ ZCRX_NOTIF_DESC_FLAG_STATS = 1 << 0, }; -struct zcrx_notif_stats { +struct zcrx_stats { __u64 copy_count; /* cumulative copy-fallback CQEs */ __u64 copy_bytes; /* cumulative bytes copied */ }; diff --git a/io_uring/query.c b/io_uring/query.c index d529d94aa8f4..2e48fddd8d3a 100644 --- a/io_uring/query.c +++ b/io_uring/query.c @@ -50,8 +50,8 @@ static ssize_t io_query_zcrx_notif(union io_query_data *data) struct io_uring_query_zcrx_notif *e = &data->zcrx_notif; e->notif_flags = ZCRX_NOTIF_TYPE_MASK; - e->notif_stats_size = sizeof(struct zcrx_notif_stats); - e->notif_stats_off_alignment = __alignof__(struct zcrx_notif_stats); + e->stats_size = sizeof(struct zcrx_stats); + e->stats_off_alignment = __alignof__(struct zcrx_stats); e->__resv1 = 0; memset(&e->__resv2, 0, sizeof(e->__resv2)); return sizeof(*e); diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c index 49163f9c39df..53dcb5110719 100644 --- a/io_uring/zcrx.c +++ b/io_uring/zcrx.c @@ -863,12 +863,12 @@ static int zcrx_validate_notif_stats(struct io_zcrx_ifq *ifq, used = reg->offsets.rqes + sizeof(struct io_uring_zcrx_rqe) * reg->rq_entries; - if (!IS_ALIGNED(stats_off, __alignof__(struct zcrx_notif_stats))) + if (!IS_ALIGNED(stats_off, __alignof__(struct zcrx_stats))) return -EINVAL; if (stats_off < used) return -ERANGE; if (check_add_overflow(stats_off, - sizeof(struct zcrx_notif_stats), + sizeof(struct zcrx_stats), &end)) return -ERANGE; if (end > io_region_size(&ifq->rq_region)) diff --git a/io_uring/zcrx.h b/io_uring/zcrx.h index fa00900e479e..a8d301b6191f 100644 --- a/io_uring/zcrx.h +++ b/io_uring/zcrx.h @@ -80,7 +80,7 @@ struct io_zcrx_ifq { u32 allowed_notif_mask; u32 fired_notifs; u64 notif_data; - struct zcrx_notif_stats *notif_stats; + struct zcrx_stats *notif_stats; }; #if defined(CONFIG_IO_URING_ZCRX) -- cgit v1.2.3 From 201e30810223a40275f17c5e6ee410fa857b6b44 Mon Sep 17 00:00:00 2001 From: Pavel Begunkov Date: Wed, 22 Jul 2026 14:42:16 +0100 Subject: io_uring/zcrx: rename ZCRX_NOTIF_NO_BUFFERS ZCRX_NOTIF_NO_BUFFERS tells when page pool fails to allocate memory from zcrx. "No buffers" could be more confusing, rename it to ZCRX_NOTIF_ALLOC_FAIL. Signed-off-by: Pavel Begunkov Link: https://patch.msgid.link/29bd4fc069bc89691868beba0627ffbe570c2722.1784726895.git.asml.silence@gmail.com Signed-off-by: Jens Axboe --- include/uapi/linux/io_uring/zcrx.h | 2 +- io_uring/zcrx.c | 2 +- io_uring/zcrx.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/io_uring/zcrx.h b/include/uapi/linux/io_uring/zcrx.h index 99e56ec26ead..abb898d59a4b 100644 --- a/include/uapi/linux/io_uring/zcrx.h +++ b/include/uapi/linux/io_uring/zcrx.h @@ -69,7 +69,7 @@ enum zcrx_features { }; enum zcrx_notification_type { - ZCRX_NOTIF_NO_BUFFERS, + ZCRX_NOTIF_ALLOC_FAIL, ZCRX_NOTIF_COPY, __ZCRX_NOTIF_TYPE_LAST, diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c index 53dcb5110719..872d5794d3de 100644 --- a/io_uring/zcrx.c +++ b/io_uring/zcrx.c @@ -1244,7 +1244,7 @@ static netmem_ref io_pp_zc_alloc_netmems(struct page_pool *pp, gfp_t gfp) allocated = io_zcrx_refill_slow(pp, ifq, netmems, to_alloc); if (!allocated) { - zcrx_send_notif(ifq, ZCRX_NOTIF_NO_BUFFERS); + zcrx_send_notif(ifq, ZCRX_NOTIF_ALLOC_FAIL); return 0; } out_return: diff --git a/io_uring/zcrx.h b/io_uring/zcrx.h index a8d301b6191f..d16206eb9e30 100644 --- a/io_uring/zcrx.h +++ b/io_uring/zcrx.h @@ -11,7 +11,7 @@ #define ZCRX_SUPPORTED_REG_FLAGS (ZCRX_REG_IMPORT | ZCRX_REG_NODEV) #define ZCRX_FEATURES (ZCRX_FEATURE_RX_PAGE_SIZE |\ ZCRX_FEATURE_NOTIFICATION) -#define ZCRX_NOTIF_TYPE_MASK ((1U << ZCRX_NOTIF_NO_BUFFERS) | (1U << ZCRX_NOTIF_COPY)) +#define ZCRX_NOTIF_TYPE_MASK ((1U << ZCRX_NOTIF_ALLOC_FAIL) | (1U << ZCRX_NOTIF_COPY)) struct io_zcrx_mem { unsigned long size; -- cgit v1.2.3 From e366c15e1610ef11d0717ecd875ae63050282676 Mon Sep 17 00:00:00 2001 From: Pavel Begunkov Date: Wed, 22 Jul 2026 14:42:17 +0100 Subject: io_uring/zcrx: rename notif to event "Notification" is too long and the abbreviated version is used in several places, which is inconsistent and more ambiguous for users. Rename it to event, which is easier to keep consistent. To keep the change small, only change uapi/ + do necessary fix ups, and the rest of internals can be adjusted in the next release. Signed-off-by: Pavel Begunkov Link: https://patch.msgid.link/f95ca6717da3c8d3649a1a7f0d883a563f545052.1784726895.git.asml.silence@gmail.com Signed-off-by: Jens Axboe --- include/uapi/linux/io_uring/query.h | 8 ++++---- include/uapi/linux/io_uring/zcrx.h | 28 ++++++++++++++-------------- io_uring/query.c | 8 ++++---- io_uring/zcrx.c | 30 +++++++++++++++--------------- io_uring/zcrx.h | 5 +++-- 5 files changed, 40 insertions(+), 39 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/io_uring/query.h b/include/uapi/linux/io_uring/query.h index 708bcdd585dd..e6493e4a7a4d 100644 --- a/include/uapi/linux/io_uring/query.h +++ b/include/uapi/linux/io_uring/query.h @@ -23,7 +23,7 @@ enum { IO_URING_QUERY_OPCODES = 0, IO_URING_QUERY_ZCRX = 1, IO_URING_QUERY_SCQ = 2, - IO_URING_QUERY_ZCRX_NOTIF = 3, + IO_URING_QUERY_ZCRX_EVENT = 3, __IO_URING_QUERY_MAX, }; @@ -63,9 +63,9 @@ struct io_uring_query_zcrx { __u64 __resv2; }; -struct io_uring_query_zcrx_notif { - /* Bitmask of supported ZCRX_NOTIF_* flags */ - __u32 notif_flags; +struct io_uring_query_zcrx_event { + /* Bitmask of supported ZCRX_EVENT_* flags */ + __u32 event_flags; /* Size of zcrx_stats */ __u32 stats_size; /* Required alignment for the stats struct within the region (ie stats_offset) */ diff --git a/include/uapi/linux/io_uring/zcrx.h b/include/uapi/linux/io_uring/zcrx.h index abb898d59a4b..e01bc0e34b24 100644 --- a/include/uapi/linux/io_uring/zcrx.h +++ b/include/uapi/linux/io_uring/zcrx.h @@ -65,19 +65,19 @@ enum zcrx_features { * value in struct io_uring_zcrx_ifq_reg::rx_buf_len. */ ZCRX_FEATURE_RX_PAGE_SIZE = 1 << 0, - ZCRX_FEATURE_NOTIFICATION = 1 << 1, + ZCRX_FEATURE_EVENT = 1 << 1, }; -enum zcrx_notification_type { - ZCRX_NOTIF_ALLOC_FAIL, - ZCRX_NOTIF_COPY, +enum zcrx_event_type { + ZCRX_EVENT_ALLOC_FAIL, + ZCRX_EVENT_COPY, - __ZCRX_NOTIF_TYPE_LAST, + __ZCRX_EVENT_TYPE_LAST, }; -enum zcrx_notification_desc_flags { +enum zcrx_event_desc_flags { /* If set, stats_offset holds a valid offset to a zcrx_stats struct */ - ZCRX_NOTIF_DESC_FLAG_STATS = 1 << 0, + ZCRX_EVENT_DESC_FLAG_STATS = 1 << 0, }; struct zcrx_stats { @@ -85,10 +85,10 @@ struct zcrx_stats { __u64 copy_bytes; /* cumulative bytes copied */ }; -struct zcrx_notification_desc { +struct zcrx_event_desc { __u64 user_data; __u32 type_mask; - __u32 flags; /* see enum zcrx_notification_desc_flags */ + __u32 flags; /* see enum zcrx_event_desc_flags */ __u64 stats_offset; /* offset from the beginning of refill ring region for stats */ __u64 __resv2[9]; }; @@ -108,14 +108,14 @@ struct io_uring_zcrx_ifq_reg { struct io_uring_zcrx_offsets offsets; __u32 zcrx_id; __u32 rx_buf_len; - __u64 notif_desc; /* see struct zcrx_notification_desc */ + __u64 event_desc; /* see struct zcrx_event_desc */ __u64 __resv[2]; }; enum zcrx_ctrl_op { ZCRX_CTRL_FLUSH_RQ, ZCRX_CTRL_EXPORT, - ZCRX_CTRL_ARM_NOTIFICATION, + ZCRX_CTRL_ARM_EVENT, __ZCRX_CTRL_LAST, }; @@ -129,8 +129,8 @@ struct zcrx_ctrl_export { __u32 __resv1[11]; }; -struct zcrx_ctrl_arm_notif { - __u32 notif_type; +struct zcrx_ctrl_arm_event { + __u32 event_type; /* see enum zcrx_event_type */ __u32 __resv[11]; }; @@ -142,7 +142,7 @@ struct zcrx_ctrl { union { struct zcrx_ctrl_export zc_export; struct zcrx_ctrl_flush_rq zc_flush; - struct zcrx_ctrl_arm_notif zc_arm_notif; + struct zcrx_ctrl_arm_event zc_arm_event; }; }; diff --git a/io_uring/query.c b/io_uring/query.c index 2e48fddd8d3a..88a325736992 100644 --- a/io_uring/query.c +++ b/io_uring/query.c @@ -9,7 +9,7 @@ union io_query_data { struct io_uring_query_opcode opcodes; struct io_uring_query_zcrx zcrx; - struct io_uring_query_zcrx_notif zcrx_notif; + struct io_uring_query_zcrx_event zcrx_notif; struct io_uring_query_scq scq; }; @@ -47,9 +47,9 @@ static ssize_t io_query_zcrx(union io_query_data *data) static ssize_t io_query_zcrx_notif(union io_query_data *data) { - struct io_uring_query_zcrx_notif *e = &data->zcrx_notif; + struct io_uring_query_zcrx_event *e = &data->zcrx_notif; - e->notif_flags = ZCRX_NOTIF_TYPE_MASK; + e->event_flags = ZCRX_EVENT_TYPE_MASK; e->stats_size = sizeof(struct zcrx_stats); e->stats_off_alignment = __alignof__(struct zcrx_stats); e->__resv1 = 0; @@ -96,7 +96,7 @@ static int io_handle_query_entry(union io_query_data *data, void __user *uhdr, case IO_URING_QUERY_ZCRX: ret = io_query_zcrx(data); break; - case IO_URING_QUERY_ZCRX_NOTIF: + case IO_URING_QUERY_ZCRX_EVENT: ret = io_query_zcrx_notif(data); break; case IO_URING_QUERY_SCQ: diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c index 872d5794d3de..7d9fcf34539a 100644 --- a/io_uring/zcrx.c +++ b/io_uring/zcrx.c @@ -766,7 +766,7 @@ static int import_zcrx(struct io_ring_ctx *ctx, return -EINVAL; if (reg->if_rxq || reg->rq_entries || reg->area_ptr || reg->region_ptr) return -EINVAL; - if (reg->notif_desc) + if (reg->event_desc) return -EINVAL; if (reg->flags & ~ZCRX_REG_IMPORT) return -EINVAL; @@ -855,7 +855,7 @@ netdev_put_unlock: static int zcrx_validate_notif_stats(struct io_zcrx_ifq *ifq, const struct io_uring_zcrx_ifq_reg *reg, - const struct zcrx_notification_desc *notif) + const struct zcrx_event_desc *notif) { size_t stats_off = notif->stats_offset; size_t used, end; @@ -883,7 +883,7 @@ static int zcrx_validate_notif_stats(struct io_zcrx_ifq *ifq, int io_register_zcrx(struct io_ring_ctx *ctx, struct io_uring_zcrx_ifq_reg __user *arg) { - struct zcrx_notification_desc notif; + struct zcrx_event_desc notif; struct io_uring_zcrx_area_reg area; struct io_uring_zcrx_ifq_reg reg; struct io_uring_region_desc rd; @@ -928,14 +928,14 @@ int io_register_zcrx(struct io_ring_ctx *ctx, return -EFAULT; memset(¬if, 0, sizeof(notif)); - if (reg.notif_desc && copy_from_user(¬if, u64_to_user_ptr(reg.notif_desc), + if (reg.event_desc && copy_from_user(¬if, u64_to_user_ptr(reg.event_desc), sizeof(notif))) return -EFAULT; - if (notif.type_mask & ~ZCRX_NOTIF_TYPE_MASK) + if (notif.type_mask & ~ZCRX_EVENT_TYPE_MASK) return -EINVAL; - if (notif.flags & ~ZCRX_NOTIF_DESC_FLAG_STATS) + if (notif.flags & ~ZCRX_EVENT_DESC_FLAG_STATS) return -EINVAL; - if (!(notif.flags & ZCRX_NOTIF_DESC_FLAG_STATS)) { + if (!(notif.flags & ZCRX_EVENT_DESC_FLAG_STATS)) { if (notif.stats_offset) return -EINVAL; } @@ -970,7 +970,7 @@ int io_register_zcrx(struct io_ring_ctx *ctx, if (ret) goto err; - if (notif.flags & ZCRX_NOTIF_DESC_FLAG_STATS) { + if (notif.flags & ZCRX_EVENT_DESC_FLAG_STATS) { ret = zcrx_validate_notif_stats(ifq, ®, ¬if); if (ret) goto err; @@ -1244,7 +1244,7 @@ static netmem_ref io_pp_zc_alloc_netmems(struct page_pool *pp, gfp_t gfp) allocated = io_zcrx_refill_slow(pp, ifq, netmems, to_alloc); if (!allocated) { - zcrx_send_notif(ifq, ZCRX_NOTIF_ALLOC_FAIL); + zcrx_send_notif(ifq, ZCRX_EVENT_ALLOC_FAIL); return 0; } out_return: @@ -1398,16 +1398,16 @@ static int zcrx_flush_rq(struct io_ring_ctx *ctx, struct io_zcrx_ifq *zcrx, static int zcrx_arm_notif(struct io_ring_ctx *ctx, struct io_zcrx_ifq *zcrx, struct zcrx_ctrl *ctrl) { - const struct zcrx_ctrl_arm_notif *an = &ctrl->zc_arm_notif; + const struct zcrx_ctrl_arm_event *an = &ctrl->zc_arm_event; unsigned type_mask; - if (an->notif_type >= __ZCRX_NOTIF_TYPE_LAST) + if (an->event_type >= __ZCRX_EVENT_TYPE_LAST) return -EINVAL; if (!mem_is_zero(&an->__resv, sizeof(an->__resv))) return -EINVAL; guard(spinlock_bh)(&zcrx->ctx_lock); - type_mask = 1U << an->notif_type; + type_mask = 1U << an->event_type; if (type_mask & ~zcrx->fired_notifs) return -EINVAL; zcrx->fired_notifs &= ~type_mask; @@ -1420,7 +1420,7 @@ int io_zcrx_ctrl(struct io_ring_ctx *ctx, void __user *arg, unsigned nr_args) struct io_zcrx_ifq *zcrx; BUILD_BUG_ON(sizeof(ctrl.zc_export) != sizeof(ctrl.zc_flush)); - BUILD_BUG_ON(sizeof(ctrl.zc_export) != sizeof(ctrl.zc_arm_notif)); + BUILD_BUG_ON(sizeof(ctrl.zc_export) != sizeof(ctrl.zc_arm_event)); if (nr_args) return -EINVAL; @@ -1438,7 +1438,7 @@ int io_zcrx_ctrl(struct io_ring_ctx *ctx, void __user *arg, unsigned nr_args) return zcrx_flush_rq(ctx, zcrx, &ctrl); case ZCRX_CTRL_EXPORT: return zcrx_export(ctx, zcrx, &ctrl, arg); - case ZCRX_CTRL_ARM_NOTIFICATION: + case ZCRX_CTRL_ARM_EVENT: return zcrx_arm_notif(ctx, zcrx, &ctrl); } @@ -1584,7 +1584,7 @@ static int io_zcrx_copy_frag(struct io_kiocb *req, struct io_zcrx_ifq *ifq, zcrx_stat_add(&ifq->notif_stats->copy_count, 1); zcrx_stat_add(&ifq->notif_stats->copy_bytes, ret); } - zcrx_send_notif(ifq, ZCRX_NOTIF_COPY); + zcrx_send_notif(ifq, ZCRX_EVENT_COPY); } return ret; diff --git a/io_uring/zcrx.h b/io_uring/zcrx.h index d16206eb9e30..c1005f23caff 100644 --- a/io_uring/zcrx.h +++ b/io_uring/zcrx.h @@ -10,8 +10,9 @@ #define ZCRX_SUPPORTED_REG_FLAGS (ZCRX_REG_IMPORT | ZCRX_REG_NODEV) #define ZCRX_FEATURES (ZCRX_FEATURE_RX_PAGE_SIZE |\ - ZCRX_FEATURE_NOTIFICATION) -#define ZCRX_NOTIF_TYPE_MASK ((1U << ZCRX_NOTIF_ALLOC_FAIL) | (1U << ZCRX_NOTIF_COPY)) + ZCRX_FEATURE_EVENT) +#define ZCRX_EVENT_TYPE_MASK ((1U << ZCRX_EVENT_ALLOC_FAIL) |\ + (1U << ZCRX_EVENT_COPY)) struct io_zcrx_mem { unsigned long size; -- cgit v1.2.3 From 9b0ee13b80798feb0e6a83a6d69e915f23fcb6bc Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Thu, 23 Jul 2026 09:52:09 -0700 Subject: capability: remove non-kernel-doc comments Convert 2 "/**" comments to use plain C "/*" comment style to avoid kernel-doc warnings: Warning: include/uapi/linux/capability.h:114 expecting prototype for POSIX(). Prototype was for CAP_CHOWN() instead Warning: include/uapi/linux/capability.h:175 expecting prototype for Linux(). Prototype was for CAP_SETPCAP() instead Signed-off-by: Randy Dunlap Reviewed-by: Paul Moore Signed-off-by: Serge Hallyn --- include/uapi/linux/capability.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/capability.h b/include/uapi/linux/capability.h index ea5a0899ecf0..06f206faee04 100644 --- a/include/uapi/linux/capability.h +++ b/include/uapi/linux/capability.h @@ -104,9 +104,9 @@ struct vfs_ns_cap_data { #endif -/** - ** POSIX-draft defined capabilities. - **/ +/* + * POSIX-draft defined capabilities. + */ /* In a system with the [_POSIX_CHOWN_RESTRICTED] option defined, this overrides the restriction of changing file ownership and group @@ -159,9 +159,9 @@ struct vfs_ns_cap_data { #define CAP_SETUID 7 -/** - ** Linux-specific capabilities - **/ +/* + * Linux-specific capabilities + */ /* Without VFS support for capabilities: * Transfer any capability in your permitted set to any pid, -- cgit v1.2.3 From fa0d6d945e5ce96cff14b114eec7527f13d7f23a Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 24 Jul 2026 15:41:17 +0200 Subject: fs: add failfs nullfs provides a permanently empty and immutable directory. Lookups fail with ENOENT. The directory can be opened, read, stat, mounted upon. It behaves like nothing is there. Add its counterpart failfs where the semantics are not "there is nothing here" but "nothing is supported here". Every operation that reaches the filesystem fails with EOPNOTSUPP. Even statfs()/fstatfs() fail so the filesystem cannot be discovered through an fd to it. EOPNOTSUPP rather than a permission errno keeps that coherent. There is no permission model in which anything could ever be allowed and EACCES or EPERM would merely suggest that different credentials might succeed while EIO would suggest corruption. It also makes hitting the failfs boundary mostly quite dinstinguishable. A task anchoring its lookups at real directory file descriptors may be able to tell a failfs refusal from an ordinary permission failure. I wouldn't go so far as guaranteeing that but it should mostly work. No path lookup can open the root, not even with O_PATH. It is never reached by a lookup in a parent directory. The only way to a path-walk terminal at the root is a jump through a /proc//{root,cwd} magic link or by mountpoint traversal. The root also refuses ->d_weak_revalidate() which the VFS calls for jumped terminals. That covers the jump-based references too: an O_PATH open is refused, name_to_handle_at() cannot encode it into a file handle, and following a magic link into it fails. A plain readlink() of such a link still works and shows "failfs:/". There is a single instance of failfs mounted during early boot via kern_mount() making it logically distinct from every mount namespace. Since the mount is a member of no mount namespace mounting onto it fails. So nothing can ever be mounted on top of it. It cannot be cloned via OPEN_TREE_CLONE and it does not show up in statmount()/listmount() or /proc//mountinfo. The filesystem is not registered so it is not visible in /proc/filesystems and cannot be mounted from userspace. This lets tasks shed their filesystem state completely. A process with its root directory or working directory in failfs must anchor every path lookup at an explicit file descriptor or is doomed to fail any lookup. Absolute paths, absolute symlinks, and AT_FDCWD-relative lookups simply fail. Followup patches will expose it via a new FD_FAILFS_ROOT file descriptor sentinel understood by fchdir() and the new fchroot() system call. Link: https://patch.msgid.link/20260724-work-failfs-v2-1-485dabbae185@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/Makefile | 2 +- fs/d_path.c | 3 +- fs/failfs.c | 155 +++++++++++++++++++++++++++++++++++++++++++++ fs/internal.h | 3 + fs/namespace.c | 1 + include/uapi/linux/magic.h | 1 + 6 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 fs/failfs.c (limited to 'include/uapi/linux') diff --git a/fs/Makefile b/fs/Makefile index 89a8a9d207d1..73b6cab7738e 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -16,7 +16,7 @@ obj-y := open.o read_write.o file_table.o super.o \ stack.o fs_struct.o statfs.o fs_pin.o nsfs.o \ fs_dirent.o fs_context.o fs_parser.o fsopen.o init.o \ kernel_read_file.o mnt_idmapping.o remap_range.o pidfs.o \ - file_attr.o fserror.o nullfs.o + file_attr.o fserror.o nullfs.o failfs.o obj-$(CONFIG_BUFFER_HEAD) += buffer.o mpage.o obj-$(CONFIG_PROC_FS) += proc_namespace.o diff --git a/fs/d_path.c b/fs/d_path.c index a48957c0971e..c25309006d5d 100644 --- a/fs/d_path.c +++ b/fs/d_path.c @@ -279,7 +279,8 @@ char *d_path(const struct path *path, char *buf, int buflen) * and instead have d_path return the mounted path. */ if (path->dentry->d_op && path->dentry->d_op->d_dname && - (!IS_ROOT(path->dentry) || path->dentry != path->mnt->mnt_root)) + (!IS_ROOT(path->dentry) || path->dentry != path->mnt->mnt_root || + failfs_mnt(path->mnt))) return path->dentry->d_op->d_dname(path->dentry, buf, buflen); rcu_read_lock(); diff --git a/fs/failfs.c b/fs/failfs.c new file mode 100644 index 000000000000..d0ca1fc6c459 --- /dev/null +++ b/fs/failfs.c @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2026 Christian Brauner */ +#include +#include +#include +#include +#include + +#include "internal.h" + +static struct path failfs_root_path = {}; + +void failfs_get_root(struct path *path) +{ + *path = failfs_root_path; + path_get(path); +} + +bool failfs_mnt(const struct vfsmount *mnt) +{ + return mnt->mnt_sb == failfs_root_path.mnt->mnt_sb; +} + +static int failfs_permission(struct mnt_idmap *idmap, struct inode *inode, + int mask) +{ + return -EOPNOTSUPP; +} + +static struct dentry *failfs_lookup(struct inode *dir, struct dentry *dentry, + unsigned int flags) +{ + /* Unreachable: ->permission() already failed the walk. */ + return ERR_PTR(-EOPNOTSUPP); +} + +static int failfs_getattr(struct mnt_idmap *idmap, const struct path *path, + struct kstat *stat, u32 request_mask, + unsigned int query_flags) +{ + return -EOPNOTSUPP; +} + +static const struct inode_operations failfs_dir_inode_operations = { + .permission = failfs_permission, + .lookup = failfs_lookup, + .getattr = failfs_getattr, +}; + +static const struct file_operations failfs_dir_operations = {}; + +static int failfs_d_weak_revalidate(struct dentry *dentry, unsigned int flags) +{ + /* + * The root is only ever reached as a path-walk terminal by jumping + * to it: as "/" when it is the caller's root, or through a + * /proc//{root,cwd} magic link. ->permission() already fails + * every walk of a component, but a jump lands on the root without + * one. Refuse here too so the root cannot be pinned by an O_PATH + * open or encoded into a file handle. + */ + return -EOPNOTSUPP; +} + +static char *failfs_dname(struct dentry *dentry, char *buffer, int buflen) +{ + return dynamic_dname(buffer, buflen, "failfs:/"); +} + +static const struct dentry_operations failfs_dentry_operations = { + .d_dname = failfs_dname, + .d_weak_revalidate = failfs_d_weak_revalidate, +}; + +static int failfs_statfs(struct dentry *dentry, struct kstatfs *buf) +{ + return -EOPNOTSUPP; +} + +static const struct super_operations failfs_super_operations = { + .statfs = failfs_statfs, +}; + +static int failfs_fill_super(struct super_block *s, struct fs_context *fc) +{ + struct inode *inode; + + s->s_maxbytes = MAX_LFS_FILESIZE; + s->s_blocksize = PAGE_SIZE; + s->s_blocksize_bits = PAGE_SHIFT; + s->s_magic = FAIL_FS_MAGIC; + s->s_op = &failfs_super_operations; + s->s_export_op = NULL; + s->s_xattr = NULL; + s->s_time_gran = 1; + s->s_d_flags = 0; + + inode = new_inode(s); + if (!inode) + return -ENOMEM; + + /* failfs supports no operations... */ + inode->i_mode = S_IFDIR; + set_nlink(inode, 2); + inode->i_op = &failfs_dir_inode_operations; + inode->i_fop = &failfs_dir_operations; + simple_inode_init_ts(inode); + inode->i_ino = 1; + /* ... and is immutable. */ + inode->i_flags |= S_IMMUTABLE; + + set_default_d_op(s, &failfs_dentry_operations); + s->s_root = d_make_root(inode); + if (!s->s_root) + return -ENOMEM; + + return 0; +} + +static int failfs_get_tree(struct fs_context *fc) +{ + return get_tree_single(fc, failfs_fill_super); +} + +static const struct fs_context_operations failfs_context_ops = { + .get_tree = failfs_get_tree, +}; + +static int failfs_init_fs_context(struct fs_context *fc) +{ + fc->ops = &failfs_context_ops; + fc->global = true; + fc->sb_flags |= SB_NOUSER; + fc->s_iflags |= SB_I_NOEXEC | SB_I_NODEV; + return 0; +} + +static struct file_system_type failfs_fs_type = { + .name = "failfs", + .init_fs_context = failfs_init_fs_context, + .kill_sb = kill_anon_super, +}; + +void __init failfs_init(void) +{ + struct vfsmount *mnt; + + /* A single instance that is member of no mount namespace. */ + mnt = kern_mount(&failfs_fs_type); + if (IS_ERR(mnt)) + panic("VFS: Failed to create failfs"); + + failfs_root_path.mnt = mnt; + failfs_root_path.dentry = mnt->mnt_root; +} diff --git a/fs/internal.h b/fs/internal.h index 355d93f92208..ce7f12c5a65b 100644 --- a/fs/internal.h +++ b/fs/internal.h @@ -362,3 +362,6 @@ int anon_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr); void pidfs_get_root(struct path *path); void nsfs_get_root(struct path *path); +void failfs_get_root(struct path *path); +void __init failfs_init(void); +bool failfs_mnt(const struct vfsmount *mnt); diff --git a/fs/namespace.c b/fs/namespace.c index 3d5cd5bf3b05..87c365f2f82b 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -6274,6 +6274,7 @@ void __init mnt_init(void) shmem_init(); init_rootfs(); init_mount_tree(); + failfs_init(); } void put_mnt_ns(struct mnt_namespace *ns) diff --git a/include/uapi/linux/magic.h b/include/uapi/linux/magic.h index 4f2da935a76c..fd5f0e95648e 100644 --- a/include/uapi/linux/magic.h +++ b/include/uapi/linux/magic.h @@ -105,5 +105,6 @@ #define PID_FS_MAGIC 0x50494446 /* "PIDF" */ #define GUEST_MEMFD_MAGIC 0x474d454d /* "GMEM" */ #define NULL_FS_MAGIC 0x4E554C4C /* "NULL" */ +#define FAIL_FS_MAGIC 0x4641494C /* "FAIL" */ #endif /* __LINUX_MAGIC_H__ */ -- cgit v1.2.3 From cdf930a00949af72fbe9a22da2a8efa77981baa7 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Fri, 24 Jul 2026 15:41:18 +0200 Subject: fs: support FD_FAILFS_ROOT in fchdir() Add a new file descriptor sentinel FD_FAILFS_ROOT following FD_PIDFS_ROOT and FD_NSFS_ROOT and teach fchdir() to accept it. A process calling fchdir(FD_FAILFS_ROOT) moves its working directory into failfs. Every AT_FDCWD-relative lookup afterwards fails with EOPNOTSUPP including "." and ".." and getcwd() reports the working directory as unreachable from the process root by returning a path prefixed with "(unreachable)". Lookups relative to explicit directory file descriptors are unaffected. The sentinel is the only way in. No privilege or gating is required. Setting the working directory to a directory in which every operation fails grants nothing and loses nothing that closing file descriptors couldn't lose. An unlinked working directory behaves the same way today modulo errno. The working directory also plays no role in confining ".." resolution so no boundary is weakened. Link: https://patch.msgid.link/20260724-work-failfs-v2-2-485dabbae185@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/failfs.c | 11 +++++++++++ fs/internal.h | 1 + fs/open.c | 5 ++++- include/uapi/linux/fcntl.h | 1 + 4 files changed, 17 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/fs/failfs.c b/fs/failfs.c index d0ca1fc6c459..66a36da3d236 100644 --- a/fs/failfs.c +++ b/fs/failfs.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -135,6 +136,16 @@ static int failfs_init_fs_context(struct fs_context *fc) return 0; } +int failfs_current_chdir(void) +{ + struct path path; + + failfs_get_root(&path); + set_fs_pwd(current->fs, &path); + path_put(&path); + return 0; +} + static struct file_system_type failfs_fs_type = { .name = "failfs", .init_fs_context = failfs_init_fs_context, diff --git a/fs/internal.h b/fs/internal.h index ce7f12c5a65b..67aa0444351b 100644 --- a/fs/internal.h +++ b/fs/internal.h @@ -365,3 +365,4 @@ void nsfs_get_root(struct path *path); void failfs_get_root(struct path *path); void __init failfs_init(void); bool failfs_mnt(const struct vfsmount *mnt); +int failfs_current_chdir(void); diff --git a/fs/open.c b/fs/open.c index 408925d7bd0b..56b6032d4d81 100644 --- a/fs/open.c +++ b/fs/open.c @@ -570,9 +570,12 @@ retry: SYSCALL_DEFINE1(fchdir, unsigned int, fd) { - CLASS(fd_raw, f)(fd); int error; + if ((int)fd == FD_FAILFS_ROOT) + return failfs_current_chdir(); + + CLASS(fd_raw, f)(fd); if (fd_empty(f)) return -EBADF; diff --git a/include/uapi/linux/fcntl.h b/include/uapi/linux/fcntl.h index aadfbf6e0cb3..e43e3de3e9ee 100644 --- a/include/uapi/linux/fcntl.h +++ b/include/uapi/linux/fcntl.h @@ -124,6 +124,7 @@ struct delegation { #define FD_PIDFS_ROOT -10002 /* Root of the pidfs filesystem */ #define FD_NSFS_ROOT -10003 /* Root of the nsfs filesystem */ +#define FD_FAILFS_ROOT -10004 /* Root of the failfs filesystem */ #define FD_INVALID -10009 /* Invalid file descriptor: -10000 - EBADF = -10009 */ /* Generic flags for the *at(2) family of syscalls. */ -- cgit v1.2.3 From bb401df68c06b13db7f1f4de97d7c588b9e22a03 Mon Sep 17 00:00:00 2001 From: Jacopo Mondi Date: Tue, 30 Jun 2026 09:41:27 +0200 Subject: media: mali-c55: Add support for CCM Add support for the CCM (Color Correction Matrix) for the Mali C55 ISP. Define a new block in the uAPI using the extensible v4l2-isp format and implement support for configuring the CCM parameters in the mali-c55 ISP driver. Signed-off-by: Jacopo Mondi Reviewed-by: Vincenzo Frascino Reviewed-by: Linus Walleij Signed-off-by: Hans Verkuil --- .../media/platform/arm/mali-c55/mali-c55-params.c | 52 ++++++++++++++++++++++ include/uapi/linux/media/arm/mali-c55-config.h | 41 ++++++++++++++++- 2 files changed, 92 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/drivers/media/platform/arm/mali-c55/mali-c55-params.c b/drivers/media/platform/arm/mali-c55/mali-c55-params.c index 33e2232ec8f5..98f780cd46fe 100644 --- a/drivers/media/platform/arm/mali-c55/mali-c55-params.c +++ b/drivers/media/platform/arm/mali-c55/mali-c55-params.c @@ -46,6 +46,7 @@ * @awb_config: For header->type == MALI_C55_PARAM_BLOCK_AWB_CONFIG * @shading_config: For header->type == MALI_C55_PARAM_MESH_SHADING_CONFIG * @shading_selection: For header->type == MALI_C55_PARAM_MESH_SHADING_SELECTION + * @ccm: For header->type == MALI_C55_PARAM_BLOCK_CCM * @data: Allows easy initialisation of a union variable with a * pointer into a __u8 array. */ @@ -59,6 +60,7 @@ union mali_c55_params_block { const struct mali_c55_params_awb_config *awb_config; const struct mali_c55_params_mesh_shading_config *shading_config; const struct mali_c55_params_mesh_shading_selection *shading_selection; + const struct mali_c55_params_ccm *ccm; const __u8 *data; }; @@ -415,6 +417,52 @@ static void mali_c55_params_lsc_selection(struct mali_c55 *mali_c55, params->mesh_strength); } +static void mali_c55_params_ccm(struct mali_c55 *mali_c55, + union mali_c55_params_block block) +{ + const struct mali_c55_params_ccm *params = block.ccm; + + if (block.header->flags & V4L2_ISP_PARAMS_FL_BLOCK_DISABLE) { + mali_c55_ctx_write(mali_c55, MALI_C55_REG_CCM_ENABLE, 0); + return; + } + + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_CCM_COEF_R_R, + MALI_C55_CCM_COEF_MASK, params->coeffs[0][0]); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_CCM_COEF_R_G, + MALI_C55_CCM_COEF_MASK, params->coeffs[0][1]); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_CCM_COEF_R_B, + MALI_C55_CCM_COEF_MASK, params->coeffs[0][2]); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_CCM_COEF_G_R, + MALI_C55_CCM_COEF_MASK, params->coeffs[1][0]); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_CCM_COEF_G_G, + MALI_C55_CCM_COEF_MASK, params->coeffs[1][1]); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_CCM_COEF_G_B, + MALI_C55_CCM_COEF_MASK, params->coeffs[1][2]); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_CCM_COEF_B_R, + MALI_C55_CCM_COEF_MASK, params->coeffs[2][0]); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_CCM_COEF_B_G, + MALI_C55_CCM_COEF_MASK, params->coeffs[2][1]); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_CCM_COEF_B_B, + MALI_C55_CCM_COEF_MASK, params->coeffs[2][2]); + + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_CCM_ANTIFOG_GAIN_R, + MALI_C55_CCM_ANTIFOG_GAIN_MASK, params->gains[0]); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_CCM_ANTIFOG_GAIN_G, + MALI_C55_CCM_ANTIFOG_GAIN_MASK, params->gains[1]); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_CCM_ANTIFOG_GAIN_B, + MALI_C55_CCM_ANTIFOG_GAIN_MASK, params->gains[2]); + + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_CCM_ANTIFOG_OFFSET_R, + MALI_C55_CCM_ANTIFOG_OFFSET_MASK, params->offs[0]); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_CCM_ANTIFOG_OFFSET_G, + MALI_C55_CCM_ANTIFOG_OFFSET_MASK, params->offs[1]); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_CCM_ANTIFOG_OFFSET_B, + MALI_C55_CCM_ANTIFOG_OFFSET_MASK, params->offs[2]); + + mali_c55_ctx_write(mali_c55, MALI_C55_REG_CCM_ENABLE, 1); +} + static const mali_c55_params_handler mali_c55_params_handlers[] = { [MALI_C55_PARAM_BLOCK_SENSOR_OFFS] = &mali_c55_params_sensor_offs, [MALI_C55_PARAM_BLOCK_AEXP_HIST] = &mali_c55_params_aexp_hist, @@ -427,6 +475,7 @@ static const mali_c55_params_handler mali_c55_params_handlers[] = { [MALI_C55_PARAM_BLOCK_AWB_GAINS_AEXP] = &mali_c55_params_awb_gains, [MALI_C55_PARAM_MESH_SHADING_CONFIG] = &mali_c55_params_lsc_config, [MALI_C55_PARAM_MESH_SHADING_SELECTION] = &mali_c55_params_lsc_selection, + [MALI_C55_PARAM_BLOCK_CCM] = &mali_c55_params_ccm, }; static const struct v4l2_isp_params_block_type_info @@ -464,6 +513,9 @@ mali_c55_params_block_types_info[] = { [MALI_C55_PARAM_MESH_SHADING_SELECTION] = { .size = sizeof(struct mali_c55_params_mesh_shading_selection), }, + [MALI_C55_PARAM_BLOCK_CCM] = { + .size = sizeof(struct mali_c55_params_ccm), + }, }; static_assert(ARRAY_SIZE(mali_c55_params_handlers) == diff --git a/include/uapi/linux/media/arm/mali-c55-config.h b/include/uapi/linux/media/arm/mali-c55-config.h index 3d335f950eeb..0b18863f973d 100644 --- a/include/uapi/linux/media/arm/mali-c55-config.h +++ b/include/uapi/linux/media/arm/mali-c55-config.h @@ -219,6 +219,7 @@ struct mali_c55_stats_buffer { * @MALI_C55_PARAM_BLOCK_AWB_GAINS_AEXP: Auto-white balance gains for AEXP-0 tap * @MALI_C55_PARAM_MESH_SHADING_CONFIG : Mesh shading tables configuration * @MALI_C55_PARAM_MESH_SHADING_SELECTION: Mesh shading table selection + * @MALI_C55_PARAM_BLOCK_CCM: Colour correction matrix */ enum mali_c55_param_block_type { MALI_C55_PARAM_BLOCK_SENSOR_OFFS, @@ -232,6 +233,7 @@ enum mali_c55_param_block_type { MALI_C55_PARAM_BLOCK_AWB_GAINS_AEXP, MALI_C55_PARAM_MESH_SHADING_CONFIG, MALI_C55_PARAM_MESH_SHADING_SELECTION, + MALI_C55_PARAM_BLOCK_CCM, }; /** @@ -757,6 +759,42 @@ struct mali_c55_params_mesh_shading_selection { __u16 mesh_strength; }; +/** + * struct mali_c55_params_ccm - Coefficients, offsets and gains for the colour + * correction matrix + * + * The colour correction module converts images data from a sensor-specific + * colour space to known one. + * + * Colour correction is applied after demosaicing and each pixel is represented + * as a column vector of the three RGB colour channels on which the following + * operations take place: + * 1) An offset is subtracted from each colour channel + * 2) Each colour channel is multiplied by a gain + * 3) The pixel column vector is multiplied by the colour correction matrix + * + * This struct allows users to configure the coefficients for CCM and the + * per-channel offsets and gains. The nine matrix coefficients are expressed as + * 13 bits signed Q4.8 Sign/Magnitude fixed-point numbers, the three gain + * multipliers are expressed as 12 bits unsigned Q4.8 fixed-point numbers and + * the three offsets are expressed as a 12 bits unsigned integers. + * + * header.type should be set to MALI_C55_PARAM_BLOCK_CCM from + * :c:type:`mali_c55_param_block_type`. + * + * @header: The Mali-C55 parameters block header + * @coeffs: 3x3 color conversion matrix coefficients in sign/magnitude + * Q4.8 format + * @gains: Gains for red, green and blue channels in unsigned Q4.8 format + * @offs: Offsets for red, green and blue channels + */ +struct mali_c55_params_ccm { + struct v4l2_isp_params_block_header header; + __u16 coeffs[3][3]; + __u16 gains[3]; + __u16 offs[3]; +}; + /** * define MALI_C55_PARAMS_MAX_SIZE - Maximum size of all Mali C55 Parameters * @@ -780,6 +818,7 @@ struct mali_c55_params_mesh_shading_selection { sizeof(struct mali_c55_params_awb_config) + \ sizeof(struct mali_c55_params_awb_gains) + \ sizeof(struct mali_c55_params_mesh_shading_config) + \ - sizeof(struct mali_c55_params_mesh_shading_selection)) + sizeof(struct mali_c55_params_mesh_shading_selection) + \ + sizeof(struct mali_c55_params_ccm)) #endif /* __UAPI_MALI_C55_CONFIG_H */ -- cgit v1.2.3 From 6962703102c4a65e8d2f34fd43aeb8c3df45869d Mon Sep 17 00:00:00 2001 From: Jacopo Mondi Date: Tue, 30 Jun 2026 09:41:28 +0200 Subject: media: mali-c55: Add support for RGB Gamma Add support for Gamma curve correction for the Mali C55 ISP. Define a new block in the uAPI using the extensible v4l2-isp format and implement support for configuring the RGB Gamma parameters in the mali-c55 parameters handler. While at it, rename the MALI_C55_REG_GAMMA_GAINS_[1|2] register name to MALI_C55_REG_GAMMA_GAINS_[RG|B] and the MALI_C55_REG_GAMMA_OFFSETS_[1|2] register name to MALI_C55_REG_GAMMA_OFFSETS_[RG|B] to better clarify their intent. Signed-off-by: Jacopo Mondi Reviewed-by: Vincenzo Frascino Reviewed-by: Linus Walleij Signed-off-by: Hans Verkuil --- .../media/platform/arm/mali-c55/mali-c55-params.c | 75 ++++++++++++++++++++++ .../platform/arm/mali-c55/mali-c55-registers.h | 13 ++-- include/uapi/linux/media/arm/mali-c55-config.h | 47 +++++++++++++- 3 files changed, 130 insertions(+), 5 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/media/platform/arm/mali-c55/mali-c55-params.c b/drivers/media/platform/arm/mali-c55/mali-c55-params.c index 98f780cd46fe..e53c7b512dbd 100644 --- a/drivers/media/platform/arm/mali-c55/mali-c55-params.c +++ b/drivers/media/platform/arm/mali-c55/mali-c55-params.c @@ -47,6 +47,8 @@ * @shading_config: For header->type == MALI_C55_PARAM_MESH_SHADING_CONFIG * @shading_selection: For header->type == MALI_C55_PARAM_MESH_SHADING_SELECTION * @ccm: For header->type == MALI_C55_PARAM_BLOCK_CCM + * @gamma: For header->type == MALI_C55_PARAM_BLOCK_GAMMA_FR and + * header->type = MALI_C55_PARAM_BLOCK_GAMMA_DS * @data: Allows easy initialisation of a union variable with a * pointer into a __u8 array. */ @@ -61,6 +63,7 @@ union mali_c55_params_block { const struct mali_c55_params_mesh_shading_config *shading_config; const struct mali_c55_params_mesh_shading_selection *shading_selection; const struct mali_c55_params_ccm *ccm; + const struct mali_c55_params_gamma *gamma; const __u8 *data; }; @@ -463,6 +466,70 @@ static void mali_c55_params_ccm(struct mali_c55 *mali_c55, mali_c55_ctx_write(mali_c55, MALI_C55_REG_CCM_ENABLE, 1); } +static void mali_c55_params_gamma(struct mali_c55 *mali_c55, + union mali_c55_params_block block, + __u32 offset, __u32 lut_base) +{ + const struct mali_c55_params_gamma *params = block.gamma; + + if (block.header->flags & V4L2_ISP_PARAMS_FL_BLOCK_DISABLE) { + mali_c55_ctx_update_bits(mali_c55, + MALI_C55_REG_GAMMA_RGB_ENABLE + offset, + MALI_C55_GAMMA_ENABLE_MASK, 0x00); + return; + } + + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_GAMMA_GAINS_RG + offset, + MALI_C55_GAMMA_GAIN_R_MASK, params->gains[0]); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_GAMMA_GAINS_RG + offset, + MALI_C55_GAMMA_GAIN_G_MASK, + MALI_C55_GAMMA_GAIN_G(params->gains[1])); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_GAMMA_GAINS_B + offset, + MALI_C55_GAMMA_GAIN_B_MASK, params->gains[2]); + mali_c55_ctx_update_bits(mali_c55, + MALI_C55_REG_GAMMA_OFFSETS_RG + offset, + MALI_C55_GAMMA_OFFSET_R_MASK, + params->offs[0]); + mali_c55_ctx_update_bits(mali_c55, + MALI_C55_REG_GAMMA_OFFSETS_RG + offset, + MALI_C55_GAMMA_OFFSET_G_MASK, + MALI_C55_GAMMA_OFFSET_G(params->offs[1])); + mali_c55_ctx_update_bits(mali_c55, + MALI_C55_REG_GAMMA_OFFSETS_B + offset, + MALI_C55_GAMMA_OFFSET_B_MASK, + params->offs[2]); + + for (unsigned int i = 0; i < MALI_C55_NUM_GAMMA_LUT_ELEMENTS; i++) { + __u32 addr = lut_base + (i * 4); + + mali_c55_ctx_write(mali_c55, addr, params->lut[i]); + } + + mali_c55_ctx_update_bits(mali_c55, + MALI_C55_REG_GAMMA_RGB_ENABLE + offset, + MALI_C55_GAMMA_ENABLE_MASK, 0x1); +} + +static void mali_c55_params_gamma_fr(struct mali_c55 *mali_c55, + union mali_c55_params_block block) +{ + return mali_c55_params_gamma(mali_c55, block, + MALI_C55_CAP_DEV_FR_REG_OFFSET, + MALI_C55_REG_FR_GAMMA_RGB_MEM); +} + +static void mali_c55_params_gamma_ds(struct mali_c55 *mali_c55, + union mali_c55_params_block block) +{ + /* We cannot apply parameters to DS if it is not fitted. */ + if (!(mali_c55->capabilities & MALI_C55_GPS_DS_PIPE_FITTED)) + return; + + return mali_c55_params_gamma(mali_c55, block, + MALI_C55_CAP_DEV_DS_REG_OFFSET, + MALI_C55_REG_DS_GAMMA_RGB_MEM); +} + static const mali_c55_params_handler mali_c55_params_handlers[] = { [MALI_C55_PARAM_BLOCK_SENSOR_OFFS] = &mali_c55_params_sensor_offs, [MALI_C55_PARAM_BLOCK_AEXP_HIST] = &mali_c55_params_aexp_hist, @@ -476,6 +543,8 @@ static const mali_c55_params_handler mali_c55_params_handlers[] = { [MALI_C55_PARAM_MESH_SHADING_CONFIG] = &mali_c55_params_lsc_config, [MALI_C55_PARAM_MESH_SHADING_SELECTION] = &mali_c55_params_lsc_selection, [MALI_C55_PARAM_BLOCK_CCM] = &mali_c55_params_ccm, + [MALI_C55_PARAM_BLOCK_GAMMA_FR] = &mali_c55_params_gamma_fr, + [MALI_C55_PARAM_BLOCK_GAMMA_DS] = &mali_c55_params_gamma_ds, }; static const struct v4l2_isp_params_block_type_info @@ -516,6 +585,12 @@ mali_c55_params_block_types_info[] = { [MALI_C55_PARAM_BLOCK_CCM] = { .size = sizeof(struct mali_c55_params_ccm), }, + [MALI_C55_PARAM_BLOCK_GAMMA_FR] = { + .size = sizeof(struct mali_c55_params_gamma), + }, + [MALI_C55_PARAM_BLOCK_GAMMA_DS] = { + .size = sizeof(struct mali_c55_params_gamma), + }, }; static_assert(ARRAY_SIZE(mali_c55_params_handlers) == diff --git a/drivers/media/platform/arm/mali-c55/mali-c55-registers.h b/drivers/media/platform/arm/mali-c55/mali-c55-registers.h index 4cd13b702d9f..8a7c3bfe7051 100644 --- a/drivers/media/platform/arm/mali-c55/mali-c55-registers.h +++ b/drivers/media/platform/arm/mali-c55/mali-c55-registers.h @@ -422,15 +422,17 @@ enum mali_c55_interrupts { #define MALI_C55_REG_GAMMA_RGB_ENABLE 0x1c064 #define MALI_C55_GAMMA_ENABLE_MASK BIT(0) -#define MALI_C55_REG_GAMMA_GAINS_1 0x1c068 +#define MALI_C55_REG_GAMMA_GAINS_RG 0x1c068 #define MALI_C55_GAMMA_GAIN_R_MASK GENMASK(11, 0) #define MALI_C55_GAMMA_GAIN_G_MASK GENMASK(27, 16) -#define MALI_C55_REG_GAMMA_GAINS_2 0x1c06c +#define MALI_C55_GAMMA_GAIN_G(x) ((x) << 16) +#define MALI_C55_REG_GAMMA_GAINS_B 0x1c06c #define MALI_C55_GAMMA_GAIN_B_MASK GENMASK(11, 0) -#define MALI_C55_REG_GAMMA_OFFSETS_1 0x1c070 +#define MALI_C55_REG_GAMMA_OFFSETS_RG 0x1c070 #define MALI_C55_GAMMA_OFFSET_R_MASK GENMASK(11, 0) #define MALI_C55_GAMMA_OFFSET_G_MASK GENMASK(27, 16) -#define MALI_C55_REG_GAMMA_OFFSETS_2 0x1c074 +#define MALI_C55_GAMMA_OFFSET_G(x) ((x) << 16) +#define MALI_C55_REG_GAMMA_OFFSETS_B 0x1c074 #define MALI_C55_GAMMA_OFFSET_B_MASK GENMASK(11, 0) /* @@ -441,6 +443,9 @@ enum mali_c55_interrupts { #define MALI_C55_REG_FR_GAMMA_RGB_ENABLE 0x1c064 #define MALI_C55_REG_DS_GAMMA_RGB_ENABLE 0x1c1d8 +#define MALI_C55_REG_FR_GAMMA_RGB_MEM 0x18280 +#define MALI_C55_REG_DS_GAMMA_RGB_MEM 0x18484 + #define MALI_C55_REG_FR_SCALER_HFILT 0x34a8 #define MALI_C55_REG_FR_SCALER_VFILT 0x44a8 #define MALI_C55_REG_DS_SCALER_HFILT 0x14a8 diff --git a/include/uapi/linux/media/arm/mali-c55-config.h b/include/uapi/linux/media/arm/mali-c55-config.h index 0b18863f973d..ef0fe17df96e 100644 --- a/include/uapi/linux/media/arm/mali-c55-config.h +++ b/include/uapi/linux/media/arm/mali-c55-config.h @@ -36,6 +36,9 @@ */ #define MALI_C55_MAX_ZONES (15 * 15) +/* Number of RGB gamma LUT entries. */ +#define MALI_C55_NUM_GAMMA_LUT_ELEMENTS 129 + /** * struct mali_c55_ae_1024bin_hist - Auto Exposure 1024-bin histogram statistics * @@ -220,6 +223,8 @@ struct mali_c55_stats_buffer { * @MALI_C55_PARAM_MESH_SHADING_CONFIG : Mesh shading tables configuration * @MALI_C55_PARAM_MESH_SHADING_SELECTION: Mesh shading table selection * @MALI_C55_PARAM_BLOCK_CCM: Colour correction matrix + * @MALI_C55_PARAM_BLOCK_GAMMA_FR: Gamma gain and offset for FR pipe + * @MALI_C55_PARAM_BLOCK_GAMMA_DS: Gamma gain and offset for DS pipe */ enum mali_c55_param_block_type { MALI_C55_PARAM_BLOCK_SENSOR_OFFS, @@ -234,6 +239,8 @@ enum mali_c55_param_block_type { MALI_C55_PARAM_MESH_SHADING_CONFIG, MALI_C55_PARAM_MESH_SHADING_SELECTION, MALI_C55_PARAM_BLOCK_CCM, + MALI_C55_PARAM_BLOCK_GAMMA_FR, + MALI_C55_PARAM_BLOCK_GAMMA_DS, }; /** @@ -795,6 +802,42 @@ struct mali_c55_params_ccm { __u16 offs[3]; }; +/** + * struct mali_c55_params_gamma - RGB Gamma correction + * + * Gamma correction is used to program a standard gamma curve such as the sRGB + * one. It provides gains and offsets to implement contrast adjustments. + * + * Gamma correction is applied on both the FR and DS pipes separately in the RGB + * colour domain where the following operations take place: + * 1) An offset is subtracted from each colour channel + * 2) Each colour channel is multiplied by a gain + * 3) The Gamma LUT is applied to each colour channel + * + * The Gamma LUT has 129 entries where each node is an unsigned 12 bit number. + * It is expected that LUT[0]=0 and LUT[128]=0xfff, with the other 127 values + * defining the Gamma correction curve. The three gain multipliers are expressed + * as 12-bits unsigned Q4.8 fixed-point numbers and the three offsets are + * expressed as a 12-bits unsigned integers. + * + * As one Gamma correction block is available on both the FR and DS pipes, the + * header.type field should be set to one of either + * MALI_C55_PARAM_BLOCK_GAMMA_FR or MALI_C55_PARAM_BLOCK_GAMMA_DS from + * :c:type:`mali_c55_param_block_type`. + * + * @header: The Mali-C55 parameters block header + * @gains: Gains for the red, green and blue channel in unsigned Q4.8 format + * @offs: Offsets subtracted from the red, green and blue channels + * in unsigned 12 bits format + * @lut: 129-node Gamma LUT in unsigned 12 bits format + */ +struct mali_c55_params_gamma { + struct v4l2_isp_params_block_header header; + __u16 gains[3]; + __u16 offs[3]; + __u32 lut[MALI_C55_NUM_GAMMA_LUT_ELEMENTS]; +}; + /** * define MALI_C55_PARAMS_MAX_SIZE - Maximum size of all Mali C55 Parameters * @@ -819,6 +862,8 @@ struct mali_c55_params_ccm { sizeof(struct mali_c55_params_awb_gains) + \ sizeof(struct mali_c55_params_mesh_shading_config) + \ sizeof(struct mali_c55_params_mesh_shading_selection) + \ - sizeof(struct mali_c55_params_ccm)) + sizeof(struct mali_c55_params_ccm) + \ + sizeof(struct mali_c55_params_gamma) + \ + sizeof(struct mali_c55_params_gamma)) #endif /* __UAPI_MALI_C55_CONFIG_H */ -- cgit v1.2.3 From d501d66815eeb0e6641ce61e7eeb2beea19eadf5 Mon Sep 17 00:00:00 2001 From: Ashish Mhetre Date: Sun, 26 Jul 2026 08:19:03 +0000 Subject: iommu/arm-smmu-v3-iommufd: Report CFGI/TLBI-repeat erratum A guest with access to VCMDQ generates its own invalidation commands and must apply any invalidation errata before submitting them. If the host also repeats those commands, each affected invalidation is issued four times instead of twice. Add IOMMU_HW_INFO_ARM_SMMUV3_ERRATA_REPEAT_TLBI_CFGI to report the CFGI/TLBI-repeat erratum to user space. This allows the VMM to expose the erratum to the guest or apply the workaround itself. Use the raw __arm_smmu_cmdq_issue_cmdlist() helper for user-provided invalidations so the host does not apply the workaround a second time. Add arm_smmu_erratum_repeat_tlbi_cfgi() to query the static key when populating the SMMUv3 hardware information. Signed-off-by: Ashish Mhetre Reviewed-by: Nicolin Chen Reviewed-by: Jason Gunthorpe Signed-off-by: Will Deacon --- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c | 7 +++++-- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 7 ++++++- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 1 + include/uapi/linux/iommufd.h | 13 ++++++++++++- 4 files changed, 24 insertions(+), 4 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c index 1e9f7d2de344..76333091ec15 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c @@ -33,6 +33,9 @@ void *arm_smmu_hw_info(struct device *dev, u32 *length, info->iidr = readl_relaxed(master->smmu->base + ARM_SMMU_IIDR); info->aidr = readl_relaxed(master->smmu->base + ARM_SMMU_AIDR); + if (arm_smmu_erratum_repeat_tlbi_cfgi()) + info->flags |= IOMMU_HW_INFO_ARM_SMMUV3_ERRATA_REPEAT_TLBI_CFGI; + *length = sizeof(*info); *type = IOMMU_HW_INFO_TYPE_ARM_SMMUV3; @@ -386,8 +389,8 @@ int arm_vsmmu_cache_invalidate(struct iommufd_viommu *viommu, continue; /* FIXME always uses the main cmdq rather than trying to group by type */ - ret = arm_smmu_cmdq_issue_cmdlist(smmu, &smmu->cmdq, &last->cmd, - cur - last, true); + ret = __arm_smmu_cmdq_issue_cmdlist(smmu, &smmu->cmdq, &last->cmd, + cur - last, true); if (ret) { cur--; goto out; diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c index 8dc0586f015e..a78e85f4b457 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c @@ -829,11 +829,16 @@ int __arm_smmu_cmdq_issue_cmdlist(struct arm_smmu_device *smmu, return ret; } +bool arm_smmu_erratum_repeat_tlbi_cfgi(void) +{ + return static_branch_unlikely(&arm_smmu_erratum_repeat_tlbi_cfgi_key); +} + static bool arm_smmu_erratum_cmd_needs_repeating(struct arm_smmu_cmd *cmd) { u8 opcode; - if (!static_branch_unlikely(&arm_smmu_erratum_repeat_tlbi_cfgi_key)) + if (!arm_smmu_erratum_repeat_tlbi_cfgi()) return false; opcode = FIELD_GET(CMDQ_0_OP, cmd->data[0]); diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h index 3030f07a7c85..43f4d24e7847 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h @@ -1216,6 +1216,7 @@ int arm_smmu_cmdq_issue_cmdlist(struct arm_smmu_device *smmu, struct arm_smmu_cmdq *cmdq, struct arm_smmu_cmd *cmds, int n, bool sync); +bool arm_smmu_erratum_repeat_tlbi_cfgi(void); #ifdef CONFIG_ARM_SMMU_V3_SVA bool arm_smmu_sva_supported(struct arm_smmu_device *smmu); diff --git a/include/uapi/linux/iommufd.h b/include/uapi/linux/iommufd.h index 0425d452d41e..f73a392b3d3f 100644 --- a/include/uapi/linux/iommufd.h +++ b/include/uapi/linux/iommufd.h @@ -574,11 +574,22 @@ struct iommu_hw_info_vtd { __aligned_u64 ecap_reg; }; +/** + * enum iommu_hw_info_arm_smmuv3_flags - Flags for ARM SMMUv3 hw_info + * @IOMMU_HW_INFO_ARM_SMMUV3_ERRATA_REPEAT_TLBI_CFGI: + * If set, user space must issue TLBI/CFGI+SYNC commands twice due to + * hardware erratum T264-SMMU-3. See the description at + * arm_smmu_erratum_repeat_tlbi_cfgi_key. + */ +enum iommu_hw_info_arm_smmuv3_flags { + IOMMU_HW_INFO_ARM_SMMUV3_ERRATA_REPEAT_TLBI_CFGI = 1 << 0, +}; + /** * struct iommu_hw_info_arm_smmuv3 - ARM SMMUv3 hardware information * (IOMMU_HW_INFO_TYPE_ARM_SMMUV3) * - * @flags: Must be set to 0 + * @flags: Combination of enum iommu_hw_info_arm_smmuv3_flags * @__reserved: Must be 0 * @idr: Implemented features for ARM SMMU Non-secure programming interface * @iidr: Information about the implementation and implementer of ARM SMMU, -- cgit v1.2.3 From c64b2ab2ff516a94109020e8caea605ae09ea6d9 Mon Sep 17 00:00:00 2001 From: David Strahan Date: Wed, 22 Jul 2026 17:03:59 -0500 Subject: scsi: smartpqi: Add support for CCISS_BIG_PASSTHRU ioctl Add pqi_big_passthru_ioctl() to handle CCISS_BIG_PASSTHRU ioctl requests. The existing passthru ioctl uses a 16-bit integer for the I/O buffer size, limiting transfers to 64KB. The big passthru ioctl uses BIG_IOCTL_Command_struct which stores the buffer size as a 32-bit integer, allowing larger transfers required by some management utilities. Add CCISS_BIG_PASSTHRU_SUPPORTED to uapi/linux/cciss_ioctl.h and return 0 from pqi_ioctl() to advertise driver support. Userspace tools can send this ioctl to probe whether the driver supports CCISS_BIG_PASSTHRU before issuing it. Co-developed-by: Mike McGowen Signed-off-by: Mike McGowen Signed-off-by: David Strahan Acked-by: Don Brace Link: https://lore.kernel.org/linux-scsi/20260722220401.6357-3-david.strahan@microchip.com/ Link: https://lore.kernel.org/linux-scsi/20260722220401.6357-1-david.strahan@microchip.com/ Link: https://patch.msgid.link/20260722220401.6357-3-david.strahan@microchip.com Signed-off-by: Martin K. Petersen --- drivers/scsi/smartpqi/smartpqi_init.c | 144 ++++++++++++++++++++++++++++++++++ include/uapi/linux/cciss_ioctl.h | 1 + 2 files changed, 145 insertions(+) (limited to 'include/uapi/linux') diff --git a/drivers/scsi/smartpqi/smartpqi_init.c b/drivers/scsi/smartpqi/smartpqi_init.c index 3a75b9fbedf4..473f1c9e4738 100644 --- a/drivers/scsi/smartpqi/smartpqi_init.c +++ b/drivers/scsi/smartpqi/smartpqi_init.c @@ -6935,6 +6935,144 @@ out: return rc; } +static int pqi_big_passthru_ioctl(struct pqi_ctrl_info *ctrl_info, void __user *arg) +{ + int rc; + char *kernel_buffer = NULL; + u16 iu_length; + size_t sense_data_length; + BIG_IOCTL_Command_struct iocommand; + struct pqi_raid_path_request request; + struct pqi_raid_error_info pqi_error_info; + struct ciss_error_info ciss_error_info; + + if (pqi_ctrl_offline(ctrl_info)) + return -ENXIO; + if (pqi_ofa_in_progress(ctrl_info) && pqi_ctrl_blocked(ctrl_info)) + return -EBUSY; + if (!arg) + return -EINVAL; + if (!capable(CAP_SYS_RAWIO)) + return -EPERM; + if (copy_from_user(&iocommand, arg, sizeof(iocommand))) + return -EFAULT; + if (iocommand.buf_size < 1 && + iocommand.Request.Type.Direction != XFER_NONE) + return -EINVAL; + if (iocommand.Request.CDBLen > sizeof(request.cdb)) + return -EINVAL; + if (iocommand.Request.Type.Type != TYPE_CMD) + return -EINVAL; + + switch (iocommand.Request.Type.Direction) { + case XFER_NONE: + case XFER_WRITE: + case XFER_READ: + case XFER_READ | XFER_WRITE: + break; + default: + return -EINVAL; + } + + if (iocommand.buf_size > 0) { + kernel_buffer = kmalloc(iocommand.buf_size, GFP_KERNEL); + if (!kernel_buffer) + return -ENOMEM; + if (iocommand.Request.Type.Direction & XFER_WRITE) { + if (copy_from_user(kernel_buffer, iocommand.buf, + iocommand.buf_size)) { + rc = -EFAULT; + goto out; + } + } else { + memset(kernel_buffer, 0, iocommand.buf_size); + } + } + + memset(&request, 0, sizeof(request)); + + request.header.iu_type = PQI_REQUEST_IU_RAID_PATH_IO; + iu_length = offsetof(struct pqi_raid_path_request, sg_descriptors) - + PQI_REQUEST_HEADER_LENGTH; + memcpy(request.lun_number, iocommand.LUN_info.LunAddrBytes, sizeof(request.lun_number)); + memcpy(request.cdb, iocommand.Request.CDB, iocommand.Request.CDBLen); + request.additional_cdb_bytes_usage = SOP_ADDITIONAL_CDB_BYTES_0; + + switch (iocommand.Request.Type.Direction) { + case XFER_NONE: + request.data_direction = SOP_NO_DIRECTION_FLAG; + break; + case XFER_WRITE: + request.data_direction = SOP_WRITE_FLAG; + break; + case XFER_READ: + request.data_direction = SOP_READ_FLAG; + break; + case XFER_READ | XFER_WRITE: + request.data_direction = SOP_BIDIRECTIONAL; + break; + } + + request.task_attribute = SOP_TASK_ATTRIBUTE_SIMPLE; + + if (iocommand.buf_size > 0) { + put_unaligned_le32(iocommand.buf_size, &request.buffer_length); + + rc = pqi_map_single(ctrl_info->pci_dev, + &request.sg_descriptors[0], kernel_buffer, + iocommand.buf_size, DMA_BIDIRECTIONAL); + if (rc) + goto out; + + iu_length += sizeof(request.sg_descriptors[0]); + } + + put_unaligned_le16(iu_length, &request.header.iu_length); + + if (ctrl_info->raid_iu_timeout_supported) + put_unaligned_le32(iocommand.Request.Timeout, &request.timeout); + + rc = pqi_submit_raid_request_synchronous(ctrl_info, &request.header, + PQI_SYNC_FLAGS_INTERRUPTABLE, &pqi_error_info); + + if (iocommand.buf_size > 0) + pqi_pci_unmap(ctrl_info->pci_dev, request.sg_descriptors, 1, DMA_BIDIRECTIONAL); + + memset(&iocommand.error_info, 0, sizeof(iocommand.error_info)); + + if (rc == 0) { + pqi_error_info_to_ciss(&pqi_error_info, &ciss_error_info); + iocommand.error_info.ScsiStatus = ciss_error_info.scsi_status; + iocommand.error_info.CommandStatus = ciss_error_info.command_status; + sense_data_length = ciss_error_info.sense_data_length; + if (sense_data_length) { + if (sense_data_length > sizeof(iocommand.error_info.SenseInfo)) + sense_data_length = sizeof(iocommand.error_info.SenseInfo); + memcpy(iocommand.error_info.SenseInfo, + pqi_error_info.data, sense_data_length); + iocommand.error_info.SenseLen = sense_data_length; + } + } + + if (copy_to_user(arg, &iocommand, sizeof(iocommand))) { + rc = -EFAULT; + goto out; + } + + if (rc == 0 && iocommand.buf_size > 0 && + (iocommand.Request.Type.Direction & XFER_READ)) { + if (copy_to_user(iocommand.buf, kernel_buffer, + iocommand.buf_size)) { + rc = -EFAULT; + } + } + +out: + kfree(kernel_buffer); + + return rc; +} + static int pqi_ioctl(struct scsi_device *sdev, unsigned int cmd, void __user *arg) { @@ -6958,6 +7096,12 @@ static int pqi_ioctl(struct scsi_device *sdev, unsigned int cmd, case CCISS_PASSTHRU: rc = pqi_passthru_ioctl(ctrl_info, arg); break; + case CCISS_BIG_PASSTHRU: + rc = pqi_big_passthru_ioctl(ctrl_info, arg); + break; + case CCISS_BIG_PASSTHRU_SUPPORTED: + rc = 0; + break; default: rc = -EINVAL; break; diff --git a/include/uapi/linux/cciss_ioctl.h b/include/uapi/linux/cciss_ioctl.h index 562230199461..8295f1bf3bf3 100644 --- a/include/uapi/linux/cciss_ioctl.h +++ b/include/uapi/linux/cciss_ioctl.h @@ -85,5 +85,6 @@ typedef struct _LogvolInfo_struct{ #define CCISS_RESCANDISK _IO(CCISS_IOC_MAGIC, 16) #define CCISS_GETLUNINFO _IOR(CCISS_IOC_MAGIC, 17, LogvolInfo_struct) #define CCISS_BIG_PASSTHRU _IOWR(CCISS_IOC_MAGIC, 18, BIG_IOCTL_Command_struct) +#define CCISS_BIG_PASSTHRU_SUPPORTED _IO(CCISS_IOC_MAGIC, 19) #endif /* _UAPICCISS_IOCTLH */ -- cgit v1.2.3 From 01498beb8d8651c9b7b67fb6bda1ed0f091a4d23 Mon Sep 17 00:00:00 2001 From: Antoine Bouyer Date: Sat, 27 Jun 2026 15:41:58 +0200 Subject: media: uapi: v4l2-isp: Add extensible statistics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend the v4l2-isp extensible format introduced for isp parameters buffers to the support buffers of ISP statistic. Like for ISP configuration purpose, that will help supporting various ISP hardware versions reporting different statistics data with less impact on userspace. Rename all 'v4l2_isp_params' types to generic 'v4l2_isp' types to prepare to use them for statistics as well and maintain the existing types for compatibility with existing userspace only. Signed-off-by: Antoine Bouyer Reviewed-by: Niklas Söderlund [Rework to remove 'v4l2_isp_stats' and unify types] Signed-off-by: Jacopo Mondi Signed-off-by: Sakari Ailus --- include/uapi/linux/media/v4l2-isp.h | 125 +++++++++++++++++++++++------------- 1 file changed, 79 insertions(+), 46 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/media/v4l2-isp.h b/include/uapi/linux/media/v4l2-isp.h index 779168f9058e..e4607e1217e1 100644 --- a/include/uapi/linux/media/v4l2-isp.h +++ b/include/uapi/linux/media/v4l2-isp.h @@ -13,25 +13,33 @@ #include /** - * enum v4l2_isp_params_version - V4L2 ISP parameters versioning + * enum v4l2_isp_version - V4L2 ISP serialization format versioning * - * @V4L2_ISP_PARAMS_VERSION_V0: First version of the V4L2 ISP parameters format - * (for compatibility) - * @V4L2_ISP_PARAMS_VERSION_V1: First version of the V4L2 ISP parameters format + * @V4L2_ISP_VERSION_V0: First version of the V4L2 ISP serialization format + * (for compatibility) + * @V4L2_ISP_VERSION_V1: First version of the V4L2 ISP serialization format * * V0 and V1 are identical in order to support drivers compatible with the V4L2 - * ISP parameters format already upstreamed which use either 0 or 1 as their - * versioning identifier. Both V0 and V1 refers to the first version of the - * V4L2 ISP parameters format. + * ISP format already upstreamed which use either 0 or 1 as their versioning + * identifier. Both V0 and V1 refers to the first version of the V4L2 ISP + * serialization format. * - * Future revisions of the V4L2 ISP parameters format should start from the + * Future revisions of the V4L2 ISP serialization format should start from the * value of 2. */ -enum v4l2_isp_params_version { - V4L2_ISP_PARAMS_VERSION_V0 = 0, - V4L2_ISP_PARAMS_VERSION_V1 +enum v4l2_isp_version { + V4L2_ISP_VERSION_V0 = 0, + V4L2_ISP_VERSION_V1 }; +/* + * Compatibility with existing users of v4l2_isp_params which pre-date the + * introduction of v4l2_isp_stats. + */ +#define v4l2_isp_params_version v4l2_isp_version +#define V4L2_ISP_PARAMS_VERSION_V0 V4L2_ISP_VERSION_V0 +#define V4L2_ISP_PARAMS_VERSION_V1 V4L2_ISP_VERSION_V1 + #define V4L2_ISP_PARAMS_FL_BLOCK_DISABLE (1U << 0) #define V4L2_ISP_PARAMS_FL_BLOCK_ENABLE (1U << 1) @@ -39,64 +47,89 @@ enum v4l2_isp_params_version { * Reserve the first 8 bits for V4L2_ISP_PARAMS_FL_* flag. * * Driver-specific flags should be defined as: - * #define DRIVER_SPECIFIC_FLAG0 ((1U << V4L2_ISP_PARAMS_FL_DRIVER_FLAGS(0)) - * #define DRIVER_SPECIFIC_FLAG1 ((1U << V4L2_ISP_PARAMS_FL_DRIVER_FLAGS(1)) + * #define DRIVER_SPECIFIC_FLAG0 ((1U << V4L2_ISP_FL_DRIVER_FLAGS(0)) + * #define DRIVER_SPECIFIC_FLAG1 ((1U << V4L2_ISP_FL_DRIVER_FLAGS(1)) */ -#define V4L2_ISP_PARAMS_FL_DRIVER_FLAGS(n) ((n) + 8) +#define V4L2_ISP_FL_DRIVER_FLAGS(n) ((n) + 8) /** - * struct v4l2_isp_params_block_header - V4L2 extensible parameters block header - * @type: The parameters block type (driver-specific) + * struct v4l2_isp_block_header - V4L2 extensible block header + * @type: The parameters or statistics block type (driver-specific) * @flags: A bitmask of block flags (driver-specific) - * @size: Size (in bytes) of the parameters block, including this header + * @size: Size (in bytes) of the block, including this header * - * This structure represents the common part of all the ISP configuration - * blocks. Each parameters block shall embed an instance of this structure type - * as its first member, followed by the block-specific configuration data. + * This structure represents the common part of all the ISP configuration or + * statistic blocks. Each block shall embed an instance of this structure type + * as its first member, followed by the block-specific configuration or + * statistic data. * * The @type field is an ISP driver-specific value that identifies the block - * type. The @size field specifies the size of the parameters block. + * type. The @size field specifies the size of the block, including this + * header. * - * The @flags field is a bitmask of per-block flags V4L2_PARAMS_ISP_FL_* and - * driver-specific flags specified by the driver header. + * The @flags field is a bitmask of per-block flags. If a block is used for + * configuration parameters this field can be a combination of + * V4L2_ISP_PARAMS_FL_* and driver-specific flags. If a block is used + * for statistics this fields is used to report optional + * driver-specific flags, if any. */ -struct v4l2_isp_params_block_header { +struct v4l2_isp_block_header { __u16 type; __u16 flags; __u32 size; } __attribute__((aligned(8))); /** - * struct v4l2_isp_params_buffer - V4L2 extensible parameters configuration - * @version: The parameters buffer version (driver-specific) - * @data_size: The configuration data effective size, excluding this header - * @data: The configuration data + * v4l2_isp_params_block_header - V4L2 extensible parameters block header + * + * Compatibility with existing users of v4l2_isp_params_block_header + * which pre-date the introduction of v4l2_isp_block_header. + */ +#define v4l2_isp_params_block_header v4l2_isp_block_header + +/** + * struct v4l2_isp_buffer - V4L2 extensible buffer + * @version: The extensible buffer version (driver-specific) + * @data_size: The data effective size, excluding this header + * @data: The configuration or statistics data * - * This structure contains the configuration parameters of the ISP algorithms, - * serialized by userspace into a data buffer. Each configuration parameter - * block is represented by a block-specific structure which contains a - * :c:type:`v4l2_isp_params_block_header` entry as first member. Userspace - * populates the @data buffer with configuration parameters for the blocks that - * it intends to configure. As a consequence, the data buffer effective size - * changes according to the number of ISP blocks that userspace intends to - * configure and is set by userspace in the @data_size field. + * This structure contains ISP configuration parameters or ISP hardware + * statistics serialized into a data buffer. Each block is represented by a + * block-specific structure which contains a :c:type:`v4l2_isp_block_header` + * entry as first member. * - * The parameters buffer is versioned by the @version field to allow modifying - * and extending its definition. Userspace shall populate the @version field to - * inform the driver about the version it intends to use. The driver will parse - * and handle the @data buffer according to the data layout specific to the - * indicated version and return an error if the desired version is not + * When used for ISP parameters, userspace populates the @data buffer with + * configuration parameters for the blocks that it intends to configure. As a + * consequence, the data buffer effective size changes according to the number + * of ISP blocks that userspace intends to configure. + * + * When used to report ISP statistics, the driver populates the @data buffer + * with statistics for each supported measurement block. + * + * The buffer is versioned by the @version field to allow modifying + * and extending its definition. The writer shall populate the @version field + * to inform the reader about the version it intends to use. The reader will + * parse and handle the @data buffer according to the data layout specific to + * the indicated version and return an error if the desired version is not * supported. * - * For each ISP block that userspace wants to configure, a block-specific - * structure is appended to the @data buffer, one after the other without gaps - * in between. Userspace shall populate the @data_size field with the effective - * size, in bytes, of the @data buffer. + * For each ISP block, a block-specific structure is appended to the @data + * buffer, one after the other without gaps in between. The writer shall + * populate the @data_size field with the effective size, in bytes, of the + * @data buffer. */ -struct v4l2_isp_params_buffer { +struct v4l2_isp_buffer { __u32 version; __u32 data_size; __u8 data[] __counted_by(data_size); }; +/** + * v4l2_isp_params_buffer - V4L2 extensible parameters compatibility + * + * Compatibility with existing users of v4l2_isp_params_buffer which + * pre-date the introduction of v4l2_isp_buffer. + */ +#define v4l2_isp_params_buffer v4l2_isp_buffer + #endif /* _UAPI_V4L2_ISP_H_ */ -- cgit v1.2.3 From aeda66c841f6acc13f390f512d06e6471b33da78 Mon Sep 17 00:00:00 2001 From: Jai Luthra Date: Thu, 30 Jul 2026 19:12:29 +0200 Subject: media: Add RPPX1_PARAMS and RPPX1_STATS meta formats MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Register V4L2 metadata fourcc codes for the Dreamchip RPP-X1 ISP parameters and statistics buffers. These formats are used by the driver to exchange ISP configuration and 3A statistics with userspace through the extensible parameters framework. Signed-off-by: Jai Luthra Reviewed-by: Jacopo Mondi Reviewed-by: Niklas Söderlund Signed-off-by: Niklas Söderlund Signed-off-by: Sakari Ailus --- drivers/media/v4l2-core/v4l2-ioctl.c | 2 ++ include/uapi/linux/videodev2.h | 4 ++++ 2 files changed, 6 insertions(+) (limited to 'include/uapi/linux') diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c index a2b650f4ec3c..17ba1ae70735 100644 --- a/drivers/media/v4l2-core/v4l2-ioctl.c +++ b/drivers/media/v4l2-core/v4l2-ioctl.c @@ -1471,6 +1471,8 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt) case V4L2_META_FMT_C3ISP_STATS: descr = "Amlogic C3 ISP Statistics"; break; case V4L2_META_FMT_MALI_C55_PARAMS: descr = "ARM Mali-C55 ISP Parameters"; break; case V4L2_META_FMT_MALI_C55_STATS: descr = "ARM Mali-C55 ISP 3A Statistics"; break; + case V4L2_META_FMT_RPPX1_PARAMS: descr = "Dreamchip RPP-X1 ISP Parameters"; break; + case V4L2_META_FMT_RPPX1_STATS: descr = "Dreamchip RPP-X1 ISP Statistics"; break; case V4L2_PIX_FMT_NV12_8L128: descr = "NV12 (8x128 Linear)"; break; case V4L2_PIX_FMT_NV12M_8L128: descr = "NV12M (8x128 Linear)"; break; case V4L2_PIX_FMT_NV12_10BE_8L128: descr = "10-bit NV12 (8x128 Linear, BE)"; break; diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h index eda4492e40dc..5373dba640fa 100644 --- a/include/uapi/linux/videodev2.h +++ b/include/uapi/linux/videodev2.h @@ -889,6 +889,10 @@ struct v4l2_pix_format { #define V4L2_META_FMT_MALI_C55_PARAMS v4l2_fourcc('C', '5', '5', 'P') /* ARM Mali-C55 Parameters */ #define V4L2_META_FMT_MALI_C55_STATS v4l2_fourcc('C', '5', '5', 'S') /* ARM Mali-C55 3A Statistics */ +/* Vendor specific - used for Dreamchip RPP-X1 ISP */ +#define V4L2_META_FMT_RPPX1_PARAMS v4l2_fourcc('D', 'R', '1', 'P') /* Dreamchip RPP-X1 Parameters */ +#define V4L2_META_FMT_RPPX1_STATS v4l2_fourcc('D', 'R', '1', 'S') /* Dreamchip RPP-X1 Statistics */ + #ifdef __KERNEL__ /* * Line-based metadata formats. Remember to update v4l_fill_fmtdesc() when -- cgit v1.2.3 From d2655e8b788f2e1cd0c601c4bd582831f04de7d8 Mon Sep 17 00:00:00 2001 From: Jai Luthra Date: Thu, 30 Jul 2026 19:12:30 +0200 Subject: media: uapi: Add extensible param and stats blocks for RPPX1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the scaffolding for the parameters and statistics buffers for Dreamchip RPPX1. The parameters for each ISP function block will be added together with the logic for it. In addition to the scaffolding add a common struct that describes the measurement window. This struct is not specific to any block and will used by many of them. Signed-off-by: Jai Luthra [Niklas: Commit message] Signed-off-by: Niklas Söderlund Reviewed-by: Jacopo Mondi Signed-off-by: Sakari Ailus --- include/uapi/linux/media/dreamchip/rppx1-config.h | 69 +++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 include/uapi/linux/media/dreamchip/rppx1-config.h (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/media/dreamchip/rppx1-config.h b/include/uapi/linux/media/dreamchip/rppx1-config.h new file mode 100644 index 000000000000..1f2548db73ad --- /dev/null +++ b/include/uapi/linux/media/dreamchip/rppx1-config.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Dreamchip RPP-X1 ISP Driver - Userspace API + * + * Copyright (C) 2026 Renesas Electronics Corp. + * Copyright (C) 2026 Ideas on Board Oy + * Copyright (C) 2026 Ragnatech AB + */ + +#ifndef __UAPI_RPP_X1_CONFIG_H +#define __UAPI_RPP_X1_CONFIG_H + +#include + +/** + * struct rppx1_window - Measurement window + * + * RPP-X1 measurement window. Different blocks use a window or multiple + * windows for measurement purposes. This defines a common type for all of + * them. The number of relevant bits depends on the block where the window is + * used and is specified in the per-block description + * + * @h_offs: horizontal offset from the left of the frame in pixels + * @v_offs: vertical offset from the top of the frame in pixels + * @h_size: horizontal size of the window in pixels + * @v_size: vertical size of the window in pixels + */ +struct rppx1_window { + __u16 h_offs; + __u16 v_offs; + __u16 h_size; + __u16 v_size; +}; + +/* --------------------------------------------------------------------------- + * Parameter Structures + * + * The same ISP block might be instantiated in multiple pipeliness and operate + * on a different bitdepth/precision. For fields of varying length among + * different instances of the same block, use a data type that can accommodate + * the larger bitdepth/precision. + */ + +/** + * RPPX1_PARAMS_MAX_SIZE - Maximum size of all RPP-X1 parameter blocks + * + * Some types are reported twice as the same block might be instantiated in + * multiple pipes. + */ +#define RPPX1_PARAMS_MAX_SIZE 0 + +/* --------------------------------------------------------------------------- + * Statistics Structures + * + * The same ISP block might be instantiated in multiple pipeliness and operate + * on a different bitdepth/precision. For fields of varying length among + * different instances of the same block, use a data type that can accommodate + * the larger bitdepth/precision. + */ + +/** + * RPPX1_STATS_MAX_SIZE - Maximum size of all RPP-X1 statistics + * + * Some types are reported twice as the same block might be instantiated in + * multiple pipes. + */ +#define RPPX1_STATS_MAX_SIZE 0 + +#endif /* __UAPI_RPP_X1_CONFIG_H */ -- cgit v1.2.3 From 884d6794a816473070fa66a6b16b631861d15566 Mon Sep 17 00:00:00 2001 From: Niklas Söderlund Date: Thu, 30 Jul 2026 19:12:33 +0200 Subject: media: rppx1: wbmeas: Add support for white balance measurement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend the RPPX1 driver to allow setting the white balance measurement configuration and consuming the resulting statistics. It uses the RPPX1 framework for parameters and its writer abstraction to allow the user to control how, and when, configuration is applied to the RPPX1. Signed-off-by: Niklas Söderlund Co-developed-by: Jai Luthra Signed-off-by: Jai Luthra Co-developed-by: Jacopo Mondi Signed-off-by: Jacopo Mondi Reviewed-by: Jacopo Mondi Signed-off-by: Sakari Ailus --- .../media/platform/dreamchip/rppx1/rpp_module.h | 2 + .../media/platform/dreamchip/rppx1/rpp_params.c | 4 + drivers/media/platform/dreamchip/rppx1/rpp_stats.c | 18 ++++ .../media/platform/dreamchip/rppx1/rppx1_wbmeas.c | 103 ++++++++++++++++++++ include/uapi/linux/media/dreamchip/rppx1-config.h | 107 ++++++++++++++++++++- 5 files changed, 232 insertions(+), 2 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/media/platform/dreamchip/rppx1/rpp_module.h b/drivers/media/platform/dreamchip/rppx1/rpp_module.h index e92e77542980..64a1b848f1ea 100644 --- a/drivers/media/platform/dreamchip/rppx1/rpp_module.h +++ b/drivers/media/platform/dreamchip/rppx1/rpp_module.h @@ -47,10 +47,12 @@ void rpp_module_clrset(struct rpp_module *mod, u32 offset, u32 mask, u32 value); union rppx1_params_block { struct v4l2_isp_block_header header; + struct rppx1_wbmeas_params wbmeas; }; union rppx1_stats_block { struct v4l2_isp_block_header header; + struct rppx1_wbmeas_stats wbmeas; }; struct rpp_module_ops { diff --git a/drivers/media/platform/dreamchip/rppx1/rpp_params.c b/drivers/media/platform/dreamchip/rppx1/rpp_params.c index af45ad5750b3..7325abeba92a 100644 --- a/drivers/media/platform/dreamchip/rppx1/rpp_params.c +++ b/drivers/media/platform/dreamchip/rppx1/rpp_params.c @@ -17,6 +17,7 @@ static const struct v4l2_isp_params_block_type_info rppx1_ext_params_blocks_info[] = { + RPPX1_PARAMS_BLOCK_INFO(WBMEAS_POST, wbmeas), }; int rppx1_params(struct rppx1 *rpp, struct vb2_buffer *vb, size_t max_size, @@ -50,6 +51,9 @@ int rppx1_params(struct rppx1 *rpp, struct vb2_buffer *vb, size_t max_size, block_offset += block->header.size; switch (block->header.type) { + case RPPX1_PARAMS_BLOCK_TYPE_WBMEAS_POST: + module = &rpp->post.wbmeas; + break; default: dev_warn(rpp->dev, "Not handled RPPX1 block type: 0x%04x\n", diff --git a/drivers/media/platform/dreamchip/rppx1/rpp_stats.c b/drivers/media/platform/dreamchip/rppx1/rpp_stats.c index f7eb60deae10..c99ea158abcf 100644 --- a/drivers/media/platform/dreamchip/rppx1/rpp_stats.c +++ b/drivers/media/platform/dreamchip/rppx1/rpp_stats.c @@ -15,6 +15,11 @@ .size = sizeof(struct rppx1_ ## block ## _stats), \ } +static const struct v4l2_isp_stats_block_type_info +rppx1_stats_blocks_info[] = { + RPPX1_STATS_BLOCK_INFO(WBMEAS_POST, wbmeas), +}; + #define rppx1_init_stats_block(rpp, buf, type) \ ((union rppx1_stats_block *) \ v4l2_isp_stats_init_block((rpp)->dev, (buf), \ @@ -24,5 +29,18 @@ void rppx1_stats_fill_isr(struct rppx1 *rpp, u32 isc, void *buf) { + struct v4l2_isp_buffer *stats = buf; + union rppx1_stats_block *block; + + v4l2_isp_stats_init_buffer(stats, V4L2_ISP_VERSION_V1); + + if (isc & RPPX1_IRQ_ID_POST_AWB_MEAS) { + block = rppx1_init_stats_block(rpp, stats, + RPPX1_STATS_BLOCK_TYPE_WBMEAS_POST); + if (IS_ERR(block)) + return; + + rpp_module_call(&rpp->post.wbmeas, fill_stats, block); + } } EXPORT_SYMBOL_GPL(rppx1_stats_fill_isr); diff --git a/drivers/media/platform/dreamchip/rppx1/rppx1_wbmeas.c b/drivers/media/platform/dreamchip/rppx1/rppx1_wbmeas.c index 60b9c076ec42..f0a943556238 100644 --- a/drivers/media/platform/dreamchip/rppx1/rppx1_wbmeas.c +++ b/drivers/media/platform/dreamchip/rppx1/rppx1_wbmeas.c @@ -57,6 +57,109 @@ static int rppx1_wbmeas_probe(struct rpp_module *mod) return 0; } +static int +rppx1_wbmeas_fill_params(struct rpp_module *mod, + const union rppx1_params_block *block, + rppx1_reg_write write, void *priv) +{ + const struct rppx1_wbmeas_params *cfg = &block->wbmeas; + u32 awb_meas_props; + + /* If the modules is disabled, simply bypass it. */ + if (cfg->header.flags & V4L2_ISP_PARAMS_FL_BLOCK_DISABLE) { + write(priv, mod->base + AWB_MEAS_PROP_REG, 0); + return 0; + } + + /* Program measurement window. */ + write(priv, mod->base + AWB_MEAS_H_OFFS_REG, cfg->wnd.h_offs); + write(priv, mod->base + AWB_MEAS_V_OFFS_REG, cfg->wnd.v_offs); + write(priv, mod->base + AWB_MEAS_H_SIZE_REG, cfg->wnd.h_size); + write(priv, mod->base + AWB_MEAS_V_SIZE_REG, cfg->wnd.v_size); + + /* Set number of frames to sample. */ + write(priv, mod->base + AWB_MEAS_FRAMES_REG, cfg->frames); + + if (cfg->mode == RPPX1_WBMEAS_MODE_YCBCR) { + write(priv, mod->base + AWB_MEAS_REF_CB_MAX_B_REG, + cfg->ref_cb_max_b); + write(priv, mod->base + AWB_MEAS_REF_CR_MAX_R_REG, + cfg->ref_cr_max_r); + write(priv, mod->base + AWB_MEAS_MAX_Y_REG, cfg->max_y); + write(priv, mod->base + AWB_MEAS_MIN_Y_MAX_G_REG, + cfg->min_y_max_g); + write(priv, mod->base + AWB_MEAS_MAX_CSUM_REG, cfg->max_csum); + write(priv, mod->base + AWB_MEAS_MIN_C_REG, cfg->min_c); + + /* + * Program the color conversion matrix coefficients and the + * per-color channel offsets. + */ + for (unsigned int i = 0; i < 3; i++) { + for (unsigned int j = 0; j < 3; j++) { + unsigned int index = i * 3 + j; + + write(priv, + mod->base + AWB_MEAS_CCOR_COEFF_REG(index), + cfg->ccor_coeff[i][j]); + } + } + + write(priv, mod->base + AWB_MEAS_CCOR_OFFSET_R_REG, + cfg->ccor_offs[0]); + write(priv, mod->base + AWB_MEAS_CCOR_OFFSET_G_REG, + cfg->ccor_offs[1]); + write(priv, mod->base + AWB_MEAS_CCOR_OFFSET_B_REG, + cfg->ccor_offs[2]); + + awb_meas_props = cfg->ymax_cmp ? AWB_MEAS_PROP_YMAX : 0; + } else { + write(priv, mod->base + AWB_MEAS_REF_CB_MAX_B_REG, + cfg->ref_cb_max_b); + write(priv, mod->base + AWB_MEAS_REF_CR_MAX_R_REG, + cfg->ref_cr_max_r); + write(priv, mod->base + AWB_MEAS_MIN_Y_MAX_G_REG, + cfg->min_y_max_g); + + /* Bypass color conversion matrix and color offsets. */ + write(priv, mod->base + AWB_MEAS_CCOR_COEFF_REG(0), 0x1000); + write(priv, mod->base + AWB_MEAS_CCOR_COEFF_REG(1), 0x0000); + write(priv, mod->base + AWB_MEAS_CCOR_COEFF_REG(2), 0x0000); + write(priv, mod->base + AWB_MEAS_CCOR_COEFF_REG(3), 0x0000); + write(priv, mod->base + AWB_MEAS_CCOR_COEFF_REG(4), 0x1000); + write(priv, mod->base + AWB_MEAS_CCOR_COEFF_REG(5), 0x0000); + write(priv, mod->base + AWB_MEAS_CCOR_COEFF_REG(6), 0x0000); + write(priv, mod->base + AWB_MEAS_CCOR_COEFF_REG(7), 0x0000); + write(priv, mod->base + AWB_MEAS_CCOR_COEFF_REG(8), 0x1000); + write(priv, mod->base + AWB_MEAS_CCOR_OFFSET_R_REG, 0x00000000); + write(priv, mod->base + AWB_MEAS_CCOR_OFFSET_G_REG, 0x00000000); + write(priv, mod->base + AWB_MEAS_CCOR_OFFSET_B_REG, 0x00000000); + + awb_meas_props = AWB_MEAS_PROP_MEAS_MODE_RGB; + } + + write(priv, mod->base + AWB_MEAS_PROP_REG, + awb_meas_props | AWB_MEAS_PROP_AWB_MODE_ON); + + return 0; +} + +static int rppx1_wbmeas_fill_stats(struct rpp_module *mod, + union rppx1_stats_block *block) +{ + struct rppx1_wbmeas_stats *stats = &block->wbmeas; + + /* Return measurements at native hardware precision. */ + stats->cnt = rpp_module_read(mod, AWB_MEAS_WHITE_CNT_REG); + stats->mean_y_or_g = rpp_module_read(mod, AWB_MEAS_MEAN_Y_G_REG); + stats->mean_cb_or_b = rpp_module_read(mod, AWB_MEAS_MEAN_CB_B_REG); + stats->mean_cr_or_r = rpp_module_read(mod, AWB_MEAS_MEAN_CR_R_REG); + + return 0; +} + const struct rpp_module_ops rppx1_wbmeas_ops = { .probe = rppx1_wbmeas_probe, + .fill_params = rppx1_wbmeas_fill_params, + .fill_stats = rppx1_wbmeas_fill_stats }; diff --git a/include/uapi/linux/media/dreamchip/rppx1-config.h b/include/uapi/linux/media/dreamchip/rppx1-config.h index 1f2548db73ad..f674b041664e 100644 --- a/include/uapi/linux/media/dreamchip/rppx1-config.h +++ b/include/uapi/linux/media/dreamchip/rppx1-config.h @@ -41,13 +41,87 @@ struct rppx1_window { * the larger bitdepth/precision. */ +/** + * enum rppx1_params_block_type - RPP-X1 extensible params block types + * + * NOTE: Only append to the enumeration as the numbers are uAPI. + * + * @RPPX1_PARAMS_BLOCK_TYPE_WBMEAS_POST: AWB Measurement Configuration + */ +enum rppx1_params_block_type { + RPPX1_PARAMS_BLOCK_TYPE_WBMEAS_POST, +}; + +/** + * enum rppx1_wbmeas_mode - AWB measurement mode + * + * @RPPX1_WBMEAS_MODE_YCBCR: YCbCr measurement mode + * @RPPX1_WBMEAS_MODE_RGB: RGB measurement mode + */ +enum rppx1_wbmeas_mode { + RPPX1_WBMEAS_MODE_YCBCR, + RPPX1_WBMEAS_MODE_RGB, +}; + +/** + * struct rppx1_wbmeas_params - AWB measurement configuration + * + * The Auto-White Balance measurement module is available on the MAIN_POST pipe. + * It supports two measurement modes, selected by the @mode field. The + * measurement window is programmed through the @wnd field. + * + * To support measurement in YCbCr mode a color conversion matrix with + * programmable offset is available in the @ccor_coeff and @ccor_offs fields. + * The color conversion matrix coefficients are represented as 16 bits signed + * Q4.12 numbers ranging from -8 to +7.99. The per-color channel offsets are + * represented as 25 bits 2's complement integer numbers ranging from -16777216 + * to +16777215. + * + * @header: block header (type = RPPX1_PARAMS_BLOCK_TYPE_WBMEAS_POST) + * @wnd: measurement window + * @mode: measurement mode (from enum rppx1_wbmeas_mode) + * @ymax_cmp: enable Y_MAX compare using @max_y + * @frames: number of frames for mean value calculation (0 = 1 frame) + * @reserved: padding + * @ref_cr_max_r: reference Cr or max red value in RGB mode, 24 bits + * @ref_cb_max_b: reference Cb or max blue value in RGB mode, 24 bits + * @min_y_max_g: luminance minimum value or max green value in RGB mode, 24 bits + * @max_y: luminance maximum value, only valid if @mode is set to YCbCr and + * @ymax_cmp is set to enabled, 24 bits + * @max_csum: chrominance sum maximum value, 24 bits + * @min_c: chrominance minimum value, 24 bits + * @ccor_coeff: coefficients for color conversion matrix, signed 16 bits Q4.6 + * @reserved2: padding + * @ccor_offs: R-G-B color conversion coefficients, signed 25 bits 2's complement + * @reserved3: padding + */ +struct rppx1_wbmeas_params { + struct v4l2_isp_params_block_header header; + struct rppx1_window wnd; + __u8 mode; + __u8 ymax_cmp; + __u8 frames; + __u8 reserved; + __u32 ref_cr_max_r; + __u32 ref_cb_max_b; + __u32 min_y_max_g; + __u32 max_y; + __u32 max_csum; + __u32 min_c; + __u16 ccor_coeff[3][3]; + __u16 reserved2; + __u32 ccor_offs[3]; + __u32 reserved3; +}; + /** * RPPX1_PARAMS_MAX_SIZE - Maximum size of all RPP-X1 parameter blocks * * Some types are reported twice as the same block might be instantiated in * multiple pipes. */ -#define RPPX1_PARAMS_MAX_SIZE 0 +#define RPPX1_PARAMS_MAX_SIZE \ + (sizeof(struct rppx1_wbmeas_params)) /* --------------------------------------------------------------------------- * Statistics Structures @@ -58,12 +132,41 @@ struct rppx1_window { * the larger bitdepth/precision. */ +/** + * enum rppx1_stats_block_type - RPP-X1 extensible stats block types + * + * NOTE: Only append to the enumeration as the numbers are uAPI. + * + * @RPPX1_STATS_BLOCK_TYPE_WBMEAS_POST: post-fusion white-balance measurement + */ +enum rppx1_stats_block_type { + RPPX1_STATS_BLOCK_TYPE_WBMEAS_POST, +}; + +/** + * struct rppx1_wbmeas_stats - AWB statistics + * + * @header: block header (type = RPPX1_STATS_BLOCK_TYPE_WBMEAS_POST) + * @cnt: Number of pixels matched + * @mean_y_or_g: mean Y (or G in RGB mode) value, 24-bit + * @mean_cb_or_b: mean Cb (or B in RGB mode) value, 24-bit + * @mean_cr_or_r: mean Cr (or R in RGB mode) value, 24-bit + */ +struct rppx1_wbmeas_stats { + struct v4l2_isp_block_header header; + __u32 cnt; + __u32 mean_y_or_g; + __u32 mean_cb_or_b; + __u32 mean_cr_or_r; +}; + /** * RPPX1_STATS_MAX_SIZE - Maximum size of all RPP-X1 statistics * * Some types are reported twice as the same block might be instantiated in * multiple pipes. */ -#define RPPX1_STATS_MAX_SIZE 0 +#define RPPX1_STATS_MAX_SIZE \ + (sizeof(struct rppx1_wbmeas_stats)) #endif /* __UAPI_RPP_X1_CONFIG_H */ -- cgit v1.2.3 From 9ebf50010c686f77ff403d07548a8ad678184f2a Mon Sep 17 00:00:00 2001 From: Niklas Söderlund Date: Thu, 30 Jul 2026 19:12:34 +0200 Subject: media: rppx1: awbg: Add support for white balance gain settings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend the RPPX1 driver to allow setting the white balance gain configuration parameters. It uses the RPPX1 framework for parameters and its writer abstraction to allow the user to control how, and when, configuration is applied to the RPPX1. Signed-off-by: Niklas Söderlund Co-developed-by: Jai Luthra Signed-off-by: Jai Luthra Co-developed-by: Jacopo Mondi Signed-off-by: Jacopo Mondi Reviewed-by: Jacopo Mondi Signed-off-by: Sakari Ailus --- .../media/platform/dreamchip/rppx1/rpp_module.h | 1 + .../media/platform/dreamchip/rppx1/rpp_params.c | 5 +++ .../media/platform/dreamchip/rppx1/rppx1_awbg.c | 32 +++++++++++++++++ include/uapi/linux/media/dreamchip/rppx1-config.h | 40 +++++++++++++++++++++- 4 files changed, 77 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/drivers/media/platform/dreamchip/rppx1/rpp_module.h b/drivers/media/platform/dreamchip/rppx1/rpp_module.h index 64a1b848f1ea..0e36b717b4ef 100644 --- a/drivers/media/platform/dreamchip/rppx1/rpp_module.h +++ b/drivers/media/platform/dreamchip/rppx1/rpp_module.h @@ -47,6 +47,7 @@ void rpp_module_clrset(struct rpp_module *mod, u32 offset, u32 mask, u32 value); union rppx1_params_block { struct v4l2_isp_block_header header; + struct rppx1_awbg_params awbg; struct rppx1_wbmeas_params wbmeas; }; diff --git a/drivers/media/platform/dreamchip/rppx1/rpp_params.c b/drivers/media/platform/dreamchip/rppx1/rpp_params.c index 7325abeba92a..17bbcf366970 100644 --- a/drivers/media/platform/dreamchip/rppx1/rpp_params.c +++ b/drivers/media/platform/dreamchip/rppx1/rpp_params.c @@ -17,6 +17,8 @@ static const struct v4l2_isp_params_block_type_info rppx1_ext_params_blocks_info[] = { + RPPX1_PARAMS_BLOCK_INFO(AWBG_PRE1, awbg), + RPPX1_PARAMS_BLOCK_INFO(AWBG_PRE2, awbg), RPPX1_PARAMS_BLOCK_INFO(WBMEAS_POST, wbmeas), }; @@ -51,6 +53,9 @@ int rppx1_params(struct rppx1 *rpp, struct vb2_buffer *vb, size_t max_size, block_offset += block->header.size; switch (block->header.type) { + case RPPX1_PARAMS_BLOCK_TYPE_AWBG_PRE1: + module = &rpp->pre1.awbg; + break; case RPPX1_PARAMS_BLOCK_TYPE_WBMEAS_POST: module = &rpp->post.wbmeas; break; diff --git a/drivers/media/platform/dreamchip/rppx1/rppx1_awbg.c b/drivers/media/platform/dreamchip/rppx1/rppx1_awbg.c index 4a242b41142b..f30e12d6f880 100644 --- a/drivers/media/platform/dreamchip/rppx1/rppx1_awbg.c +++ b/drivers/media/platform/dreamchip/rppx1/rppx1_awbg.c @@ -26,6 +26,38 @@ static int rppx1_awbg_probe(struct rpp_module *mod) return 0; } +static int +rppx1_awbg_fill_params(struct rpp_module *mod, + const union rppx1_params_block *block, + rppx1_reg_write write, void *priv) +{ + const struct rppx1_awbg_params *cfg = &block->awbg; + + /* If the modules is disabled, simply bypass it. */ + if (cfg->header.flags & V4L2_ISP_PARAMS_FL_BLOCK_DISABLE) { + write(priv, mod->base + AWB_ENABLE_REG, 0); + return 0; + } + + /* + * RPP gains are 18-bit with 12 bit fractional part and 0x1000 = 1.0, + * giving a possible range of 0.0 to 64.0. NOTE: RPP documentation is + * contradictory this is the register definition, the function + * description states 0x400 = 1.0 AND 18-bit with 12 fractional bits, + * which is not possible... + */ + + write(priv, mod->base + AWB_GAIN_GR_REG, cfg->gain_green_r); + write(priv, mod->base + AWB_GAIN_GB_REG, cfg->gain_green_b); + write(priv, mod->base + AWB_GAIN_R_REG, cfg->gain_red); + write(priv, mod->base + AWB_GAIN_B_REG, cfg->gain_blue); + + write(priv, mod->base + AWB_ENABLE_REG, AWB_ENABLE_AWB_GAIN_EN); + + return 0; +} + const struct rpp_module_ops rppx1_awbg_ops = { .probe = rppx1_awbg_probe, + .fill_params = rppx1_awbg_fill_params, }; diff --git a/include/uapi/linux/media/dreamchip/rppx1-config.h b/include/uapi/linux/media/dreamchip/rppx1-config.h index f674b041664e..b388a9003d8b 100644 --- a/include/uapi/linux/media/dreamchip/rppx1-config.h +++ b/include/uapi/linux/media/dreamchip/rppx1-config.h @@ -47,9 +47,15 @@ struct rppx1_window { * NOTE: Only append to the enumeration as the numbers are uAPI. * * @RPPX1_PARAMS_BLOCK_TYPE_WBMEAS_POST: AWB Measurement Configuration + * @RPPX1_PARAMS_BLOCK_TYPE_AWBG_PRE1: PRE1 pipe White Balance Gains + * @RPPX1_PARAMS_BLOCK_TYPE_AWBG_PRE2: PRE2 White Balance Gains + * @RPPX1_PARAMS_BLOCK_TYPE_AWBG_POST: MAIN_POST White Balance Gains */ enum rppx1_params_block_type { RPPX1_PARAMS_BLOCK_TYPE_WBMEAS_POST, + RPPX1_PARAMS_BLOCK_TYPE_AWBG_PRE1, + RPPX1_PARAMS_BLOCK_TYPE_AWBG_PRE2, + RPPX1_PARAMS_BLOCK_TYPE_AWBG_POST, }; /** @@ -114,6 +120,35 @@ struct rppx1_wbmeas_params { __u32 reserved3; }; +/** + * struct rppx1_awbg_params - WB gain configuration + * + * The RPP-X1 White Balance Gain module is available in the PRE1 and PRE2 + * pre-fusion pipes and in the MAIN_POST post-fusion pipe. Userspace selects + * which pipe to operate by setting the @header.type field to + * RPPX1_PARAMS_BLOCK_TYPE_AWBG_PRE1, RPPX1_PARAMS_BLOCK_TYPE_AWBG_PRE2 + * or RPPX1_PARAMS_BLOCK_TYPE_AWBG_POST. + * + * The White Balance module allows to specify per-color channel gains, expressed + * as unsigned fixed-point values as 18 bits unsigned integers in Q6.12 format + * with a maximum of 63.999. + * + * @header: block header (type = RPPX1_PARAMS_BLOCK_TYPE_AWBG_PRE1 or + * type = RPPX1_PARAMS_BLOCK_TYPE_AWBG_PRE2 or + * type = RPPX1_PARAMS_BLOCK_TYPE_AWBG_POST) + * @gain_red: gain for red component, 18-bit (unsigned Q6.12) + * @gain_green_r: gain for green component in red lines, 18-bit (unsigned Q6.12) + * @gain_blue: gain for blue component, 18-bit (unsigned Q6.12) + * @gain_green_b: gain for green component in blue lines, 18-bit (unsigned Q6.12) + */ +struct rppx1_awbg_params { + struct v4l2_isp_params_block_header header; + __u32 gain_red; + __u32 gain_green_r; + __u32 gain_blue; + __u32 gain_green_b; +}; + /** * RPPX1_PARAMS_MAX_SIZE - Maximum size of all RPP-X1 parameter blocks * @@ -121,7 +156,10 @@ struct rppx1_wbmeas_params { * multiple pipes. */ #define RPPX1_PARAMS_MAX_SIZE \ - (sizeof(struct rppx1_wbmeas_params)) + (sizeof(struct rppx1_wbmeas_params) + \ + sizeof(struct rppx1_awbg_params) + \ + sizeof(struct rppx1_awbg_params) + \ + sizeof(struct rppx1_awbg_params)) /* --------------------------------------------------------------------------- * Statistics Structures -- cgit v1.2.3 From b020bec416932aa923739df7d2f2b37b85cfd318 Mon Sep 17 00:00:00 2001 From: Niklas Söderlund Date: Thu, 30 Jul 2026 19:12:35 +0200 Subject: media: rppx1: exm: Add support for exposure measurement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend the RPPX1 driver to allow setting the exposure measurement configuration and consuming the resulting statistics. It uses the RPPX1 framework for parameters and its writer abstraction to allow the user to control how, and when, configuration is applied to the RPPX1. Signed-off-by: Niklas Söderlund Co-developed-by: Jai Luthra Signed-off-by: Jai Luthra Co-developed-by: Jacopo Mondi Signed-off-by: Jacopo Mondi Reviewed-by: Jacopo Mondi Signed-off-by: Sakari Ailus --- .../media/platform/dreamchip/rppx1/rpp_module.h | 2 + .../media/platform/dreamchip/rppx1/rpp_params.c | 5 + drivers/media/platform/dreamchip/rppx1/rpp_stats.c | 10 ++ drivers/media/platform/dreamchip/rppx1/rppx1_exm.c | 76 +++++++++++++ include/uapi/linux/media/dreamchip/rppx1-config.h | 125 ++++++++++++++++++++- 5 files changed, 216 insertions(+), 2 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/media/platform/dreamchip/rppx1/rpp_module.h b/drivers/media/platform/dreamchip/rppx1/rpp_module.h index 0e36b717b4ef..e968ec67b9f5 100644 --- a/drivers/media/platform/dreamchip/rppx1/rpp_module.h +++ b/drivers/media/platform/dreamchip/rppx1/rpp_module.h @@ -48,11 +48,13 @@ void rpp_module_clrset(struct rpp_module *mod, u32 offset, u32 mask, u32 value); union rppx1_params_block { struct v4l2_isp_block_header header; struct rppx1_awbg_params awbg; + struct rppx1_exm_params exm; struct rppx1_wbmeas_params wbmeas; }; union rppx1_stats_block { struct v4l2_isp_block_header header; + struct rppx1_exm_stats exm; struct rppx1_wbmeas_stats wbmeas; }; diff --git a/drivers/media/platform/dreamchip/rppx1/rpp_params.c b/drivers/media/platform/dreamchip/rppx1/rpp_params.c index 17bbcf366970..8c0f45e8066a 100644 --- a/drivers/media/platform/dreamchip/rppx1/rpp_params.c +++ b/drivers/media/platform/dreamchip/rppx1/rpp_params.c @@ -19,6 +19,8 @@ static const struct v4l2_isp_params_block_type_info rppx1_ext_params_blocks_info[] = { RPPX1_PARAMS_BLOCK_INFO(AWBG_PRE1, awbg), RPPX1_PARAMS_BLOCK_INFO(AWBG_PRE2, awbg), + RPPX1_PARAMS_BLOCK_INFO(EXM_PRE1, exm), + RPPX1_PARAMS_BLOCK_INFO(EXM_PRE2, exm), RPPX1_PARAMS_BLOCK_INFO(WBMEAS_POST, wbmeas), }; @@ -56,6 +58,9 @@ int rppx1_params(struct rppx1 *rpp, struct vb2_buffer *vb, size_t max_size, case RPPX1_PARAMS_BLOCK_TYPE_AWBG_PRE1: module = &rpp->pre1.awbg; break; + case RPPX1_PARAMS_BLOCK_TYPE_EXM_PRE1: + module = &rpp->pre1.exm; + break; case RPPX1_PARAMS_BLOCK_TYPE_WBMEAS_POST: module = &rpp->post.wbmeas; break; diff --git a/drivers/media/platform/dreamchip/rppx1/rpp_stats.c b/drivers/media/platform/dreamchip/rppx1/rpp_stats.c index c99ea158abcf..388c758d805d 100644 --- a/drivers/media/platform/dreamchip/rppx1/rpp_stats.c +++ b/drivers/media/platform/dreamchip/rppx1/rpp_stats.c @@ -17,6 +17,7 @@ static const struct v4l2_isp_stats_block_type_info rppx1_stats_blocks_info[] = { + RPPX1_STATS_BLOCK_INFO(EXM_PRE1, exm), RPPX1_STATS_BLOCK_INFO(WBMEAS_POST, wbmeas), }; @@ -34,6 +35,15 @@ void rppx1_stats_fill_isr(struct rppx1 *rpp, u32 isc, void *buf) v4l2_isp_stats_init_buffer(stats, V4L2_ISP_VERSION_V1); + if (isc & RPPX1_IRQ_ID_PRE1_EXM) { + block = rppx1_init_stats_block(rpp, stats, + RPPX1_STATS_BLOCK_TYPE_EXM_PRE1); + if (IS_ERR(block)) + return; + + rpp_module_call(&rpp->pre1.exm, fill_stats, block); + } + if (isc & RPPX1_IRQ_ID_POST_AWB_MEAS) { block = rppx1_init_stats_block(rpp, stats, RPPX1_STATS_BLOCK_TYPE_WBMEAS_POST); diff --git a/drivers/media/platform/dreamchip/rppx1/rppx1_exm.c b/drivers/media/platform/dreamchip/rppx1/rppx1_exm.c index 1dccb110d3e9..c7b24c356222 100644 --- a/drivers/media/platform/dreamchip/rppx1/rppx1_exm.c +++ b/drivers/media/platform/dreamchip/rppx1/rppx1_exm.c @@ -11,10 +11,14 @@ #define EXM_START_REG 0x0004 #define EXM_CTRL_REG 0x0008 +#define EXM_CTRL_EXM_AUTOSTOP BIT(1) /* HW doc says not supported. */ #define EXM_CTRL_EXM_UPDATE_ENABLE BIT(0) #define EXM_MODE_REG 0x000c + #define EXM_CHANNEL_SEL_REG 0x0010 +#define EXM_CHANNEL_SEL_CHANNEL_SELECT_MASK GENMASK(2, 0) + #define EXM_LAST_MEAS_LINE_REG 0x0014 #define EXM_COEFF_R_REG 0x0018 #define EXM_COEFF_G_GR_REG 0x001c @@ -46,6 +50,78 @@ static int rppx1_exm_probe(struct rpp_module *mod) return 0; } +static int +rppx1_exm_fill_params(struct rpp_module *mod, + const union rppx1_params_block *block, + rppx1_reg_write write, void *priv) +{ + const struct rppx1_exm_params *cfg = &block->exm; + u32 h_offs, v_offs, h_size, v_size; + + /* If the modules is disabled, simply bypass it. */ + if (cfg->header.flags & V4L2_ISP_PARAMS_FL_BLOCK_DISABLE) { + write(priv, mod->base + EXM_MODE_REG, 0); + return 0; + } + + switch (cfg->mode) { + case RPPX1_EXP_MEASURING_MODE_RGB: + case RPPX1_EXP_MEASURING_MODE_BAYER: + write(priv, mod->base + EXM_MODE_REG, cfg->mode); + break; + default: + write(priv, mod->base + EXM_MODE_REG, 0); + return 0; + } + + write(priv, mod->base + EXM_COEFF_R_REG, cfg->coeff_r); + write(priv, mod->base + EXM_COEFF_G_GR_REG, cfg->coeff_g_gr); + write(priv, mod->base + EXM_COEFF_GB_REG, cfg->coeff_gb); + write(priv, mod->base + EXM_COEFF_B_REG, cfg->coeff_b); + + /* Select sample point */ + write(priv, mod->base + EXM_CHANNEL_SEL_REG, + cfg->channel_sel & EXM_CHANNEL_SEL_CHANNEL_SELECT_MASK); + + /* + * Adjust and set measurement window, + * - Offsets must be even. + * - Width and height must be even and divisible in 5 windows. + */ + h_offs = cfg->wnd.h_offs & 0x1ffe; + v_offs = cfg->wnd.v_offs & 0x1ffe; + h_size = (cfg->wnd.h_size - 1) - ((cfg->wnd.h_size - 1) % 10); + v_size = (cfg->wnd.v_size - 1) - ((cfg->wnd.v_size - 1) % 10); + + write(priv, mod->base + EXM_H_OFFS_REG, h_offs); + write(priv, mod->base + EXM_V_OFFS_REG, v_offs); + write(priv, mod->base + EXM_H_SIZE_REG, h_size / 5); + write(priv, mod->base + EXM_V_SIZE_REG, v_size / 5); + + /* + * Set last measurement line for ready interrupt. Ignore the value + * from the parameters as it is only useful for fast-channel switching. + */ + write(priv, mod->base + EXM_LAST_MEAS_LINE_REG, v_offs + v_size + 1); + + write(priv, mod->base + EXM_START_REG, 1); + + return 0; +} + +static int rppx1_exm_fill_stats(struct rpp_module *mod, + union rppx1_stats_block *block) +{ + struct rppx1_exm_stats *stats = &block->exm; + + for (unsigned int i = 0; i < RPPX1_EXM_NUM_WIN; i++) + stats->exp_mean[i] = rpp_module_read(mod, EXM_MEAN_REG(i)); + + return 0; +} + const struct rpp_module_ops rppx1_exm_ops = { .probe = rppx1_exm_probe, + .fill_params = rppx1_exm_fill_params, + .fill_stats = rppx1_exm_fill_stats, }; diff --git a/include/uapi/linux/media/dreamchip/rppx1-config.h b/include/uapi/linux/media/dreamchip/rppx1-config.h index b388a9003d8b..6864df2d4436 100644 --- a/include/uapi/linux/media/dreamchip/rppx1-config.h +++ b/include/uapi/linux/media/dreamchip/rppx1-config.h @@ -32,6 +32,36 @@ struct rppx1_window { __u16 v_size; }; +/** + * enum rppx1_meas_chan - Measurement point for the Histogram and EXM Modules + * + * Measurement points for the RPP-X1 Histogram measurement module and Exposure + * measurement module. + * + * All tap points are available for the PRE1/PRE2 pipes. Only + * RPPX1_MEAS_CHAN_SEL4 and RPPX1_MEAS_CHAN_SEL7 are available for the + * MAIN_POST pipe. + * + * @RPPX1_MEAS_CHAN_SEL0: after input acquisition + * @RPPX1_MEAS_CHAN_SEL1: after black level subtraction + * @RPPX1_MEAS_CHAN_SEL2: after sensor gamma linearization + * @RPPX1_MEAS_CHAN_SEL3: after lens shading correction + * @RPPX1_MEAS_CHAN_SEL4: after auto white balance gains + * @RPPX1_MEAS_CHAN_SEL5: after defect pixel correction + * @RPPX1_MEAS_CHAN_SEL6: after denoise pre-filter + * @RPPX1_MEAS_CHAN_SEL7: after demosaicing + */ +enum rppx1_meas_chan { + RPPX1_MEAS_CHAN_SEL0, + RPPX1_MEAS_CHAN_SEL1, + RPPX1_MEAS_CHAN_SEL2, + RPPX1_MEAS_CHAN_SEL3, + RPPX1_MEAS_CHAN_SEL4, + RPPX1_MEAS_CHAN_SEL5, + RPPX1_MEAS_CHAN_SEL6, + RPPX1_MEAS_CHAN_SEL7, +}; + /* --------------------------------------------------------------------------- * Parameter Structures * @@ -50,12 +80,16 @@ struct rppx1_window { * @RPPX1_PARAMS_BLOCK_TYPE_AWBG_PRE1: PRE1 pipe White Balance Gains * @RPPX1_PARAMS_BLOCK_TYPE_AWBG_PRE2: PRE2 White Balance Gains * @RPPX1_PARAMS_BLOCK_TYPE_AWBG_POST: MAIN_POST White Balance Gains + * @RPPX1_PARAMS_BLOCK_TYPE_EXM_PRE1: PRE1 pipe Exposure Measurement + * @RPPX1_PARAMS_BLOCK_TYPE_EXM_PRE2: PRE2 pipe Exposure Measurement */ enum rppx1_params_block_type { RPPX1_PARAMS_BLOCK_TYPE_WBMEAS_POST, RPPX1_PARAMS_BLOCK_TYPE_AWBG_PRE1, RPPX1_PARAMS_BLOCK_TYPE_AWBG_PRE2, RPPX1_PARAMS_BLOCK_TYPE_AWBG_POST, + RPPX1_PARAMS_BLOCK_TYPE_EXM_PRE1, + RPPX1_PARAMS_BLOCK_TYPE_EXM_PRE2, }; /** @@ -149,6 +183,66 @@ struct rppx1_awbg_params { __u32 gain_green_b; }; +/** + * enum rppx1_exm_mode - Exposure measurement mode + * + * Exaposure measurement mode selection (RGB/Bayer). + * + * @RPPX1_EXP_MEASURING_MODE_DISABLED: no measurement + * @RPPX1_EXP_MEASURING_MODE_RGB: Y/R/G/B measurement + * @RPPX1_EXP_MEASURING_MODE_BAYER: Bayer RGB measurement + */ +enum rppx1_exm_mode { + RPPX1_EXP_MEASURING_MODE_DISABLED, + RPPX1_EXP_MEASURING_MODE_RGB, + RPPX1_EXP_MEASURING_MODE_BAYER, +}; + +/** + * struct rppx1_exm_params - Exposure measurement configuration + * + * The RPP-X1 Exposure measurement unit is available on the PRE1 and PRE2 + * pre-fusion pipes. Userspace selects which pipe to operate by setting + * the @header.type field to RPPX1_PARAMS_BLOCK_TYPE_EXM_PRE1 or + * RPPX1_PARAMS_BLOCK_TYPE_EXM_PRE2. + * + * Exposure measurement is performed in the RGB or Bayer domain, according to + * the setting of the @mode field. The exposure measurement tap point is + * selected according to the value of @channel_sel. + * + * The exposure measurement is performed on an input window specified in @wnd. + * To each color component a programmable weight coefficient is associated. + * Coefficients are represented as unsigned 8 bits integer values in Q1.7 format + * ranging from 0 to 1.992. + * + * The @last_line fields controls when the exposure measurement completes. It + * is usually programmed to the value of (@wnd.v_offs + @wnd.v_size + 1). + * + * @header: block header (type = RPPX1_PARAMS_BLOCK_TYPE_EXM_PRE1 or + * type = RPPX1_PARAMS_BLOCK_TYPE_EXM_PRE2) + * @wnd: measurement window coordinates + * @mode: exposure measure mode (from enum rppx1_exm_mode) + * @last_line: line number for which the exposure measurement completes + * @channel_sel: exposure measurement point (see enum rppx1_meas_chan) + * @coeff_r: coefficient for the red Bayer sample or red color channel, Q1.7 + * @coeff_g_gr: coefficient for the green/red Bayer sample or green color channel, Q1.7 + * @coeff_b: coefficient for the blue Bayer sample or blue color channel, Q1.7 + * @coeff_gb: coefficient for the green/blue Bayer sample, unused in RGB mode, Q1.7 + * @reserved: padding + */ +struct rppx1_exm_params { + struct v4l2_isp_params_block_header header; + struct rppx1_window wnd; + __u32 mode; + __u32 last_line; + __u8 channel_sel; + __u8 coeff_r; + __u8 coeff_g_gr; + __u8 coeff_b; + __u8 coeff_gb; + __u8 reserved[3]; +}; + /** * RPPX1_PARAMS_MAX_SIZE - Maximum size of all RPP-X1 parameter blocks * @@ -159,7 +253,9 @@ struct rppx1_awbg_params { (sizeof(struct rppx1_wbmeas_params) + \ sizeof(struct rppx1_awbg_params) + \ sizeof(struct rppx1_awbg_params) + \ - sizeof(struct rppx1_awbg_params)) + sizeof(struct rppx1_awbg_params) + \ + sizeof(struct rppx1_exm_params) + \ + sizeof(struct rppx1_exm_params)) /* --------------------------------------------------------------------------- * Statistics Structures @@ -176,9 +272,13 @@ struct rppx1_awbg_params { * NOTE: Only append to the enumeration as the numbers are uAPI. * * @RPPX1_STATS_BLOCK_TYPE_WBMEAS_POST: post-fusion white-balance measurement + * @RPPX1_STATS_BLOCK_TYPE_EXM_PRE1: pre-fusion pipe1 exposure measurement + * @RPPX1_STATS_BLOCK_TYPE_EXM_PRE2: pre-fusion pipe2 exposure measurement */ enum rppx1_stats_block_type { RPPX1_STATS_BLOCK_TYPE_WBMEAS_POST, + RPPX1_STATS_BLOCK_TYPE_EXM_PRE1, + RPPX1_STATS_BLOCK_TYPE_EXM_PRE2, }; /** @@ -198,6 +298,25 @@ struct rppx1_wbmeas_stats { __u32 mean_cr_or_r; }; +/* Exposure Measurement */ +#define RPPX1_EXM_NUM_WIN 25 + +/** + * struct rppx1_exm_stats - Exposure measurement + * + * RPP-X1 exposure measurement calculates the mean value on 25 programmable + * windows on the input picture. + * + * @header: block header (type = RPPX1_STATS_BLOCK_TYPE_EXM_PRE1) + * @exp_mean: mean luminance values per block, up to 20-bit + * @reserved: padding + */ +struct rppx1_exm_stats { + struct v4l2_isp_block_header header; + __u32 exp_mean[RPPX1_EXM_NUM_WIN]; + __u32 reserved; +}; + /** * RPPX1_STATS_MAX_SIZE - Maximum size of all RPP-X1 statistics * @@ -205,6 +324,8 @@ struct rppx1_wbmeas_stats { * multiple pipes. */ #define RPPX1_STATS_MAX_SIZE \ - (sizeof(struct rppx1_wbmeas_stats)) + (sizeof(struct rppx1_wbmeas_stats) + \ + sizeof(struct rppx1_exm_stats) + \ + sizeof(struct rppx1_exm_stats)) #endif /* __UAPI_RPP_X1_CONFIG_H */ -- cgit v1.2.3 From 9a52f7de7c20fe055b5cbb3c792e76a8562ccb2f Mon Sep 17 00:00:00 2001 From: Niklas Söderlund Date: Thu, 30 Jul 2026 19:12:36 +0200 Subject: media: rppx1: hist: Add support histogram measurement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend the RPPX1 driver to allow setting the histogram measurement configuration and consuming the resulting statistics. It uses the RPPX1 framework for parameters and its writer abstraction to allow the user to control how, and when, configuration is applied to the RPPX1. Signed-off-by: Niklas Söderlund Co-developed-by: Jai Luthra Signed-off-by: Jai Luthra Co-developed-by: Jacopo Mondi Signed-off-by: Jacopo Mondi Reviewed-by: Jacopo Mondi Signed-off-by: Sakari Ailus --- .../media/platform/dreamchip/rppx1/rpp_module.h | 2 + .../media/platform/dreamchip/rppx1/rpp_params.c | 6 ++ drivers/media/platform/dreamchip/rppx1/rpp_stats.c | 10 ++ .../media/platform/dreamchip/rppx1/rppx1_hist.c | 111 ++++++++++++++++++- include/uapi/linux/media/dreamchip/rppx1-config.h | 117 ++++++++++++++++++++- 5 files changed, 243 insertions(+), 3 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/media/platform/dreamchip/rppx1/rpp_module.h b/drivers/media/platform/dreamchip/rppx1/rpp_module.h index e968ec67b9f5..b134d140fe22 100644 --- a/drivers/media/platform/dreamchip/rppx1/rpp_module.h +++ b/drivers/media/platform/dreamchip/rppx1/rpp_module.h @@ -48,12 +48,14 @@ void rpp_module_clrset(struct rpp_module *mod, u32 offset, u32 mask, u32 value); union rppx1_params_block { struct v4l2_isp_block_header header; struct rppx1_awbg_params awbg; + struct rppx1_hist_params hist; struct rppx1_exm_params exm; struct rppx1_wbmeas_params wbmeas; }; union rppx1_stats_block { struct v4l2_isp_block_header header; + struct rppx1_hist_stats hist; struct rppx1_exm_stats exm; struct rppx1_wbmeas_stats wbmeas; }; diff --git a/drivers/media/platform/dreamchip/rppx1/rpp_params.c b/drivers/media/platform/dreamchip/rppx1/rpp_params.c index 8c0f45e8066a..975ce3a42fb5 100644 --- a/drivers/media/platform/dreamchip/rppx1/rpp_params.c +++ b/drivers/media/platform/dreamchip/rppx1/rpp_params.c @@ -19,6 +19,9 @@ static const struct v4l2_isp_params_block_type_info rppx1_ext_params_blocks_info[] = { RPPX1_PARAMS_BLOCK_INFO(AWBG_PRE1, awbg), RPPX1_PARAMS_BLOCK_INFO(AWBG_PRE2, awbg), + RPPX1_PARAMS_BLOCK_INFO(HIST_PRE1, hist), + RPPX1_PARAMS_BLOCK_INFO(HIST_PRE2, hist), + RPPX1_PARAMS_BLOCK_INFO(HIST_POST, hist), RPPX1_PARAMS_BLOCK_INFO(EXM_PRE1, exm), RPPX1_PARAMS_BLOCK_INFO(EXM_PRE2, exm), RPPX1_PARAMS_BLOCK_INFO(WBMEAS_POST, wbmeas), @@ -58,6 +61,9 @@ int rppx1_params(struct rppx1 *rpp, struct vb2_buffer *vb, size_t max_size, case RPPX1_PARAMS_BLOCK_TYPE_AWBG_PRE1: module = &rpp->pre1.awbg; break; + case RPPX1_PARAMS_BLOCK_TYPE_HIST_POST: + module = &rpp->post.hist; + break; case RPPX1_PARAMS_BLOCK_TYPE_EXM_PRE1: module = &rpp->pre1.exm; break; diff --git a/drivers/media/platform/dreamchip/rppx1/rpp_stats.c b/drivers/media/platform/dreamchip/rppx1/rpp_stats.c index 388c758d805d..4c7fe611d004 100644 --- a/drivers/media/platform/dreamchip/rppx1/rpp_stats.c +++ b/drivers/media/platform/dreamchip/rppx1/rpp_stats.c @@ -17,6 +17,7 @@ static const struct v4l2_isp_stats_block_type_info rppx1_stats_blocks_info[] = { + RPPX1_STATS_BLOCK_INFO(HIST_POST, hist), RPPX1_STATS_BLOCK_INFO(EXM_PRE1, exm), RPPX1_STATS_BLOCK_INFO(WBMEAS_POST, wbmeas), }; @@ -35,6 +36,15 @@ void rppx1_stats_fill_isr(struct rppx1 *rpp, u32 isc, void *buf) v4l2_isp_stats_init_buffer(stats, V4L2_ISP_VERSION_V1); + if (isc & RPPX1_IRQ_ID_POST_HIST_MEAS) { + block = rppx1_init_stats_block(rpp, stats, + RPPX1_STATS_BLOCK_TYPE_HIST_POST); + if (IS_ERR(block)) + return; + + rpp_module_call(&rpp->post.hist, fill_stats, block); + } + if (isc & RPPX1_IRQ_ID_PRE1_EXM) { block = rppx1_init_stats_block(rpp, stats, RPPX1_STATS_BLOCK_TYPE_EXM_PRE1); diff --git a/drivers/media/platform/dreamchip/rppx1/rppx1_hist.c b/drivers/media/platform/dreamchip/rppx1/rppx1_hist.c index 7c1b42e96b96..8fe8cbbc85df 100644 --- a/drivers/media/platform/dreamchip/rppx1/rppx1_hist.c +++ b/drivers/media/platform/dreamchip/rppx1/rppx1_hist.c @@ -7,6 +7,8 @@ #include "rpp_module.h" +#include + #define HIST_VERSION_REG 0x0000 #define HIST_CTRL_REG 0x0004 @@ -26,6 +28,9 @@ #define HIST_LAST_MEAS_LINE_REG 0x0010 #define HIST_SUBSAMPLING_REG 0x0014 +#define HIST_SUBSAMPLING_V_STEPSIZE(x) (((x) & 0x7f) << 24) +#define HIST_SUBSAMPLING_H_STEP_INC(x) (((x) & 0x1ffff)) + #define HIST_COEFF_R_REG 0x0018 #define HIST_COEFF_G_REG 0x001c #define HIST_COEFF_B_REG 0x0020 @@ -49,7 +54,6 @@ #define HIST_FORCED_UPDATE_REG 0x0058 #define HIST_VSTART_STATUS_REG 0x005c -#define HIST_BIN_REG_NUM 32 #define HIST_BIN_REG(n) (0x0060 + (4 * (n))) static int rppx1_hist_probe(struct rpp_module *mod) @@ -72,6 +76,111 @@ static int rppx1_hist_probe(struct rpp_module *mod) return 0; } +#define RPPX1_HIST_WEIGHT(v0, v1, v2, v3) \ + (((v0) & 0x1f) | (((v1) & 0x1f) << 8) | \ + (((v2) & 0x1f) << 16) | \ + (((v3) & 0x1f) << 24)) + +static int rppx1_hist_fill_params(struct rpp_module *mod, + const union rppx1_params_block *block, + rppx1_reg_write write, void *priv) +{ + const struct rppx1_hist_params *cfg = &block->hist; + u32 h_offs, v_offs, h_size, v_size; + + /* If the modules is disabled, simply bypass it. */ + if (cfg->header.flags & V4L2_ISP_PARAMS_FL_BLOCK_DISABLE) { + write(priv, mod->base + HIST_MODE_REG, + HIST_MODE_HIST_MODE_DISABLE); + return 0; + } + + /* Select sample point */ + write(priv, mod->base + HIST_CHANNEL_SEL_REG, + cfg->channel_sel & HIST_CHANNEL_SEL_CHANNEL_SELECT_MASK); + + /* + * Configure the input subsampling. + * + * In Bayer mode the vertical and horizontal subsampling counters are + * only incremented for color channels selected by hist_mode. + */ + write(priv, mod->base + HIST_SUBSAMPLING_REG, + HIST_SUBSAMPLING_V_STEPSIZE(cfg->v_stepsize) | + HIST_SUBSAMPLING_H_STEP_INC(cfg->h_step_inc)); + + /* + * Adjust and set measurement window to hardware limitations, + * - Offsets must be even. + * - Width and height must be even and divisible in 5 windows. + */ + h_offs = cfg->wnd.h_offs & 0x1ffe; + v_offs = cfg->wnd.v_offs & 0x1ffe; + h_size = cfg->wnd.h_size - cfg->wnd.h_size % 10; + v_size = cfg->wnd.v_size - cfg->wnd.v_size % 10; + + write(priv, mod->base + HIST_H_OFFS_REG, h_offs); + write(priv, mod->base + HIST_V_OFFS_REG, v_offs); + write(priv, mod->base + HIST_H_SIZE_REG, h_size / 5); + write(priv, mod->base + HIST_V_SIZE_REG, v_size / 5); + + /* + * Set last measurement line for ready interrupt. Ignore the value + * from the parameters as it is only useful for fast-channel switching. + */ + write(priv, mod->base + HIST_LAST_MEAS_LINE_REG, v_offs + v_size + 1); + + /* Set measurement window weights. */ + write(priv, mod->base + HIST_WEIGHT_00TO30_REG, + RPPX1_HIST_WEIGHT(cfg->weights[0], cfg->weights[1], + cfg->weights[2], cfg->weights[3])); + write(priv, mod->base + HIST_WEIGHT_40TO21_REG, + RPPX1_HIST_WEIGHT(cfg->weights[4], cfg->weights[5], + cfg->weights[6], cfg->weights[7])); + write(priv, mod->base + HIST_WEIGHT_31TO12_REG, + RPPX1_HIST_WEIGHT(cfg->weights[8], cfg->weights[9], + cfg->weights[10], cfg->weights[11])); + write(priv, mod->base + HIST_WEIGHT_22TO03_REG, + RPPX1_HIST_WEIGHT(cfg->weights[12], cfg->weights[13], + cfg->weights[14], cfg->weights[15])); + write(priv, mod->base + HIST_WEIGHT_13TO43_REG, + RPPX1_HIST_WEIGHT(cfg->weights[16], cfg->weights[17], + cfg->weights[18], cfg->weights[19])); + write(priv, mod->base + HIST_WEIGHT_04TO34_REG, + RPPX1_HIST_WEIGHT(cfg->weights[20], cfg->weights[21], + cfg->weights[22], cfg->weights[23])); + write(priv, mod->base + HIST_WEIGHT_44_REG, + RPPX1_HIST_WEIGHT(cfg->weights[24], 0, 0, 0)); + + write(priv, mod->base + HIST_MODE_REG, cfg->mode); + write(priv, mod->base + HIST_COEFF_R_REG, cfg->coeff[0]); + write(priv, mod->base + HIST_COEFF_G_REG, cfg->coeff[1]); + write(priv, mod->base + HIST_COEFF_B_REG, cfg->coeff[2]); + + u32 sample_reg = FIELD_PREP(HIST_SAMPLE_RANGE_SAMPLE_SHIFT_MASK, + cfg->sample_shift) | + FIELD_PREP(HIST_SAMPLE_RANGE_SAMPLE_OFFSET_MASK, + cfg->sample_offs); + write(priv, mod->base + HIST_SAMPLE_RANGE_REG, sample_reg); + + write(priv, mod->base + HIST_FORCED_UPDATE_REG, 1); + + return 0; +} + +static int rppx1_hist_fill_stats(struct rpp_module *mod, + union rppx1_stats_block *block) +{ + struct rppx1_hist_stats *stats = &block->hist; + + for (unsigned int i = 0; i < RPPX1_HIST_NUM_BINS; i++) + stats->hist_bins[i] = rpp_module_read(mod, HIST_BIN_REG(i)); + + return 0; +} + const struct rpp_module_ops rppx1_hist_ops = { .probe = rppx1_hist_probe, + .fill_params = rppx1_hist_fill_params, + .fill_stats = rppx1_hist_fill_stats, }; diff --git a/include/uapi/linux/media/dreamchip/rppx1-config.h b/include/uapi/linux/media/dreamchip/rppx1-config.h index 6864df2d4436..934e18731b08 100644 --- a/include/uapi/linux/media/dreamchip/rppx1-config.h +++ b/include/uapi/linux/media/dreamchip/rppx1-config.h @@ -82,6 +82,9 @@ enum rppx1_meas_chan { * @RPPX1_PARAMS_BLOCK_TYPE_AWBG_POST: MAIN_POST White Balance Gains * @RPPX1_PARAMS_BLOCK_TYPE_EXM_PRE1: PRE1 pipe Exposure Measurement * @RPPX1_PARAMS_BLOCK_TYPE_EXM_PRE2: PRE2 pipe Exposure Measurement + * @RPPX1_PARAMS_BLOCK_TYPE_HIST_PRE1: PRE1 pipe Histogram Measurement + * @RPPX1_PARAMS_BLOCK_TYPE_HIST_PRE2: PRE2 pipe Histogram Measurement + * @RPPX1_PARAMS_BLOCK_TYPE_HIST_POST: POST pipe Histogram Measurement */ enum rppx1_params_block_type { RPPX1_PARAMS_BLOCK_TYPE_WBMEAS_POST, @@ -90,6 +93,9 @@ enum rppx1_params_block_type { RPPX1_PARAMS_BLOCK_TYPE_AWBG_POST, RPPX1_PARAMS_BLOCK_TYPE_EXM_PRE1, RPPX1_PARAMS_BLOCK_TYPE_EXM_PRE2, + RPPX1_PARAMS_BLOCK_TYPE_HIST_PRE1, + RPPX1_PARAMS_BLOCK_TYPE_HIST_PRE2, + RPPX1_PARAMS_BLOCK_TYPE_HIST_POST, }; /** @@ -243,6 +249,87 @@ struct rppx1_exm_params { __u8 reserved[3]; }; +/* Histogram */ +#define RPPX1_HIST_WEIGHT_GRIDS_SIZE 25 + +/** + * enum rppx1_hist_mode - Histogram measurement mode + * + * Histogram measurement mode. Select which channel or combination of channels + * the histogram measurement is performed on. + * + * @RPPX1_HIST_MODE_DISABLE: histogram disabled + * @RPPX1_HIST_MODE_RGB_COMBINED: combined RGB histogram + * @RPPX1_HIST_MODE_R_HISTOGRAM: red channel histogram + * @RPPX1_HIST_MODE_GR_HISTOGRAM: green/red channel histogram + * @RPPX1_HIST_MODE_B_HISTOGRAM: blue channel histogram + * @RPPX1_HIST_MODE_GB_HISTOGRAM: green/blue histogram + */ +enum rppx1_hist_mode { + RPPX1_HIST_MODE_DISABLE, + RPPX1_HIST_MODE_RGB_COMBINED, + RPPX1_HIST_MODE_R_HISTOGRAM, + RPPX1_HIST_MODE_GR_HISTOGRAM, + RPPX1_HIST_MODE_B_HISTOGRAM, + RPPX1_HIST_MODE_GB_HISTOGRAM, +}; + +/** + * struct rppx1_hist_params - Histogram measurement configuration + * + * The RPP-X1 Histogram measurement unit is available on the PRE1, PRE2 and + * MAIN_POST pipes. Userspace selects which pipe to operate by setting the + * @header.type field to RPPX1_PARAMS_BLOCK_TYPE_HIST_PRE1, + * RPPX1_PARAMS_BLOCK_TYPE_HIST_PRE2 or + * RPPX1_PARAMS_BLOCK_TYPE_HIST_POST. + * + * The histogram measurement point is selected using the @channel field while + * histogram measurement mode is selected using the @mode field. + * + * Histogram measurement is performed by programming subsampling factors using + * the @v_stepsize and @h_step_inc fields and by weighted windowing, by + * programming the size of the measurement window @wnd with @weights associated + * to each cell of the 5x5 measurement grid. Weights are represented as 5 bits + * integer values ranging from 0 to 16. + * + * The @last_line fields controls when the histogram measurement completes. It + * is usually programmed to the value of (@wnd.v_offs + @wnd.v_size - 1). + * + * Histogram values are calculated by applying a per-color channel coefficient + * represented as an 8 bits unsigned Q1.7 integer value. The @sample_offs and + * @sample_shift fields allow to reduce the color dynamic range on which + * histogram data are produced. + * + * @header: block header (type = RPPX1_PARAMS_BLOCK_TYPE_HIST_PRE1, + * type = RPPX1_PARAMS_BLOCK_TYPE_HIST_PRE2 or + * type = RPPX1_PARAMS_BLOCK_TYPE_HIST_POST) + * @wnd: measurement window coordinates + * @last_line: line number for which the histogram measurement completes + * @v_stepsize: vertical subsampling divider, 7 bits + * @h_step_inc: horizontal subsampling step counter, 17 bits + * @sample_offs: sample offset, 24 bits + * @mode: histogram measurement mode (from enum rppx1_hist_mode) + * @channel_sel: histogram measurement point (see enum rppx1_meas_chan) + * @weights: weighting factors for each sub-window (5x5 grid) + * @coeff: R-G-B coefficients, 8 bits unsigned Q1.7 + * @sample_shift: sample shift, 4 bits + * @reserved: padding + */ +struct rppx1_hist_params { + struct v4l2_isp_params_block_header header; + struct rppx1_window wnd; + __u32 last_line; + __u32 v_stepsize; + __u32 h_step_inc; + __u32 sample_offs; + __u8 mode; + __u8 channel_sel; + __u8 weights[RPPX1_HIST_WEIGHT_GRIDS_SIZE]; + __u8 coeff[3]; + __u8 sample_shift; + __u8 reserved; +}; + /** * RPPX1_PARAMS_MAX_SIZE - Maximum size of all RPP-X1 parameter blocks * @@ -255,7 +342,10 @@ struct rppx1_exm_params { sizeof(struct rppx1_awbg_params) + \ sizeof(struct rppx1_awbg_params) + \ sizeof(struct rppx1_exm_params) + \ - sizeof(struct rppx1_exm_params)) + sizeof(struct rppx1_exm_params) + \ + sizeof(struct rppx1_hist_params) + \ + sizeof(struct rppx1_hist_params) + \ + sizeof(struct rppx1_hist_params)) /* --------------------------------------------------------------------------- * Statistics Structures @@ -274,11 +364,17 @@ struct rppx1_exm_params { * @RPPX1_STATS_BLOCK_TYPE_WBMEAS_POST: post-fusion white-balance measurement * @RPPX1_STATS_BLOCK_TYPE_EXM_PRE1: pre-fusion pipe1 exposure measurement * @RPPX1_STATS_BLOCK_TYPE_EXM_PRE2: pre-fusion pipe2 exposure measurement + * @RPPX1_STATS_BLOCK_TYPE_HIST_PRE1: pre-fusion pipe1 histogram + * @RPPX1_STATS_BLOCK_TYPE_HIST_PRE2: pre-fusion pipe2 histogram + * @RPPX1_STATS_BLOCK_TYPE_HIST_POST: post-fusion histogram */ enum rppx1_stats_block_type { RPPX1_STATS_BLOCK_TYPE_WBMEAS_POST, RPPX1_STATS_BLOCK_TYPE_EXM_PRE1, RPPX1_STATS_BLOCK_TYPE_EXM_PRE2, + RPPX1_STATS_BLOCK_TYPE_HIST_PRE1, + RPPX1_STATS_BLOCK_TYPE_HIST_PRE2, + RPPX1_STATS_BLOCK_TYPE_HIST_POST, }; /** @@ -317,6 +413,20 @@ struct rppx1_exm_stats { __u32 reserved; }; +/* Histogram */ +#define RPPX1_HIST_NUM_BINS 32 + +/** + * struct rppx1_hist_stats - Histogram statistics + * + * @header: block header (type = RPPX1_STATS_BLOCK_TYPE_HIST_POST) + * @hist_bins: accumulation histogram results in unsigned 20-bit Q16.4 format + */ +struct rppx1_hist_stats { + struct v4l2_isp_block_header header; + __u32 hist_bins[RPPX1_HIST_NUM_BINS]; +}; + /** * RPPX1_STATS_MAX_SIZE - Maximum size of all RPP-X1 statistics * @@ -326,6 +436,9 @@ struct rppx1_exm_stats { #define RPPX1_STATS_MAX_SIZE \ (sizeof(struct rppx1_wbmeas_stats) + \ sizeof(struct rppx1_exm_stats) + \ - sizeof(struct rppx1_exm_stats)) + sizeof(struct rppx1_exm_stats) + \ + sizeof(struct rppx1_hist_stats) + \ + sizeof(struct rppx1_hist_stats) + \ + sizeof(struct rppx1_hist_stats)) #endif /* __UAPI_RPP_X1_CONFIG_H */ -- cgit v1.2.3 From 3f8a2a88365501998df6cd35ecd384be823bfa39 Mon Sep 17 00:00:00 2001 From: Niklas Söderlund Date: Thu, 30 Jul 2026 19:12:37 +0200 Subject: media: rppx1: bls: Add support for black level compensation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend the RPPX1 driver to allow setting the black level measurement and gain configuration. It uses the RPPX1 framework for parameters and its writer abstraction to allow the user to control how, and when, configuration is applied to the RPPX1. Signed-off-by: Niklas Söderlund Co-developed-by: Jai Luthra Signed-off-by: Jai Luthra Co-developed-by: Jacopo Mondi Signed-off-by: Jacopo Mondi Signed-off-by: Sakari Ailus --- .../media/platform/dreamchip/rppx1/rpp_module.h | 1 + .../media/platform/dreamchip/rppx1/rpp_params.c | 5 + drivers/media/platform/dreamchip/rppx1/rppx1_bls.c | 97 +++++++++++++++++++ include/uapi/linux/media/dreamchip/rppx1-config.h | 107 ++++++++++++++++++++- 4 files changed, 209 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/drivers/media/platform/dreamchip/rppx1/rpp_module.h b/drivers/media/platform/dreamchip/rppx1/rpp_module.h index b134d140fe22..5e20fcdcbcc4 100644 --- a/drivers/media/platform/dreamchip/rppx1/rpp_module.h +++ b/drivers/media/platform/dreamchip/rppx1/rpp_module.h @@ -47,6 +47,7 @@ void rpp_module_clrset(struct rpp_module *mod, u32 offset, u32 mask, u32 value); union rppx1_params_block { struct v4l2_isp_block_header header; + struct rppx1_bls_params bls; struct rppx1_awbg_params awbg; struct rppx1_hist_params hist; struct rppx1_exm_params exm; diff --git a/drivers/media/platform/dreamchip/rppx1/rpp_params.c b/drivers/media/platform/dreamchip/rppx1/rpp_params.c index 975ce3a42fb5..831cf7ca154c 100644 --- a/drivers/media/platform/dreamchip/rppx1/rpp_params.c +++ b/drivers/media/platform/dreamchip/rppx1/rpp_params.c @@ -17,6 +17,8 @@ static const struct v4l2_isp_params_block_type_info rppx1_ext_params_blocks_info[] = { + RPPX1_PARAMS_BLOCK_INFO(BLS_PRE1, bls), + RPPX1_PARAMS_BLOCK_INFO(BLS_PRE2, bls), RPPX1_PARAMS_BLOCK_INFO(AWBG_PRE1, awbg), RPPX1_PARAMS_BLOCK_INFO(AWBG_PRE2, awbg), RPPX1_PARAMS_BLOCK_INFO(HIST_PRE1, hist), @@ -58,6 +60,9 @@ int rppx1_params(struct rppx1 *rpp, struct vb2_buffer *vb, size_t max_size, block_offset += block->header.size; switch (block->header.type) { + case RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE1: + module = &rpp->pre1.bls; + break; case RPPX1_PARAMS_BLOCK_TYPE_AWBG_PRE1: module = &rpp->pre1.awbg; break; diff --git a/drivers/media/platform/dreamchip/rppx1/rppx1_bls.c b/drivers/media/platform/dreamchip/rppx1/rppx1_bls.c index 882a9a819229..01a61db279bf 100644 --- a/drivers/media/platform/dreamchip/rppx1/rppx1_bls.c +++ b/drivers/media/platform/dreamchip/rppx1/rppx1_bls.c @@ -6,6 +6,7 @@ */ #include "rpp_module.h" +#include "rppx1.h" #define BLS_VERSION_REG 0x0000 @@ -32,6 +33,8 @@ #define BLS_B_MEASURED_REG 0x0040 #define BLS_C_MEASURED_REG 0x0044 #define BLS_D_MEASURED_REG 0x0048 +#define BLS_PRE1_FIXED_MASK GENMASK(24, 0) +#define BLS_PRE2_FIXED_MASK GENMASK(12, 0) static int rppx1_bls_probe(struct rpp_module *mod) { @@ -55,6 +58,100 @@ static int rppx1_bls_probe(struct rpp_module *mod) return 0; } +static void +rppx1_bls_swap_regs(struct rpp_module *mod, const u32 input[4], u32 output[4]) +{ + static const unsigned int swap[4][4] = { + [RPP_RGGB] = { 0, 1, 2, 3 }, + [RPP_GRBG] = { 1, 0, 3, 2 }, + [RPP_GBRG] = { 2, 3, 0, 1 }, + [RPP_BGGR] = { 3, 2, 1, 0 }, + }; + + /* Swap to pattern used in our path, PRE1 or PRE2. */ + struct rpp_module *acq = mod == &mod->rpp->pre1.bls ? + &mod->rpp->pre1.acq : &mod->rpp->pre2.bls; + enum rpp_raw_pattern pattern = acq->info.acq.raw_pattern; + + for (unsigned int i = 0; i < 4; ++i) + output[i] = input[swap[pattern][i]]; +} + +static int +rppx1_bls_fill_params(struct rpp_module *mod, + const union rppx1_params_block *block, + rppx1_reg_write write, void *priv) +{ + const struct rppx1_bls_params *cfg = &block->bls; + + /* If the modules is disabled, simply bypass it. */ + if (cfg->header.flags & V4L2_ISP_PARAMS_FL_BLOCK_DISABLE) { + write(priv, mod->base + BLS_CTRL_REG, 0); + return 0; + } + + u32 ctrl = BLS_CTRL_BLS_EN; + + if (cfg->mode == RPPX1_BLS_MODE_FIXED) { + static const u32 regs[] = { + BLS_A_FIXED_REG, + BLS_B_FIXED_REG, + BLS_C_FIXED_REG, + BLS_D_FIXED_REG, + }; + u32 swapped[4]; + + rppx1_bls_swap_regs(mod, regs, swapped); + + /* + * The PRE1 pipe fixed values are 24-bits + 1 sign bit, while + * the PRE2 pipe values are 12-bits + 1 sign bit. + */ + u32 mask; + + switch (cfg->header.type) { + case RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE1: + mask = BLS_PRE1_FIXED_MASK; + break; + case RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE2: + mask = BLS_PRE2_FIXED_MASK; + break; + default: + return -EINVAL; + } + + write(priv, mod->base + swapped[0], cfg->fixed.a & mask); + write(priv, mod->base + swapped[1], cfg->fixed.b & mask); + write(priv, mod->base + swapped[2], cfg->fixed.c & mask); + write(priv, mod->base + swapped[3], cfg->fixed.d & mask); + } else { + write(priv, mod->base + BLS_SAMPLES_REG, cfg->samples); + + if (cfg->en_windows & RPPX1_BLS_WIN_EN_WIN1) { + write(priv, mod->base + BLS_H1_START_REG, cfg->window1.h_offs); + write(priv, mod->base + BLS_H1_STOP_REG, cfg->window1.h_size); + write(priv, mod->base + BLS_V1_START_REG, cfg->window1.v_offs); + write(priv, mod->base + BLS_V1_STOP_REG, cfg->window1.v_size); + ctrl |= BLS_CTRL_BLS_WIN1; + } + + if (cfg->en_windows & RPPX1_BLS_WIN_EN_WIN2) { + write(priv, mod->base + BLS_H2_START_REG, cfg->window2.h_offs); + write(priv, mod->base + BLS_H2_STOP_REG, cfg->window2.h_size); + write(priv, mod->base + BLS_V2_START_REG, cfg->window2.v_offs); + write(priv, mod->base + BLS_V2_STOP_REG, cfg->window2.v_size); + ctrl |= BLS_CTRL_BLS_WIN2; + } + + ctrl |= BLS_CTRL_BLS_MODE_MEASURED; + } + + write(priv, mod->base + BLS_CTRL_REG, ctrl); + + return 0; +} + const struct rpp_module_ops rppx1_bls_ops = { .probe = rppx1_bls_probe, + .fill_params = rppx1_bls_fill_params, }; diff --git a/include/uapi/linux/media/dreamchip/rppx1-config.h b/include/uapi/linux/media/dreamchip/rppx1-config.h index 934e18731b08..0049977870ad 100644 --- a/include/uapi/linux/media/dreamchip/rppx1-config.h +++ b/include/uapi/linux/media/dreamchip/rppx1-config.h @@ -85,6 +85,8 @@ enum rppx1_meas_chan { * @RPPX1_PARAMS_BLOCK_TYPE_HIST_PRE1: PRE1 pipe Histogram Measurement * @RPPX1_PARAMS_BLOCK_TYPE_HIST_PRE2: PRE2 pipe Histogram Measurement * @RPPX1_PARAMS_BLOCK_TYPE_HIST_POST: POST pipe Histogram Measurement + * @RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE1: PRE1 pipe Black Level Subtraction + * @RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE2: PRE2 pipe Black Level Subtraction */ enum rppx1_params_block_type { RPPX1_PARAMS_BLOCK_TYPE_WBMEAS_POST, @@ -96,6 +98,8 @@ enum rppx1_params_block_type { RPPX1_PARAMS_BLOCK_TYPE_HIST_PRE1, RPPX1_PARAMS_BLOCK_TYPE_HIST_PRE2, RPPX1_PARAMS_BLOCK_TYPE_HIST_POST, + RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE1, + RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE2, }; /** @@ -330,6 +334,105 @@ struct rppx1_hist_params { __u8 reserved; }; +/** + * struct rppx1_bls_fixed - BLS fixed subtraction values + * + * Fixed black level values subtracted from sensor data per Bayer channel. + * Negative values result in addition. + * + * The PRE1 pipe BLS module operates on a 24-bits input data and fixed black + * levels are stored as a signed 2's complement representation ranging from + * -2^24 to 2^24-1. + * + * The PRE2 pipe BLS module operates on a 12-bits input data and fixed black + * levels are stored as a signed 2's complement representation ranging from + * -2^12 to 2^12-1. + * + * Userspace is expected to provide fixed black level values with a bit-depth + * matching the one of pipe in use. + * + * These subtraction values are matched with the sensor native Bayer components + * ordering according to the cropping configuration on the input port. + * + * @a: subtraction value for channel A + * @b: subtraction value for channel B + * @c: subtraction value for channel C + * @d: subtraction value for channel D + */ +struct rppx1_bls_fixed { + __u32 a; + __u32 b; + __u32 c; + __u32 d; +}; + +/** + * enum rppx1_bls_mode - BLS subtraction mode + * + * Select if subtracted black level come from fixed or measured values. + * + * @RPPX1_BLS_MODE_FIXED: subtract fixed values + * @RPPX1_BLS_MODE_MEAS: subtract measured values + */ +enum rppx1_bls_mode { + RPPX1_BLS_MODE_FIXED, + RPPX1_BLS_MODE_MEAS, +}; + +/** + * enum rppx1_bls_win_en: BLS measurement configuration + * + * Select the measurement window to use for measured black level values. + * + * @RPPX1_BLS_WIN_EN_OFF: disable measurement + * @RPPX1_BLS_WIN_EN_WIN1: Enable measurement from window 1 + * @RPPX1_BLS_WIN_EN_WIN2: enable measurement from window 2 + * @RPPX1_BLS_WIN_EN_WIN12: enable measurement from window 1 and window 2 + */ +enum rppx1_bls_win_en { + RPPX1_BLS_WIN_EN_OFF, + RPPX1_BLS_WIN_EN_WIN1, + RPPX1_BLS_WIN_EN_WIN2, + RPPX1_BLS_WIN_EN_WIN12, +}; + +/** + * struct rppx1_bls_params - RPP-X1 Black Level Subtraction Module + * + * The RPP-X1 Black Level Subtraction module is available on the PRE1 and PRE2 + * pre-fusion pipes. Userspace selects which pipe to operate by setting the + * @header.type field to RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE1 or + * RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE2. + * + * The BLS module operates on fixed or measured data according to the setting of + * the @mode field. When RPPX1_BLS_MODE_FIXED is used userspace shall provide + * the per-channel black levels in @fixed. When RPPX1_BLS_MODE_MEAS is used + * userspace shall configure the measurement windows @window1 and optionally + * @window2 to select the optically black pixels region in the input frame. The + * @samples fields controls how many measure samples are used for averaging the + * measured black levels. + * + * @header: block header (type = RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE1 or + * type == RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE2) + * @window1: BLS measurement window 1 (14 bits) + * @window2: BLS measurement window 2 (14 bits) + * @fixed: fixed subtraction values (see enum rppx1_bls_fixed) + * @mode: BLS subtraction mode (see enum rppx1_bls_mode) + * @en_windows: BLS measurement mode (see rppx1_bls_win_en) + * @samples: log2 of the number of measured pixels per Bayer position + * @reserved: padding + */ +struct rppx1_bls_params { + struct v4l2_isp_params_block_header header; + struct rppx1_window window1; + struct rppx1_window window2; + struct rppx1_bls_fixed fixed; + __u8 mode; + __u8 en_windows; + __u8 samples; + __u8 reserved[5]; +}; + /** * RPPX1_PARAMS_MAX_SIZE - Maximum size of all RPP-X1 parameter blocks * @@ -345,7 +448,9 @@ struct rppx1_hist_params { sizeof(struct rppx1_exm_params) + \ sizeof(struct rppx1_hist_params) + \ sizeof(struct rppx1_hist_params) + \ - sizeof(struct rppx1_hist_params)) + sizeof(struct rppx1_hist_params) + \ + sizeof(struct rppx1_bls_params) + \ + sizeof(struct rppx1_bls_params)) /* --------------------------------------------------------------------------- * Statistics Structures -- cgit v1.2.3 From aaeee9b7433c6015c0e5d5f3c4e2627330f23ae5 Mon Sep 17 00:00:00 2001 From: Niklas Söderlund Date: Thu, 30 Jul 2026 19:12:38 +0200 Subject: media: rppx1: ccor: Add support for color correction matrix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend the RPPX1 driver to allow setting the color correction matrix configuration parameters. It uses the RPPX1 framework for parameters and its writer abstraction to allow the user to control how, and when, configuration is applied to the RPPX1. Signed-off-by: Niklas Söderlund Co-developed-by: Jai Luthra Signed-off-by: Jai Luthra Co-developed-by: Jacopo Mondi Signed-off-by: Jacopo Mondi Signed-off-by: Sakari Ailus --- .../media/platform/dreamchip/rppx1/rpp_module.h | 1 + .../media/platform/dreamchip/rppx1/rpp_params.c | 4 ++ .../media/platform/dreamchip/rppx1/rppx1_ccor.c | 61 ++++++++++++++++++++++ include/uapi/linux/media/dreamchip/rppx1-config.h | 30 ++++++++++- 4 files changed, 95 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/drivers/media/platform/dreamchip/rppx1/rpp_module.h b/drivers/media/platform/dreamchip/rppx1/rpp_module.h index 5e20fcdcbcc4..e039746ac542 100644 --- a/drivers/media/platform/dreamchip/rppx1/rpp_module.h +++ b/drivers/media/platform/dreamchip/rppx1/rpp_module.h @@ -49,6 +49,7 @@ union rppx1_params_block { struct v4l2_isp_block_header header; struct rppx1_bls_params bls; struct rppx1_awbg_params awbg; + struct rppx1_ccor_params ccor; struct rppx1_hist_params hist; struct rppx1_exm_params exm; struct rppx1_wbmeas_params wbmeas; diff --git a/drivers/media/platform/dreamchip/rppx1/rpp_params.c b/drivers/media/platform/dreamchip/rppx1/rpp_params.c index 831cf7ca154c..a83d393d0504 100644 --- a/drivers/media/platform/dreamchip/rppx1/rpp_params.c +++ b/drivers/media/platform/dreamchip/rppx1/rpp_params.c @@ -21,6 +21,7 @@ rppx1_ext_params_blocks_info[] = { RPPX1_PARAMS_BLOCK_INFO(BLS_PRE2, bls), RPPX1_PARAMS_BLOCK_INFO(AWBG_PRE1, awbg), RPPX1_PARAMS_BLOCK_INFO(AWBG_PRE2, awbg), + RPPX1_PARAMS_BLOCK_INFO(CCOR_POST, ccor), RPPX1_PARAMS_BLOCK_INFO(HIST_PRE1, hist), RPPX1_PARAMS_BLOCK_INFO(HIST_PRE2, hist), RPPX1_PARAMS_BLOCK_INFO(HIST_POST, hist), @@ -66,6 +67,9 @@ int rppx1_params(struct rppx1 *rpp, struct vb2_buffer *vb, size_t max_size, case RPPX1_PARAMS_BLOCK_TYPE_AWBG_PRE1: module = &rpp->pre1.awbg; break; + case RPPX1_PARAMS_BLOCK_TYPE_CCOR_POST: + module = &rpp->post.ccor; + break; case RPPX1_PARAMS_BLOCK_TYPE_HIST_POST: module = &rpp->post.hist; break; diff --git a/drivers/media/platform/dreamchip/rppx1/rppx1_ccor.c b/drivers/media/platform/dreamchip/rppx1/rppx1_ccor.c index 3bfad3ba12e6..5ddc7edf6930 100644 --- a/drivers/media/platform/dreamchip/rppx1/rppx1_ccor.c +++ b/drivers/media/platform/dreamchip/rppx1/rppx1_ccor.c @@ -67,9 +67,70 @@ static int rppx1_ccor_start(struct rpp_module *mod, return 0; } +static int +rppx1_ccor_fill_params(struct rpp_module *mod, + const union rppx1_params_block *block, + rppx1_reg_write write, void *priv) +{ + const struct rppx1_ccor_params *cfg = &block->ccor; + + /* If the modules is disabled, configure in bypass mode. */ + if (cfg->header.flags & V4L2_ISP_PARAMS_FL_BLOCK_DISABLE) { + write(priv, mod->base + CCOR_COEFF_REG(0), 0x1000); + write(priv, mod->base + CCOR_COEFF_REG(1), 0x0000); + write(priv, mod->base + CCOR_COEFF_REG(2), 0x0000); + + write(priv, mod->base + CCOR_COEFF_REG(3), 0x0000); + write(priv, mod->base + CCOR_COEFF_REG(4), 0x1000); + write(priv, mod->base + CCOR_COEFF_REG(5), 0x0000); + + write(priv, mod->base + CCOR_COEFF_REG(6), 0x0000); + write(priv, mod->base + CCOR_COEFF_REG(7), 0x0000); + write(priv, mod->base + CCOR_COEFF_REG(8), 0x1000); + + write(priv, mod->base + CCOR_OFFSET_R_REG, 0x00000000); + write(priv, mod->base + CCOR_OFFSET_G_REG, 0x00000000); + write(priv, mod->base + CCOR_OFFSET_B_REG, 0x00000000); + + return 0; + } + + /* + * Coefficient n for color correction matrix. + * + * RPP coefficients are 16-bit signed fixed-point numbers with 4 bit + * integer and 12 bit fractional part ranging from -8 (0x8000) to + * +7.9996 (0x7FFF). 0 is represented by 0x0000 and a coefficient + * value of 1 as 0x1000. + */ + write(priv, mod->base + CCOR_COEFF_REG(0), cfg->coeff[0][0]); + write(priv, mod->base + CCOR_COEFF_REG(1), cfg->coeff[0][1]); + write(priv, mod->base + CCOR_COEFF_REG(2), cfg->coeff[0][2]); + + write(priv, mod->base + CCOR_COEFF_REG(3), cfg->coeff[1][0]); + write(priv, mod->base + CCOR_COEFF_REG(4), cfg->coeff[1][1]); + write(priv, mod->base + CCOR_COEFF_REG(5), cfg->coeff[1][2]); + + write(priv, mod->base + CCOR_COEFF_REG(6), cfg->coeff[2][0]); + write(priv, mod->base + CCOR_COEFF_REG(7), cfg->coeff[2][1]); + write(priv, mod->base + CCOR_COEFF_REG(8), cfg->coeff[2][2]); + + /* + * Offset for color components correction matrix. + * + * Values are a two's complement integer with one sign bit. + */ + write(priv, mod->base + CCOR_OFFSET_R_REG, cfg->offset[0]); + write(priv, mod->base + CCOR_OFFSET_G_REG, cfg->offset[1]); + write(priv, mod->base + CCOR_OFFSET_B_REG, cfg->offset[2]); + + return 0; +} + const struct rpp_module_ops rppx1_ccor_ops = { .probe = rppx1_ccor_probe, .start = rppx1_ccor_start, + .fill_params = rppx1_ccor_fill_params, }; static int rppx1_ccor_csm_start(struct rpp_module *mod, diff --git a/include/uapi/linux/media/dreamchip/rppx1-config.h b/include/uapi/linux/media/dreamchip/rppx1-config.h index 0049977870ad..feea30585d4c 100644 --- a/include/uapi/linux/media/dreamchip/rppx1-config.h +++ b/include/uapi/linux/media/dreamchip/rppx1-config.h @@ -87,6 +87,7 @@ enum rppx1_meas_chan { * @RPPX1_PARAMS_BLOCK_TYPE_HIST_POST: POST pipe Histogram Measurement * @RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE1: PRE1 pipe Black Level Subtraction * @RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE2: PRE2 pipe Black Level Subtraction + * @RPPX1_PARAMS_BLOCK_TYPE_CCOR_POST: POST pipe Color Correction */ enum rppx1_params_block_type { RPPX1_PARAMS_BLOCK_TYPE_WBMEAS_POST, @@ -100,6 +101,7 @@ enum rppx1_params_block_type { RPPX1_PARAMS_BLOCK_TYPE_HIST_POST, RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE1, RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE2, + RPPX1_PARAMS_BLOCK_TYPE_CCOR_POST, }; /** @@ -433,6 +435,31 @@ struct rppx1_bls_params { __u8 reserved[5]; }; +/** + * struct rppx1_ccor_params - Color CORrection configuration + * + * The CCOR (Color Correction) module is available on the MAIN_POST pipe. It + * performs color space correction on a pixel-per-pixel basis using a 3x3 matrix + * of coefficients and per-color channel offsets. + * + * The matrix coefficients are represented as 16 bits signed fixed point values + * in Q4.12 format ranging from -8 to +7.999. + * + * The per-channel color offsets are represented as 2's complement values + * stored in 25 bits ranging from -16777216 to 16777215. + * + * @header: block header (type = RPPX1_PARAMS_BLOCK_TYPE_CCOR_POST) + * @coeff: color correction matrix coefficients, 16 bits signed Q4.12 + * @reserved: padding + * @offset: R, G, B offsets, 2's complement 25 bits + */ +struct rppx1_ccor_params { + struct v4l2_isp_params_block_header header; + __u16 coeff[3][3]; + __u16 reserved; + __u32 offset[3]; +}; + /** * RPPX1_PARAMS_MAX_SIZE - Maximum size of all RPP-X1 parameter blocks * @@ -450,7 +477,8 @@ struct rppx1_bls_params { sizeof(struct rppx1_hist_params) + \ sizeof(struct rppx1_hist_params) + \ sizeof(struct rppx1_bls_params) + \ - sizeof(struct rppx1_bls_params)) + sizeof(struct rppx1_bls_params) + \ + sizeof(struct rppx1_ccor_params)) /* --------------------------------------------------------------------------- * Statistics Structures -- cgit v1.2.3 From b39656efb71ad1e598b89e4bb1a39696d5c2be28 Mon Sep 17 00:00:00 2001 From: Niklas Söderlund Date: Thu, 30 Jul 2026 19:12:39 +0200 Subject: media: rppx1: lsc: Add support for lens shade correction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend the RPPX1 driver to allow setting the lens shade correction configuration parameters. It uses the RPPX1 framework for parameters and its writer abstraction to allow the user to control how, and when, configuration is applied to the RPPX1. Signed-off-by: Niklas Söderlund Co-developed-by: Jai Luthra Signed-off-by: Jai Luthra Co-developed-by: Jacopo Mondi Signed-off-by: Jacopo Mondi Reviewed-by: Jacopo Mondi Signed-off-by: Sakari Ailus --- .../media/platform/dreamchip/rppx1/rpp_module.h | 1 + .../media/platform/dreamchip/rppx1/rpp_params.c | 5 + drivers/media/platform/dreamchip/rppx1/rppx1_lsc.c | 119 +++++++++++++++++++++ include/uapi/linux/media/dreamchip/rppx1-config.h | 54 +++++++++- 4 files changed, 178 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/drivers/media/platform/dreamchip/rppx1/rpp_module.h b/drivers/media/platform/dreamchip/rppx1/rpp_module.h index e039746ac542..48b61b5c35b4 100644 --- a/drivers/media/platform/dreamchip/rppx1/rpp_module.h +++ b/drivers/media/platform/dreamchip/rppx1/rpp_module.h @@ -48,6 +48,7 @@ void rpp_module_clrset(struct rpp_module *mod, u32 offset, u32 mask, u32 value); union rppx1_params_block { struct v4l2_isp_block_header header; struct rppx1_bls_params bls; + struct rppx1_lsc_params lsc; struct rppx1_awbg_params awbg; struct rppx1_ccor_params ccor; struct rppx1_hist_params hist; diff --git a/drivers/media/platform/dreamchip/rppx1/rpp_params.c b/drivers/media/platform/dreamchip/rppx1/rpp_params.c index a83d393d0504..8d85d0c7bff1 100644 --- a/drivers/media/platform/dreamchip/rppx1/rpp_params.c +++ b/drivers/media/platform/dreamchip/rppx1/rpp_params.c @@ -19,6 +19,8 @@ static const struct v4l2_isp_params_block_type_info rppx1_ext_params_blocks_info[] = { RPPX1_PARAMS_BLOCK_INFO(BLS_PRE1, bls), RPPX1_PARAMS_BLOCK_INFO(BLS_PRE2, bls), + RPPX1_PARAMS_BLOCK_INFO(LSC_PRE1, lsc), + RPPX1_PARAMS_BLOCK_INFO(LSC_PRE2, lsc), RPPX1_PARAMS_BLOCK_INFO(AWBG_PRE1, awbg), RPPX1_PARAMS_BLOCK_INFO(AWBG_PRE2, awbg), RPPX1_PARAMS_BLOCK_INFO(CCOR_POST, ccor), @@ -64,6 +66,9 @@ int rppx1_params(struct rppx1 *rpp, struct vb2_buffer *vb, size_t max_size, case RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE1: module = &rpp->pre1.bls; break; + case RPPX1_PARAMS_BLOCK_TYPE_LSC_PRE1: + module = &rpp->pre1.lsc; + break; case RPPX1_PARAMS_BLOCK_TYPE_AWBG_PRE1: module = &rpp->pre1.awbg; break; diff --git a/drivers/media/platform/dreamchip/rppx1/rppx1_lsc.c b/drivers/media/platform/dreamchip/rppx1/rppx1_lsc.c index be49fc17ea26..8badeca23e24 100644 --- a/drivers/media/platform/dreamchip/rppx1/rppx1_lsc.c +++ b/drivers/media/platform/dreamchip/rppx1/rppx1_lsc.c @@ -55,6 +55,10 @@ #define LSC_TABLE_SEL_REG 0x00a8 #define LSC_STATUS_REG 0x00ac +#define LSC_R_TABLE_DATA_VALUE(v1, v2) (((v1) & 0xfff) | (((v2) & 0xfff) << 12)) +#define LSC_GRAD_VALUE(v1, v2) (((v1) & 0xfff) | (((v2) & 0xfff) << 16)) +#define LSC_SIZE_VALUE(v1, v2) (((v1) & 0x1ff) | (((v2) & 0x1ff) << 16)) + static int rppx1_lsc_probe(struct rpp_module *mod) { /* Version check. */ @@ -64,6 +68,121 @@ static int rppx1_lsc_probe(struct rpp_module *mod) return 0; } +static int +rppx1_lsc_fill_params(struct rpp_module *mod, + const union rppx1_params_block *block, + rppx1_reg_write write, void *priv) +{ + const struct rppx1_lsc_params *cfg = &block->lsc; + const __u16 *v; + + /* Always disable module as it needs be disabled before configuring. */ + write(priv, mod->base + LSC_CTRL_REG, 0); + if (cfg->header.flags & V4L2_ISP_PARAMS_FL_BLOCK_DISABLE) + return 0; + + /* + * Program the color correction sectors. + * + * There are two tables to one can program and switch between. As the + * RPPX1 supports preparing a buffer of commands to be applied later + * only use table 0. This works as long as the ISP is not used in + * inline-mode. + * + * For inline-mode support using DMA for configuration is not possible + * so this is not an issue, but needs to be address if inline-mode + * support is added to the driver. + */ + + /* Start writing at beginning of table 0. */ + write(priv, mod->base + LSC_R_TABLE_ADDR_REG, 0); + write(priv, mod->base + LSC_GR_TABLE_ADDR_REG, 0); + write(priv, mod->base + LSC_B_TABLE_ADDR_REG, 0); + write(priv, mod->base + LSC_GB_TABLE_ADDR_REG, 0); + + /* Program data tables. */ + for (unsigned int i = 0; i < RPPX1_LSC_SAMPLES_MAX; i++) { + const __u16 *r = cfg->r_data[i]; + const __u16 *gr = cfg->gr_data[i]; + const __u16 *b = cfg->b_data[i]; + const __u16 *gb = cfg->gb_data[i]; + unsigned int j; + + for (j = 0; j < RPPX1_LSC_SAMPLES_MAX - 1; j += 2) { + write(priv, mod->base + LSC_R_TABLE_DATA_REG, + LSC_R_TABLE_DATA_VALUE(r[j], r[j + 1])); + write(priv, mod->base + LSC_GR_TABLE_DATA_REG, + LSC_R_TABLE_DATA_VALUE(gr[j], gr[j + 1])); + write(priv, mod->base + LSC_B_TABLE_DATA_REG, + LSC_R_TABLE_DATA_VALUE(b[j], b[j + 1])); + write(priv, mod->base + LSC_GB_TABLE_DATA_REG, + LSC_R_TABLE_DATA_VALUE(gb[j], gb[j + 1])); + } + + write(priv, mod->base + LSC_R_TABLE_DATA_REG, + LSC_R_TABLE_DATA_VALUE(r[j], 0)); + write(priv, mod->base + LSC_GR_TABLE_DATA_REG, + LSC_R_TABLE_DATA_VALUE(gr[j], 0)); + write(priv, mod->base + LSC_B_TABLE_DATA_REG, + LSC_R_TABLE_DATA_VALUE(b[j], 0)); + write(priv, mod->base + LSC_GB_TABLE_DATA_REG, + LSC_R_TABLE_DATA_VALUE(gb[j], 0)); + } + + /* Activate table 0. */ + write(priv, mod->base + LSC_TABLE_SEL_REG, 0); + + /* + * Program X- and Y- sizes, and gradients. + */ + + v = cfg->x_grad; + write(priv, mod->base + LSC_XGRAD_01_REG, LSC_GRAD_VALUE(v[0], v[1])); + write(priv, mod->base + LSC_XGRAD_23_REG, LSC_GRAD_VALUE(v[2], v[3])); + write(priv, mod->base + LSC_XGRAD_45_REG, LSC_GRAD_VALUE(v[4], v[5])); + write(priv, mod->base + LSC_XGRAD_67_REG, LSC_GRAD_VALUE(v[6], v[7])); + write(priv, mod->base + LSC_XGRAD_89_REG, LSC_GRAD_VALUE(v[8], v[9])); + write(priv, mod->base + LSC_XGRAD_1011_REG, LSC_GRAD_VALUE(v[10], v[11])); + write(priv, mod->base + LSC_XGRAD_1213_REG, LSC_GRAD_VALUE(v[12], v[13])); + write(priv, mod->base + LSC_XGRAD_1415_REG, LSC_GRAD_VALUE(v[14], v[15])); + + v = cfg->y_grad; + write(priv, mod->base + LSC_YGRAD_01_REG, LSC_GRAD_VALUE(v[0], v[1])); + write(priv, mod->base + LSC_YGRAD_23_REG, LSC_GRAD_VALUE(v[2], v[3])); + write(priv, mod->base + LSC_YGRAD_45_REG, LSC_GRAD_VALUE(v[4], v[5])); + write(priv, mod->base + LSC_YGRAD_67_REG, LSC_GRAD_VALUE(v[6], v[7])); + write(priv, mod->base + LSC_YGRAD_89_REG, LSC_GRAD_VALUE(v[8], v[9])); + write(priv, mod->base + LSC_YGRAD_1011_REG, LSC_GRAD_VALUE(v[10], v[11])); + write(priv, mod->base + LSC_YGRAD_1213_REG, LSC_GRAD_VALUE(v[12], v[13])); + write(priv, mod->base + LSC_YGRAD_1415_REG, LSC_GRAD_VALUE(v[14], v[15])); + + v = cfg->x_sect_size; + write(priv, mod->base + LSC_XSIZE_01_REG, LSC_GRAD_VALUE(v[0], v[1])); + write(priv, mod->base + LSC_XSIZE_23_REG, LSC_GRAD_VALUE(v[2], v[3])); + write(priv, mod->base + LSC_XSIZE_45_REG, LSC_GRAD_VALUE(v[4], v[5])); + write(priv, mod->base + LSC_XSIZE_67_REG, LSC_GRAD_VALUE(v[6], v[7])); + write(priv, mod->base + LSC_XSIZE_89_REG, LSC_GRAD_VALUE(v[8], v[9])); + write(priv, mod->base + LSC_XSIZE_1011_REG, LSC_GRAD_VALUE(v[10], v[11])); + write(priv, mod->base + LSC_XSIZE_1213_REG, LSC_GRAD_VALUE(v[12], v[13])); + write(priv, mod->base + LSC_XSIZE_1415_REG, LSC_GRAD_VALUE(v[14], v[15])); + + v = cfg->y_sect_size; + write(priv, mod->base + LSC_YSIZE_01_REG, LSC_GRAD_VALUE(v[0], v[1])); + write(priv, mod->base + LSC_YSIZE_23_REG, LSC_GRAD_VALUE(v[2], v[3])); + write(priv, mod->base + LSC_YSIZE_45_REG, LSC_GRAD_VALUE(v[4], v[5])); + write(priv, mod->base + LSC_YSIZE_67_REG, LSC_GRAD_VALUE(v[6], v[7])); + write(priv, mod->base + LSC_YSIZE_89_REG, LSC_GRAD_VALUE(v[8], v[9])); + write(priv, mod->base + LSC_YSIZE_1011_REG, LSC_GRAD_VALUE(v[10], v[11])); + write(priv, mod->base + LSC_YSIZE_1213_REG, LSC_GRAD_VALUE(v[12], v[13])); + write(priv, mod->base + LSC_YSIZE_1415_REG, LSC_GRAD_VALUE(v[14], v[15])); + + /* Enable module. */ + write(priv, mod->base + LSC_CTRL_REG, LSC_CTRL_LSC_EN); + + return 0; +} + const struct rpp_module_ops rppx1_lsc_ops = { .probe = rppx1_lsc_probe, + .fill_params = rppx1_lsc_fill_params, }; diff --git a/include/uapi/linux/media/dreamchip/rppx1-config.h b/include/uapi/linux/media/dreamchip/rppx1-config.h index feea30585d4c..eec8a9c50bbe 100644 --- a/include/uapi/linux/media/dreamchip/rppx1-config.h +++ b/include/uapi/linux/media/dreamchip/rppx1-config.h @@ -88,6 +88,8 @@ enum rppx1_meas_chan { * @RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE1: PRE1 pipe Black Level Subtraction * @RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE2: PRE2 pipe Black Level Subtraction * @RPPX1_PARAMS_BLOCK_TYPE_CCOR_POST: POST pipe Color Correction + * @RPPX1_PARAMS_BLOCK_TYPE_LSC_PRE1: PRE1 pipe Lens Shading Correction + * @RPPX1_PARAMS_BLOCK_TYPE_LSC_PRE2: PRE2 pipe Lens Shading Correction */ enum rppx1_params_block_type { RPPX1_PARAMS_BLOCK_TYPE_WBMEAS_POST, @@ -102,6 +104,8 @@ enum rppx1_params_block_type { RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE1, RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE2, RPPX1_PARAMS_BLOCK_TYPE_CCOR_POST, + RPPX1_PARAMS_BLOCK_TYPE_LSC_PRE1, + RPPX1_PARAMS_BLOCK_TYPE_LSC_PRE2, }; /** @@ -460,6 +464,52 @@ struct rppx1_ccor_params { __u32 offset[3]; }; +/* Lens Shade Correction */ +#define RPPX1_LSC_SAMPLES_MAX 17 +#define RPPX1_LSC_NUM_SECTORS 16 + +/** + * struct rppx1_lsc_params - Lens Shading Correction configuration + * + * The RPP-X1 Lens shading correction module is available on the PRE1 and PRE2 + * pre-fusion pipes. Userspace selects which pipe to operate by setting the + * @header.type field to RPPX1_PARAMS_BLOCK_TYPE_LSC_PRE1 or + * RPPX1_PARAMS_BLOCK_TYPE_LSC_PRE2. + * + * The module applies per-color channel correction factors @r_data, @gr_data, + * @gb_data and @b_data as a 16x16 grid mapped on the image. The size of each + * grid segment is expressed by the @x_sect_size and @y_sect_size arrays. Each + * segment shall be at least 8 pixels in size and the sum of all horizontal + * segments @x_sect_size shall match the input frame size width. + * + * The correction factors values are expressed as unsigned Q2.10 integers + * ranging from 1 to 3.999. + * + * Pre-calculated interpolation factors shall be provided in the @x_grad + * and @y_grad fields, expressed as 12 bits integer values. + * + * @header: block header (type = RPPX1_PARAMS_BLOCK_TYPE_LSC) + * @r_data: correction factors for the red channel in Q2.10 format + * @gr_data: correction factors for the green (red) channel in Q2.10 format + * @gb_data: correction factors for the green (blue) channel in Q2.10 format + * @b_data: correction factors for the blue channel in Q2.10 format + * @x_grad: Interpolation gradients for each horizontal sector (12 bits) + * @y_grad: Interpolation gradients for each vertical sector (12 bits) + * @x_sect_size: Horizontal sectors sizes + * @y_sect_size: Vertical sectors sizes + */ +struct rppx1_lsc_params { + struct v4l2_isp_params_block_header header; + __u16 r_data[RPPX1_LSC_SAMPLES_MAX][RPPX1_LSC_SAMPLES_MAX]; + __u16 gr_data[RPPX1_LSC_SAMPLES_MAX][RPPX1_LSC_SAMPLES_MAX]; + __u16 gb_data[RPPX1_LSC_SAMPLES_MAX][RPPX1_LSC_SAMPLES_MAX]; + __u16 b_data[RPPX1_LSC_SAMPLES_MAX][RPPX1_LSC_SAMPLES_MAX]; + __u16 x_grad[RPPX1_LSC_NUM_SECTORS]; + __u16 y_grad[RPPX1_LSC_NUM_SECTORS]; + __u16 x_sect_size[RPPX1_LSC_NUM_SECTORS]; + __u16 y_sect_size[RPPX1_LSC_NUM_SECTORS]; +}; + /** * RPPX1_PARAMS_MAX_SIZE - Maximum size of all RPP-X1 parameter blocks * @@ -478,7 +528,9 @@ struct rppx1_ccor_params { sizeof(struct rppx1_hist_params) + \ sizeof(struct rppx1_bls_params) + \ sizeof(struct rppx1_bls_params) + \ - sizeof(struct rppx1_ccor_params)) + sizeof(struct rppx1_ccor_params) + \ + sizeof(struct rppx1_lsc_params) + \ + sizeof(struct rppx1_lsc_params)) /* --------------------------------------------------------------------------- * Statistics Structures -- cgit v1.2.3 From d011daefa1f1279e949a9b10a9439fc02f5093b1 Mon Sep 17 00:00:00 2001 From: Niklas Söderlund Date: Thu, 30 Jul 2026 19:12:40 +0200 Subject: media: rppx1: ga: Add support for gamma out correction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend the RPPX1 driver to allow setting the gamma out correction configuration parameters. It uses the RPPX1 framework for parameters and its writer abstraction to allow the user to control how, and when, configuration is applied to the RPPX1. Signed-off-by: Niklas Söderlund Co-developed-by: Jai Luthra Signed-off-by: Jai Luthra Co-developed-by: Jacopo Mondi Signed-off-by: Jacopo Mondi Reviewed-by: Jacopo Mondi Signed-off-by: Sakari Ailus --- .../media/platform/dreamchip/rppx1/rpp_module.h | 1 + .../media/platform/dreamchip/rppx1/rpp_params.c | 5 ++ drivers/media/platform/dreamchip/rppx1/rppx1_ga.c | 43 ++++++++++++++++- include/uapi/linux/media/dreamchip/rppx1-config.h | 55 +++++++++++++++++++++- 4 files changed, 102 insertions(+), 2 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/media/platform/dreamchip/rppx1/rpp_module.h b/drivers/media/platform/dreamchip/rppx1/rpp_module.h index 48b61b5c35b4..948e26d7361d 100644 --- a/drivers/media/platform/dreamchip/rppx1/rpp_module.h +++ b/drivers/media/platform/dreamchip/rppx1/rpp_module.h @@ -54,6 +54,7 @@ union rppx1_params_block { struct rppx1_hist_params hist; struct rppx1_exm_params exm; struct rppx1_wbmeas_params wbmeas; + struct rppx1_ga_params ga; }; union rppx1_stats_block { diff --git a/drivers/media/platform/dreamchip/rppx1/rpp_params.c b/drivers/media/platform/dreamchip/rppx1/rpp_params.c index 8d85d0c7bff1..86d445b52504 100644 --- a/drivers/media/platform/dreamchip/rppx1/rpp_params.c +++ b/drivers/media/platform/dreamchip/rppx1/rpp_params.c @@ -30,6 +30,8 @@ rppx1_ext_params_blocks_info[] = { RPPX1_PARAMS_BLOCK_INFO(EXM_PRE1, exm), RPPX1_PARAMS_BLOCK_INFO(EXM_PRE2, exm), RPPX1_PARAMS_BLOCK_INFO(WBMEAS_POST, wbmeas), + RPPX1_PARAMS_BLOCK_INFO(GA_HV, ga), + RPPX1_PARAMS_BLOCK_INFO(GA_MV, ga), }; int rppx1_params(struct rppx1 *rpp, struct vb2_buffer *vb, size_t max_size, @@ -84,6 +86,9 @@ int rppx1_params(struct rppx1 *rpp, struct vb2_buffer *vb, size_t max_size, case RPPX1_PARAMS_BLOCK_TYPE_WBMEAS_POST: module = &rpp->post.wbmeas; break; + case RPPX1_PARAMS_BLOCK_TYPE_GA_HV: + module = &rpp->hv.ga; + break; default: dev_warn(rpp->dev, "Not handled RPPX1 block type: 0x%04x\n", diff --git a/drivers/media/platform/dreamchip/rppx1/rppx1_ga.c b/drivers/media/platform/dreamchip/rppx1/rppx1_ga.c index 0667672b2694..1d9c24c43f77 100644 --- a/drivers/media/platform/dreamchip/rppx1/rppx1_ga.c +++ b/drivers/media/platform/dreamchip/rppx1/rppx1_ga.c @@ -15,9 +15,11 @@ #define GAMMA_OUT_MODE_REG 0x0008 #define GAMMA_OUT_MODE_GAMMA_OUT_EQU_SEGM BIT(0) -#define GAMMA_OUT_Y_REG_NUM 17 #define GAMMA_OUT_Y_REG(n) (0x000c + (4 * (n))) +#define GAMMA_OUT_HV_GAMMA_CURVE_MASK GENMASK(11, 0) +#define GAMMA_OUT_MV_GAMMA_CURVE_MASK GENMASK(23, 0) + static int rppx1_ga_probe(struct rpp_module *mod) { /* Version check. */ @@ -44,7 +46,46 @@ static int rppx1_ga_start(struct rpp_module *mod, return 0; } +static int +rppx1_ga_fill_params(struct rpp_module *mod, + const union rppx1_params_block *block, + rppx1_reg_write write, void *priv) +{ + const struct rppx1_ga_params *cfg = &block->ga; + u32 mask; + + /* If the modules is disabled, simply bypass it. */ + if (cfg->header.flags & V4L2_ISP_PARAMS_FL_BLOCK_DISABLE) { + write(priv, mod->base + GAMMA_OUT_ENABLE_REG, 0); + return 0; + } + + switch (cfg->header.type) { + case RPPX1_PARAMS_BLOCK_TYPE_GA_HV: + mask = GAMMA_OUT_HV_GAMMA_CURVE_MASK; + break; + case RPPX1_PARAMS_BLOCK_TYPE_GA_MV: + mask = GAMMA_OUT_MV_GAMMA_CURVE_MASK; + break; + default: + return -EINVAL; + } + + write(priv, mod->base + GAMMA_OUT_MODE_REG, cfg->mode); + + for (unsigned int i = 0; i < RPPX1_GA_MAX_SAMPLES; i++) + write(priv, mod->base + GAMMA_OUT_Y_REG(i), + cfg->gamma_y[i] & mask); + + /* Enable module. */ + write(priv, mod->base + GAMMA_OUT_ENABLE_REG, + GAMMA_OUT_ENABLE_GAMMA_OUT_EN); + + return 0; +} + const struct rpp_module_ops rppx1_ga_ops = { .probe = rppx1_ga_probe, .start = rppx1_ga_start, + .fill_params = rppx1_ga_fill_params, }; diff --git a/include/uapi/linux/media/dreamchip/rppx1-config.h b/include/uapi/linux/media/dreamchip/rppx1-config.h index eec8a9c50bbe..aa8a078493af 100644 --- a/include/uapi/linux/media/dreamchip/rppx1-config.h +++ b/include/uapi/linux/media/dreamchip/rppx1-config.h @@ -90,6 +90,8 @@ enum rppx1_meas_chan { * @RPPX1_PARAMS_BLOCK_TYPE_CCOR_POST: POST pipe Color Correction * @RPPX1_PARAMS_BLOCK_TYPE_LSC_PRE1: PRE1 pipe Lens Shading Correction * @RPPX1_PARAMS_BLOCK_TYPE_LSC_PRE2: PRE2 pipe Lens Shading Correction + * @RPPX1_PARAMS_BLOCK_TYPE_GA_HV: Human Vision Pipe Gamma Out Correction + * @RPPX1_PARAMS_BLOCK_TYPE_GA_MV: Machine Vision Gamma Out Correction */ enum rppx1_params_block_type { RPPX1_PARAMS_BLOCK_TYPE_WBMEAS_POST, @@ -106,6 +108,8 @@ enum rppx1_params_block_type { RPPX1_PARAMS_BLOCK_TYPE_CCOR_POST, RPPX1_PARAMS_BLOCK_TYPE_LSC_PRE1, RPPX1_PARAMS_BLOCK_TYPE_LSC_PRE2, + RPPX1_PARAMS_BLOCK_TYPE_GA_HV, + RPPX1_PARAMS_BLOCK_TYPE_GA_MV, }; /** @@ -510,6 +514,53 @@ struct rppx1_lsc_params { __u16 y_sect_size[RPPX1_LSC_NUM_SECTORS]; }; +/* Gamma Out */ +#define RPPX1_GA_MAX_SAMPLES 17 + +/** + * enum rppx1_ga_seg_mode - Gamma out curve segmentation mode + * + * Segmentation mode of the 16 input sampling points for the Gamma Out + * Correction module. + * + * @RPPX1_GA_SEG_MODE_LOGARITHMIC: logarithmic-like segmentation mode + * @RPPX1_GA_SEG_MODE_EQUIDISTANT: equidistant segmentation mode + */ +enum rppx1_ga_seg_mode { + RPPX1_GA_SEG_MODE_LOGARITHMIC, + RPPX1_GA_SEG_MODE_EQUIDISTANT +}; + +/** + * struct rppx1_ga_params - Gamma Out Correction configuration + * + * The Gamma Out Correction module is available on the Human Vision Output + * Pipe (HV) and the Machine Vision Output Pipe (MV). Userspace selects + * which pipe to operate by setting the @header.type field to + * RPPX1_PARAMS_BLOCK_TYPE_GA_HV or RPPX1_PARAMS_BLOCK_TYPE_GA_MV. + * + * The module allows to apply a @gamma_y gamma correction curve to RGB data + * represented as a table of 16 entries. The 16 input sampling points can be + * equidistant or segmented using a logarithmic scale according to the value of + * @mode. + * + * The gamma curve values are 12 bits on the HV output pipe and 24 bits on the + * MV output pipe. Userspace is expected to provide the curve values with a + * bit-depth matching the one of pipe in use. + * + * @header: block header (type = RPPX1_PARAMS_BLOCK_TYPE_GA_HV or + * type = RPPX1_PARAMS_BLOCK_TYPE_GA_MV) + * @gamma_y: gamma out curve y-axis values + * @mode: gamma curve input segmentation mode (see rppx1_ga_seg_mode) + * @reserved: padding + */ +struct rppx1_ga_params { + struct v4l2_isp_params_block_header header; + __u32 gamma_y[RPPX1_GA_MAX_SAMPLES]; + __u8 mode; + __u8 reserved[3]; +}; + /** * RPPX1_PARAMS_MAX_SIZE - Maximum size of all RPP-X1 parameter blocks * @@ -530,7 +581,9 @@ struct rppx1_lsc_params { sizeof(struct rppx1_bls_params) + \ sizeof(struct rppx1_ccor_params) + \ sizeof(struct rppx1_lsc_params) + \ - sizeof(struct rppx1_lsc_params)) + sizeof(struct rppx1_lsc_params) + \ + sizeof(struct rppx1_ga_params) + \ + sizeof(struct rppx1_ga_params)) /* --------------------------------------------------------------------------- * Statistics Structures -- cgit v1.2.3 From 465425926ff36b9f5fb1f069e4277918e6109f5a Mon Sep 17 00:00:00 2001 From: Jai Luthra Date: Thu, 30 Jul 2026 19:12:41 +0200 Subject: media: rppx1: lin: Add support for gamma sensor linearization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend the RPPX1 driver to allow setting the gamma sensor linearization configuration parameters. It uses the RPPX1 framework for parameters and its writer abstraction to allow the user to control how, and when, configuration is applied to the RPPX1. Signed-off-by: Jai Luthra Signed-off-by: Niklas Söderlund Reviewed-by: Jacopo Mondi Signed-off-by: Sakari Ailus --- .../media/platform/dreamchip/rppx1/rpp_module.h | 1 + .../media/platform/dreamchip/rppx1/rpp_params.c | 5 ++ drivers/media/platform/dreamchip/rppx1/rppx1_lin.c | 53 ++++++++++++++++++++++ include/uapi/linux/media/dreamchip/rppx1-config.h | 50 +++++++++++++++++++- 4 files changed, 108 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/drivers/media/platform/dreamchip/rppx1/rpp_module.h b/drivers/media/platform/dreamchip/rppx1/rpp_module.h index 948e26d7361d..072309d8662d 100644 --- a/drivers/media/platform/dreamchip/rppx1/rpp_module.h +++ b/drivers/media/platform/dreamchip/rppx1/rpp_module.h @@ -48,6 +48,7 @@ void rpp_module_clrset(struct rpp_module *mod, u32 offset, u32 mask, u32 value); union rppx1_params_block { struct v4l2_isp_block_header header; struct rppx1_bls_params bls; + struct rppx1_lin_params lin; struct rppx1_lsc_params lsc; struct rppx1_awbg_params awbg; struct rppx1_ccor_params ccor; diff --git a/drivers/media/platform/dreamchip/rppx1/rpp_params.c b/drivers/media/platform/dreamchip/rppx1/rpp_params.c index 86d445b52504..a75a27a8afd0 100644 --- a/drivers/media/platform/dreamchip/rppx1/rpp_params.c +++ b/drivers/media/platform/dreamchip/rppx1/rpp_params.c @@ -19,6 +19,8 @@ static const struct v4l2_isp_params_block_type_info rppx1_ext_params_blocks_info[] = { RPPX1_PARAMS_BLOCK_INFO(BLS_PRE1, bls), RPPX1_PARAMS_BLOCK_INFO(BLS_PRE2, bls), + RPPX1_PARAMS_BLOCK_INFO(LIN_PRE1, lin), + RPPX1_PARAMS_BLOCK_INFO(LIN_PRE2, lin), RPPX1_PARAMS_BLOCK_INFO(LSC_PRE1, lsc), RPPX1_PARAMS_BLOCK_INFO(LSC_PRE2, lsc), RPPX1_PARAMS_BLOCK_INFO(AWBG_PRE1, awbg), @@ -68,6 +70,9 @@ int rppx1_params(struct rppx1 *rpp, struct vb2_buffer *vb, size_t max_size, case RPPX1_PARAMS_BLOCK_TYPE_BLS_PRE1: module = &rpp->pre1.bls; break; + case RPPX1_PARAMS_BLOCK_TYPE_LIN_PRE1: + module = &rpp->pre1.lin; + break; case RPPX1_PARAMS_BLOCK_TYPE_LSC_PRE1: module = &rpp->pre1.lsc; break; diff --git a/drivers/media/platform/dreamchip/rppx1/rppx1_lin.c b/drivers/media/platform/dreamchip/rppx1/rppx1_lin.c index 0a0e7ab2d1d6..b389778f9383 100644 --- a/drivers/media/platform/dreamchip/rppx1/rppx1_lin.c +++ b/drivers/media/platform/dreamchip/rppx1/rppx1_lin.c @@ -25,6 +25,11 @@ #define LIN_B_Y_REG_NUM 17 #define LIN_B_Y_REG(n) (0x0098 + (4 * (n))) +#define LIN_PRE1_DEGAMMA_CURVE_MASK GENMASK(23, 0) +#define LIN_PRE1_SAMPLE_POINTS_MASK GENMASK(3, 0) +#define LIN_PRE2_DEGAMMA_CURVE_MASK GENMASK(11, 0) +#define LIN_PRE2_SAMPLE_POINTS_MASK GENMASK(2, 0) + static int rppx1_lin_probe(struct rpp_module *mod) { /* Version check. */ @@ -53,7 +58,55 @@ static int rppx1_lin_start(struct rpp_module *mod, return 0; } +static int rppx1_lin_fill_params(struct rpp_module *mod, + const union rppx1_params_block *block, + rppx1_reg_write write, void *priv) +{ + const struct rppx1_lin_params *cfg = &block->lin; + u8 sample_mask; + u32 dx_lo = 0; + u32 dx_hi = 0; + u32 mask; + + if (cfg->header.flags & V4L2_ISP_PARAMS_FL_BLOCK_DISABLE) { + write(priv, mod->base + LIN_ENABLE_REG, 0); + return 0; + } + + switch (cfg->header.type) { + case RPPX1_PARAMS_BLOCK_TYPE_LIN_PRE1: + mask = LIN_PRE1_DEGAMMA_CURVE_MASK; + sample_mask = LIN_PRE1_SAMPLE_POINTS_MASK; + break; + case RPPX1_PARAMS_BLOCK_TYPE_LIN_PRE2: + mask = LIN_PRE2_DEGAMMA_CURVE_MASK; + sample_mask = LIN_PRE2_SAMPLE_POINTS_MASK; + break; + default: + return -EINVAL; + } + + for (unsigned int i = 0; i < 8; ++i) { + dx_lo |= (cfg->dx[i] & sample_mask) << 4 * i; + dx_hi |= (cfg->dx[i + 8] & sample_mask) << 4 * i; + } + + write(priv, mod->base + LIN_DX_LO_REG, dx_lo); + write(priv, mod->base + LIN_DX_HI_REG, dx_hi); + + for (unsigned int i = 0; i < RPPX1_LIN_DEGAMMA_CURVE_NUM; i++) { + write(priv, mod->base + LIN_R_Y_REG(i), cfg->curve_r[i] & mask); + write(priv, mod->base + LIN_G_Y_REG(i), cfg->curve_g[i] & mask); + write(priv, mod->base + LIN_B_Y_REG(i), cfg->curve_b[i] & mask); + } + + write(priv, mod->base + LIN_ENABLE_REG, LIN_ENABLE_GAMMA_IN_EN); + + return 0; +} + const struct rpp_module_ops rppx1_lin_ops = { .probe = rppx1_lin_probe, .start = rppx1_lin_start, + .fill_params = rppx1_lin_fill_params, }; diff --git a/include/uapi/linux/media/dreamchip/rppx1-config.h b/include/uapi/linux/media/dreamchip/rppx1-config.h index aa8a078493af..3507a9ab03df 100644 --- a/include/uapi/linux/media/dreamchip/rppx1-config.h +++ b/include/uapi/linux/media/dreamchip/rppx1-config.h @@ -92,6 +92,8 @@ enum rppx1_meas_chan { * @RPPX1_PARAMS_BLOCK_TYPE_LSC_PRE2: PRE2 pipe Lens Shading Correction * @RPPX1_PARAMS_BLOCK_TYPE_GA_HV: Human Vision Pipe Gamma Out Correction * @RPPX1_PARAMS_BLOCK_TYPE_GA_MV: Machine Vision Gamma Out Correction + * @RPPX1_PARAMS_BLOCK_TYPE_LIN_PRE1: PRE1 pipe Linearization (Sensor De-gamma) + * @RPPX1_PARAMS_BLOCK_TYPE_LIN_PRE2: PRE2 pipe Linearization (Sensor De-gamma) */ enum rppx1_params_block_type { RPPX1_PARAMS_BLOCK_TYPE_WBMEAS_POST, @@ -110,6 +112,8 @@ enum rppx1_params_block_type { RPPX1_PARAMS_BLOCK_TYPE_LSC_PRE2, RPPX1_PARAMS_BLOCK_TYPE_GA_HV, RPPX1_PARAMS_BLOCK_TYPE_GA_MV, + RPPX1_PARAMS_BLOCK_TYPE_LIN_PRE1, + RPPX1_PARAMS_BLOCK_TYPE_LIN_PRE2, }; /** @@ -561,6 +565,48 @@ struct rppx1_ga_params { __u8 reserved[3]; }; +/* Linearization (Sensor De-gamma) */ +#define RPPX1_LIN_SAMPLE_POINTS_NUM 16 +#define RPPX1_LIN_DEGAMMA_CURVE_NUM 17 + +/** + * struct rppx1_lin_params - Linearization (Sensor De-gamma) configuration + * + * The RPP-X1 linearization module is available on the PRE1 and PRE2 pre-fusion + * pipes. Userspace selects which pipe to operate by setting the @header.type + * field to RPPX1_PARAMS_BLOCK_TYPE_LIN_PRE1 or + * RPPX1_PARAMS_BLOCK_TYPE_LIN_PRE2. + * + * The LIN module applies the per-color channel de-gamma linearization curves + * @curve_r, @curve_g and @curve_b defined on the input sampling points @dx. + * + * For the PRE1 pipe the de-gamma curves values are 24-bits, for the PRE2 pipe + * the de-gamma curve values are 12-bits. + * + * For the PRE1 pipe de-gamma module sampling points @dx values are in the range + * [0, 15] (4 bits). For the PRE2 pipe de-gamma module sampling points values + * are in the range [0, 7] (3 bits). + * + * Userspace is expected to provide the curve values and sampling points with a + * bit-depth matching the one of pipe in use. + * + * @header: block header (type = RPPX1_PARAMS_BLOCK_TYPE_LIN_PRE1 or + * RPPX1_PARAMS_BLOCK_TYPE_LIN_PRE2) + * @curve_r: de-gamma linearization curve for red channel + * @curve_g: de-gamma linearization curve for green channel + * @curve_b: de-gamma linearization curve for blue channel + * @dx: input sampling points + * @reserved: padding + */ +struct rppx1_lin_params { + struct v4l2_isp_params_block_header header; + __u32 curve_r[RPPX1_LIN_DEGAMMA_CURVE_NUM]; + __u32 curve_g[RPPX1_LIN_DEGAMMA_CURVE_NUM]; + __u32 curve_b[RPPX1_LIN_DEGAMMA_CURVE_NUM]; + __u8 dx[RPPX1_LIN_SAMPLE_POINTS_NUM]; + __u32 reserved; +}; + /** * RPPX1_PARAMS_MAX_SIZE - Maximum size of all RPP-X1 parameter blocks * @@ -583,7 +629,9 @@ struct rppx1_ga_params { sizeof(struct rppx1_lsc_params) + \ sizeof(struct rppx1_lsc_params) + \ sizeof(struct rppx1_ga_params) + \ - sizeof(struct rppx1_ga_params)) + sizeof(struct rppx1_ga_params) + \ + sizeof(struct rppx1_lin_params) + \ + sizeof(struct rppx1_lin_params)) /* --------------------------------------------------------------------------- * Statistics Structures -- cgit v1.2.3 From 53531e6a644a48c2d5a9423f084ae4e91ec6019a Mon Sep 17 00:00:00 2001 From: Jack Ma Date: Fri, 24 Jul 2026 00:26:16 +0000 Subject: net: nexthop: add NHA_DST_PORT for fdb nexthops Commit 1274e1cc4226 ("vxlan: ecmp support for mac fdb entries") lets a single inner MAC be reached through a group of remote VTEPs, with the kernel flow-hashing across the group members. Each member carries its own remote IP, but the UDP destination port is always taken from the VXLAN device (vxlan->cfg.dst_port) and cannot be set per member. Some deployments pack several receivers behind one underlay IP and tell them apart by UDP port, so they need a per-nexthop destination port to spread flows across (IP, port) tuples rather than IP alone. Add a netlink attribute NHA_DST_PORT (__be16, mirroring NDA_PORT) that carries an optional UDP destination port on an fdb nexthop. It is only accepted together with NHA_FDB and NHA_GATEWAY; it is stored in struct nh_info and echoed back on dump. The attribute is named generically rather than fdb-specific so it can be reused should another nexthop type ever need a destination port. This patch is control-plane plumbing only; the VXLAN datapath is wired up in a follow-up patch, so behaviour is unchanged for now. Signed-off-by: Jack Ma Reviewed-by: Ido Schimmel Reviewed-by: David Ahern Link: https://patch.msgid.link/20260724-b4-vxlan-fdb-port-v5-1-cd1c6aeee058@gmail.com Signed-off-by: Jakub Kicinski --- include/net/nexthop.h | 2 ++ include/uapi/linux/nexthop.h | 3 +++ net/ipv4/nexthop.c | 20 +++++++++++++++++++- 3 files changed, 24 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/include/net/nexthop.h b/include/net/nexthop.h index 572e69cda476..7673aaeff3e2 100644 --- a/include/net/nexthop.h +++ b/include/net/nexthop.h @@ -28,6 +28,7 @@ struct nh_config { u8 nh_protocol; u8 nh_blackhole; u8 nh_fdb; + __be16 nh_dst_port; u32 nh_flags; int nh_ifindex; @@ -63,6 +64,7 @@ struct nh_info { u8 family; bool reject_nh; bool fdb_nh; + __be16 dst_port; union { struct fib_nh_common fib_nhc; diff --git a/include/uapi/linux/nexthop.h b/include/uapi/linux/nexthop.h index bc49baf4a267..59cf1cee93bd 100644 --- a/include/uapi/linux/nexthop.h +++ b/include/uapi/linux/nexthop.h @@ -83,6 +83,9 @@ enum { /* u32; read-only; whether any driver collects HW stats */ NHA_HW_STATS_USED, + /* be16; UDP destination port for an fdb nexthop (e.g. VXLAN) */ + NHA_DST_PORT, + __NHA_MAX, }; diff --git a/net/ipv4/nexthop.c b/net/ipv4/nexthop.c index 0f1e21a5c812..af1dcb8ea427 100644 --- a/net/ipv4/nexthop.c +++ b/net/ipv4/nexthop.c @@ -39,6 +39,7 @@ static const struct nla_policy rtm_nh_policy_new[] = { [NHA_ENCAP_TYPE] = { .type = NLA_U16 }, [NHA_ENCAP] = { .type = NLA_NESTED }, [NHA_FDB] = { .type = NLA_FLAG }, + [NHA_DST_PORT] = NLA_POLICY_MIN(NLA_BE16, 1), [NHA_RES_GROUP] = { .type = NLA_NESTED }, [NHA_HW_STATS_ENABLE] = NLA_POLICY_MAX(NLA_U32, true), }; @@ -956,6 +957,9 @@ static int nh_fill_node(struct sk_buff *skb, struct nexthop *nh, } else if (nhi->fdb_nh) { if (nla_put_flag(skb, NHA_FDB)) goto nla_put_failure; + if (nhi->dst_port && + nla_put_be16(skb, NHA_DST_PORT, nhi->dst_port)) + goto nla_put_failure; } else { const struct net_device *dev; @@ -1055,6 +1059,9 @@ static size_t nh_nlmsg_size_single(struct nexthop *nh) break; } + if (nhi->dst_port) + sz += nla_total_size(2); /* NHA_DST_PORT */ + if (nhi->fib_nhc.nhc_lwtstate) { sz += lwtunnel_get_encap_size(nhi->fib_nhc.nhc_lwtstate); sz += nla_total_size(2); /* NHA_ENCAP_TYPE */ @@ -2965,8 +2972,10 @@ static struct nexthop *nexthop_create(struct net *net, struct nh_config *cfg, nhi->family = cfg->nh_family; nhi->fib_nhc.nhc_scope = RT_SCOPE_LINK; - if (cfg->nh_fdb) + if (cfg->nh_fdb) { nhi->fdb_nh = 1; + nhi->dst_port = cfg->nh_dst_port; + } if (cfg->nh_blackhole) { nhi->reject_nh = 1; @@ -3156,6 +3165,15 @@ static int rtm_to_nh_config(struct net *net, struct sk_buff *skb, cfg->nh_fdb = nla_get_flag(tb[NHA_FDB]); } + if (tb[NHA_DST_PORT]) { + if (!tb[NHA_FDB] || !tb[NHA_GATEWAY]) { + NL_SET_ERR_MSG(extack, + "Destination port can only be set on fdb nexthops that have a gateway"); + goto out; + } + cfg->nh_dst_port = nla_get_be16(tb[NHA_DST_PORT]); + } + if (tb[NHA_GROUP]) { if (nhm->nh_family != AF_UNSPEC) { NL_SET_ERR_MSG(extack, "Invalid family for group"); -- cgit v1.2.3 From 66a4e9e11e43f19123777d0a1b419a2d9bcb9ce7 Mon Sep 17 00:00:00 2001 From: Ackerley Tng Date: Thu, 2 Jul 2026 09:21:46 -0700 Subject: mm: hugetlb: move mpol interpretation out of alloc_buddy_hugetlb_folio_with_mpol() Move memory policy interpretation out of alloc_buddy_hugetlb_folio_with_mpol() and into alloc_hugetlb_folio() to separate reading and interpretation of memory policy from actual allocation. This will later allow memory policy to be interpreted outside of the process of allocating a hugetlb folio entirely. This opens doors for other callers of the HugeTLB folio allocation function, such as guest_memfd, where memory may not always be mapped and hence may not have an associated vma. Introduce struct mempolicy_interpreted to hold all the components of an interpreted memory policy. Rename alloc_buddy_hugetlb_folio_with_mpol() to alloc_buddy_hugetlb_folio() since the function no longer interprets memory policy. No functional change intended. Link: https://lore.kernel.org/20260702-hugetlb-open-up-v4-2-d53cefcccf34@google.com Signed-off-by: Ackerley Tng Reviewed-by: James Houghton Acked-by: Oscar Salvador Cc: Alistair Popple Cc: Byungchul Park Cc: David Hildenbrand Cc: David Rientjes Cc: "Edgecombe, Rick P" Cc: Frank van der Linden Cc: Gregory Price Cc: "Huang, Ying" Cc: Jason Gunthorpe Cc: Jiaqi Yan Cc: Joshua Hahn Cc: Matthew Brost Cc: Michael Roth Cc: Michal Hocko Cc: Muchun Song Cc: Paolo Bonzini Cc: Pasha Tatashin Cc: Peter Xu Cc: Pratyush Yadav Cc: Qi Zheng Cc: Rakie Kim Cc: Roman Gushchin Cc: Sean Christopherson Cc: Shakeel Butt Cc: Shivank Garg Cc: Vishal Annapurve Cc: Yan Zhao Cc: Zi Yan Signed-off-by: Andrew Morton --- include/uapi/linux/mempolicy.h | 2 +- mm/hugetlb.c | 54 ++++++++++++++++++++++++++++-------------- 2 files changed, 37 insertions(+), 19 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/mempolicy.h b/include/uapi/linux/mempolicy.h index 6c962d866e86..7f6fc9599693 100644 --- a/include/uapi/linux/mempolicy.h +++ b/include/uapi/linux/mempolicy.h @@ -16,7 +16,7 @@ */ /* Policies */ -enum { +enum mempolicy_mode { MPOL_DEFAULT, MPOL_PREFERRED, MPOL_BIND, diff --git a/mm/hugetlb.c b/mm/hugetlb.c index dcf8df3d71b9..622ef772c0ea 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -1316,6 +1316,12 @@ static unsigned long available_huge_pages(struct hstate *h) return h->free_huge_pages - h->resv_huge_pages; } +struct mempolicy_interpreted { + int nid; + nodemask_t *nodemask; + enum mempolicy_mode mode; +}; + static struct folio *dequeue_hugetlb_folio_vma(struct hstate *h, struct vm_area_struct *vma, unsigned long address) @@ -2137,32 +2143,28 @@ static struct folio *alloc_migrate_hugetlb_folio(struct hstate *h, gfp_t gfp_mas return folio; } -/* - * Use the VMA's mpolicy to allocate a huge page from the buddy. - */ static -struct folio *alloc_buddy_hugetlb_folio_with_mpol(struct hstate *h, - struct vm_area_struct *vma, unsigned long addr) +struct folio *alloc_buddy_hugetlb_folio(struct hstate *h, + gfp_t gfp_mask, struct mempolicy_interpreted *mpoli) { struct folio *folio = NULL; - struct mempolicy *mpol; - gfp_t gfp_mask = htlb_alloc_mask(h); - int nid; - nodemask_t *nodemask; + nodemask_t *nodemask = mpoli->nodemask; - nid = huge_node(vma, addr, gfp_mask, &mpol, &nodemask); - if (mpol_is_preferred_many(mpol)) { + if (mpoli->mode == MPOL_PREFERRED_MANY) { gfp_t gfp = gfp_mask & ~(__GFP_DIRECT_RECLAIM | __GFP_NOFAIL); - folio = alloc_surplus_hugetlb_folio(h, gfp, nid, nodemask); + folio = alloc_surplus_hugetlb_folio(h, gfp, mpoli->nid, + nodemask); /* Fallback to all nodes if page==NULL */ nodemask = NULL; } - if (!folio) - folio = alloc_surplus_hugetlb_folio(h, gfp_mask, nid, nodemask); - mpol_cond_put(mpol); + if (!folio) { + folio = alloc_surplus_hugetlb_folio(h, gfp_mask, mpoli->nid, + nodemask); + } + return folio; } @@ -2852,7 +2854,7 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma, int ret, idx; struct hugetlb_cgroup *h_cg = NULL; struct hugetlb_cgroup *h_cg_rsvd = NULL; - gfp_t gfp = htlb_alloc_mask(h) | __GFP_RETRY_MAYFAIL; + gfp_t gfp = htlb_alloc_mask(h); idx = hstate_index(h); @@ -2924,8 +2926,24 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma, folio = dequeue_hugetlb_folio_vma(h, vma, addr); if (!folio) { + struct mempolicy_interpreted mpoli; + struct mempolicy *mpol; + nodemask_t *nodemask; + int nid; + spin_unlock_irq(&hugetlb_lock); - folio = alloc_buddy_hugetlb_folio_with_mpol(h, vma, addr); + nid = huge_node(vma, addr, gfp, &mpol, &nodemask); + mpoli = (struct mempolicy_interpreted){ + .nid = nid, +#ifdef CONFIG_NUMA + .mode = mpol ? mpol->mode : MPOL_DEFAULT, +#else + .mode = MPOL_DEFAULT, +#endif + .nodemask = nodemask, + }; + folio = alloc_buddy_hugetlb_folio(h, gfp, &mpoli); + mpol_cond_put(mpol); if (!folio) goto out_uncharge_cgroup; spin_lock_irq(&hugetlb_lock); @@ -2981,7 +2999,7 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma, } } - ret = mem_cgroup_charge_hugetlb(folio, gfp); + ret = mem_cgroup_charge_hugetlb(folio, gfp | __GFP_RETRY_MAYFAIL); /* * Unconditionally increment NR_HUGETLB here. If it turns out that * mem_cgroup_charge_hugetlb failed, then immediately free the page and -- cgit v1.2.3 From ad220e275c3987aea865d4c05ad66ba0295c494d Mon Sep 17 00:00:00 2001 From: Congkai Tan Date: Wed, 22 Jul 2026 20:27:02 +0000 Subject: KVM: arm64: Add KVM_ARM_VCPU_PMU_V3_STRICT vCPU feature Introduce a new vCPU feature KVM_ARM_VCPU_PMU_V3_STRICT. When set, KVM does not create a default PMU when initializing the vCPU, and userspace must select one explicitly via KVM_ARM_VCPU_PMU_V3_SET_PMU before the first KVM_RUN. The flag forces the VMM to be aware of the PMU implementation of the guest to be created, so that certain information about the PMU becomes deterministic (if on a heterogeneous system) and becomes safe to be exposed to the guest. It can be used as an umbrella flag to gate future PMUv3 UAPI changes. When no default PMU is created, kvm->arch.arm_pmu stays NULL until SET_PMU runs, so kvm_arm_pmu_v3_init() now refuses to run if kvm->arch.arm_pmu is NULL. Signed-off-by: Congkai Tan Reviewed-by: Geoff Blake Reviewed-by: Haris Okanovic Reviewed-by: Stanislav Spassov Co-developed-by: Oliver Upton Reviewed-by: Fuad Tabba Tested-by: Fuad Tabba Link: https://patch.msgid.link/20260722202702.4165917-5-congkai@amazon.com Signed-off-by: Oliver Upton --- Documentation/virt/kvm/api.rst | 11 +++++++++++ Documentation/virt/kvm/devices/vcpu.rst | 11 +++++++++-- arch/arm64/include/asm/kvm_host.h | 2 +- arch/arm64/kvm/arm.c | 19 +++++++++++++++---- arch/arm64/kvm/pmu-emul.c | 18 +++++++++++++++++- include/uapi/linux/kvm.h | 1 + 6 files changed, 54 insertions(+), 8 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst index a5f9ee92f43e..4988c32df4bf 100644 --- a/Documentation/virt/kvm/api.rst +++ b/Documentation/virt/kvm/api.rst @@ -3515,6 +3515,17 @@ Possible features: Depends on KVM_CAP_ARM_PSCI_0_2. - KVM_ARM_VCPU_PMU_V3: Emulate PMUv3 for the CPU. Depends on KVM_CAP_ARM_PMU_V3. + - KVM_ARM_VCPU_PMU_V3_STRICT: Enable strict PMUv3 UAPI. + Requires KVM_ARM_VCPU_PMU_V3. Depends on KVM_CAP_ARM_PMU_V3_STRICT. + When enabled: + + * Userspace must explicitly select a PMU implementation before + initializing the PMU or configuring a PMU event filter + + * If the PMU implements FEAT_PMUv3p4, PMMIR_EL1.SLOTS provides the + hardware value of the underlying implementation + + * Writes to PMCR_EL0.N via KVM_SET_ONE_REG are ignored - KVM_ARM_VCPU_PTRAUTH_ADDRESS: Enables Address Pointer authentication for arm64 only. diff --git a/Documentation/virt/kvm/devices/vcpu.rst b/Documentation/virt/kvm/devices/vcpu.rst index 66e714f2fcfa..deb5c51bc00c 100644 --- a/Documentation/virt/kvm/devices/vcpu.rst +++ b/Documentation/virt/kvm/devices/vcpu.rst @@ -53,8 +53,9 @@ Returns: ======= ====================================================== -EEXIST Interrupt number already used -ENODEV PMUv3 not supported or GIC not initialized - -ENXIO PMUv3 not supported, missing VCPU feature or interrupt - number not set (non-GICv5 guests, only) + -ENXIO PMUv3 not supported, missing VCPU feature, missing + hardware PMU, or interrupt number not set (non-GICv5 + guests, only) -EBUSY PMUv3 already initialized ======= ====================================================== @@ -62,6 +63,9 @@ Request the initialization of the PMUv3. If using the PMUv3 with an in-kernel virtual GIC implementation, this must be done after initializing the in-kernel irqchip. +When the KVM_ARM_VCPU_PMU_V3_STRICT vCPU feature is enabled this must be done +after selecting a hardware PMU. + 1.3 ATTRIBUTE: KVM_ARM_VCPU_PMU_V3_FILTER ----------------------------------------- @@ -108,6 +112,9 @@ hardware event. Filtering event 0x1E (CHAIN) has no effect either, as it isn't strictly speaking an event. Filtering the cycle counter is possible using event 0x11 (CPU_CYCLES). +When the KVM_ARM_VCPU_PMU_V3_STRICT vCPU feature is enabled this must be done +after selecting a hardware PMU. + 1.4 ATTRIBUTE: KVM_ARM_VCPU_PMU_V3_SET_PMU ------------------------------------------ diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h index 9e035d587970..39f7fc740d07 100644 --- a/arch/arm64/include/asm/kvm_host.h +++ b/arch/arm64/include/asm/kvm_host.h @@ -39,7 +39,7 @@ #define KVM_MAX_VCPUS VGIC_V3_MAX_CPUS -#define KVM_VCPU_MAX_FEATURES 9 +#define KVM_VCPU_MAX_FEATURES 10 #define KVM_VCPU_VALID_FEATURES (BIT(KVM_VCPU_MAX_FEATURES) - 1) #define KVM_REQ_SLEEP \ diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c index 50adfff75be8..7dbefdd846aa 100644 --- a/arch/arm64/kvm/arm.c +++ b/arch/arm64/kvm/arm.c @@ -452,6 +452,7 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext) r = get_num_wrps(); break; case KVM_CAP_ARM_PMU_V3: + case KVM_CAP_ARM_PMU_V3_STRICT: r = kvm_supports_guest_pmuv3(); break; case KVM_CAP_ARM_INJECT_SERROR_ESR: @@ -1563,8 +1564,10 @@ static unsigned long system_supported_vcpu_features(void) if (!cpus_have_final_cap(ARM64_HAS_32BIT_EL1)) clear_bit(KVM_ARM_VCPU_EL1_32BIT, &features); - if (!kvm_supports_guest_pmuv3()) + if (!kvm_supports_guest_pmuv3()) { clear_bit(KVM_ARM_VCPU_PMU_V3, &features); + clear_bit(KVM_ARM_VCPU_PMU_V3_STRICT, &features); + } if (!system_supports_sve()) clear_bit(KVM_ARM_VCPU_SVE, &features); @@ -1605,6 +1608,11 @@ static int kvm_vcpu_init_check_features(struct kvm_vcpu *vcpu, test_bit(KVM_ARM_VCPU_PTRAUTH_GENERIC, &features)) return -EINVAL; + /* Strict PMUv3 UAPI requires PMUv3. */ + if (test_bit(KVM_ARM_VCPU_PMU_V3_STRICT, &features) && + !test_bit(KVM_ARM_VCPU_PMU_V3, &features)) + return -EINVAL; + if (!test_bit(KVM_ARM_VCPU_EL1_32BIT, &features)) return 0; @@ -1634,10 +1642,13 @@ static int kvm_setup_vcpu(struct kvm_vcpu *vcpu) int ret = 0; /* - * When the vCPU has a PMU, but no PMU is set for the guest - * yet, set the default one. + * When the vCPU has a PMU, but no PMU is set for the guest yet, set + * the default one. If KVM_ARM_VCPU_PMU_V3_STRICT is set, no default + * PMU is created, and userspace must select a PMU via + * KVM_ARM_VCPU_PMU_V3_SET_PMU. */ - if (kvm_vcpu_has_pmu(vcpu) && !kvm->arch.arm_pmu) + if (kvm_vcpu_has_pmu(vcpu) && !kvm->arch.arm_pmu && + !kvm_vcpu_has_pmuv3_strict(vcpu)) ret = kvm_arm_set_default_pmu(kvm); /* Prepare for nested if required */ diff --git a/arch/arm64/kvm/pmu-emul.c b/arch/arm64/kvm/pmu-emul.c index bcfd0a91e114..5b1af7e2176f 100644 --- a/arch/arm64/kvm/pmu-emul.c +++ b/arch/arm64/kvm/pmu-emul.c @@ -939,6 +939,10 @@ int kvm_arm_pmu_v3_enable(struct kvm_vcpu *vcpu) static int kvm_arm_pmu_v3_init(struct kvm_vcpu *vcpu) { + /* Only possible when using KVM_ARM_VCPU_PMU_V3_STRICT */ + if (!vcpu->kvm->arch.arm_pmu) + return -ENXIO; + if (irqchip_in_kernel(vcpu->kvm)) { int ret; @@ -1009,6 +1013,14 @@ u8 kvm_arm_pmu_get_max_counters(struct kvm *kvm) { struct arm_pmu *arm_pmu = kvm->arch.arm_pmu; + /* + * Under KVM_ARM_VCPU_PMU_V3_STRICT no PMU exists until userspace sets + * one, so this can be reached before arm_pmu is set. Report no + * counters in that case. + */ + if (!arm_pmu) + return 0; + /* * PMUv3 requires that all event counters are capable of counting any * event, though the same may not be true of non-PMUv3 hardware. @@ -1050,7 +1062,8 @@ static void kvm_arm_set_pmu(struct kvm *kvm, struct arm_pmu *arm_pmu) } /** - * kvm_arm_set_default_pmu - No PMU set, get the default one. + * kvm_arm_set_default_pmu - No PMU set and KVM_ARM_VCPU_PMU_V3_STRICT not + * set, get the default one. * @kvm: The kvm pointer * * The observant among you will notice that the supported_cpus @@ -1190,6 +1203,9 @@ int kvm_arm_pmu_v3_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr) if (kvm_vm_has_ran_once(kvm)) return -EBUSY; + if (!kvm->arch.arm_pmu) + return -ENXIO; + if (!kvm->arch.pmu_filter) { kvm->arch.pmu_filter = bitmap_alloc(nr_events, GFP_KERNEL_ACCOUNT); if (!kvm->arch.pmu_filter) diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h index 419011097fa8..9fc8dfdfd65f 100644 --- a/include/uapi/linux/kvm.h +++ b/include/uapi/linux/kvm.h @@ -997,6 +997,7 @@ struct kvm_enable_cap { #define KVM_CAP_S390_KEYOP 247 #define KVM_CAP_S390_VSIE_ESAMODE 248 #define KVM_CAP_S390_HPAGE_2G 249 +#define KVM_CAP_ARM_PMU_V3_STRICT 250 struct kvm_irq_routing_irqchip { __u32 irqchip; -- cgit v1.2.3 From 42c8ed5835921a7b2523517fd6caad1d79b69146 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Thu, 23 Jul 2026 20:10:21 -0700 Subject: nstree: add/fix struct ns_id_req kernel-doc member fields - drop non-existent @filter - add missing descriptions for @ns_type and @spare2 - change the descriptions of @ns_id and @user_ns_id based on their commit to prevent these kernel-doc warnings: Warning: ../include/uapi/linux/nsfs.h:117 struct member 'ns_type' not described in 'ns_id_req' Warning: ../include/uapi/linux/nsfs.h:117 struct member 'spare2' not described in 'ns_id_req' Warning: ../include/uapi/linux/nsfs.h:117 Excess struct member 'filter' description in 'ns_id_req' Fixes: 76b6f5dfb3fd ("nstree: add listns()") Signed-off-by: Randy Dunlap Link: https://patch.msgid.link/20260724031021.814599-1-rdunlap@infradead.org Signed-off-by: Christian Brauner (Amutable) --- include/uapi/linux/nsfs.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/nsfs.h b/include/uapi/linux/nsfs.h index a25e38d1c874..007fed5971b4 100644 --- a/include/uapi/linux/nsfs.h +++ b/include/uapi/linux/nsfs.h @@ -96,9 +96,10 @@ enum ns_type { * struct ns_id_req - namespace ID request structure * @size: size of this structure * @spare: reserved for future use - * @filter: filter mask - * @ns_id: last namespace id - * @user_ns_id: owning user namespace ID + * @ns_id: last namespace ID + * @ns_type: bit mask of namespace types to include + * @spare2: reserved for future use + * @user_ns_id: filter on this user namespace ID (or 0) * * Structure for passing namespace ID and miscellaneous parameters to * statns(2) and listns(2). -- cgit v1.2.3 From d7722c03089e35bf579405e8ad7dba41800e39e7 Mon Sep 17 00:00:00 2001 From: Javen Xu Date: Tue, 28 Jul 2026 15:31:03 +0800 Subject: net: phy: c45: add setup and read master/slave helpers This patch adds two static helpers in drivers/net/phy/phy-c45.c to configure and read back master-slave roles for non BASE-T1 Clause 45 PHYs via the 10GBASE-T AN control/status registers. These helpers are wired into genphy_c45_config_aneg() and genphy_c45_read_status(). This changes the observable ethtool output for drivers using the generic c45 read path. Reviewed-by: Andrew Lunn Signed-off-by: Javen Xu Link: https://patch.msgid.link/20260728073106.1515-3-javen_xu@realsil.com.cn Signed-off-by: Jakub Kicinski --- drivers/net/phy/phy-c45.c | 103 ++++++++++++++++++++++++++++++++++++++++++++++ include/uapi/linux/mdio.h | 5 +++ 2 files changed, 108 insertions(+) (limited to 'include/uapi/linux') diff --git a/drivers/net/phy/phy-c45.c b/drivers/net/phy/phy-c45.c index c1f817a59739..870920311f9a 100644 --- a/drivers/net/phy/phy-c45.c +++ b/drivers/net/phy/phy-c45.c @@ -406,6 +406,97 @@ int genphy_c45_pma_soft_reset(struct phy_device *phydev) } EXPORT_SYMBOL_GPL(genphy_c45_pma_soft_reset); +/** + * genphy_c45_an_setup_master_slave - Configure Master/Slave setting for C45 PHYs + * @phydev: target phy_device struct + * + * Description: Configure the forced or preferred Master/Slave role + * 10GBASE-T control register (MMD 7, Register 0x0020) according to + * IEEE 802.3 standards. + * + * Return: negative errno code on failure, 0 if Master/Slave didn't change, + * or 1 if Master/Slave modes changed. + */ +static int genphy_c45_an_setup_master_slave(struct phy_device *phydev) +{ + u16 ctl = 0; + + switch (phydev->master_slave_set) { + case MASTER_SLAVE_CFG_MASTER_PREFERRED: + ctl = MDIO_AN_10GBT_CTRL_MS_PORT_TYPE; + break; + case MASTER_SLAVE_CFG_SLAVE_PREFERRED: + break; + case MASTER_SLAVE_CFG_MASTER_FORCE: + ctl = MDIO_AN_10GBT_CTRL_MS_ENABLE | MDIO_AN_10GBT_CTRL_MS_VALUE; + break; + case MASTER_SLAVE_CFG_SLAVE_FORCE: + ctl = MDIO_AN_10GBT_CTRL_MS_ENABLE; + break; + case MASTER_SLAVE_CFG_UNKNOWN: + case MASTER_SLAVE_CFG_UNSUPPORTED: + return 0; + default: + phydev_warn(phydev, "Unsupported Master/Slave mode\n"); + return -EOPNOTSUPP; + } + + return phy_modify_mmd_changed(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL, + MDIO_AN_10GBT_CTRL_MS_ENABLE | + MDIO_AN_10GBT_CTRL_MS_VALUE | + MDIO_AN_10GBT_CTRL_MS_PORT_TYPE, ctl); +} + +/** + * genphy_c45_read_master_slave - read master/slave status + * @phydev: target phy_device struct + * + * Description: Read the Master/Slave configuration and status + * from 10GBASE-T control/status registers (MMD 7, Reg 0x0020 and 0x0021). + * + * Return: 0 on success, or a negative error code on failure. + */ +static int genphy_c45_read_master_slave(struct phy_device *phydev) +{ + int val; + + phydev->master_slave_get = MASTER_SLAVE_CFG_UNKNOWN; + phydev->master_slave_state = MASTER_SLAVE_STATE_UNKNOWN; + + val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_CTRL); + if (val < 0) + return val; + + if (val & MDIO_AN_10GBT_CTRL_MS_ENABLE) { + if (val & MDIO_AN_10GBT_CTRL_MS_VALUE) + phydev->master_slave_get = MASTER_SLAVE_CFG_MASTER_FORCE; + else + phydev->master_slave_get = MASTER_SLAVE_CFG_SLAVE_FORCE; + } else { + if (val & MDIO_AN_10GBT_CTRL_MS_PORT_TYPE) + phydev->master_slave_get = MASTER_SLAVE_CFG_MASTER_PREFERRED; + else + phydev->master_slave_get = MASTER_SLAVE_CFG_SLAVE_PREFERRED; + } + + val = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_AN_10GBT_STAT); + if (val < 0) + return val; + + if (val & MDIO_AN_10GBT_STAT_MS_FAULT) { + phydev->master_slave_state = MASTER_SLAVE_STATE_ERR; + } else if (phydev->link) { + if (val & MDIO_AN_10GBT_STAT_MS_RES) + phydev->master_slave_state = MASTER_SLAVE_STATE_MASTER; + else + phydev->master_slave_state = MASTER_SLAVE_STATE_SLAVE; + } else { + phydev->master_slave_state = MASTER_SLAVE_STATE_UNKNOWN; + } + + return 0; +} + /** * genphy_c45_aneg_done - return auto-negotiation complete status * @phydev: target phy_device struct @@ -1214,6 +1305,10 @@ int genphy_c45_read_status(struct phy_device *phydev) ret = genphy_c45_baset1_read_status(phydev); if (ret < 0) return ret; + } else { + ret = genphy_c45_read_master_slave(phydev); + if (ret < 0) + return ret; } phy_resolve_aneg_linkmode(phydev); @@ -1247,6 +1342,14 @@ int genphy_c45_config_aneg(struct phy_device *phydev) if (ret > 0) changed = true; + if (!genphy_c45_baset1_able(phydev)) { + ret = genphy_c45_an_setup_master_slave(phydev); + if (ret < 0) + return ret; + if (ret > 0) + changed = true; + } + return genphy_c45_check_and_restart_aneg(phydev, changed); } EXPORT_SYMBOL_GPL(genphy_c45_config_aneg); diff --git a/include/uapi/linux/mdio.h b/include/uapi/linux/mdio.h index b2541c948fc1..06f4bc3c20c7 100644 --- a/include/uapi/linux/mdio.h +++ b/include/uapi/linux/mdio.h @@ -332,8 +332,13 @@ #define MDIO_AN_10GBT_CTRL_ADV2_5G 0x0080 /* Advertise 2.5GBASE-T */ #define MDIO_AN_10GBT_CTRL_ADV5G 0x0100 /* Advertise 5GBASE-T */ #define MDIO_AN_10GBT_CTRL_ADV10G 0x1000 /* Advertise 10GBASE-T */ +#define MDIO_AN_10GBT_CTRL_MS_ENABLE 0x8000 /* Master/slave manual config enable */ +#define MDIO_AN_10GBT_CTRL_MS_VALUE 0x4000 /* Master/slave config value (1=Master) */ +#define MDIO_AN_10GBT_CTRL_MS_PORT_TYPE 0x2000 /* Master Preferred Type */ /* AN 10GBASE-T status register. */ +#define MDIO_AN_10GBT_STAT_MS_FAULT 0x8000 /* Master/slave fault */ +#define MDIO_AN_10GBT_STAT_MS_RES 0x4000 /* Master/slave resolution (1=Master) */ #define MDIO_AN_10GBT_STAT_LP2_5G 0x0020 /* LP is 2.5GBT capable */ #define MDIO_AN_10GBT_STAT_LP5G 0x0040 /* LP is 5GBT capable */ #define MDIO_AN_10GBT_STAT_LPTRR 0x0200 /* LP training reset req. */ -- cgit v1.2.3 From 9141eda9eec76b538bb64e89185034ec61d341ca Mon Sep 17 00:00:00 2001 From: "Kiryl Shutsemau (Meta)" Date: Mon, 29 Jun 2026 16:07:16 +0100 Subject: firmware: arm_sdei: add SDEI_EVENT_SIGNAL support Add sdei_event_signal(), a thin wrapper over the SDEI_EVENT_SIGNAL call (DEN0054) that makes the software-signalled event (event 0) pending on a target PE -- delivered NMI-like even when that PE has interrupts masked. It takes no locks, so it is safe to call from NMI / crash context. Signed-off-by: Kiryl Shutsemau (Meta) Reviewed-by: Douglas Anderson Tested-by: Yin Fengwei Signed-off-by: Will Deacon --- drivers/firmware/arm_sdei.c | 12 ++++++++++++ include/linux/arm_sdei.h | 6 ++++++ include/uapi/linux/arm_sdei.h | 1 + 3 files changed, 19 insertions(+) (limited to 'include/uapi/linux') diff --git a/drivers/firmware/arm_sdei.c b/drivers/firmware/arm_sdei.c index c161cf263547..e8dd2f0f3919 100644 --- a/drivers/firmware/arm_sdei.c +++ b/drivers/firmware/arm_sdei.c @@ -339,6 +339,18 @@ static void _ipi_unmask_cpu(void *ignored) sdei_unmask_local_cpu(); } +/* + * Signal the software-signalled event (event 0) to @mpidr. Does nothing + * but the SMC -- no locks, no event lookup -- so it is safe from NMI / + * crash context (e.g. the cross-CPU NMI service). + */ +int sdei_event_signal(u32 event_num, u64 mpidr) +{ + return invoke_sdei_fn(SDEI_1_0_FN_SDEI_EVENT_SIGNAL, event_num, + mpidr, 0, 0, 0, NULL); +} +NOKPROBE_SYMBOL(sdei_event_signal); + /* * Was SDEI firmware probed and is it usable? Lets optional consumers skip * registering an event -- and the warning a failed registration emits -- on diff --git a/include/linux/arm_sdei.h b/include/linux/arm_sdei.h index b07113eeeff7..b9dc21c241be 100644 --- a/include/linux/arm_sdei.h +++ b/include/linux/arm_sdei.h @@ -37,6 +37,12 @@ int sdei_event_unregister(u32 event_num); int sdei_event_enable(u32 event_num); int sdei_event_disable(u32 event_num); +/* + * Signal the software-signalled event (event 0) to another PE, NMI-like. + * @mpidr is the target's MPIDR affinity. + */ +int sdei_event_signal(u32 event_num, u64 mpidr); + /* Was SDEI firmware probed and usable? */ bool sdei_is_present(void); diff --git a/include/uapi/linux/arm_sdei.h b/include/uapi/linux/arm_sdei.h index af0630ba5437..22eb61612673 100644 --- a/include/uapi/linux/arm_sdei.h +++ b/include/uapi/linux/arm_sdei.h @@ -22,6 +22,7 @@ #define SDEI_1_0_FN_SDEI_PE_UNMASK SDEI_1_0_FN(0x0C) #define SDEI_1_0_FN_SDEI_INTERRUPT_BIND SDEI_1_0_FN(0x0D) #define SDEI_1_0_FN_SDEI_INTERRUPT_RELEASE SDEI_1_0_FN(0x0E) +#define SDEI_1_0_FN_SDEI_EVENT_SIGNAL SDEI_1_0_FN(0x0F) #define SDEI_1_0_FN_SDEI_PRIVATE_RESET SDEI_1_0_FN(0x11) #define SDEI_1_0_FN_SDEI_SHARED_RESET SDEI_1_0_FN(0x12) -- cgit v1.2.3 From 773f2b9e367019c03ed18f2736c8f67307305088 Mon Sep 17 00:00:00 2001 From: Nicolin Chen Date: Tue, 28 Jul 2026 10:33:33 -0700 Subject: iommu/arm-smmu-v3: Support IDR5.DS and widen the TLBI SCALE field An SMMU implementing SMMU_IDR5.DS extends the range invalidation commands: the SCALE field grows a 6th bit, raising its maximum value from 31 to 39, and TTL == 0b01 becomes a valid level hint for a 16KB translation granule. Add a new ARM_SMMU_FEAT_DS feature detecting the DS bit, and widen the CMDQ_TLBI_0_SCALE field to its architectural 6 bits. Mask the scale value explicitly in arm_smmu_cmdq_batch_add_range(), so the range invalidation path emits the same commands as before, keeping the pre-existing 5-bit truncation of a scale above 31. Also list DS as a valid IDR5 field in the iommu_hw_info_arm_smmuv3 kdoc: iommufd has always reported the raw IDR5 register, so a VMM may conclude from that bit alone that it can expose DS to its guest. Suggested-by: Jason Gunthorpe Reviewed-by: Jason Gunthorpe Reviewed-by: Pranjal Shrivastava Assisted-by: Claude:claude-fable-5 Signed-off-by: Nicolin Chen Signed-off-by: Will Deacon --- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 6 +++++- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 4 +++- include/uapi/linux/iommufd.h | 4 ++-- 3 files changed, 10 insertions(+), 4 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c index de5fba0b6d09..2587c33c778f 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c @@ -2511,9 +2511,10 @@ static void arm_smmu_cmdq_batch_add_range(struct arm_smmu_device *smmu, /* Determine how many chunks of 2^scale size we have */ num = (num_pages >> scale) & CMDQ_TLBI_RANGE_NUM_MAX; + /* Keep the pre-DS 5-bit truncation when scale > 31 */ cmd->data[0] = orig_data0 | FIELD_PREP(CMDQ_TLBI_0_NUM, num - 1) | - FIELD_PREP(CMDQ_TLBI_0_SCALE, scale); + FIELD_PREP(CMDQ_TLBI_0_SCALE, scale & 0x1f); /* range is num * 2^scale * pgsize */ inv_range = num << (scale + tg); @@ -5197,6 +5198,9 @@ static int arm_smmu_device_hw_probe(struct arm_smmu_device *smmu) /* Maximum number of outstanding stalls */ smmu->evtq.max_stalls = FIELD_GET(IDR5_STALL_MAX, reg); + if (reg & IDR5_DS) + smmu->features |= ARM_SMMU_FEAT_DS; + /* Page sizes */ if (reg & IDR5_GRAN64K) smmu->pgsize_bitmap |= SZ_64K | SZ_512M; diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h index 98191075242a..50f8321e979c 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h @@ -65,6 +65,7 @@ struct arm_vsmmu; #define ARM_SMMU_IDR5 0x14 #define IDR5_STALL_MAX GENMASK(31, 16) +#define IDR5_DS (1 << 7) #define IDR5_GRAN64K (1 << 6) #define IDR5_GRAN16K (1 << 5) #define IDR5_GRAN4K (1 << 4) @@ -417,7 +418,7 @@ struct arm_smmu_cmd { #define CMDQ_TLBI_0_NUM GENMASK_ULL(16, 12) #define CMDQ_TLBI_RANGE_NUM_MAX 31 -#define CMDQ_TLBI_0_SCALE GENMASK_ULL(24, 20) +#define CMDQ_TLBI_0_SCALE GENMASK_ULL(25, 20) #define CMDQ_TLBI_0_VMID GENMASK_ULL(47, 32) #define CMDQ_TLBI_0_ASID GENMASK_ULL(63, 48) #define CMDQ_TLBI_1_LEAF (1UL << 0) @@ -925,6 +926,7 @@ struct arm_smmu_device { #define ARM_SMMU_FEAT_S2FWB (1 << 23) #define ARM_SMMU_FEAT_BBML2 (1 << 24) #define ARM_SMMU_FEAT_HAFT (1 << 25) +#define ARM_SMMU_FEAT_DS (1 << 26) u32 features; #define ARM_SMMU_OPT_SKIP_PREFETCH (1 << 0) diff --git a/include/uapi/linux/iommufd.h b/include/uapi/linux/iommufd.h index f73a392b3d3f..c6851c7655c6 100644 --- a/include/uapi/linux/iommufd.h +++ b/include/uapi/linux/iommufd.h @@ -605,7 +605,7 @@ enum iommu_hw_info_arm_smmuv3_flags { * idr[0]: ST_LEVEL, TERM_MODEL, STALL_MODEL, TTENDIAN , CD2L, ASID16, TTF * idr[1]: SIDSIZE, SSIDSIZE * idr[3]: BBML, RIL - * idr[5]: VAX, GRAN64K, GRAN16K, GRAN4K + * idr[5]: VAX, GRAN64K, GRAN16K, GRAN4K, DS * * - S1P should be assumed to be true if a NESTED HWPT can be created * - VFIO/iommufd only support platforms with COHACC, it should be assumed to be @@ -613,7 +613,7 @@ enum iommu_hw_info_arm_smmuv3_flags { * - ATS is a per-device property. If the VMM describes any devices as ATS * capable in ACPI/DT it should set the corresponding idr. * - * This list may expand in future (eg E0PD, AIE, PBHA, D128, DS etc). It is + * This list may expand in future (eg E0PD, AIE, PBHA, D128 etc). It is * important that VMMs do not read bits outside the list to allow for * compatibility with future kernels. Several features in the SMMUv3 * architecture are not currently supported by the kernel for nesting: HTTU, -- cgit v1.2.3 From b0f09c07966b05a5d46830b4c2581a266c4a2baa Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Tue, 21 Jul 2026 16:13:50 +0200 Subject: exec: add AT_FLAGS_TRANSPARENT_INTERP A transparent binfmt_misc dispatch hands the binary to the interpreter through AT_EXECFD and leaves the argument vector exactly as the caller built it. The loader on the receiving end has to know which contract it got. On the classic 'O'/'C' entries the binary's path is spliced into the argument vector and the loader consumes arguments. In transparent mode nothing was spliced and argv belongs entirely to the program. This cannot be inferred from AT_EXECFD alone. Raise a new AT_FLAGS bit following the AT_FLAGS_PRESERVE_ARGV0 precedent added for qemu-user in commit 2347961b11d4 ("binfmt_misc: pass binfmt_misc flags to the interpreter"). The bit also announces that mm->exe_file names the binary rather than the interpreter (added in the next commit). A loader that sees the bit may finish the identity polish by fixing up AT_PHDR/AT_ENTRY/AT_BASE in saved_auxv and fix the code/data markers via one uncapped PR_SET_MM_MAP once it has mapped the binary. I've got glibc patches for this as well but it's useful for any loader. BINPRM_FLAGS_TRANSPARENT_INTERP carries the mode from binfmt_misc to the ELF loaders. Both had their own copy of the AT_FLAGS translation, so give them one bprm_at_flags() to share instead of a second copy that can drift. Nothing sets the bprm flag yet. Link: https://patch.msgid.link/20260721-work-bpf-binfmt_misc-ptinterp-v2-8-e57866e4ae0f@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/binfmt_elf.c | 5 +---- fs/binfmt_elf_fdpic.c | 5 +---- include/linux/binfmts.h | 22 ++++++++++++++++++++++ include/uapi/linux/binfmts.h | 7 +++++++ 4 files changed, 31 insertions(+), 8 deletions(-) (limited to 'include/uapi/linux') diff --git a/fs/binfmt_elf.c b/fs/binfmt_elf.c index 16a56b6b3f6c..be8fd437b5a3 100644 --- a/fs/binfmt_elf.c +++ b/fs/binfmt_elf.c @@ -179,7 +179,6 @@ create_elf_tables(struct linux_binprm *bprm, const struct elfhdr *exec, unsigned char k_rand_bytes[16]; int items; elf_addr_t *elf_info; - elf_addr_t flags = 0; int ei_index; const struct cred *cred = current_cred(); struct vm_area_struct *vma; @@ -254,9 +253,7 @@ create_elf_tables(struct linux_binprm *bprm, const struct elfhdr *exec, NEW_AUX_ENT(AT_PHENT, sizeof(struct elf_phdr)); NEW_AUX_ENT(AT_PHNUM, exec->e_phnum); NEW_AUX_ENT(AT_BASE, interp_load_addr); - if (bprm->interp_flags & BINPRM_FLAGS_PRESERVE_ARGV0) - flags |= AT_FLAGS_PRESERVE_ARGV0; - NEW_AUX_ENT(AT_FLAGS, flags); + NEW_AUX_ENT(AT_FLAGS, bprm_at_flags(bprm)); NEW_AUX_ENT(AT_ENTRY, e_entry); NEW_AUX_ENT(AT_UID, from_kuid_munged(cred->user_ns, cred->uid)); NEW_AUX_ENT(AT_EUID, from_kuid_munged(cred->user_ns, cred->euid)); diff --git a/fs/binfmt_elf_fdpic.c b/fs/binfmt_elf_fdpic.c index fe0b5c5ed2bc..0a3cdf280307 100644 --- a/fs/binfmt_elf_fdpic.c +++ b/fs/binfmt_elf_fdpic.c @@ -509,7 +509,6 @@ static int create_elf_fdpic_tables(struct linux_binprm *bprm, char *k_platform, *k_base_platform; char __user *u_platform, *u_base_platform, *p; int loop; - unsigned long flags = 0; int ei_index; elf_addr_t *elf_info; @@ -649,9 +648,7 @@ static int create_elf_fdpic_tables(struct linux_binprm *bprm, NEW_AUX_ENT(AT_PHENT, sizeof(struct elf_phdr)); NEW_AUX_ENT(AT_PHNUM, exec_params->hdr.e_phnum); NEW_AUX_ENT(AT_BASE, interp_params->elfhdr_addr); - if (bprm->interp_flags & BINPRM_FLAGS_PRESERVE_ARGV0) - flags |= AT_FLAGS_PRESERVE_ARGV0; - NEW_AUX_ENT(AT_FLAGS, flags); + NEW_AUX_ENT(AT_FLAGS, bprm_at_flags(bprm)); NEW_AUX_ENT(AT_ENTRY, exec_params->entry_addr); NEW_AUX_ENT(AT_UID, (elf_addr_t) from_kuid_munged(cred->user_ns, cred->uid)); NEW_AUX_ENT(AT_EUID, (elf_addr_t) from_kuid_munged(cred->user_ns, cred->euid)); diff --git a/include/linux/binfmts.h b/include/linux/binfmts.h index 03e1794b5cbb..62465574e2a0 100644 --- a/include/linux/binfmts.h +++ b/include/linux/binfmts.h @@ -93,6 +93,28 @@ struct linux_binprm { #define BINPRM_FLAGS_PRESERVE_ARGV0_BIT 3 #define BINPRM_FLAGS_PRESERVE_ARGV0 (1 << BINPRM_FLAGS_PRESERVE_ARGV0_BIT) +/* binfmt_misc dispatched to the interpreter transparently */ +#define BINPRM_FLAGS_TRANSPARENT_INTERP_BIT 4 +#define BINPRM_FLAGS_TRANSPARENT_INTERP (1 << BINPRM_FLAGS_TRANSPARENT_INTERP_BIT) + +/** + * bprm_at_flags - the AT_FLAGS this invocation implies + * @bprm: binary that is being executed + * + * Tell the program on the receiving end which dispatch contract it got. + * + * Return: the AT_FLAGS value for this exec + */ +static inline unsigned long bprm_at_flags(const struct linux_binprm *bprm) +{ + /* Transparency preserves the whole argv, argv[0] included. */ + if (bprm->interp_flags & BINPRM_FLAGS_TRANSPARENT_INTERP) + return AT_FLAGS_TRANSPARENT_INTERP; + if (bprm->interp_flags & BINPRM_FLAGS_PRESERVE_ARGV0) + return AT_FLAGS_PRESERVE_ARGV0; + return 0; +} + /* * This structure defines the functions that are used to load the binary formats that * linux accepts. diff --git a/include/uapi/linux/binfmts.h b/include/uapi/linux/binfmts.h index c6f9450efc12..aafc07d78b80 100644 --- a/include/uapi/linux/binfmts.h +++ b/include/uapi/linux/binfmts.h @@ -22,4 +22,11 @@ struct pt_regs; #define AT_FLAGS_PRESERVE_ARGV0_BIT 0 #define AT_FLAGS_PRESERVE_ARGV0 (1 << AT_FLAGS_PRESERVE_ARGV0_BIT) +/* + * The interpreter runs transparently: the argument vector and the exe + * link belong to the binary passed in AT_EXECFD. + */ +#define AT_FLAGS_TRANSPARENT_INTERP_BIT 1 +#define AT_FLAGS_TRANSPARENT_INTERP (1 << AT_FLAGS_TRANSPARENT_INTERP_BIT) + #endif /* _UAPI_LINUX_BINFMTS_H */ -- cgit v1.2.3 From b5c53df53414635385a1711d0e6262b78b7f4123 Mon Sep 17 00:00:00 2001 From: Crescent Hsieh Date: Fri, 31 Jul 2026 15:48:08 +0800 Subject: serial: 8250: add Moxa MUEx50 UART port type Add a new 8250 port type for the Moxa MUEx50 UART and describe its basic FIFO size and trigger characteristics in the 8250 port configuration table. The 8250_mxpcie driver sets UPF_FIXED_TYPE and uses PORT_MUEX50 so that the generic 8250 core applies the correct defaults. Signed-off-by: Crescent Hsieh Link: https://patch.msgid.link/20260731074820.735619-3-crescentcy.hsieh@moxa.com Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/8250/8250_mxpcie.c | 3 ++- drivers/tty/serial/8250/8250_port.c | 8 ++++++++ include/uapi/linux/serial_core.h | 3 +++ 3 files changed, 13 insertions(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/drivers/tty/serial/8250/8250_mxpcie.c b/drivers/tty/serial/8250/8250_mxpcie.c index e75446923d08..9f4374d75fcc 100644 --- a/drivers/tty/serial/8250/8250_mxpcie.c +++ b/drivers/tty/serial/8250/8250_mxpcie.c @@ -218,7 +218,8 @@ static int mxpcie8250_probe(struct pci_dev *pdev, const struct pci_device_id *id up.port.dev = dev; up.port.irq = pdev->irq; up.port.uartclk = MOXA_PUART_BASE_BAUD * 16; - up.port.flags = UPF_SKIP_TEST | UPF_BOOT_AUTOCONF | UPF_SHARE_IRQ; + up.port.flags = UPF_SKIP_TEST | UPF_BOOT_AUTOCONF | UPF_SHARE_IRQ | UPF_FIXED_TYPE; + up.port.type = PORT_MUEX50; up.port.iotype = UPIO_MEM; up.port.iobase = 0; diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c index 6c10bff10970..12902036e511 100644 --- a/drivers/tty/serial/8250/8250_port.c +++ b/drivers/tty/serial/8250/8250_port.c @@ -311,6 +311,14 @@ static const struct serial8250_config uart_config[] = { .rxtrig_bytes = {1, 8, 16, 30}, .flags = UART_CAP_FIFO | UART_CAP_AFE, }, + [PORT_MUEX50] = { + .name = "Moxa MUEx50 UART", + .fifo_size = 128, + .tx_loadsz = 128, + .fcr = UART_FCR_ENABLE_FIFO | UART_FCR_R_TRIG_10, + .rxtrig_bytes = {15, 31, 63, 111}, + .flags = UART_CAP_FIFO, + }, }; /* Uart divisor latch read */ diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h index 9c007a106330..377884e3856a 100644 --- a/include/uapi/linux/serial_core.h +++ b/include/uapi/linux/serial_core.h @@ -100,6 +100,9 @@ /* TXX9 type number */ #define PORT_TXX9 64 +/* Moxa MUEx50 UART */ +#define PORT_MUEX50 65 + /*Digi jsm */ #define PORT_JSM 69 -- cgit v1.2.3 From 5c0958d80190822c4614ca00478d9acd9671bba6 Mon Sep 17 00:00:00 2001 From: Caleb Sander Mateos Date: Mon, 3 Aug 2026 15:14:37 -0600 Subject: ublk: add UBLK_F_IO_DESC_SIZE ublk passes the parameters of incoming I/O in memory shared between the kernel ublk driver and userspace ublk server in struct ublksrv_io_desc. The size of this struct is currently fixed to 24 bytes, which has been an obstacle to extending it with additional fields [1]. Additionally, with multiple ublk server threads handling I/Os from the same ublk queue (possible with UBLK_F_PER_IO_DAEMON or UBLK_F_BATCH_IO), false sharing results from adjacent io_descs sharing the same cache line. Add a ublk feature UBLK_F_IO_DESC_SIZE to allow a ublk server to override the size of each io_desc. The size must be at least 24 and a multiple of 8 to store a properly-aligned struct ublksrv_io_desc. It's also limited to a maximum of 256, though this bound could be lifted in the future. The struct ublksrv_io_desc is located at the beginning of each io_desc and the remainder is padding. The mmap() performed for each queue must have a length of queue_depth * io_desc_size rounded up to the page size. The mmap() offset must be q_id * UBLK_MAX_QUEUE_DEPTH * io_desc_size, also rounded up to the page size. [1]: https://lore.kernel.org/linux-block/aV8QfvaNO5P6vOs6@fedora/ Suggested-by: Ming Lei Signed-off-by: Caleb Sander Mateos Link: https://patch.msgid.link/20260803211441.2538144-6-csander@purestorage.com Signed-off-by: Jens Axboe --- drivers/block/ublk_drv.c | 38 +++++++++++++++++++++++++----------- include/uapi/linux/ublk_cmd.h | 5 ++++- tools/testing/selftests/ublk/kublk.c | 1 + 3 files changed, 32 insertions(+), 12 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index 369757f5af08..08d29c9ab898 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -89,7 +89,8 @@ | UBLK_F_SAFE_STOP_DEV \ | UBLK_F_BATCH_IO \ | UBLK_F_NO_AUTO_PART_SCAN \ - | UBLK_F_SHMEM_ZC) + | UBLK_F_SHMEM_ZC \ + | UBLK_F_IO_DESC_SIZE) #define UBLK_F_ALL_RECOVERY_FLAGS (UBLK_F_USER_RECOVERY \ | UBLK_F_USER_RECOVERY_REISSUE \ @@ -107,6 +108,8 @@ UBLK_BATCH_F_HAS_BUF_ADDR | \ UBLK_BATCH_F_AUTO_BUF_REG_FALLBACK) +#define UBLK_MAX_IO_DESC_SIZE 256 + /* ublk batch fetch uring_cmd */ struct ublk_batch_fetch_cmd { struct list_head node; @@ -239,6 +242,7 @@ struct ublk_io { struct ublk_queue { u16 q_id; u16 q_depth; + u16 io_desc_size; unsigned long flags; struct ublksrv_io_desc *io_cmd_buf; @@ -405,7 +409,7 @@ static inline void ublk_io_evts_deinit(struct ublk_queue *q) static inline struct ublksrv_io_desc * ublk_get_iod(const struct ublk_queue *ubq, u16 tag) { - return &ubq->io_cmd_buf[tag]; + return (void *)ubq->io_cmd_buf + tag * (size_t)ubq->io_desc_size; } static inline bool ublk_support_zero_copy(const struct ublk_queue *ubq) @@ -1248,19 +1252,20 @@ ublk_queue_cmd_buf(struct ublk_device *ub, u16 q_id) return ublk_get_queue(ub, q_id)->io_cmd_buf; } -static inline int __ublk_queue_cmd_buf_size(u16 depth) +static inline size_t __ublk_queue_cmd_buf_size(const struct ublk_device *ub, + u16 depth) { - return round_up(depth * sizeof(struct ublksrv_io_desc), PAGE_SIZE); + return round_up(depth * (size_t)ub->dev_info.io_desc_size, PAGE_SIZE); } -static inline int ublk_queue_cmd_buf_size(struct ublk_device *ub) +static inline size_t ublk_queue_cmd_buf_size(const struct ublk_device *ub) { - return __ublk_queue_cmd_buf_size(ub->dev_info.queue_depth); + return __ublk_queue_cmd_buf_size(ub, ub->dev_info.queue_depth); } -static int ublk_max_cmd_buf_size(void) +static size_t ublk_max_cmd_buf_size(const struct ublk_device *ub) { - return __ublk_queue_cmd_buf_size(UBLK_MAX_QUEUE_DEPTH); + return __ublk_queue_cmd_buf_size(ub, UBLK_MAX_QUEUE_DEPTH); } /* @@ -2662,7 +2667,7 @@ static int ublk_ch_mmap(struct file *filp, struct vm_area_struct *vma) { struct ublk_device *ub = filp->private_data; size_t sz = vma->vm_end - vma->vm_start; - unsigned max_sz = ublk_max_cmd_buf_size(); + size_t max_sz = ublk_max_cmd_buf_size(ub); unsigned long pfn, end, phys_off = vma->vm_pgoff << PAGE_SHIFT; int ret = 0; u16 q_id; @@ -4188,7 +4193,7 @@ static const struct file_operations ublk_ch_batch_io_fops = { static void __ublk_deinit_queue(struct ublk_device *ub, struct ublk_queue *ubq) { - int size; + size_t size; u16 i; size = ublk_queue_cmd_buf_size(ub); @@ -4241,7 +4246,8 @@ static int ublk_init_queue(struct ublk_device *ub, u16 q_id) struct ublk_queue *ubq; struct page *page; int numa_node; - int size, ret; + size_t size; + int ret; u16 i; /* Determine NUMA node based on queue's CPU affinity */ @@ -4266,6 +4272,7 @@ static int ublk_init_queue(struct ublk_device *ub, u16 q_id) return -ENOMEM; } ubq->io_cmd_buf = page_address(page); + ubq->io_desc_size = ub->dev_info.io_desc_size; for (i = 0; i < ubq->q_depth; i++) spin_lock_init(&ubq->ios[i].lock); @@ -4750,6 +4757,15 @@ static int ublk_ctrl_add_dev(const struct ublksrv_ctrl_cmd *header) if (info.flags & UBLK_F_INTEGRITY && !(info.flags & UBLK_F_USER_COPY)) return -EINVAL; + if (info.flags & UBLK_F_IO_DESC_SIZE) { + if (info.io_desc_size < sizeof(struct ublksrv_io_desc) || + info.io_desc_size % _Alignof(struct ublksrv_io_desc) || + info.io_desc_size > UBLK_MAX_IO_DESC_SIZE) + return -EINVAL; + } else { + info.io_desc_size = sizeof(struct ublksrv_io_desc); + } + /* the created device is always owned by current user */ ublk_store_owner_uid_gid(&info.owner_uid, &info.owner_gid); diff --git a/include/uapi/linux/ublk_cmd.h b/include/uapi/linux/ublk_cmd.h index 6991370a72ce..33b25dd13965 100644 --- a/include/uapi/linux/ublk_cmd.h +++ b/include/uapi/linux/ublk_cmd.h @@ -417,6 +417,9 @@ struct ublk_shmem_buf_reg { */ #define UBLK_F_SHMEM_ZC (1ULL << 19) +/* ublksrv_io_desc size is specified by ublksrv_ctrl_dev_info's io_desc_size */ +#define UBLK_F_IO_DESC_SIZE (1ULL << 20) + /* device state */ #define UBLK_S_DEV_DEAD 0 #define UBLK_S_DEV_LIVE 1 @@ -452,7 +455,7 @@ struct ublksrv_ctrl_dev_info { __u16 nr_hw_queues; __u16 queue_depth; __u16 state; - __u16 pad0; + __u16 io_desc_size; __u32 max_io_buf_bytes; __u32 dev_id; diff --git a/tools/testing/selftests/ublk/kublk.c b/tools/testing/selftests/ublk/kublk.c index 0b23c09daea5..5c4a1f18d0a3 100644 --- a/tools/testing/selftests/ublk/kublk.c +++ b/tools/testing/selftests/ublk/kublk.c @@ -1970,6 +1970,7 @@ static int cmd_dev_get_features(void) FEAT_NAME(UBLK_F_BATCH_IO), FEAT_NAME(UBLK_F_NO_AUTO_PART_SCAN), FEAT_NAME(UBLK_F_SHMEM_ZC), + FEAT_NAME(UBLK_F_IO_DESC_SIZE), }; struct ublk_dev *dev; __u64 features = 0; -- cgit v1.2.3 From ca8f6548e6f79761cba66ec8cb45bf026da8bf7d Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Tue, 4 Aug 2026 05:50:22 -0700 Subject: block: implement async io_uring zone reset all Add a new BLOCK_URING_CMD_ZONE_RESET_ALL uring cmd to reset all zones for a given block device. This can be used by storage systems or file system mkfs tools to initialize multiple devices in parallel. Signed-off-by: Christoph Hellwig Reviewed-by: Damien Le Moal Link: https://patch.msgid.link/20260804125038.740388-7-hch@lst.de Signed-off-by: Jens Axboe --- block/ioctl.c | 37 +++++++++++++++++++++++++++++++++++++ include/uapi/linux/blkdev.h | 1 + 2 files changed, 38 insertions(+) (limited to 'include/uapi/linux') diff --git a/block/ioctl.c b/block/ioctl.c index af2a897f13f9..64b4e6c0f696 100644 --- a/block/ioctl.c +++ b/block/ioctl.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include "blk.h" #include "blk-crypto-internal.h" @@ -943,6 +944,40 @@ static int blkdev_cmd_discard(struct io_uring_cmd *cmd) return -EIOCBQUEUED; } +static int blkdev_cmd_zone_reset_all(struct io_uring_cmd *cmd) +{ + struct blk_iou_cmd *bic = io_uring_cmd_to_pdu(cmd, struct blk_iou_cmd); + struct block_device *bdev = I_BDEV(cmd->file->f_mapping->host); + struct bio *bio; + int err; + + if (!(file_to_blk_mode(cmd->file) & BLK_OPEN_WRITE)) + return -EBADF; + if (bdev_read_only(bdev)) + return -EPERM; + if (!bdev_is_zoned(bdev)) + return -EOPNOTSUPP; + if (bic->start || bic->len) + return -EINVAL; + + err = filemap_invalidate_pages(bdev->bd_mapping, 0, + bdev_nr_bytes(bdev) - 1, bic->nowait); + if (err) + return err; + + bio = bio_alloc(bdev, 0, REQ_OP_ZONE_RESET_ALL, + bic->nowait ? GFP_NOWAIT : GFP_KERNEL); + if (!bio) + return -EAGAIN; + if (bic->nowait) + bio->bi_opf |= REQ_NOWAIT; + trace_blkdev_zone_mgmt(bio, 0); + bio->bi_private = cmd; + bio->bi_end_io = bio_cmd_bio_end_io; + submit_bio(bio); + return -EIOCBQUEUED; +} + int blkdev_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags) { struct blk_iou_cmd *bic = io_uring_cmd_to_pdu(cmd, struct blk_iou_cmd); @@ -966,6 +1001,8 @@ int blkdev_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags) switch (cmd_op) { case BLOCK_URING_CMD_DISCARD: return blkdev_cmd_discard(cmd); + case BLOCK_URING_CMD_ZONE_RESET_ALL: + return blkdev_cmd_zone_reset_all(cmd); } return -EINVAL; } diff --git a/include/uapi/linux/blkdev.h b/include/uapi/linux/blkdev.h index 66373cd1a83a..57b0bbd04e2b 100644 --- a/include/uapi/linux/blkdev.h +++ b/include/uapi/linux/blkdev.h @@ -10,5 +10,6 @@ * It's a different number space from ioctl(), reuse the block's code 0x12. */ #define BLOCK_URING_CMD_DISCARD _IO(0x12, 0) +#define BLOCK_URING_CMD_ZONE_RESET_ALL _IO(0x12, 1) #endif -- cgit v1.2.3 From 3ebcaccc8871ba723ea336db9da9084ae3cffcb3 Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Mon, 8 Jun 2026 09:56:29 +0200 Subject: batman-adv: add missing kernel-doc comments batman-adv requires kernel-doc for all functions and data types visible outside their own translation unit. Promoting a function from static to module-wide visibility currently requires adding documentation from scratch. However, this burden falls on whoever promotes a function from static to module-wide visibility, rather than its original implementer. Add the missing kernel-doc comments for the remaining undocumented functions and data types to reduce the complexity for new contributions. Signed-off-by: Sven Eckelmann --- include/uapi/linux/batadv_packet.h | 16 ++- net/batman-adv/bat_algo.c | 10 ++ net/batman-adv/bat_iv_ogm.c | 166 +++++++++++++++++++++++++++++-- net/batman-adv/bat_v.c | 52 ++++++++++ net/batman-adv/bitarray.c | 9 +- net/batman-adv/distributed-arp-table.c | 38 +++++++ net/batman-adv/gateway_client.c | 8 ++ net/batman-adv/hard-interface.c | 82 +++++++++++++++ net/batman-adv/hash.c | 5 +- net/batman-adv/hash.h | 34 +++++-- net/batman-adv/main.c | 36 ++++++- net/batman-adv/mesh-interface.c | 63 ++++++++++++ net/batman-adv/netlink.c | 8 +- net/batman-adv/originator.c | 7 ++ net/batman-adv/routing.c | 31 ++++++ net/batman-adv/translation-table.c | 177 ++++++++++++++++++++++++++++++++- net/batman-adv/types.h | 4 +- 17 files changed, 711 insertions(+), 35 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/batadv_packet.h b/include/uapi/linux/batadv_packet.h index 1241285b866c..32436560ecc8 100644 --- a/include/uapi/linux/batadv_packet.h +++ b/include/uapi/linux/batadv_packet.h @@ -192,13 +192,19 @@ enum batadv_tvlv_type { }; #pragma pack(2) -/* the destination hardware field in the ARP frame is used to - * transport the claim type and the group id +/** + * struct batadv_bla_claim_dst - layout of the destination MAC of a BLA claim + * frame + * @magic: fixed magic prefix (FF:43:05) identifying claim frames + * @type: claim frame type, see &enum batadv_bla_claimframe + * @group: group identifier of the announcing backbone gateway + * + * used in the destination hardware field of the ARP frame */ struct batadv_bla_claim_dst { - __u8 magic[3]; /* FF:43:05 */ - __u8 type; /* bla_claimframe */ - __be16 group; /* group id */ + __u8 magic[3]; + __u8 type; + __be16 group; }; /** diff --git a/net/batman-adv/bat_algo.c b/net/batman-adv/bat_algo.c index 49e5861b58ec..a040141cdf1a 100644 --- a/net/batman-adv/bat_algo.c +++ b/net/batman-adv/bat_algo.c @@ -116,6 +116,16 @@ int batadv_algo_select(struct batadv_priv *bat_priv, const char *name) return 0; } +/** + * batadv_param_set_ra() - Validate and store routing_algo module parameter + * @val: new value for the routing_algo module parameter + * @kp: kernel parameter description used to store the value + * + * Check that the requested algorithm is known to batman-adv and then store + * the name as the new default routing algorithm. + * + * Return: 0 on success or negative error number in case of failure + */ static int batadv_param_set_ra(const char *val, const struct kernel_param *kp) { struct batadv_algo_ops *bat_algo_ops; diff --git a/net/batman-adv/bat_iv_ogm.c b/net/batman-adv/bat_iv_ogm.c index 22622283f59b..a9e80330fcb6 100644 --- a/net/batman-adv/bat_iv_ogm.c +++ b/net/batman-adv/bat_iv_ogm.c @@ -171,6 +171,14 @@ free_orig_node_hash: return NULL; } +/** + * batadv_iv_ogm_neigh_new() - retrieve or create a B.A.T.M.A.N. IV neighbour + * @hard_iface: the interface where the neighbour is connected to + * @neigh_addr: the mac address of the neighbour + * @orig_node: originator object representing the neighbour + * + * Return: pointer to the neigh_node or NULL in case of failure + */ static struct batadv_neigh_node * batadv_iv_ogm_neigh_new(struct batadv_hard_iface *hard_iface, const u8 *neigh_addr, @@ -183,6 +191,14 @@ batadv_iv_ogm_neigh_new(struct batadv_hard_iface *hard_iface, return neigh_node; } +/** + * batadv_iv_ogm_iface_enable() - prepare an interface for B.A.T.M.A.N. IV + * @hard_iface: the interface to prepare + * + * Allocate and prepare the per-interface OGM buffer + * + * Return: 0 on success or negative error number in case of failure + */ static int batadv_iv_ogm_iface_enable(struct batadv_hard_iface *hard_iface) { struct batadv_ogm_packet *batadv_ogm_packet; @@ -220,6 +236,14 @@ static int batadv_iv_ogm_iface_enable(struct batadv_hard_iface *hard_iface) return 0; } +/** + * batadv_iv_ogm_iface_disable() - release B.A.T.M.A.N. IV resources of an + * interface + * @hard_iface: the interface which is shutting down + * + * Free the per-interface OGM buffer and cancel a possibly pending OGM + * rescheduling work. + */ static void batadv_iv_ogm_iface_disable(struct batadv_hard_iface *hard_iface) { mutex_lock(&hard_iface->bat_iv.ogm_buff_mutex); @@ -233,6 +257,11 @@ static void batadv_iv_ogm_iface_disable(struct batadv_hard_iface *hard_iface) disable_delayed_work_sync(&hard_iface->bat_iv.reschedule_work); } +/** + * batadv_iv_ogm_iface_update_mac() - update the originator MAC stored in the + * per-interface OGM buffer + * @hard_iface: the interface for which the OGM buffer should be updated + */ static void batadv_iv_ogm_iface_update_mac(struct batadv_hard_iface *hard_iface) { struct batadv_ogm_packet *batadv_ogm_packet; @@ -254,6 +283,14 @@ unlock: mutex_unlock(&hard_iface->bat_iv.ogm_buff_mutex); } +/** + * batadv_iv_ogm_primary_iface_set() - apply primary interface state to + * a batadv_hard_iface + * @hard_iface: interface which just became the primary + * + * The primary interface uses the full TTL for its own OGMs, so adjust the TTL + * stored in the OGM template buffer accordingly. + */ static void batadv_iv_ogm_primary_iface_set(struct batadv_hard_iface *hard_iface) { @@ -273,7 +310,17 @@ unlock: mutex_unlock(&hard_iface->bat_iv.ogm_buff_mutex); } -/* when do we schedule our own ogm to be sent */ +/** + * batadv_iv_ogm_emit_send_time() - calculate the jiffies when an own OGM + * should be sent next + * @bat_priv: the bat priv with all the mesh interface information + * + * The next emission point is the configured orig_interval randomised by + * +/- BATADV_JITTER milliseconds to reduce chance of potential collisions + * between neighbours. + * + * Return: jiffies value when the next OGM should be transmitted + */ static unsigned long batadv_iv_ogm_emit_send_time(const struct batadv_priv *bat_priv) { @@ -285,13 +332,24 @@ batadv_iv_ogm_emit_send_time(const struct batadv_priv *bat_priv) return jiffies + msecs_to_jiffies(msecs); } -/* when do we schedule a ogm packet to be sent */ +/** + * batadv_iv_ogm_fwd_send_time() - calculate the jiffies when a forwarded OGM + * should be sent next + * + * Return: jiffies value at which a forwarded OGM should be transmitted + */ static unsigned long batadv_iv_ogm_fwd_send_time(void) { return jiffies + msecs_to_jiffies(get_random_u32_below(BATADV_JITTER / 2)); } -/* apply hop penalty for a normal link */ +/** + * batadv_hop_penalty() - apply the configured hop penalty to a TQ value + * @tq: input TQ value to be reduced + * @bat_priv: the bat priv with all the mesh interface information + * + * Return: the TQ value after the hop penalty has been applied + */ static u8 batadv_hop_penalty(u8 tq, const struct batadv_priv *bat_priv) { int hop_penalty = READ_ONCE(bat_priv->hop_penalty); @@ -337,7 +395,15 @@ batadv_iv_ogm_aggr_packet(int buff_pos, int packet_len, return next_buff_pos <= packet_len; } -/* send a batman ogm to a given interface */ +/** + * batadv_iv_ogm_send_to_if() - send a batman OGM to a given interface + * @forw_packet: forward packet containing the OGM(s) to be transmitted + * @hard_iface: interface to send the OGM out on + * + * Update the direct link flags of each aggregated OGM for the outgoing + * interface, log the transmission and finally hand a clone of the skb to the + * lower layer for broadcast. + */ static void batadv_iv_ogm_send_to_if(struct batadv_forw_packet *forw_packet, struct batadv_hard_iface *hard_iface) { @@ -401,7 +467,13 @@ static void batadv_iv_ogm_send_to_if(struct batadv_forw_packet *forw_packet, } } -/* send a batman ogm packet */ +/** + * batadv_iv_ogm_emit() - emit an (aggregated) OGM packet + * @forw_packet: forward packet which should be emitted + * + * The @forw_packet will be emitted but not consumed. When the interface is + * no longer active, the transmission will be skipped. + */ static void batadv_iv_ogm_emit(struct batadv_forw_packet *forw_packet) { if (!forw_packet->if_incoming) { @@ -603,7 +675,14 @@ static bool batadv_iv_ogm_aggregate_new(const unsigned char *packet_buff, return true; } -/* aggregate a new packet into the existing ogm packet */ +/** + * batadv_iv_ogm_aggregate() - append an OGM to an existing aggregated forward + * packet + * @forw_packet_aggr: aggregated forward packet to extend + * @packet_buff: pointer to the OGM to append + * @packet_len: length of the OGM to append + * @direct_link: true if @packet_buff was received as a direct link OGM + */ static void batadv_iv_ogm_aggregate(struct batadv_forw_packet *forw_packet_aggr, const unsigned char *packet_buff, int packet_len, bool direct_link) @@ -699,6 +778,21 @@ static bool batadv_iv_ogm_queue_add(struct batadv_priv *bat_priv, } } +/** + * batadv_iv_ogm_forward() - rebroadcast a received OGM + * @orig_node: originator that sent the OGM + * @ethhdr: ethernet header of the OGM packet + * @batadv_ogm_packet: OGM packet to be forwarded + * @is_single_hop_neigh: true if the OGM was received via a one-hop neighbour + * @is_from_best_next_hop: true if the sender is the currently selected best + * next hop towards @orig_node + * @if_incoming: interface where the packet was received + * @if_outgoing: interface for which the retransmission should be considered + * + * Decrement the TTL, apply the hop penalty and queue the OGM for + * retransmission. OGMs that do not arrive over the best next hop are only + * forwarded for link-quality measurement reasons (and marked accordingly). + */ static void batadv_iv_ogm_forward(struct batadv_orig_node *orig_node, const struct ethhdr *ethhdr, struct batadv_ogm_packet *batadv_ogm_packet, @@ -899,6 +993,13 @@ out: batadv_hardif_put(primary_if); } +/** + * batadv_iv_ogm_schedule() - schedule the next OGM transmission on an + * interface + * @hard_iface: interface for which the next OGM should be scheduled + * + * Take the OGM buffer mutex and prepare the next OGM for transmission. + */ static void batadv_iv_ogm_schedule(struct batadv_hard_iface *hard_iface) { if (hard_iface->if_status == BATADV_IF_TO_BE_REMOVED) @@ -909,6 +1010,13 @@ static void batadv_iv_ogm_schedule(struct batadv_hard_iface *hard_iface) mutex_unlock(&hard_iface->bat_iv.ogm_buff_mutex); } +/** + * batadv_iv_ogm_reschedule() - work-queue helper to rerun batadv_iv_ogm_schedule() + * @work: work item embedded in the hard interface + * + * Invoked from the per-interface reschedule_work delayed work when the + * previous attempt to enqueue the own OGM failed. + */ static void batadv_iv_ogm_reschedule(struct work_struct *work) { struct delayed_work *delayed_work = to_delayed_work(work); @@ -1765,6 +1873,14 @@ static void batadv_iv_ogm_process(const struct sk_buff *skb, int ogm_offset, batadv_orig_node_put(orig_node); } +/** + * batadv_iv_send_outstanding_bat_ogm_packet() - work-queue helper to emit a + * queued forward packet + * @work: work item embedded in the forward packet + * + * Emit the queued OGM forward packet and, for own primary-interface packets, + * schedule the next periodic OGM. The forward packet is freed afterwards. + */ static void batadv_iv_send_outstanding_bat_ogm_packet(struct work_struct *work) { struct delayed_work *delayed_work; @@ -1803,6 +1919,17 @@ out: batadv_forw_packet_free(forw_packet, dropped); } +/** + * batadv_iv_ogm_receive() - receive a B.A.T.M.A.N. IV OGM packet + * @skb: skb containing the OGM packet + * @if_incoming: interface where the packet was received + * + * Validate the packet, then split the aggregated OGM packet into individual + * OGMs and hand each of them to batadv_iv_ogm_process(). Ownership of @skb is + * always taken over by this function. + * + * Return: NET_RX_SUCCESS or NET_RX_DROP + */ static int batadv_iv_ogm_receive(struct sk_buff *skb, struct batadv_hard_iface *if_incoming) { @@ -2310,6 +2437,12 @@ batadv_iv_ogm_neigh_is_sob(struct batadv_neigh_node *neigh1, return ret; } +/** + * batadv_iv_iface_enabled() - notification handler for activated interfaces + * @hard_iface: interface that was just activated + * + * Set up the per-interface reschedule work and start sending periodic OGMs. + */ static void batadv_iv_iface_enabled(struct batadv_hard_iface *hard_iface) { INIT_DELAYED_WORK(&hard_iface->bat_iv.reschedule_work, batadv_iv_ogm_reschedule); @@ -2328,6 +2461,14 @@ static void batadv_iv_init_sel_class(struct batadv_priv *bat_priv) WRITE_ONCE(bat_priv->gw.sel_class, 20); } +/** + * batadv_iv_gw_get_best_gw_node() - retrieve the best gateway node based on + * the B.A.T.M.A.N. IV metric and the configured GW selection class + * @bat_priv: the bat priv with all the mesh interface information + * + * Return: gateway node with the highest score for the current selection class, + * or NULL if no eligible gateway exists. + */ static struct batadv_gw_node * batadv_iv_gw_get_best_gw_node(struct batadv_priv *bat_priv) { @@ -2405,6 +2546,19 @@ next: return curr_gw; } +/** + * batadv_iv_gw_is_eligible() - check whether a new gateway should replace the + * currently selected one + * @bat_priv: the bat priv with all the mesh interface information + * @curr_gw_orig: originator of the currently selected gateway + * @orig_node: originator of the gateway candidate + * + * Compare the TQ values of @curr_gw_orig and @orig_node, taking the configured + * gateway selection class into account. + * + * Return: true if @orig_node should take over as the active gateway, false + * otherwise + */ static bool batadv_iv_gw_is_eligible(struct batadv_priv *bat_priv, struct batadv_orig_node *curr_gw_orig, struct batadv_orig_node *orig_node) diff --git a/net/batman-adv/bat_v.c b/net/batman-adv/bat_v.c index db6f5bdcaa98..0068f0e238da 100644 --- a/net/batman-adv/bat_v.c +++ b/net/batman-adv/bat_v.c @@ -41,6 +41,13 @@ #include "netlink.h" #include "originator.h" +/** + * batadv_v_iface_activate() - finalise the activation of a hard interface + * @hard_iface: interface to activate + * + * Reuse the currently selected primary interface to seed the ELP packet. + * Immediately activate the interface. + */ static void batadv_v_iface_activate(struct batadv_hard_iface *hard_iface) { struct batadv_priv *bat_priv = netdev_priv(hard_iface->mesh_iface); @@ -61,6 +68,16 @@ static void batadv_v_iface_activate(struct batadv_hard_iface *hard_iface) hard_iface->if_status = BATADV_IF_ACTIVE; } +/** + * batadv_v_iface_enable() - enable the B.A.T.M.A.N. V protocol on an + * interface + * @hard_iface: interface to enable + * + * Enable the ELP and OGM components for @hard_iface. On error, the partially + * enabled state is rolled back before returning. + * + * Return: 0 on success or negative error number in case of failure + */ static int batadv_v_iface_enable(struct batadv_hard_iface *hard_iface) { int ret; @@ -76,12 +93,21 @@ static int batadv_v_iface_enable(struct batadv_hard_iface *hard_iface) return ret; } +/** + * batadv_v_iface_disable() - disable the B.A.T.M.A.N. V protocol on an + * interface + * @hard_iface: interface to disable + */ static void batadv_v_iface_disable(struct batadv_hard_iface *hard_iface) { batadv_v_ogm_iface_disable(hard_iface); batadv_v_elp_iface_disable(hard_iface); } +/** + * batadv_v_primary_iface_set() - apply primary interface state + * @hard_iface: interface which just became the primary + */ static void batadv_v_primary_iface_set(struct batadv_hard_iface *hard_iface) { batadv_v_elp_primary_iface_set(hard_iface); @@ -109,6 +135,11 @@ out: batadv_hardif_put(primary_if); } +/** + * batadv_v_hardif_neigh_init() - initialise the B.A.T.M.A.N. V state of a + * hard interface neighbour + * @hardif_neigh: hard interface neighbour to initialise + */ static void batadv_v_hardif_neigh_init(struct batadv_hardif_neigh_node *hardif_neigh) { @@ -444,6 +475,16 @@ batadv_v_orig_dump(struct sk_buff *msg, struct netlink_callback *cb, cb->args[2] = sub; } +/** + * batadv_v_neigh_cmp() - compare two B.A.T.M.A.N. V neighbours by throughput + * @neigh1: first neighbour to compare + * @if_outgoing1: outgoing interface to use for @neigh1 + * @neigh2: second neighbour to compare + * @if_outgoing2: outgoing interface to use for @neigh2 + * + * Return: a positive value if @neigh1 is better, a negative value if @neigh2 + * is better and 0 if both have equal throughput + */ static int batadv_v_neigh_cmp(struct batadv_neigh_node *neigh1, struct batadv_hard_iface *if_outgoing1, struct batadv_neigh_node *neigh2, @@ -469,6 +510,17 @@ err_ifinfo1: return ret; } +/** + * batadv_v_neigh_is_sob() - check whether two B.A.T.M.A.N. V neighbours have + * a similar or better throughput + * @neigh1: first neighbour to compare + * @if_outgoing1: outgoing interface to use for @neigh1 + * @neigh2: second neighbour to compare + * @if_outgoing2: outgoing interface to use for @neigh2 + * + * Return: true if the throughput of @neigh2 is at least 3/4 of the + * @neigh1 throughput + */ static bool batadv_v_neigh_is_sob(struct batadv_neigh_node *neigh1, struct batadv_hard_iface *if_outgoing1, struct batadv_neigh_node *neigh2, diff --git a/net/batman-adv/bitarray.c b/net/batman-adv/bitarray.c index 67cb356332bf..f814756d9533 100644 --- a/net/batman-adv/bitarray.c +++ b/net/batman-adv/bitarray.c @@ -11,7 +11,14 @@ #include "log.h" -/* shift the packet array by n places. */ +/** + * batadv_bitmap_shift_left() - shift the sequence number bitmap left + * @seq_bits: the sequence number bitmap to shift + * @n: number of positions to shift left + * + * Shift @seq_bits by @n positions. No-op if @n is not within the bounds of + * the bitmap. + */ static void batadv_bitmap_shift_left(unsigned long *seq_bits, s32 n) { if (n <= 0 || n >= BATADV_TQ_LOCAL_WINDOW_SIZE) diff --git a/net/batman-adv/distributed-arp-table.c b/net/batman-adv/distributed-arp-table.c index c5fca53f3074..d284b090fdf1 100644 --- a/net/batman-adv/distributed-arp-table.c +++ b/net/batman-adv/distributed-arp-table.c @@ -50,27 +50,65 @@ #include "translation-table.h" #include "tvlv.h" +/** + * enum batadv_bootpop - BOOTP/DHCP message op codes + */ enum batadv_bootpop { + /** @BATADV_BOOTREPLY: server-to-client reply */ BATADV_BOOTREPLY = 2, }; +/** + * enum batadv_boothtype - BOOTP/DHCP hardware address types + */ enum batadv_boothtype { + /** @BATADV_HTYPE_ETHERNET: Ethernet (10Mb) */ BATADV_HTYPE_ETHERNET = 1, }; +/** + * enum batadv_dhcpoptioncode - DHCP option codes relevant for batman-adv DAT + */ enum batadv_dhcpoptioncode { + /** @BATADV_DHCP_OPT_PAD: pad option */ BATADV_DHCP_OPT_PAD = 0, + + /** @BATADV_DHCP_OPT_MSG_TYPE: DHCP message type option */ BATADV_DHCP_OPT_MSG_TYPE = 53, + + /** @BATADV_DHCP_OPT_END: end of options marker */ BATADV_DHCP_OPT_END = 255, }; +/** + * enum batadv_dhcptype - DHCP message types relevant for batman-adv DAT + */ enum batadv_dhcptype { + /** @BATADV_DHCPACK: DHCPACK message */ BATADV_DHCPACK = 5, }; /* { 99, 130, 83, 99 } */ #define BATADV_DHCP_MAGIC 1669485411 +/** + * struct batadv_dhcp_packet - BOOTP/DHCP packet header + * @op: message op code / message type + * @htype: hardware address type + * @hlen: hardware address length + * @hops: number of relay hops + * @xid: transaction identifier + * @secs: seconds elapsed since client started trying to boot + * @flags: BOOTP/DHCP flags + * @ciaddr: client IP address + * @yiaddr: "your" (client) IP address as assigned by the server + * @siaddr: IP address of next server to use in bootstrap + * @giaddr: relay agent IP address + * @chaddr: client hardware address + * @sname: optional server host name + * @file: boot file name + * @magic: BOOTP/DHCP magic cookie identifying the start of options + */ struct batadv_dhcp_packet { __u8 op; __u8 htype; diff --git a/net/batman-adv/gateway_client.c b/net/batman-adv/gateway_client.c index a5ac82eabd25..48fc711b8fd6 100644 --- a/net/batman-adv/gateway_client.c +++ b/net/batman-adv/gateway_client.c @@ -125,6 +125,14 @@ out: return orig_node; } +/** + * batadv_gw_select() - select a new currently active gateway + * @bat_priv: the bat priv with all the mesh interface information + * @new_gw_node: gateway node to be set as the current gateway, may be NULL + * + * Atomically replace the currently active gateway with @new_gw_node and drop + * the reference to the previous one. + */ static void batadv_gw_select(struct batadv_priv *bat_priv, struct batadv_gw_node *new_gw_node) { diff --git a/net/batman-adv/hard-interface.c b/net/batman-adv/hard-interface.c index b6867576bbaf..1950b8809d99 100644 --- a/net/batman-adv/hard-interface.c +++ b/net/batman-adv/hard-interface.c @@ -197,6 +197,17 @@ static bool batadv_is_on_batman_iface(const struct net_device *net_dev) return ret; } +/** + * batadv_is_valid_iface() - check whether a net_device can be used as a hard + * interface + * @net_dev: the net_device to check + * + * Refuse loopback devices, non-Ethernet devices, devices with a non-Ethernet + * address length and any device that is already part of a batman-adv mesh + * interface stack. + * + * Return: true if @net_dev can be used as a hard interface, false otherwise + */ static bool batadv_is_valid_iface(const struct net_device *net_dev) { if (net_dev->flags & IFF_LOOPBACK) @@ -467,6 +478,14 @@ out: return ret; } +/** + * batadv_hardif_get_active() - retrieve an active hard interface for a mesh + * interface + * @mesh_iface: mesh interface to search + * + * Return: first hard interface in BATADV_IF_ACTIVE state attached to + * @mesh_iface, or NULL if none is active. + */ static struct batadv_hard_iface * batadv_hardif_get_active(struct net_device *mesh_iface) { @@ -487,6 +506,15 @@ out: return hard_iface; } +/** + * batadv_primary_if_update_addr() - propagate the new primary interface MAC + * address to all dependent components + * @bat_priv: the bat priv with all the mesh interface information + * @oldif: previously used primary interface, or NULL if none + * + * Inform DAT and BLA that the originator address has changed so they can + * adjust their state accordingly. + */ static void batadv_primary_if_update_addr(struct batadv_priv *bat_priv, struct batadv_hard_iface *oldif) { @@ -502,6 +530,15 @@ out: batadv_hardif_put(primary_if); } +/** + * batadv_primary_if_select() - select the new primary interface + * @bat_priv: the bat priv with all the mesh interface information + * @new_hard_iface: new primary interface, may be NULL + * + * Replace the currently selected primary interface with @new_hard_iface, + * invoke the algorithm-specific primary_set hook and update the originator + * MAC address. + */ static void batadv_primary_if_select(struct batadv_priv *bat_priv, struct batadv_hard_iface *new_hard_iface) { @@ -525,6 +562,13 @@ out: batadv_hardif_put(curr_hard_iface); } +/** + * batadv_hardif_is_iface_up() - check whether the underlying net_device of a + * hard interface is up + * @hard_iface: the hard interface to check + * + * Return: true if the underlying net_device has the IFF_UP flag set + */ static bool batadv_hardif_is_iface_up(const struct batadv_hard_iface *hard_iface) { @@ -534,6 +578,15 @@ batadv_hardif_is_iface_up(const struct batadv_hard_iface *hard_iface) return false; } +/** + * batadv_check_known_mac_addr() - warn about duplicate hard interface MAC + * addresses + * @hard_iface: hard interface that was just added or had its MAC changed + * + * Iterate over all hard interfaces of the same mesh interface and emit a + * warning if another in-use interface shares the same MAC address as + * @hard_iface. + */ static void batadv_check_known_mac_addr(const struct batadv_hard_iface *hard_iface) { struct net_device *mesh_iface = hard_iface->mesh_iface; @@ -666,6 +719,15 @@ void batadv_update_min_mtu(struct net_device *mesh_iface) batadv_tt_local_resize_to_mtu(mesh_iface); } +/** + * batadv_hardif_activate_interface() - move a hard interface to the active + * state + * @hard_iface: the interface that has come up + * + * Activate an interface, select it as the primary interface if no + * primary is currently active and invoke the algorithm-specific + * activate hook. + */ static void batadv_hardif_activate_interface(struct batadv_hard_iface *hard_iface) { @@ -699,6 +761,14 @@ out: batadv_hardif_put(primary_if); } +/** + * batadv_hardif_deactivate_interface() - move a hard interface to the + * inactive state + * @hard_iface: the interface that went down + * + * Demote @hard_iface to BATADV_IF_INACTIVE and recalculate the mesh minimum + * MTU based on the remaining active interfaces. + */ static void batadv_hardif_deactivate_interface(struct batadv_hard_iface *hard_iface) { @@ -1020,6 +1090,18 @@ static void batadv_wifi_net_device_event(unsigned long event, } } +/** + * batadv_hard_if_event() - netdevice notifier callback for hard interfaces + * @this: notifier block (unused) + * @event: the NETDEV_* event to handle + * @ptr: notifier info pointing to the affected net_device + * + * Dispatch netdevice events that affect potential or existing hard + * interfaces. Mesh interfaces are handled separately by + * batadv_hard_if_event_meshif(). + * + * Return: NOTIFY_DONE + */ static int batadv_hard_if_event(struct notifier_block *this, unsigned long event, void *ptr) { diff --git a/net/batman-adv/hash.c b/net/batman-adv/hash.c index 759fa29176db..b9a652bd523b 100644 --- a/net/batman-adv/hash.c +++ b/net/batman-adv/hash.c @@ -11,7 +11,10 @@ #include #include -/* clears the hash */ +/** + * batadv_hash_init() - clear all buckets of a hashtable + * @hash: hashtable to clear + */ static void batadv_hash_init(struct batadv_hashtable *hash) { u32 i; diff --git a/net/batman-adv/hash.h b/net/batman-adv/hash.h index 86a2c20000dc..89cbb56cdbe1 100644 --- a/net/batman-adv/hash.h +++ b/net/batman-adv/hash.h @@ -18,21 +18,35 @@ #include #include -/* callback to a compare function. should compare 2 element data for their - * keys +/** + * typedef batadv_hashdata_compare_cb - hash element comparison callback + * @node: hlist node of the element currently stored in the bucket + * @key: opaque payload to compare @node's key against + * + * Compare hash element by its keys. * - * Return: true if same and false if not same + * Return: true if both elements are considered equal, false otherwise. */ -typedef bool (*batadv_hashdata_compare_cb)(const struct hlist_node *, - const void *); +typedef bool (*batadv_hashdata_compare_cb)(const struct hlist_node *node, + const void *key); -/* the hashfunction +/** + * typedef batadv_hashdata_choose_cb - hash bucket selection callback + * @key: opaque payload whose key selects the bucket + * @size: number of buckets in the hash table + * + * Return: bucket index derived from the key in @key and the table @size. + */ +typedef u32 (*batadv_hashdata_choose_cb)(const void *key, u32 size); + +/** + * typedef batadv_hashdata_free_cb - hash element free callback + * @node: hlist node of the element being removed + * @arg: opaque caller-supplied argument forwarded from the caller * - * Return: an index based on the key in the data of the first argument and the - * size the second + * Release a previously inserted hash element. */ -typedef u32 (*batadv_hashdata_choose_cb)(const void *, u32); -typedef void (*batadv_hashdata_free_cb)(struct hlist_node *, void *); +typedef void (*batadv_hashdata_free_cb)(struct hlist_node *node, void *arg); /** * struct batadv_hashtable - Wrapper of simple hlist based hashtable diff --git a/net/batman-adv/main.c b/net/batman-adv/main.c index 73becb054948..78b81daaeff9 100644 --- a/net/batman-adv/main.c +++ b/net/batman-adv/main.c @@ -82,6 +82,14 @@ static char *batadv_uev_type_str[] = { "bla", }; +/** + * batadv_init() - batman-adv module init function + * + * Initialise the global state used by all mesh interfaces, register the + * netdevice notifier and the netlink/rtnl family. + * + * Return: 0 on success or negative error number in case of failure + */ static int __init batadv_init(void) { int ret; @@ -128,6 +136,12 @@ err_create_wq: return ret; } +/** + * batadv_exit() - batman-adv module exit function + * + * Unregister the netdevice notifier and tear down all global state allocated + * by batadv_init(). + */ static void __exit batadv_exit(void) { batadv_netlink_unregister(); @@ -398,6 +412,17 @@ void batadv_skb_set_priority(struct sk_buff *skb, int offset) skb->priority = prio + 256; } +/** + * batadv_recv_unhandled_packet() - default RX handler for unsupported packet + * types + * @skb: incoming packet + * @recv_if: interface on which the packet was received (unused) + * + * Drop incoming packets whose packet_type has no dedicated RX handler + * registered. + * + * Return: NET_RX_DROP + */ static int batadv_recv_unhandled_packet(struct sk_buff *skb, struct batadv_hard_iface *recv_if) { @@ -406,10 +431,6 @@ static int batadv_recv_unhandled_packet(struct sk_buff *skb, return NET_RX_DROP; } -/* incoming packets with the batman ethertype received on any active hard - * interface - */ - /** * batadv_batman_skb_recv() - Handle incoming message from an hard interface * @skb: the received packet @@ -492,6 +513,13 @@ err_out: return NET_RX_DROP; } +/** + * batadv_recv_handler_init() - initialise the RX handler dispatch table + * + * Initialise all entries of the RX handler table as either "unhandled" or with + * protocol indepentend handlers, and perform compile-time size sanity checks on + * all on-wire packet structs. + */ static void batadv_recv_handler_init(void) { int i; diff --git a/net/batman-adv/mesh-interface.c b/net/batman-adv/mesh-interface.c index 1dc6cfae9e9a..effa37ff3bb1 100644 --- a/net/batman-adv/mesh-interface.c +++ b/net/batman-adv/mesh-interface.c @@ -98,6 +98,15 @@ static u64 batadv_sum_counter(struct batadv_priv *bat_priv, size_t idx) return sum; } +/** + * batadv_interface_stats() - return netdev stats for a mesh interface + * @dev: the mesh interface to query + * + * Aggregate the per-CPU traffic counters into the standard netdev stats + * structure. + * + * Return: pointer to the populated net_device_stats structure + */ static struct net_device_stats *batadv_interface_stats(struct net_device *dev) { struct batadv_priv *bat_priv = netdev_priv(dev); @@ -111,6 +120,18 @@ static struct net_device_stats *batadv_interface_stats(struct net_device *dev) return stats; } +/** + * batadv_interface_set_mac_addr() - change the MAC address of a mesh + * interface + * @dev: the mesh interface to modify + * @p: pointer to a struct sockaddr holding the new MAC address + * + * Replace the MAC address of the mesh interface. If the mesh is already + * active, also update the local translation table entries for all configured + * VLANs so that the new MAC is announced and the old one is removed. + * + * Return: 0 on success or negative error number in case of failure + */ static int batadv_interface_set_mac_addr(struct net_device *dev, void *p) { struct batadv_priv *bat_priv = netdev_priv(dev); @@ -140,6 +161,16 @@ static int batadv_interface_set_mac_addr(struct net_device *dev, void *p) return 0; } +/** + * batadv_interface_change_mtu() - change the MTU of a mesh interface + * @dev: the mesh interface to modify + * @new_mtu: requested new MTU value + * + * Validate that @new_mtu fits within the range supported by the configured + * hard interfaces and remember it as the user-configured MTU. + * + * Return: 0 on success or -EINVAL if @new_mtu is out of range + */ static int batadv_interface_change_mtu(struct net_device *dev, int new_mtu) { struct batadv_priv *bat_priv = netdev_priv(dev); @@ -166,6 +197,13 @@ static void batadv_interface_set_rx_mode(struct net_device *dev) { } +/** + * batadv_interface_tx() - transmit a frame on a mesh interface + * @skb: the frame to send + * @mesh_iface: the mesh interface the frame was queued on + * + * Return: NETDEV_TX_OK on success + */ static netdev_tx_t batadv_interface_tx(struct sk_buff *skb, struct net_device *mesh_iface) { @@ -883,6 +921,11 @@ static const struct net_device_ops batadv_netdev_ops = { .ndo_del_slave = batadv_meshif_slave_del, }; +/** + * batadv_get_drvinfo() - ethtool driver info handler for mesh interfaces + * @dev: the mesh interface (unused) + * @info: ethtool_drvinfo struct to populate + */ static void batadv_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info) { @@ -943,6 +986,12 @@ static const struct { #endif }; +/** + * batadv_get_strings() - ethtool string handler for mesh interfaces + * @dev: the mesh interface (unused) + * @stringset: ethtool string set to retrieve + * @data: buffer to copy the requested string set into + */ static void batadv_get_strings(struct net_device *dev, u32 stringset, u8 *data) { if (stringset == ETH_SS_STATS) @@ -950,6 +999,12 @@ static void batadv_get_strings(struct net_device *dev, u32 stringset, u8 *data) sizeof(batadv_counters_strings)); } +/** + * batadv_get_ethtool_stats() - ethtool stats handler for mesh interfaces + * @dev: the mesh interface to query + * @stats: ethtool_stats struct (unused) + * @data: destination array for the gathered counter values + */ static void batadv_get_ethtool_stats(struct net_device *dev, struct ethtool_stats *stats, u64 *data) { @@ -960,6 +1015,14 @@ static void batadv_get_ethtool_stats(struct net_device *dev, data[i] = batadv_sum_counter(bat_priv, i); } +/** + * batadv_get_sset_count() - ethtool stringset size handler for mesh interfaces + * @dev: the mesh interface (unused) + * @stringset: ethtool string set to query + * + * Return: number of entries in @stringset or -EOPNOTSUPP if @stringset is not + * supported + */ static int batadv_get_sset_count(struct net_device *dev, int stringset) { if (stringset == ETH_SS_STATS) diff --git a/net/batman-adv/netlink.c b/net/batman-adv/netlink.c index d2bc48c70714..f4aa7d205510 100644 --- a/net/batman-adv/netlink.c +++ b/net/batman-adv/netlink.c @@ -52,9 +52,15 @@ struct genl_family batadv_netlink_family; -/* multicast groups */ +/** + * enum batadv_netlink_multicast_groups - batman-adv generic netlink multicast + * groups + */ enum batadv_netlink_multicast_groups { + /** @BATADV_NL_MCGRP_CONFIG: configuration change notifications */ BATADV_NL_MCGRP_CONFIG, + + /** @BATADV_NL_MCGRP_TPMETER: throughput meter result notifications */ BATADV_NL_MCGRP_TPMETER, }; diff --git a/net/batman-adv/originator.c b/net/batman-adv/originator.c index 48f837cf665a..bab1eb61d9ef 100644 --- a/net/batman-adv/originator.c +++ b/net/batman-adv/originator.c @@ -1295,6 +1295,13 @@ void batadv_purge_orig_ref(struct batadv_priv *bat_priv) batadv_gw_election(bat_priv); } +/** + * batadv_purge_orig() - periodic worker to purge stale originator entries + * @work: delayed work embedded in the bat_priv + * + * Invoke batadv_purge_orig_ref() to drop stale originators and reschedule the + * next run after BATADV_ORIG_WORK_PERIOD milliseconds. + */ static void batadv_purge_orig(struct work_struct *work) { struct delayed_work *delayed_work; diff --git a/net/batman-adv/routing.c b/net/batman-adv/routing.c index a9d657f2a831..b62a984e73dc 100644 --- a/net/batman-adv/routing.c +++ b/net/batman-adv/routing.c @@ -328,6 +328,16 @@ out: return ret; } +/** + * batadv_recv_icmp_ttl_exceeded() - handle an ICMP packet that hit TTL 0 + * @bat_priv: the bat priv with all the mesh interface information + * @skb: ICMP packet whose TTL has expired + * + * For traceroute-style ICMP echo requests, send a TTL exceeded reply back to + * the source. Other ICMP types are simply dropped. + * + * Return: NET_XMIT_SUCCESS if the reply was queued, NET_RX_DROP otherwise + */ static int batadv_recv_icmp_ttl_exceeded(struct batadv_priv *bat_priv, struct sk_buff *skb) { @@ -706,6 +716,18 @@ next: return router; } +/** + * batadv_route_unicast_packet() - forward a unicast packet towards its + * destination originator + * @skb: the received unicast packet + * @recv_if: interface on which the packet was received + * + * Decrement the TTL, look up the originator for the destination address and + * hand the packet over to batadv_send_skb_to_orig() for transmission. Drop + * the packet when the TTL is exhausted or no route exists. + * + * Return: NET_RX_SUCCESS if the packet was forwarded, NET_RX_DROP otherwise + */ static int batadv_route_unicast_packet(struct sk_buff *skb, struct batadv_hard_iface *recv_if) { @@ -836,6 +858,15 @@ out: return ret; } +/** + * batadv_check_unicast_ttvn() - check and adjust the TTVN of a unicast packet + * @bat_priv: the bat priv with all the mesh interface information + * @skb: the unicast packet to check + * @hdr_len: length of the unicast header preceding the payload + * + * Return: true if the packet may be processed further, false if has to be + * dropped by the caller + */ static bool batadv_check_unicast_ttvn(struct batadv_priv *bat_priv, struct sk_buff *skb, int hdr_len) { diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c index dae5e1d8c038..b12ec8d1b545 100644 --- a/net/batman-adv/translation-table.c +++ b/net/batman-adv/translation-table.c @@ -535,6 +535,12 @@ static int batadv_tt_local_table_transmit_size(struct batadv_priv *bat_priv) return hdr_size + batadv_tt_len(tt_local_entries); } +/** + * batadv_tt_local_init() - allocate and initialise the local translation table + * @bat_priv: the bat priv with all the mesh interface information + * + * Return: 0 on success or -ENOMEM in case of allocation failure + */ static int batadv_tt_local_init(struct batadv_priv *bat_priv) { if (bat_priv->tt.local_hash) @@ -551,6 +557,15 @@ static int batadv_tt_local_init(struct batadv_priv *bat_priv) return 0; } +/** + * batadv_tt_global_free() - drop a global translation table entry + * @bat_priv: the bat priv with all the mesh interface information + * @tt_global: the global TT entry to remove + * @message: debug message explaining why the entry is being removed + * + * Remove @tt_global from the global TT hash and drop the reference held by + * the hash. + */ static void batadv_tt_global_free(struct batadv_priv *bat_priv, struct batadv_tt_global_entry *tt_global, const char *message) @@ -1224,6 +1239,17 @@ int batadv_tt_local_dump(struct sk_buff *msg, struct netlink_callback *cb) return ret; } +/** + * batadv_tt_local_set_pending() - mark a local TT entry as pending removal + * @bat_priv: the bat priv with all the mesh interface information + * @tt_local_entry: local TT entry to mark + * @flags: TT change flags to announce together with the pending removal + * @message: debug message describing the reason for the change + * + * Schedule the TT change announcement and set BATADV_TT_CLIENT_PENDING on the + * entry. The entry is kept in the local table until the next TTVN increment + * so that a consistency-check response can still be answered. + */ static void batadv_tt_local_set_pending(struct batadv_priv *bat_priv, struct batadv_tt_local_entry *tt_local_entry, @@ -1367,6 +1393,13 @@ static void batadv_tt_local_purge(struct batadv_priv *bat_priv, } } +/** + * batadv_tt_local_table_free() - release the local translation table + * @bat_priv: the bat priv with all the mesh interface information + * + * Drop every entry of the local TT hash, free their references and finally + * release the hashtable itself. + */ static void batadv_tt_local_table_free(struct batadv_priv *bat_priv) { struct batadv_hashtable *hash; @@ -1404,6 +1437,13 @@ static void batadv_tt_local_table_free(struct batadv_priv *bat_priv) bat_priv->tt.local_hash = NULL; } +/** + * batadv_tt_global_init() - allocate and initialise the global translation + * table + * @bat_priv: the bat priv with all the mesh interface information + * + * Return: 0 on success or -ENOMEM in case of allocation failure + */ static int batadv_tt_global_init(struct batadv_priv *bat_priv) { if (bat_priv->tt.global_hash) @@ -1420,6 +1460,12 @@ static int batadv_tt_global_init(struct batadv_priv *bat_priv) return 0; } +/** + * batadv_tt_changes_list_free() - drop all pending local TT changes + * @bat_priv: the bat priv with all the mesh interface information + * + * Discard every queued local TT change and reset the pending change counter. + */ static void batadv_tt_changes_list_free(struct batadv_priv *bat_priv) { struct batadv_tt_change_node *entry, *safe; @@ -2024,7 +2070,11 @@ _batadv_tt_global_del_orig_entry(struct batadv_tt_global_entry *tt_global_entry, batadv_tt_orig_list_entry_put(orig_entry); } -/* deletes the orig list of a tt_global_entry */ +/** + * batadv_tt_global_del_orig_list() - drop every orig_list_entry of a global + * TT entry + * @tt_global_entry: the global TT entry to clear + */ static void batadv_tt_global_del_orig_list(struct batadv_tt_global_entry *tt_global_entry) { @@ -2077,9 +2127,17 @@ batadv_tt_global_del_orig_node(struct batadv_priv *bat_priv, spin_unlock_bh(&tt_global_entry->list_lock); } -/* If the client is to be deleted, we check if it is the last origantor entry - * within tt_global entry. If yes, we set the BATADV_TT_CLIENT_ROAM flag and the - * timer, otherwise we simply remove the originator scheduled for deletion. +/** + * batadv_tt_global_del_roaming() - remove a roaming client from a global TT + * entry + * @bat_priv: the bat priv with all the mesh interface information + * @tt_global_entry: the global TT entry of the roaming client + * @orig_node: the originator that the client has roamed away from + * @message: debug message describing the reason for the change + * + * If @orig_node was the last announced source for the client, mark the entry + * for roaming so it can be cleaned up after the roaming timer expires. + * Otherwise simply remove the orig_node entry from the announcer list. */ static void batadv_tt_global_del_roaming(struct batadv_priv *bat_priv, @@ -2241,6 +2299,15 @@ void batadv_tt_global_del_orig(struct batadv_priv *bat_priv, clear_bit(BATADV_ORIG_CAPA_HAS_TT, &orig_node->capa_initialized); } +/** + * batadv_tt_global_to_purge() - check whether a global TT entry has to be + * purged + * @tt_global: global TT entry under consideration + * @msg: storage for a pointer to a human readable reason on return + * + * Return: true if the entry should be purged because its roaming or temporary + * timer has elapsed; false otherwise + */ static bool batadv_tt_global_to_purge(struct batadv_tt_global_entry *tt_global, char **msg) { @@ -2263,6 +2330,13 @@ static bool batadv_tt_global_to_purge(struct batadv_tt_global_entry *tt_global, return purge; } +/** + * batadv_tt_global_purge() - purge expired global translation table entries + * @bat_priv: the bat priv with all the mesh interface information + * + * Iterate over the global translation table and drop every entry that the + * roaming or temporary timer has expired for. + */ static void batadv_tt_global_purge(struct batadv_priv *bat_priv) { struct batadv_hashtable *hash = bat_priv->tt.global_hash; @@ -2302,6 +2376,13 @@ static void batadv_tt_global_purge(struct batadv_priv *bat_priv) } } +/** + * batadv_tt_global_table_free() - release the global translation table + * @bat_priv: the bat priv with all the mesh interface information + * + * Drop every entry of the global TT hash, free their references and finally + * release the hashtable itself. + */ static void batadv_tt_global_table_free(struct batadv_priv *bat_priv) { struct batadv_hashtable *hash; @@ -2338,6 +2419,16 @@ static void batadv_tt_global_table_free(struct batadv_priv *bat_priv) bat_priv->tt.global_hash = NULL; } +/** + * _batadv_is_ap_isolated() - check whether two clients are AP-isolated from + * each other + * @tt_local_entry: local TT entry of the sending client + * @tt_global_entry: global TT entry of the destination client + * + * Return: true if traffic between the two clients should be dropped because + * either both are WiFi clients or both carry the ISOLATION flag; false + * otherwise + */ static bool _batadv_is_ap_isolated(struct batadv_tt_local_entry *tt_local_entry, struct batadv_tt_global_entry *tt_global_entry) @@ -2590,6 +2681,10 @@ static void batadv_tt_req_node_put(struct batadv_tt_req_node *tt_req_node) kref_put(&tt_req_node->refcount, batadv_tt_req_node_release); } +/** + * batadv_tt_req_list_free() - drop all pending TT requests + * @bat_priv: the bat priv with all the mesh interface information + */ static void batadv_tt_req_list_free(struct batadv_priv *bat_priv) { struct batadv_tt_req_node *node; @@ -2605,6 +2700,18 @@ static void batadv_tt_req_list_free(struct batadv_priv *bat_priv) spin_unlock_bh(&bat_priv->tt.req_list_lock); } +/** + * batadv_tt_save_orig_buffer() - cache the latest TT TVLV payload of an + * originator + * @bat_priv: the bat priv with all the mesh interface information + * @orig_node: originator for which the buffer should be created + * @tt_buff: pointer to the TT TVLV payload to cache + * @tt_buff_len: length of @tt_buff in bytes + * + * Replace the previously cached TT payload of @orig_node with a copy of + * @tt_buff. The buffer is left untouched when @tt_buff_len is 0 so that + * empty OGM updates do not discard the previously cached data. + */ static void batadv_tt_save_orig_buffer(struct batadv_priv *bat_priv, struct batadv_orig_node *orig_node, const void *tt_buff, @@ -2626,6 +2733,10 @@ static void batadv_tt_save_orig_buffer(struct batadv_priv *bat_priv, spin_unlock_bh(&orig_node->tt_buff_lock); } +/** + * batadv_tt_req_purge() - drop timed-out TT requests + * @bat_priv: the bat priv with all the mesh interface information + */ static void batadv_tt_req_purge(struct batadv_priv *bat_priv) { struct batadv_tt_req_node *node; @@ -3253,6 +3364,18 @@ static bool batadv_send_tt_response(struct batadv_priv *bat_priv, req_dst); } +/** + * _batadv_tt_update_changes() - apply a list of TT changes to the global TT + * @bat_priv: the bat priv with all the mesh interface information + * @orig_node: originator announcing the changes + * @tt_change: array of TT change entries to apply + * @tt_num_changes: number of entries in @tt_change + * @ttvn: TTVN of @orig_node corresponding to @tt_change + * + * Walk @tt_change and add/remove the announced clients in the global TT. + * Abort early without marking the @orig_node TT as initialized if adding + * an entry fails, so that the next TT request can re-sync the full table. + */ static void _batadv_tt_update_changes(struct batadv_priv *bat_priv, struct batadv_orig_node *orig_node, struct batadv_tvlv_tt_change *tt_change, @@ -3286,6 +3409,18 @@ static void _batadv_tt_update_changes(struct batadv_priv *bat_priv, set_bit(BATADV_ORIG_CAPA_HAS_TT, &orig_node->capa_initialized); } +/** + * batadv_tt_fill_gtable() - replace the cached TT of an originator with a + * full table response + * @bat_priv: the bat priv with all the mesh interface information + * @tt_change: array of TT change entries describing the full table + * @ttvn: TTVN announced together with the full table + * @resp_src: MAC address of the responder + * @num_entries: number of entries in @tt_change + * + * Drop the previously known global TT entries of @resp_src and replace them + * with the entries from a freshly received full TT response. + */ static void batadv_tt_fill_gtable(struct batadv_priv *bat_priv, struct batadv_tvlv_tt_change *tt_change, u8 ttvn, u8 *resp_src, @@ -3316,6 +3451,15 @@ out: batadv_orig_node_put(orig_node); } +/** + * batadv_tt_update_changes() - apply an incremental TT changeset to the + * global TT + * @bat_priv: the bat priv with all the mesh interface information + * @orig_node: originator announcing the changes + * @tt_num_changes: number of entries in @tt_change + * @ttvn: TTVN of @orig_node corresponding to @tt_change + * @tt_change: array of TT change entries to apply + */ static void batadv_tt_update_changes(struct batadv_priv *bat_priv, struct batadv_orig_node *orig_node, u16 tt_num_changes, u8 ttvn, @@ -3416,6 +3560,10 @@ out: batadv_orig_node_put(orig_node); } +/** + * batadv_tt_roam_list_free() - drop all entries from the roaming clients list + * @bat_priv: the bat priv with all the mesh interface information + */ static void batadv_tt_roam_list_free(struct batadv_priv *bat_priv) { struct batadv_tt_roam_node *node, *safe; @@ -3430,6 +3578,10 @@ static void batadv_tt_roam_list_free(struct batadv_priv *bat_priv) spin_unlock_bh(&bat_priv->tt.roam_list_lock); } +/** + * batadv_tt_roam_purge() - drop timed-out roaming clients + * @bat_priv: the bat priv with all the mesh interface information + */ static void batadv_tt_roam_purge(struct batadv_priv *bat_priv) { struct batadv_tt_roam_node *node, *safe; @@ -3552,6 +3704,14 @@ out: batadv_hardif_put(primary_if); } +/** + * batadv_tt_purge() - periodic worker to maintain the translation table + * @work: delayed work embedded in the per-mesh-interface TT state + * + * Purge timed-out entries from the local and global TT, drop stale TT + * requests and roaming clients, and reschedule the next run after + * BATADV_TT_WORK_PERIOD milliseconds. + */ static void batadv_tt_purge(struct work_struct *work) { struct delayed_work *delayed_work; @@ -3638,7 +3798,14 @@ static void batadv_tt_local_set_flags(struct batadv_priv *bat_priv, u16 flags, } } -/* Purge out all the tt local entries marked with BATADV_TT_CLIENT_PENDING */ +/** + * batadv_tt_local_purge_pending_clients() - finalise removal of pending local + * clients + * @bat_priv: the bat priv with all the mesh interface information + * + * Iterate over the local TT and physically remove every entry that has been + * marked as BATADV_TT_CLIENT_PENDING. + */ static void batadv_tt_local_purge_pending_clients(struct batadv_priv *bat_priv) { struct batadv_hashtable *hash = bat_priv->tt.local_hash; diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h index cd12755d21f3..42b631573512 100644 --- a/net/batman-adv/types.h +++ b/net/batman-adv/types.h @@ -1827,10 +1827,10 @@ struct batadv_bla_claim { /** @hash_entry: hlist node for &batadv_priv_bla.claim_hash */ struct hlist_node hash_entry; - /** @refcount: number of contexts the object is used */ + /** @rcu: struct used for freeing in an RCU-safe manner */ struct rcu_head rcu; - /** @rcu: struct used for freeing in an RCU-safe manner */ + /** @refcount: number of contexts the object is used */ struct kref refcount; }; #endif -- cgit v1.2.3 From eac2cd4ed64e471b15d0b75ab7c01b8082ea5cf1 Mon Sep 17 00:00:00 2001 From: Helge Deller Date: Fri, 31 Jul 2026 14:56:51 +0200 Subject: UAPI: Drop PER_HPUX personality Two and a half decades ago, there were plans to support HP-UX syscalls in Linux. This was never fully implemented, never worked and was never actually used by anybody. Let's simply drop this define now and free it up for other usages. Reviewed-by: Christoph Hellwig Signed-off-by: Helge Deller --- include/uapi/linux/personality.h | 1 - 1 file changed, 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/personality.h b/include/uapi/linux/personality.h index 49796b7756af..243f5f80a7b8 100644 --- a/include/uapi/linux/personality.h +++ b/include/uapi/linux/personality.h @@ -62,7 +62,6 @@ enum { PER_SOLARIS = 0x000d | STICKY_TIMEOUTS, PER_UW7 = 0x000e | STICKY_TIMEOUTS | MMAP_PAGE_ZERO, PER_OSF4 = 0x000f, /* OSF/1 v4 */ - PER_HPUX = 0x0010, PER_MASK = 0x00ff, }; -- cgit v1.2.3 From 22bba9dfd2d47d9f702f641537b0ef5e0ab1a957 Mon Sep 17 00:00:00 2001 From: Wadim Mueller Date: Tue, 28 Jul 2026 23:49:40 +0200 Subject: iio: types: add IIO_VOLUMEFLOW channel type Add a new IIO channel type for liquid volumetric flow sensors. The unit exposed via the standard _scale attribute is cubic metres per second (m^3/s), following the SI convention used by the other IIO channel types. Update iio-core's name table, the iio_event_monitor whitelist and the sysfs-bus-iio ABI document to match. The new _scale attribute is folded into the existing shared _scale block; only the per-type _raw needs a fresh entry. Signed-off-by: Wadim Mueller Reviewed-by: Joshua Crofts Signed-off-by: Jonathan Cameron --- Documentation/ABI/testing/sysfs-bus-iio | 11 +++++++++++ drivers/iio/industrialio-core.c | 1 + include/uapi/linux/iio/types.h | 1 + tools/iio/iio_event_monitor.c | 2 ++ 4 files changed, 15 insertions(+) (limited to 'include/uapi/linux') diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio index 7446e07b9ab2..a4f5595722ad 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio +++ b/Documentation/ABI/testing/sysfs-bus-iio @@ -508,6 +508,8 @@ What: /sys/bus/iio/devices/iio:deviceX/in_intensity_red_scale What: /sys/bus/iio/devices/iio:deviceX/in_intensity_green_scale What: /sys/bus/iio/devices/iio:deviceX/in_intensity_blue_scale What: /sys/bus/iio/devices/iio:deviceX/in_concentration_co2_scale +What: /sys/bus/iio/devices/iio:deviceX/in_volumeflow_scale +What: /sys/bus/iio/devices/iio:deviceX/in_volumeflowY_scale KernelVersion: 2.6.35 Contact: linux-iio@vger.kernel.org Description: @@ -2498,3 +2500,12 @@ Description: seconds, expressed as: - a range specified as "[min step max]" + +What: /sys/bus/iio/devices/iio:deviceX/in_volumeflow_raw +What: /sys/bus/iio/devices/iio:deviceX/in_volumeflowY_raw +KernelVersion: 7.3 +Contact: linux-iio@vger.kernel.org +Description: + Raw (unscaled) volumetric flow rate reading from the channel. + To convert to standard units (cubic metres per second, m^3/s) + apply the channel's _scale (and _offset, when present). diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index bdf3d4c06331..37cf2817b3d0 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -99,6 +99,7 @@ static const char * const iio_chan_type_name_spec[] = { [IIO_ATTENTION] = "attention", [IIO_ALTCURRENT] = "altcurrent", [IIO_COVERAGE] = "coverage", + [IIO_VOLUMEFLOW] = "volumeflow", }; static const char * const iio_modifier_names[] = { diff --git a/include/uapi/linux/iio/types.h b/include/uapi/linux/iio/types.h index c9295c707041..d0fb714ca0fd 100644 --- a/include/uapi/linux/iio/types.h +++ b/include/uapi/linux/iio/types.h @@ -54,6 +54,7 @@ enum iio_chan_type { IIO_ATTENTION, IIO_ALTCURRENT, IIO_COVERAGE, + IIO_VOLUMEFLOW, }; enum iio_modifier { diff --git a/tools/iio/iio_event_monitor.c b/tools/iio/iio_event_monitor.c index bc3ef4c77c2b..b5c5479b9d78 100644 --- a/tools/iio/iio_event_monitor.c +++ b/tools/iio/iio_event_monitor.c @@ -66,6 +66,7 @@ static const char * const iio_chan_type_name_spec[] = { [IIO_ATTENTION] = "attention", [IIO_ALTCURRENT] = "altcurrent", [IIO_COVERAGE] = "coverage", + [IIO_VOLUMEFLOW] = "volumeflow", }; static const char * const iio_ev_type_text[] = { @@ -196,6 +197,7 @@ static bool event_is_known(struct iio_event_data *event) case IIO_ATTENTION: case IIO_ALTCURRENT: case IIO_COVERAGE: + case IIO_VOLUMEFLOW: break; default: return false; -- cgit v1.2.3 From 6eab8f2cc6465ab093b5171ece815ec24e678d4e Mon Sep 17 00:00:00 2001 From: "Kiryl Shutsemau (Meta)" Date: Wed, 8 Jul 2026 12:14:10 +0100 Subject: userfaultfd: add UFFDIO_REGISTER_MODE_RWP and UFFDIO_RWPROTECT plumbing Add the userspace interface for read-write protection tracking: - UFFDIO_REGISTER_MODE_RWP register a range for RWP tracking - UFFD_FEATURE_RWP capability bit - UFFDIO_RWPROTECT install / remove RWP on a range Introduce CONFIG_USERFAULTFD_RWP, auto-selected on 64-bit kernels with ARCH_HAS_PTE_PROTNONE and HAVE_ARCH_USERFAULTFD_WP. The symbol gates VM_UFFD_RWP (previously aliased to VM_NONE) and the smaps/trace-flag hooks added in the preparatory patches; without it the UAPI bits added here have nothing to drive and would be unreachable. Registration sets VM_UFFD_RWP on the VMA. Combining MODE_WP with MODE_RWP is rejected because both modes claim the uffd PTE bit. UFFDIO_RWPROTECT is the bidirectional counterpart of UFFDIO_WRITEPROTECT: - MODE_RWP change_protection() with MM_CP_UFFD_RWP installs PAGE_NONE and sets the uffd bit on present PTEs - !MODE_RWP change_protection() with MM_CP_UFFD_RWP_RESOLVE restores vma->vm_page_prot and clears the bit userfaultfd_clear_vma() runs the same resolve pass on unregister so RWP state cannot outlive the uffd. Re-registering a range must not drop a mode that installs per-PTE markers (WP or RWP); doing so returns -EBUSY. This also closes a pre-existing window where re-registering without MODE_WP would strand uffd-wp markers: before, those caused extra write-faults but were otherwise benign; with RWP preservation in place, a subsequent mprotect() on a VM_UFFD_RWP VMA would silently promote the stale markers to RWP. The feature is not yet advertised. UFFDIO_REGISTER_MODE_RWP, UFFD_FEATURE_RWP, and _UFFDIO_RWPROTECT are intentionally absent from UFFD_API_REGISTER_MODES, UFFD_API_FEATURES, and UFFD_API_RANGE_IOCTLS, so UFFDIO_API masks them out and the register-mode validator rejects the bit. The follow-up patch adds fault dispatch and exposes the UAPI. Link: https://lore.kernel.org/20260708111417.173443-10-kirill@shutemov.name Signed-off-by: Kiryl Shutsemau Assisted-by: Claude:claude-opus-4-6 Reviewed-by: Mike Rapoport (Microsoft) Cc: Andrea Arcangeli Cc: David Hildenbrand Cc: James Houghton Cc: Jonathan Corbet Cc: Liam Howlett Cc: Lorenzo Stoakes Cc: Paolo Bonzini Cc: Peter Xu Cc: Sean Christopherson Cc: SeongJae Park Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Zi Yan Signed-off-by: Andrew Morton --- Documentation/admin-guide/mm/userfaultfd.rst | 10 ++ include/linux/userfaultfd_k.h | 2 + include/uapi/linux/userfaultfd.h | 19 +++ mm/Kconfig | 9 ++ mm/userfaultfd.c | 191 ++++++++++++++++++++++++++- 5 files changed, 228 insertions(+), 3 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/admin-guide/mm/userfaultfd.rst b/Documentation/admin-guide/mm/userfaultfd.rst index e5cc8848dcb3..1e533639fd50 100644 --- a/Documentation/admin-guide/mm/userfaultfd.rst +++ b/Documentation/admin-guide/mm/userfaultfd.rst @@ -131,6 +131,16 @@ userfaults on the range registered. Not all ioctls will necessarily be supported for all memory types (e.g. anonymous memory vs. shmem vs. hugetlbfs), or all types of intercepted faults. +.. note:: + + Re-registering an already-registered range must not drop any of the + modes that install per-PTE markers — currently + ``UFFDIO_REGISTER_MODE_WP`` and ``UFFDIO_REGISTER_MODE_RWP``. Doing + so would strand markers with no flag to describe them, so the call + is rejected with ``-EBUSY``; userspace must issue + ``UFFDIO_UNREGISTER`` first. This differs from older kernels, which + silently replaced the mode bits on re-registration. + Userland can use the ``uffdio_register.ioctls`` to manage the virtual address space in the background (to add or potentially also remove memory from the ``userfaultfd`` registered range). This means a userfault diff --git a/include/linux/userfaultfd_k.h b/include/linux/userfaultfd_k.h index 0a0763c6e9f4..d31648ba898d 100644 --- a/include/linux/userfaultfd_k.h +++ b/include/linux/userfaultfd_k.h @@ -150,6 +150,8 @@ static inline uffd_flags_t uffd_flags_set_mode(uffd_flags_t flags, enum mfill_at extern long uffd_wp_range(struct vm_area_struct *vma, unsigned long start, unsigned long len, bool enable_wp); +extern int mrwprotect_range(struct userfaultfd_ctx *ctx, unsigned long start, + unsigned long len, bool enable_rwp); /* move_pages */ void double_pt_lock(spinlock_t *ptl1, spinlock_t *ptl2); diff --git a/include/uapi/linux/userfaultfd.h b/include/uapi/linux/userfaultfd.h index 2841e4ea8f2c..7b78aa3b5318 100644 --- a/include/uapi/linux/userfaultfd.h +++ b/include/uapi/linux/userfaultfd.h @@ -79,6 +79,7 @@ #define _UFFDIO_WRITEPROTECT (0x06) #define _UFFDIO_CONTINUE (0x07) #define _UFFDIO_POISON (0x08) +#define _UFFDIO_RWPROTECT (0x09) #define _UFFDIO_API (0x3F) /* userfaultfd ioctl ids */ @@ -103,6 +104,8 @@ struct uffdio_continue) #define UFFDIO_POISON _IOWR(UFFDIO, _UFFDIO_POISON, \ struct uffdio_poison) +#define UFFDIO_RWPROTECT _IOWR(UFFDIO, _UFFDIO_RWPROTECT, \ + struct uffdio_rwprotect) /* read() structure */ struct uffd_msg { @@ -158,6 +161,7 @@ struct uffd_msg { #define UFFD_PAGEFAULT_FLAG_WRITE (1<<0) /* If this was a write fault */ #define UFFD_PAGEFAULT_FLAG_WP (1<<1) /* If reason is VM_UFFD_WP */ #define UFFD_PAGEFAULT_FLAG_MINOR (1<<2) /* If reason is VM_UFFD_MINOR */ +#define UFFD_PAGEFAULT_FLAG_RWP (1<<3) /* If reason is VM_UFFD_RWP */ struct uffdio_api { /* userland asks for an API number and the features to enable */ @@ -230,6 +234,11 @@ struct uffdio_api { * * UFFD_FEATURE_MOVE indicates that the kernel supports moving an * existing page contents from userspace. + * + * UFFD_FEATURE_RWP indicates that the kernel supports + * UFFDIO_REGISTER_MODE_RWP for read-write protection tracking. + * Pages are made inaccessible via UFFDIO_RWPROTECT and faults + * are delivered when the pages are re-accessed. */ #define UFFD_FEATURE_PAGEFAULT_FLAG_WP (1<<0) #define UFFD_FEATURE_EVENT_FORK (1<<1) @@ -248,6 +257,7 @@ struct uffdio_api { #define UFFD_FEATURE_POISON (1<<14) #define UFFD_FEATURE_WP_ASYNC (1<<15) #define UFFD_FEATURE_MOVE (1<<16) +#define UFFD_FEATURE_RWP (1<<17) __u64 features; __u64 ioctls; @@ -263,6 +273,7 @@ struct uffdio_register { #define UFFDIO_REGISTER_MODE_MISSING ((__u64)1<<0) #define UFFDIO_REGISTER_MODE_WP ((__u64)1<<1) #define UFFDIO_REGISTER_MODE_MINOR ((__u64)1<<2) +#define UFFDIO_REGISTER_MODE_RWP ((__u64)1<<3) __u64 mode; /* @@ -356,6 +367,14 @@ struct uffdio_poison { __s64 updated; }; +struct uffdio_rwprotect { + struct uffdio_range range; + /* !RWP means undo RWP-protection */ +#define UFFDIO_RWPROTECT_MODE_RWP ((__u64)1<<0) +#define UFFDIO_RWPROTECT_MODE_DONTWAKE ((__u64)1<<1) + __u64 mode; +}; + struct uffdio_move { __u64 dst; __u64 src; diff --git a/mm/Kconfig b/mm/Kconfig index a360861c74fa..4ea070f5664a 100644 --- a/mm/Kconfig +++ b/mm/Kconfig @@ -1386,6 +1386,15 @@ config HAVE_ARCH_USERFAULTFD_MINOR help Arch has userfaultfd minor fault support +config USERFAULTFD_RWP + def_bool y + depends on 64BIT && ARCH_HAS_PTE_PROTNONE && HAVE_ARCH_USERFAULTFD_WP + help + Userfaultfd read-write protection (UFFDIO_RWPROTECT) delivers a + userfaultfd notification on every access -- read or write -- to a + protected range, letting userspace observe the working set of a + process. + menuconfig USERFAULTFD bool "Enable userfaultfd() system call" depends on MMU diff --git a/mm/userfaultfd.c b/mm/userfaultfd.c index c3c2fef8297c..83c571ecc377 100644 --- a/mm/userfaultfd.c +++ b/mm/userfaultfd.c @@ -1164,6 +1164,75 @@ out_unlock: return err; } +int mrwprotect_range(struct userfaultfd_ctx *ctx, unsigned long start, + unsigned long len, bool enable_rwp) +{ + struct mm_struct *dst_mm = ctx->mm; + unsigned long end = start + len; + struct vm_area_struct *dst_vma; + unsigned int mm_cp_flags; + struct mmu_gather tlb; + bool found = false; + VMA_ITERATOR(vmi, dst_mm, start); + + VM_WARN_ON_ONCE(start & ~PAGE_MASK); + VM_WARN_ON_ONCE(len & ~PAGE_MASK); + VM_WARN_ON_ONCE(start + len <= start); + + guard(mmap_read_lock)(dst_mm); + guard(rwsem_read)(&ctx->map_changing_lock); + + if (atomic_read(&ctx->mmap_changing)) + return -EAGAIN; + + if (enable_rwp) + mm_cp_flags = MM_CP_UFFD_RWP; + else + mm_cp_flags = MM_CP_UFFD_RWP_RESOLVE; + + /* + * Pre-scan the range: validate every spanned VMA before applying + * any change_protection() so a partial failure cannot leave the + * process with only a prefix of the range re-protected. + */ + for_each_vma_range(vmi, dst_vma, end) { + if (!userfaultfd_rwp(dst_vma)) + return -ENOENT; + + if (is_vm_hugetlb_page(dst_vma)) { + unsigned long page_mask; + + page_mask = vma_kernel_pagesize(dst_vma) - 1; + if ((start & page_mask) || (len & page_mask)) + return -EINVAL; + } + found = true; + } + if (!found) + return -ENOENT; + + vma_iter_set(&vmi, start); + tlb_gather_mmu(&tlb, dst_mm); + for_each_vma_range(vmi, dst_vma, end) { + unsigned long vma_start = max(dst_vma->vm_start, start); + unsigned long vma_end = min(dst_vma->vm_end, end); + unsigned int flags = mm_cp_flags; + + /* + * On resolve, try to upgrade writability per-VMA -- + * MM_CP_TRY_CHANGE_WRITABLE WARNs in + * maybe_change_pte_writable() if the VMA is not VM_WRITE, + * and RWP can be registered on PROT_READ-only mappings. + */ + if (!enable_rwp && vma_wants_manual_pte_write_upgrade(dst_vma)) + flags |= MM_CP_TRY_CHANGE_WRITABLE; + + change_protection(&tlb, dst_vma, vma_start, vma_end, flags); + } + tlb_finish_mmu(&tlb); + + return 0; +} void double_pt_lock(spinlock_t *ptl1, spinlock_t *ptl2) @@ -2207,9 +2276,22 @@ static struct vm_area_struct *userfaultfd_clear_vma(struct vma_iterator *vmi, if (start == vma->vm_start && end == vma->vm_end) give_up_on_oom = true; - /* Reset ptes for the whole vma range if wr-protected */ - if (userfaultfd_wp(vma)) - uffd_wp_range(vma, start, end - start, false); + /* Clear the uffd bit and/or restore protnone PTEs */ + if (userfaultfd_protected(vma)) { + unsigned int mm_cp_flags = 0; + struct mmu_gather tlb; + + if (userfaultfd_wp(vma)) + mm_cp_flags |= MM_CP_UFFD_WP_RESOLVE; + if (userfaultfd_rwp(vma)) + mm_cp_flags |= MM_CP_UFFD_RWP_RESOLVE; + if (vma_wants_manual_pte_write_upgrade(vma)) + mm_cp_flags |= MM_CP_TRY_CHANGE_WRITABLE; + + tlb_gather_mmu(&tlb, vma->vm_mm); + change_protection(&tlb, vma, start, end, mm_cp_flags); + tlb_finish_mmu(&tlb); + } ret = vma_modify_flags_uffd(vmi, prev, vma, start, end, &new_vma_flags, NULL_VM_UFFD_CTX, @@ -2258,6 +2340,14 @@ static int userfaultfd_register_range(struct userfaultfd_ctx *ctx, vma_test_all_mask(vma, vma_flags)) goto skip; + /* + * Pre-scan in userfaultfd_register() already rejected mode + * switches that would drop VM_UFFD_WP or VM_UFFD_RWP, so a + * stray bit here is a bug. + */ + VM_WARN_ON_ONCE(vma->vm_userfaultfd_ctx.ctx == ctx && + vma->vm_flags & (VM_UFFD_WP | VM_UFFD_RWP) & ~vm_flags); + if (vma->vm_start > start) start = vma->vm_start; vma_end = min(end, vma->vm_end); @@ -2524,6 +2614,8 @@ static inline struct uffd_msg userfault_msg(unsigned long address, msg.arg.pagefault.flags |= UFFD_PAGEFAULT_FLAG_WRITE; if (reason & VM_UFFD_WP) msg.arg.pagefault.flags |= UFFD_PAGEFAULT_FLAG_WP; + if (reason & VM_UFFD_RWP) + msg.arg.pagefault.flags |= UFFD_PAGEFAULT_FLAG_RWP; if (reason & VM_UFFD_MINOR) msg.arg.pagefault.flags |= UFFD_PAGEFAULT_FLAG_MINOR; if (features & UFFD_FEATURE_THREAD_ID) @@ -3623,6 +3715,22 @@ static int userfaultfd_register(struct userfaultfd_ctx *ctx, vm_flags |= VM_UFFD_WP; } + if (uffdio_register.mode & UFFDIO_REGISTER_MODE_RWP) { + if (!pgtable_supports_uffd() || VM_UFFD_RWP == VM_NONE) + goto out; + if (!(ctx->features & UFFD_FEATURE_RWP)) + goto out; + vm_flags |= VM_UFFD_RWP; + } + + /* + * WP and RWP share the uffd PTE bit and + * cannot coexist in the same VMA — the bit would carry ambiguous + * semantics. Reject the combination up front. + */ + if ((vm_flags & VM_UFFD_WP) && (vm_flags & VM_UFFD_RWP)) + goto out; + if (uffdio_register.mode & UFFDIO_REGISTER_MODE_MINOR) { #ifndef CONFIG_HAVE_ARCH_USERFAULTFD_MINOR goto out; @@ -3677,6 +3785,17 @@ static int userfaultfd_register(struct userfaultfd_ctx *ctx, if (!vma_can_userfault(cur, vm_flags, wp_async)) goto out_unlock; + /* + * RWP uses protnone as an access-tracking marker. PROT_NONE + * VMAs have vm_page_prot == PAGE_NONE, so RWP resolution + * cannot make a page accessible again. Reject at register + * time only: a VMA that later becomes inaccessible via + * mprotect() must still be unregisterable, so this is not + * part of vma_can_userfault(). + */ + if ((vm_flags & VM_UFFD_RWP) && !vma_is_accessible(cur)) + goto out_unlock; + /* * UFFDIO_COPY will fill file holes even without * PROT_WRITE. This check enforces that if this is a @@ -3716,6 +3835,16 @@ static int userfaultfd_register(struct userfaultfd_ctx *ctx, cur->vm_userfaultfd_ctx.ctx != ctx) goto out_unlock; + /* + * Mode switches that drop VM_UFFD_WP or VM_UFFD_RWP would + * leave PTE markers without the flag that describes them; + * subsequent mprotect() would then promote stale markers + * into the other mode. Require an unregister first. + */ + if (cur->vm_userfaultfd_ctx.ctx == ctx && + cur->vm_flags & (VM_UFFD_WP | VM_UFFD_RWP) & ~vm_flags) + goto out_unlock; + /* * Note vmas containing huge pages */ @@ -3749,6 +3878,10 @@ out_unlock: if (!(uffdio_register.mode & UFFDIO_REGISTER_MODE_MINOR)) ioctls_out &= ~((__u64)1 << _UFFDIO_CONTINUE); + /* RWPROTECT is only supported for RWP ranges */ + if (!(uffdio_register.mode & UFFDIO_REGISTER_MODE_RWP)) + ioctls_out &= ~((__u64)1 << _UFFDIO_RWPROTECT); + /* * Now that we scanned all vmas we can already tell * userland which ioctls methods are guaranteed to @@ -4096,6 +4229,55 @@ static int userfaultfd_writeprotect(struct userfaultfd_ctx *ctx, return ret; } +static int userfaultfd_rwprotect(struct userfaultfd_ctx *ctx, + unsigned long arg) +{ + int ret; + struct uffdio_rwprotect uffdio_rwp; + struct userfaultfd_wake_range range; + bool mode_rwp, mode_dontwake; + + if (atomic_read(&ctx->mmap_changing)) + return -EAGAIN; + + if (copy_from_user(&uffdio_rwp, (void __user *)arg, + sizeof(uffdio_rwp))) + return -EFAULT; + + ret = validate_range(ctx->mm, uffdio_rwp.range.start, + uffdio_rwp.range.len); + if (ret) + return ret; + + if (uffdio_rwp.mode & ~(UFFDIO_RWPROTECT_MODE_DONTWAKE | + UFFDIO_RWPROTECT_MODE_RWP)) + return -EINVAL; + + mode_rwp = uffdio_rwp.mode & UFFDIO_RWPROTECT_MODE_RWP; + mode_dontwake = uffdio_rwp.mode & UFFDIO_RWPROTECT_MODE_DONTWAKE; + + if (mode_rwp && mode_dontwake) + return -EINVAL; + + if (mmget_not_zero(ctx->mm)) { + ret = mrwprotect_range(ctx, uffdio_rwp.range.start, + uffdio_rwp.range.len, mode_rwp); + mmput(ctx->mm); + } else { + return -ESRCH; + } + + if (ret) + return ret; + + if (!mode_rwp && !mode_dontwake) { + range.start = uffdio_rwp.range.start; + range.len = uffdio_rwp.range.len; + wake_userfault(ctx, &range); + } + return ret; +} + static int userfaultfd_continue(struct userfaultfd_ctx *ctx, unsigned long arg) { __s64 ret; @@ -4402,6 +4584,9 @@ static long userfaultfd_ioctl(struct file *file, unsigned cmd, case UFFDIO_POISON: ret = userfaultfd_poison(ctx, arg); break; + case UFFDIO_RWPROTECT: + ret = userfaultfd_rwprotect(ctx, arg); + break; } return ret; } -- cgit v1.2.3 From 2d427bb0163978875dfbbcec7b7dd0c1320f419e Mon Sep 17 00:00:00 2001 From: "Kiryl Shutsemau (Meta)" Date: Wed, 8 Jul 2026 12:14:11 +0100 Subject: mm/userfaultfd: add RWP fault delivery and expose UFFDIO_REGISTER_MODE_RWP Wire the fault side of read-write protection tracking and turn the userspace interface on. An RWP-protected PTE is PAGE_NONE with the uffd bit set. The PROT_NONE triggers a fault on any access; the uffd bit distinguishes it from plain mprotect(PROT_NONE) or NUMA hinting. Fault dispatch, per level: PTE handle_pte_fault() -> do_uffd_rwp() PMD __handle_mm_fault() -> do_huge_pmd_uffd_rwp() hugetlb hugetlb_fault() -> hugetlb_handle_userfault() The RWP branches gate on userfaultfd_pte_rwp() / userfaultfd_huge_pmd_rwp() (VM_UFFD_RWP plus the uffd bit) and fall through to do_numa_page() / do_huge_pmd_numa_page() otherwise. Each delivers a UFFD_PAGEFAULT_FLAG_RWP message through handle_userfault(); the handler resolves it with UFFDIO_RWPROTECT clearing MODE_RWP. userfaultfd_must_wait() and userfaultfd_huge_must_wait() add matching protnone+uffd waiters so sync-mode fault handlers block correctly. Expose the UAPI: UFFDIO_REGISTER_MODE_RWP -> UFFD_API_REGISTER_MODES UFFD_FEATURE_RWP -> UFFD_API_FEATURES _UFFDIO_RWPROTECT -> UFFD_API_RANGE_IOCTLS UFFD_API_RANGE_IOCTLS_BASIC UFFD_FEATURE_RWP is masked out at UFFDIO_API time when PROT_NONE is not available or VM_UFFD_RWP aliases VM_NONE (32-bit), so userspace never sees an advertised-but-broken feature. Works on anonymous, shmem, and hugetlb memory. Link: https://lore.kernel.org/20260708111417.173443-11-kirill@shutemov.name Signed-off-by: Kiryl Shutsemau Assisted-by: Claude:claude-opus-4-6 Reviewed-by: Mike Rapoport (Microsoft) Cc: Andrea Arcangeli Cc: David Hildenbrand Cc: James Houghton Cc: Jonathan Corbet Cc: Liam Howlett Cc: Lorenzo Stoakes Cc: Paolo Bonzini Cc: Peter Xu Cc: Sean Christopherson Cc: SeongJae Park Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Zi Yan Signed-off-by: Andrew Morton --- include/linux/huge_mm.h | 7 +++++++ include/linux/userfaultfd_k.h | 24 ++++++++++++++++++++++++ include/uapi/linux/userfaultfd.h | 12 ++++++++---- mm/huge_memory.c | 5 +++++ mm/hugetlb.c | 11 +++++++++++ mm/memory.c | 31 +++++++++++++++++++++++++++++-- mm/userfaultfd.c | 32 ++++++++++++++++++++++++++++++-- 7 files changed, 114 insertions(+), 8 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/linux/huge_mm.h b/include/linux/huge_mm.h index b34e8e5e5f9a..19a7bfce279e 100644 --- a/include/linux/huge_mm.h +++ b/include/linux/huge_mm.h @@ -529,6 +529,8 @@ static inline bool folio_test_pmd_mappable(struct folio *folio) vm_fault_t do_huge_pmd_numa_page(struct vm_fault *vmf); +vm_fault_t do_huge_pmd_uffd_rwp(struct vm_fault *vmf); + vm_fault_t do_huge_pmd_device_private(struct vm_fault *vmf); extern struct folio *huge_zero_folio; @@ -723,6 +725,11 @@ static inline spinlock_t *pud_trans_huge_lock(pud_t *pud, return NULL; } +static inline vm_fault_t do_huge_pmd_uffd_rwp(struct vm_fault *vmf) +{ + return 0; +} + static inline vm_fault_t do_huge_pmd_numa_page(struct vm_fault *vmf) { return 0; diff --git a/include/linux/userfaultfd_k.h b/include/linux/userfaultfd_k.h index d31648ba898d..6f7245609bd4 100644 --- a/include/linux/userfaultfd_k.h +++ b/include/linux/userfaultfd_k.h @@ -241,6 +241,18 @@ static inline bool userfaultfd_huge_pmd_wp(struct vm_area_struct *vma, return userfaultfd_wp(vma) && pmd_uffd(pmd); } +static inline bool userfaultfd_pte_rwp(struct vm_area_struct *vma, + pte_t pte) +{ + return userfaultfd_rwp(vma) && pte_uffd(pte); +} + +static inline bool userfaultfd_huge_pmd_rwp(struct vm_area_struct *vma, + pmd_t pmd) +{ + return userfaultfd_rwp(vma) && pmd_uffd(pmd); +} + static inline bool userfaultfd_armed(struct vm_area_struct *vma) { return vma_test_any_mask(vma, __VMA_UFFD_FLAGS); @@ -371,6 +383,18 @@ static inline bool userfaultfd_huge_pmd_wp(struct vm_area_struct *vma, return false; } +static inline bool userfaultfd_pte_rwp(struct vm_area_struct *vma, + pte_t pte) +{ + return false; +} + +static inline bool userfaultfd_huge_pmd_rwp(struct vm_area_struct *vma, + pmd_t pmd) +{ + return false; +} + static inline bool userfaultfd_armed(struct vm_area_struct *vma) { return false; diff --git a/include/uapi/linux/userfaultfd.h b/include/uapi/linux/userfaultfd.h index 7b78aa3b5318..d803e76d47ad 100644 --- a/include/uapi/linux/userfaultfd.h +++ b/include/uapi/linux/userfaultfd.h @@ -25,7 +25,8 @@ #define UFFD_API ((__u64)0xAA) #define UFFD_API_REGISTER_MODES (UFFDIO_REGISTER_MODE_MISSING | \ UFFDIO_REGISTER_MODE_WP | \ - UFFDIO_REGISTER_MODE_MINOR) + UFFDIO_REGISTER_MODE_MINOR | \ + UFFDIO_REGISTER_MODE_RWP) #define UFFD_API_FEATURES (UFFD_FEATURE_PAGEFAULT_FLAG_WP | \ UFFD_FEATURE_EVENT_FORK | \ UFFD_FEATURE_EVENT_REMAP | \ @@ -42,7 +43,8 @@ UFFD_FEATURE_WP_UNPOPULATED | \ UFFD_FEATURE_POISON | \ UFFD_FEATURE_WP_ASYNC | \ - UFFD_FEATURE_MOVE) + UFFD_FEATURE_MOVE | \ + UFFD_FEATURE_RWP) #define UFFD_API_IOCTLS \ ((__u64)1 << _UFFDIO_REGISTER | \ (__u64)1 << _UFFDIO_UNREGISTER | \ @@ -54,13 +56,15 @@ (__u64)1 << _UFFDIO_MOVE | \ (__u64)1 << _UFFDIO_WRITEPROTECT | \ (__u64)1 << _UFFDIO_CONTINUE | \ - (__u64)1 << _UFFDIO_POISON) + (__u64)1 << _UFFDIO_POISON | \ + (__u64)1 << _UFFDIO_RWPROTECT) #define UFFD_API_RANGE_IOCTLS_BASIC \ ((__u64)1 << _UFFDIO_WAKE | \ (__u64)1 << _UFFDIO_COPY | \ (__u64)1 << _UFFDIO_WRITEPROTECT | \ (__u64)1 << _UFFDIO_CONTINUE | \ - (__u64)1 << _UFFDIO_POISON) + (__u64)1 << _UFFDIO_POISON | \ + (__u64)1 << _UFFDIO_RWPROTECT) /* * Valid ioctl command number range with this API is from 0x00 to diff --git a/mm/huge_memory.c b/mm/huge_memory.c index 991f216f4f5b..dfbf6f3219ac 100644 --- a/mm/huge_memory.c +++ b/mm/huge_memory.c @@ -2202,6 +2202,11 @@ static inline bool can_change_pmd_writable(struct vm_area_struct *vma, return pmd_dirty(pmd); } +vm_fault_t do_huge_pmd_uffd_rwp(struct vm_fault *vmf) +{ + return handle_userfault(vmf, VM_UFFD_RWP); +} + /* NUMA hinting page fault entry point for trans huge pmds */ vm_fault_t do_huge_pmd_numa_page(struct vm_fault *vmf) { diff --git a/mm/hugetlb.c b/mm/hugetlb.c index bb6219002a5c..d6ebf18568fa 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -6095,6 +6095,17 @@ vm_fault_t hugetlb_fault(struct mm_struct *mm, struct vm_area_struct *vma, goto out_mutex; } + /* + * Protnone hugetlb PTEs with the uffd bit are used by + * userfaultfd RWP for access tracking. Plain PROT_NONE (without the + * marker) is not an RWP fault and is not expected on hugetlb (no + * NUMA hinting), so let normal hugetlb fault handling proceed. + */ + if (pte_protnone(vmf.orig_pte) && vma_is_accessible(vma) && + userfaultfd_rwp(vma) && huge_pte_uffd(vmf.orig_pte)) { + return hugetlb_handle_userfault(&vmf, mapping, VM_UFFD_RWP); + } + /* * If we are going to COW/unshare the mapping later, we examine the * pending reservations for this page now. This will ensure that any diff --git a/mm/memory.c b/mm/memory.c index 182d1516c0c1..6e01690fd0e4 100644 --- a/mm/memory.c +++ b/mm/memory.c @@ -6119,6 +6119,16 @@ static void numa_rebuild_large_mapping(struct vm_fault *vmf, struct vm_area_stru if (!pte_present(ptent) || !pte_protnone(ptent)) continue; + /* + * RWP-armed PTEs are also protnone but carry _PAGE_UFFD as a + * marker. Leave them alone -- rewriting to vm_page_prot would + * stop the RWP trap. Gate on userfaultfd_rwp(vma) too: + * NUMA balancing preserves _PAGE_UFFD on UFFD_WP-marked PTEs + * when applying PROT_NONE, and those still need rebuilding. + */ + if (userfaultfd_rwp(vma) && pte_uffd(ptent)) + continue; + if (pfn_folio(pte_pfn(ptent)) != folio) continue; @@ -6134,6 +6144,12 @@ static void numa_rebuild_large_mapping(struct vm_fault *vmf, struct vm_area_stru } } +static vm_fault_t do_uffd_rwp(struct vm_fault *vmf) +{ + pte_unmap(vmf->pte); + return handle_userfault(vmf, VM_UFFD_RWP); +} + static vm_fault_t do_numa_page(struct vm_fault *vmf) { struct vm_area_struct *vma = vmf->vma; @@ -6409,8 +6425,16 @@ static vm_fault_t handle_pte_fault(struct vm_fault *vmf) if (!pte_present(vmf->orig_pte)) return do_swap_page(vmf); - if (pte_protnone(vmf->orig_pte) && vma_is_accessible(vmf->vma)) + if (pte_protnone(vmf->orig_pte) && vma_is_accessible(vmf->vma)) { + /* + * RWP-protected PTEs are protnone plus the uffd bit. On a + * VM_UFFD_RWP VMA, a protnone PTE without the uffd bit is + * NUMA hinting and must still fall through to do_numa_page(). + */ + if (userfaultfd_pte_rwp(vmf->vma, vmf->orig_pte)) + return do_uffd_rwp(vmf); return do_numa_page(vmf); + } spin_lock(vmf->ptl); entry = vmf->orig_pte; @@ -6524,8 +6548,11 @@ retry_pud: return 0; } if (pmd_trans_huge(vmf.orig_pmd)) { - if (pmd_protnone(vmf.orig_pmd) && vma_is_accessible(vma)) + if (pmd_protnone(vmf.orig_pmd) && vma_is_accessible(vma)) { + if (userfaultfd_huge_pmd_rwp(vma, vmf.orig_pmd)) + return do_huge_pmd_uffd_rwp(&vmf); return do_huge_pmd_numa_page(&vmf); + } if ((flags & (FAULT_FLAG_WRITE|FAULT_FLAG_UNSHARE)) && !pmd_write(vmf.orig_pmd)) { diff --git a/mm/userfaultfd.c b/mm/userfaultfd.c index 83c571ecc377..f4a64971d9dc 100644 --- a/mm/userfaultfd.c +++ b/mm/userfaultfd.c @@ -2669,6 +2669,12 @@ static inline bool userfaultfd_huge_must_wait(struct userfaultfd_ctx *ctx, */ if (!huge_pte_write(pte) && (reason & VM_UFFD_WP)) return true; + /* + * PTE is still RW-protected (protnone with uffd bit), wait for + * resolution. Plain PROT_NONE without the marker is not an RWP fault. + */ + if (pte_protnone(pte) && huge_pte_uffd(pte) && (reason & VM_UFFD_RWP)) + return true; return false; } @@ -2729,8 +2735,14 @@ again: if (!pmd_present(_pmd)) return false; - if (pmd_trans_huge(_pmd)) - return !pmd_write(_pmd) && (reason & VM_UFFD_WP); + if (pmd_trans_huge(_pmd)) { + if (!pmd_write(_pmd) && (reason & VM_UFFD_WP)) + return true; + if (pmd_protnone(_pmd) && pmd_uffd(_pmd) && + (reason & VM_UFFD_RWP)) + return true; + return false; + } pte = pte_offset_map(pmd, address); if (!pte) @@ -2766,6 +2778,13 @@ again: */ if (!pte_write(ptent) && (reason & VM_UFFD_WP)) goto out; + /* + * PTE is still RW-protected (protnone with uffd bit), wait for + * userspace to resolve. Plain PROT_NONE without the marker is not + * an RWP fault. + */ + if (pte_protnone(ptent) && pte_uffd(ptent) && (reason & VM_UFFD_RWP)) + goto out; ret = false; out: @@ -4518,6 +4537,15 @@ static int userfaultfd_api(struct userfaultfd_ctx *ctx, uffdio_api.features &= ~UFFD_FEATURE_WP_UNPOPULATED; uffdio_api.features &= ~UFFD_FEATURE_WP_ASYNC; } + /* + * RWP needs both PROT_NONE support and the uffd-wp PTE bit. The + * VM_UFFD_RWP check covers compile-time unavailability; the + * pgtable_supports_uffd() check covers runtime (e.g. riscv + * without the SVRSW60T59B extension) where the PTE bit is declared + * but not actually usable. + */ + if (VM_UFFD_RWP == VM_NONE || !pgtable_supports_uffd()) + uffdio_api.features &= ~UFFD_FEATURE_RWP; ret = -EINVAL; if (features & ~uffdio_api.features) -- cgit v1.2.3 From 3389648819829299517a8d482fda213fd7166a2c Mon Sep 17 00:00:00 2001 From: "Kiryl Shutsemau (Meta)" Date: Wed, 8 Jul 2026 12:14:12 +0100 Subject: mm/pagemap: add PAGE_IS_ACCESSED for RWP tracking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PAGEMAP_SCAN already reports PAGE_IS_WRITTEN from the inverted uffd PTE bit, targeting the UFFDIO_WRITEPROTECT workflow. UFFDIO_RWPROTECT reuses the same PTE bit as a marker for read-write protection, but "has been written" and "has been accessed" are distinct semantic signals — they happen to share one PTE bit today only because the two implementations share infrastructure. Give RWP its own pagemap category so the UAPI does not conflate them: PAGE_IS_WRITTEN reported on VM_UFFD_WP VMAs, !pte_uffd(pte) PAGE_IS_ACCESSED reported on VM_UFFD_RWP VMAs, !pte_uffd(pte) Both still read the same PTE bit today, but each is scoped to the VMA whose registered mode makes the bit meaningful. If a future implementation moves RWP to a separate PTE bit, only PAGE_IS_ACCESSED switches over. This is a UAPI narrowing. Outside VM_UFFD_WP VMAs the uffd bit is always clear, so PAGEMAP_SCAN used to flag PAGE_IS_WRITTEN on every present PTE there — a meaningless duplicate of PAGE_IS_PRESENT. Now PAGE_IS_WRITTEN fires only inside VM_UFFD_WP VMAs. pagemap_hugetlb_category() now takes the vma like its PTE/PMD peers. Link: https://lore.kernel.org/20260708111417.173443-12-kirill@shutemov.name Signed-off-by: Kiryl Shutsemau Assisted-by: Claude:claude-opus-4-6 Acked-by: Mike Rapoport (Microsoft) Cc: Andrea Arcangeli Cc: David Hildenbrand Cc: James Houghton Cc: Jonathan Corbet Cc: Liam Howlett Cc: Lorenzo Stoakes Cc: Paolo Bonzini Cc: Peter Xu Cc: Sean Christopherson Cc: SeongJae Park Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Zi Yan Signed-off-by: Andrew Morton --- Documentation/admin-guide/mm/pagemap.rst | 13 +++-- fs/proc/task_mmu.c | 81 ++++++++++++++++++++++---------- include/uapi/linux/fs.h | 1 + tools/include/uapi/linux/fs.h | 1 + 4 files changed, 67 insertions(+), 29 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/admin-guide/mm/pagemap.rst b/Documentation/admin-guide/mm/pagemap.rst index c57e61b5d8aa..ffa690a171c8 100644 --- a/Documentation/admin-guide/mm/pagemap.rst +++ b/Documentation/admin-guide/mm/pagemap.rst @@ -19,8 +19,11 @@ There are four components to pagemap: * Bit 55 pte is soft-dirty (see Documentation/admin-guide/mm/soft-dirty.rst) * Bit 56 page exclusively mapped (since 4.2) - * Bit 57 pte is uffd-wp write-protected (since 5.13) (see - Documentation/admin-guide/mm/userfaultfd.rst) + * Bit 57 pte is tracked by userfaultfd (since 5.13) — in a + ``VM_UFFD_WP`` VMA this indicates a write-protected PTE; in a + ``VM_UFFD_RWP`` VMA it indicates an RWP-protected PTE. WP and + RWP are mutually exclusive per VMA, so the meaning is + unambiguous. See Documentation/admin-guide/mm/userfaultfd.rst. * Bit 58 pte is a guard region (since 6.15) (see madvise (2) man page) * Bits 59-60 zero * Bit 61 page is file-page or shared-anon (since 3.5) @@ -244,7 +247,8 @@ in this IOCTL: Following flags about pages are currently supported: - ``PAGE_IS_WPALLOWED`` - Page has async-write-protection enabled -- ``PAGE_IS_WRITTEN`` - Page has been written to from the time it was write protected +- ``PAGE_IS_WRITTEN`` - Page in a ``UFFDIO_REGISTER_MODE_WP`` VMA has been + written to since it was write-protected. Only reported inside such VMAs. - ``PAGE_IS_FILE`` - Page is file backed - ``PAGE_IS_PRESENT`` - Page is present in the memory - ``PAGE_IS_SWAPPED`` - Page is in swapped @@ -252,6 +256,9 @@ Following flags about pages are currently supported: - ``PAGE_IS_HUGE`` - Page is PMD-mapped THP or Hugetlb backed - ``PAGE_IS_SOFT_DIRTY`` - Page is soft-dirty - ``PAGE_IS_GUARD`` - Page is a part of a guard region +- ``PAGE_IS_ACCESSED`` - Page in a ``UFFDIO_REGISTER_MODE_RWP`` VMA has been + accessed since RWP was applied. Only reported inside such VMAs. See + Documentation/admin-guide/mm/userfaultfd.rst for the RWP workflow. The ``struct pm_scan_arg`` is used as the argument of the IOCTL. diff --git a/fs/proc/task_mmu.c b/fs/proc/task_mmu.c index 407d8dde2a90..817e3e0f9194 100644 --- a/fs/proc/task_mmu.c +++ b/fs/proc/task_mmu.c @@ -2284,7 +2284,7 @@ static const struct mm_walk_ops pagemap_ops = { * Bits 5-54 swap offset if swapped * Bit 55 pte is soft-dirty (see Documentation/admin-guide/mm/soft-dirty.rst) * Bit 56 page exclusively mapped - * Bit 57 pte is uffd-wp write-protected + * Bit 57 pte is tracked by userfaultfd (uffd-wp or RWP) * Bit 58 pte is a guard region * Bits 59-60 zero * Bit 61 page is file-page or shared-anon @@ -2419,7 +2419,7 @@ static int pagemap_release(struct inode *inode, struct file *file) PAGE_IS_FILE | PAGE_IS_PRESENT | \ PAGE_IS_SWAPPED | PAGE_IS_PFNZERO | \ PAGE_IS_HUGE | PAGE_IS_SOFT_DIRTY | \ - PAGE_IS_GUARD) + PAGE_IS_GUARD | PAGE_IS_ACCESSED) #define PM_SCAN_FLAGS (PM_SCAN_WP_MATCHING | PM_SCAN_CHECK_WPASYNC) struct pagemap_scan_private { @@ -2438,15 +2438,17 @@ static unsigned long pagemap_page_category(struct pagemap_scan_private *p, if (pte_none(pte)) { /* - * An unpopulated pte carries no uffd-wp marker, i.e. it is not - * write-protected, the same condition under which the present - * and swap cases below report PAGE_IS_WRITTEN. Report it here - * too so this generic path agrees with the PAGE_IS_WRITTEN fast - * path in pagemap_scan_pmd_entry(), which reports pte_none as - * written and, under PM_SCAN_WP_MATCHING, arms a marker. The - * fast path applies no VMA test, so neither does this. + * An unpopulated pte carries no uffd bit, i.e. it is not + * write-protected. The PAGE_IS_WRITTEN fast path in + * pagemap_scan_pmd_entry() is now gated on a VM_UFFD_WP VMA; + * gate the pte_none report here the same way so the two paths + * still agree. RWP has no such fast path and an unpopulated + * page is not part of the RWP working set, so it is reported as + * neither. */ - return PAGE_IS_WRITTEN; + if (userfaultfd_wp(vma)) + return PAGE_IS_WRITTEN; + return 0; } if (pte_present(pte)) { @@ -2454,8 +2456,12 @@ static unsigned long pagemap_page_category(struct pagemap_scan_private *p, categories = PAGE_IS_PRESENT; - if (!pte_uffd(pte)) - categories |= PAGE_IS_WRITTEN; + if (!pte_uffd(pte)) { + if (userfaultfd_wp(vma)) + categories |= PAGE_IS_WRITTEN; + if (userfaultfd_rwp(vma)) + categories |= PAGE_IS_ACCESSED; + } if (p->masks_of_interest & PAGE_IS_FILE) { page = vm_normal_page(vma, addr, pte); @@ -2472,8 +2478,12 @@ static unsigned long pagemap_page_category(struct pagemap_scan_private *p, categories = PAGE_IS_SWAPPED; - if (!pte_swp_uffd_any(pte)) - categories |= PAGE_IS_WRITTEN; + if (!pte_swp_uffd_any(pte)) { + if (userfaultfd_wp(vma)) + categories |= PAGE_IS_WRITTEN; + if (userfaultfd_rwp(vma)) + categories |= PAGE_IS_ACCESSED; + } entry = softleaf_from_pte(pte); if (softleaf_is_guard_marker(entry)) @@ -2522,8 +2532,12 @@ static unsigned long pagemap_thp_category(struct pagemap_scan_private *p, struct page *page; categories |= PAGE_IS_PRESENT; - if (!pmd_uffd(pmd)) - categories |= PAGE_IS_WRITTEN; + if (!pmd_uffd(pmd)) { + if (userfaultfd_wp(vma)) + categories |= PAGE_IS_WRITTEN; + if (userfaultfd_rwp(vma)) + categories |= PAGE_IS_ACCESSED; + } if (p->masks_of_interest & PAGE_IS_FILE) { page = vm_normal_page_pmd(vma, addr, pmd); @@ -2537,8 +2551,12 @@ static unsigned long pagemap_thp_category(struct pagemap_scan_private *p, categories |= PAGE_IS_SOFT_DIRTY; } else { categories |= PAGE_IS_SWAPPED; - if (!pmd_swp_uffd(pmd)) - categories |= PAGE_IS_WRITTEN; + if (!pmd_swp_uffd(pmd)) { + if (userfaultfd_wp(vma)) + categories |= PAGE_IS_WRITTEN; + if (userfaultfd_rwp(vma)) + categories |= PAGE_IS_ACCESSED; + } if (pmd_swp_soft_dirty(pmd)) categories |= PAGE_IS_SOFT_DIRTY; @@ -2571,7 +2589,8 @@ static void make_uffd_wp_pmd(struct vm_area_struct *vma, #endif /* CONFIG_TRANSPARENT_HUGEPAGE */ #ifdef CONFIG_HUGETLB_PAGE -static unsigned long pagemap_hugetlb_category(pte_t pte) +static unsigned long pagemap_hugetlb_category(struct vm_area_struct *vma, + pte_t pte) { unsigned long categories = PAGE_IS_HUGE; @@ -2586,8 +2605,12 @@ static unsigned long pagemap_hugetlb_category(pte_t pte) if (pte_present(pte)) { categories |= PAGE_IS_PRESENT; - if (!huge_pte_uffd(pte)) - categories |= PAGE_IS_WRITTEN; + if (!huge_pte_uffd(pte)) { + if (userfaultfd_wp(vma)) + categories |= PAGE_IS_WRITTEN; + if (userfaultfd_rwp(vma)) + categories |= PAGE_IS_ACCESSED; + } if (!PageAnon(pte_page(pte))) categories |= PAGE_IS_FILE; if (is_zero_pfn(pte_pfn(pte))) @@ -2597,8 +2620,12 @@ static unsigned long pagemap_hugetlb_category(pte_t pte) } else { categories |= PAGE_IS_SWAPPED; - if (!pte_swp_uffd_any(pte)) - categories |= PAGE_IS_WRITTEN; + if (!pte_swp_uffd_any(pte)) { + if (userfaultfd_wp(vma)) + categories |= PAGE_IS_WRITTEN; + if (userfaultfd_rwp(vma)) + categories |= PAGE_IS_ACCESSED; + } if (pte_swp_soft_dirty(pte)) categories |= PAGE_IS_SOFT_DIRTY; } @@ -2874,7 +2901,8 @@ static int pagemap_scan_pmd_entry(pmd_t *pmd, unsigned long start, goto flush_and_return; } - if (!p->arg.category_anyof_mask && !p->arg.category_inverted && + if (userfaultfd_wp(vma) && !p->arg.category_anyof_mask && + !p->arg.category_inverted && p->arg.category_mask == PAGE_IS_WRITTEN && p->arg.return_mask == PAGE_IS_WRITTEN) { for (addr = start; addr < end; pte++, addr += PAGE_SIZE) { @@ -2949,7 +2977,8 @@ static int pagemap_scan_hugetlb_entry(pte_t *ptep, unsigned long hmask, /* Go the short route when not write-protecting pages. */ pte = huge_ptep_get(walk->mm, start, ptep); - categories = p->cur_vma_category | pagemap_hugetlb_category(pte); + categories = p->cur_vma_category | + pagemap_hugetlb_category(vma, pte); if (!pagemap_scan_is_interesting_page(categories, p)) return 0; @@ -2961,7 +2990,7 @@ static int pagemap_scan_hugetlb_entry(pte_t *ptep, unsigned long hmask, ptl = huge_pte_lock(hstate_vma(vma), vma->vm_mm, ptep); pte = huge_ptep_get(walk->mm, start, ptep); - categories = p->cur_vma_category | pagemap_hugetlb_category(pte); + categories = p->cur_vma_category | pagemap_hugetlb_category(vma, pte); if (!pagemap_scan_is_interesting_page(categories, p)) goto out_unlock; diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h index bd87262f2e34..34c6f219462a 100644 --- a/include/uapi/linux/fs.h +++ b/include/uapi/linux/fs.h @@ -471,6 +471,7 @@ typedef int __bitwise __kernel_rwf_t; #define PAGE_IS_HUGE (1 << 6) #define PAGE_IS_SOFT_DIRTY (1 << 7) #define PAGE_IS_GUARD (1 << 8) +#define PAGE_IS_ACCESSED (1 << 9) /* * struct page_region - Page region with flags diff --git a/tools/include/uapi/linux/fs.h b/tools/include/uapi/linux/fs.h index 24ddf7bc4f25..f0a26309b6d5 100644 --- a/tools/include/uapi/linux/fs.h +++ b/tools/include/uapi/linux/fs.h @@ -364,6 +364,7 @@ typedef int __bitwise __kernel_rwf_t; #define PAGE_IS_HUGE (1 << 6) #define PAGE_IS_SOFT_DIRTY (1 << 7) #define PAGE_IS_GUARD (1 << 8) +#define PAGE_IS_ACCESSED (1 << 9) /* * struct page_region - Page region with flags -- cgit v1.2.3 From 45347e3d32c15bc70f097bef9ef1a7b90218d450 Mon Sep 17 00:00:00 2001 From: "Kiryl Shutsemau (Meta)" Date: Wed, 8 Jul 2026 12:14:13 +0100 Subject: userfaultfd: add UFFD_FEATURE_RWP_ASYNC for async fault resolution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sync RWP delivers a message and blocks the faulting thread until the handler resolves the fault. For working-set tracking the VMM does not need the message: it just needs to know, at scan time, which pages were touched. Async RWP serves that use case — the kernel restores access in-place and the faulting thread continues without blocking. The VMM reconstructs the access pattern after the fact via PAGEMAP_SCAN: pages whose uffd bit is still set (inverted PAGE_IS_ACCESSED) were not re-accessed since the last RWP cycle. Worth calling out: async resolution upgrades writable private anon PTEs via pte_mkwrite() when can_change_pte_writable() allows, mirroring do_numa_page(). Without it, every re-access of an RWP'd writable page would COW-fault a second time. UFFD_FEATURE_RWP_ASYNC requires UFFD_FEATURE_RWP. Link: https://lore.kernel.org/20260708111417.173443-13-kirill@shutemov.name Signed-off-by: Kiryl Shutsemau Assisted-by: Claude:claude-opus-4-6 Acked-by: Mike Rapoport (Microsoft) Cc: Andrea Arcangeli Cc: David Hildenbrand Cc: James Houghton Cc: Jonathan Corbet Cc: Liam Howlett Cc: Lorenzo Stoakes Cc: Paolo Bonzini Cc: Peter Xu Cc: Sean Christopherson Cc: SeongJae Park Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Zi Yan Signed-off-by: Andrew Morton --- include/linux/userfaultfd_k.h | 6 ++++++ include/uapi/linux/userfaultfd.h | 11 ++++++++++- mm/huge_memory.c | 25 ++++++++++++++++++++++++- mm/hugetlb.c | 32 +++++++++++++++++++++++++++++++- mm/memory.c | 27 +++++++++++++++++++++++++-- mm/userfaultfd.c | 19 ++++++++++++++++++- 6 files changed, 114 insertions(+), 6 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/linux/userfaultfd_k.h b/include/linux/userfaultfd_k.h index 6f7245609bd4..bfbd6a59909f 100644 --- a/include/linux/userfaultfd_k.h +++ b/include/linux/userfaultfd_k.h @@ -286,6 +286,7 @@ extern void userfaultfd_unmap_complete(struct mm_struct *mm, struct list_head *uf); extern bool userfaultfd_wp_unpopulated(struct vm_area_struct *vma); extern bool userfaultfd_wp_async(struct vm_area_struct *vma); +extern bool userfaultfd_rwp_async(struct vm_area_struct *vma); static inline bool userfaultfd_wp_use_markers(struct vm_area_struct *vma) { @@ -464,6 +465,11 @@ static inline bool userfaultfd_wp_async(struct vm_area_struct *vma) return false; } +static inline bool userfaultfd_rwp_async(struct vm_area_struct *vma) +{ + return false; +} + static inline bool vma_has_uffd_without_event_remap(struct vm_area_struct *vma) { return false; diff --git a/include/uapi/linux/userfaultfd.h b/include/uapi/linux/userfaultfd.h index d803e76d47ad..c10f08f8a618 100644 --- a/include/uapi/linux/userfaultfd.h +++ b/include/uapi/linux/userfaultfd.h @@ -44,7 +44,8 @@ UFFD_FEATURE_POISON | \ UFFD_FEATURE_WP_ASYNC | \ UFFD_FEATURE_MOVE | \ - UFFD_FEATURE_RWP) + UFFD_FEATURE_RWP | \ + UFFD_FEATURE_RWP_ASYNC) #define UFFD_API_IOCTLS \ ((__u64)1 << _UFFDIO_REGISTER | \ (__u64)1 << _UFFDIO_UNREGISTER | \ @@ -243,6 +244,13 @@ struct uffdio_api { * UFFDIO_REGISTER_MODE_RWP for read-write protection tracking. * Pages are made inaccessible via UFFDIO_RWPROTECT and faults * are delivered when the pages are re-accessed. + * + * UFFD_FEATURE_RWP_ASYNC indicates asynchronous mode for + * UFFDIO_REGISTER_MODE_RWP. When set, faults on read-write + * protected pages are auto-resolved by the kernel (PTE + * permissions restored immediately) without delivering a message + * to the userfaultfd handler. Use PAGEMAP_SCAN with inverted + * PAGE_IS_ACCESSED to find pages that were not re-accessed. */ #define UFFD_FEATURE_PAGEFAULT_FLAG_WP (1<<0) #define UFFD_FEATURE_EVENT_FORK (1<<1) @@ -262,6 +270,7 @@ struct uffdio_api { #define UFFD_FEATURE_WP_ASYNC (1<<15) #define UFFD_FEATURE_MOVE (1<<16) #define UFFD_FEATURE_RWP (1<<17) +#define UFFD_FEATURE_RWP_ASYNC (1<<18) __u64 features; __u64 ioctls; diff --git a/mm/huge_memory.c b/mm/huge_memory.c index dfbf6f3219ac..b6b0355268ea 100644 --- a/mm/huge_memory.c +++ b/mm/huge_memory.c @@ -2204,7 +2204,30 @@ static inline bool can_change_pmd_writable(struct vm_area_struct *vma, vm_fault_t do_huge_pmd_uffd_rwp(struct vm_fault *vmf) { - return handle_userfault(vmf, VM_UFFD_RWP); + struct vm_area_struct *vma = vmf->vma; + pmd_t pmd; + + if (!userfaultfd_rwp_async(vma)) + return handle_userfault(vmf, VM_UFFD_RWP); + + vmf->ptl = pmd_lock(vma->vm_mm, vmf->pmd); + if (unlikely(!pmd_same(pmdp_get(vmf->pmd), vmf->orig_pmd))) { + spin_unlock(vmf->ptl); + return 0; + } + pmd = pmd_modify(vmf->orig_pmd, vma->vm_page_prot); + /* pmd_modify() preserves _PAGE_UFFD; drop it on resolution */ + pmd = pmd_clear_uffd(pmd); + pmd = pmd_mkyoung(pmd); + if (!pmd_write(pmd) && + vma_wants_manual_pte_write_upgrade(vma) && + can_change_pmd_writable(vma, vmf->address, pmd)) + pmd = pmd_mkwrite(pmd, vma); + set_pmd_at(vma->vm_mm, vmf->address & HPAGE_PMD_MASK, + vmf->pmd, pmd); + update_mmu_cache_pmd(vma, vmf->address, vmf->pmd); + spin_unlock(vmf->ptl); + return 0; } /* NUMA hinting page fault entry point for trans huge pmds */ diff --git a/mm/hugetlb.c b/mm/hugetlb.c index d6ebf18568fa..dcaca9309015 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -6103,7 +6103,37 @@ vm_fault_t hugetlb_fault(struct mm_struct *mm, struct vm_area_struct *vma, */ if (pte_protnone(vmf.orig_pte) && vma_is_accessible(vma) && userfaultfd_rwp(vma) && huge_pte_uffd(vmf.orig_pte)) { - return hugetlb_handle_userfault(&vmf, mapping, VM_UFFD_RWP); + spinlock_t *ptl; + pte_t pte; + + /* Sync: drop hugetlb locks before blocking in handle_userfault() */ + if (!userfaultfd_rwp_async(vma)) + return hugetlb_handle_userfault(&vmf, mapping, VM_UFFD_RWP); + + ptl = huge_pte_lock(h, mm, vmf.pte); + pte = huge_ptep_get(mm, vmf.address, vmf.pte); + if (pte_protnone(pte) && huge_pte_uffd(pte)) { + unsigned int shift = huge_page_shift(h); + + pte = huge_pte_modify(pte, vma->vm_page_prot); + pte = arch_make_huge_pte(pte, shift, vma->vm_flags); + /* huge_pte_modify() preserves _PAGE_UFFD; drop it on resolution */ + pte = huge_pte_clear_uffd(pte); + pte = pte_mkyoung(pte); + /* + * Unlike do_uffd_rwp(), do not upgrade to writable + * here. Hugetlb lacks a can_change_huge_pte_writable() + * equivalent, so a write access will take a separate + * COW fault — acceptable for the rare private hugetlb + * case. + */ + set_huge_pte_at(mm, vmf.address, vmf.pte, pte, + huge_page_size(h)); + update_mmu_cache(vma, vmf.address, vmf.pte); + } + spin_unlock(ptl); + ret = 0; + goto out_mutex; } /* diff --git a/mm/memory.c b/mm/memory.c index 6e01690fd0e4..87db93f35bcf 100644 --- a/mm/memory.c +++ b/mm/memory.c @@ -6146,8 +6146,31 @@ static void numa_rebuild_large_mapping(struct vm_fault *vmf, struct vm_area_stru static vm_fault_t do_uffd_rwp(struct vm_fault *vmf) { - pte_unmap(vmf->pte); - return handle_userfault(vmf, VM_UFFD_RWP); + pte_t pte; + + if (!userfaultfd_rwp_async(vmf->vma)) { + /* Sync mode: unmap PTE and deliver to userfaultfd handler */ + pte_unmap(vmf->pte); + return handle_userfault(vmf, VM_UFFD_RWP); + } + + spin_lock(vmf->ptl); + if (unlikely(!pte_same(ptep_get(vmf->pte), vmf->orig_pte))) { + pte_unmap_unlock(vmf->pte, vmf->ptl); + return 0; + } + pte = pte_modify(vmf->orig_pte, vmf->vma->vm_page_prot); + /* pte_modify() preserves _PAGE_UFFD; drop it on resolution */ + pte = pte_clear_uffd(pte); + pte = pte_mkyoung(pte); + if (!pte_write(pte) && + vma_wants_manual_pte_write_upgrade(vmf->vma) && + can_change_pte_writable(vmf->vma, vmf->address, pte)) + pte = pte_mkwrite(pte, vmf->vma); + set_pte_at(vmf->vma->vm_mm, vmf->address, vmf->pte, pte); + update_mmu_cache(vmf->vma, vmf->address, vmf->pte); + pte_unmap_unlock(vmf->pte, vmf->ptl); + return 0; } static vm_fault_t do_numa_page(struct vm_fault *vmf) diff --git a/mm/userfaultfd.c b/mm/userfaultfd.c index f4a64971d9dc..52e544296bf5 100644 --- a/mm/userfaultfd.c +++ b/mm/userfaultfd.c @@ -2488,6 +2488,11 @@ static bool userfaultfd_wp_async_ctx(struct userfaultfd_ctx *ctx) return ctx && (ctx->features & UFFD_FEATURE_WP_ASYNC); } +static bool userfaultfd_rwp_async_ctx(struct userfaultfd_ctx *ctx) +{ + return ctx && (ctx->features & UFFD_FEATURE_RWP_ASYNC); +} + /* * Whether WP_UNPOPULATED is enabled on the uffd context. It is only * meaningful when userfaultfd_wp()==true on the vma and when it's @@ -4420,6 +4425,11 @@ bool userfaultfd_wp_async(struct vm_area_struct *vma) return userfaultfd_wp_async_ctx(vma->vm_userfaultfd_ctx.ctx); } +bool userfaultfd_rwp_async(struct vm_area_struct *vma) +{ + return userfaultfd_rwp_async_ctx(vma->vm_userfaultfd_ctx.ctx); +} + static inline unsigned int uffd_ctx_features(__u64 user_features) { /* @@ -4523,6 +4533,12 @@ static int userfaultfd_api(struct userfaultfd_ctx *ctx, if (features & UFFD_FEATURE_WP_ASYNC) features |= UFFD_FEATURE_WP_UNPOPULATED; + ret = -EINVAL; + /* RWP_ASYNC requires RWP */ + if ((features & UFFD_FEATURE_RWP_ASYNC) && + !(features & UFFD_FEATURE_RWP)) + goto err_out; + /* report all available features and ioctls to userland */ uffdio_api.features = UFFD_API_FEATURES; #ifndef CONFIG_HAVE_ARCH_USERFAULTFD_MINOR @@ -4545,7 +4561,8 @@ static int userfaultfd_api(struct userfaultfd_ctx *ctx, * but not actually usable. */ if (VM_UFFD_RWP == VM_NONE || !pgtable_supports_uffd()) - uffdio_api.features &= ~UFFD_FEATURE_RWP; + uffdio_api.features &= + ~(UFFD_FEATURE_RWP | UFFD_FEATURE_RWP_ASYNC); ret = -EINVAL; if (features & ~uffdio_api.features) -- cgit v1.2.3 From 39a7c34ea5082d721c980db2064e749c61068535 Mon Sep 17 00:00:00 2001 From: "Kiryl Shutsemau (Meta)" Date: Wed, 8 Jul 2026 12:14:14 +0100 Subject: userfaultfd: add UFFDIO_SET_MODE for runtime sync/async toggle Add an ioctl to toggle async mode at runtime without re-registering the userfaultfd. This allows a VMM to switch between sync and async RWP modes on-the-fly -- for example, starting in async mode for working set scanning, then switching to sync mode to intercept faults during page eviction. UFFDIO_SET_MODE takes an enable/disable bitmask of UFFD_FEATURE_* flags. Only UFFD_FEATURE_RWP_ASYNC is toggleable today; the ioctl rejects any other bit with -EINVAL. Enabling RWP_ASYNC also requires RWP to have been negotiated at UFFDIO_API time, mirroring the UFFDIO_API invariant. Fault-path readers of ctx->features run under mmap_read_lock or a per-VMA lock; the RMW takes mmap_write_lock and calls vma_start_write() on every UFFD-armed VMA, so those readers are fully excluded. userfaultfd_show_fdinfo(), however, reads ctx->features without any lock, so the RMW is written as a single WRITE_ONCE and fdinfo reads it with READ_ONCE. That keeps the lockless observer from seeing a mid-RMW intermediate and removes the audit burden when new toggleable bits are added later. When switching to async, pending sync waiters are woken so they retry and auto-resolve under the new mode. Link: https://lore.kernel.org/20260708111417.173443-14-kirill@shutemov.name Signed-off-by: Kiryl Shutsemau (Meta) Assisted-by: Claude:claude-opus-4-6 Reviewed-by: Mike Rapoport (Microsoft) Cc: Andrea Arcangeli Cc: David Hildenbrand Cc: James Houghton Cc: Jonathan Corbet Cc: Liam Howlett Cc: Lorenzo Stoakes Cc: Paolo Bonzini Cc: Peter Xu Cc: Sean Christopherson Cc: SeongJae Park Cc: Suren Baghdasaryan Cc: Vlastimil Babka Cc: Zi Yan Signed-off-by: Andrew Morton --- include/uapi/linux/userfaultfd.h | 14 ++++ mm/userfaultfd.c | 152 +++++++++++++++++++++++++++++++-------- 2 files changed, 137 insertions(+), 29 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/userfaultfd.h b/include/uapi/linux/userfaultfd.h index c10f08f8a618..cea11aad6b54 100644 --- a/include/uapi/linux/userfaultfd.h +++ b/include/uapi/linux/userfaultfd.h @@ -49,6 +49,7 @@ #define UFFD_API_IOCTLS \ ((__u64)1 << _UFFDIO_REGISTER | \ (__u64)1 << _UFFDIO_UNREGISTER | \ + (__u64)1 << _UFFDIO_SET_MODE | \ (__u64)1 << _UFFDIO_API) #define UFFD_API_RANGE_IOCTLS \ ((__u64)1 << _UFFDIO_WAKE | \ @@ -85,6 +86,7 @@ #define _UFFDIO_CONTINUE (0x07) #define _UFFDIO_POISON (0x08) #define _UFFDIO_RWPROTECT (0x09) +#define _UFFDIO_SET_MODE (0x0A) #define _UFFDIO_API (0x3F) /* userfaultfd ioctl ids */ @@ -111,6 +113,8 @@ struct uffdio_poison) #define UFFDIO_RWPROTECT _IOWR(UFFDIO, _UFFDIO_RWPROTECT, \ struct uffdio_rwprotect) +#define UFFDIO_SET_MODE _IOW(UFFDIO, _UFFDIO_SET_MODE, \ + struct uffdio_set_mode) /* read() structure */ struct uffd_msg { @@ -406,6 +410,16 @@ struct uffdio_move { __s64 move; }; +struct uffdio_set_mode { + /* + * Toggle async mode for features at runtime. + * Supported: UFFD_FEATURE_RWP_ASYNC. + * Setting a bit in both enable and disable is invalid. + */ + __u64 enable; + __u64 disable; +}; + /* * Flags for the userfaultfd(2) system call itself. */ diff --git a/mm/userfaultfd.c b/mm/userfaultfd.c index 52e544296bf5..9d0357768bcf 100644 --- a/mm/userfaultfd.c +++ b/mm/userfaultfd.c @@ -2478,19 +2478,29 @@ struct userfaultfd_wake_range { /* internal indication that UFFD_API ioctl was successfully executed */ #define UFFD_FEATURE_INITIALIZED (1u << 31) +/* + * UFFDIO_SET_MODE updates ctx->features under mmap_write_lock with + * WRITE_ONCE; readers that run outside mmap_read_lock or the per-VMA + * lock (poll/read_iter/ioctl, fdinfo) must pair with READ_ONCE. + */ +static unsigned int userfaultfd_features(struct userfaultfd_ctx *ctx) +{ + return READ_ONCE(ctx->features); +} + static bool userfaultfd_is_initialized(struct userfaultfd_ctx *ctx) { - return ctx->features & UFFD_FEATURE_INITIALIZED; + return userfaultfd_features(ctx) & UFFD_FEATURE_INITIALIZED; } static bool userfaultfd_wp_async_ctx(struct userfaultfd_ctx *ctx) { - return ctx && (ctx->features & UFFD_FEATURE_WP_ASYNC); + return ctx && (userfaultfd_features(ctx) & UFFD_FEATURE_WP_ASYNC); } static bool userfaultfd_rwp_async_ctx(struct userfaultfd_ctx *ctx) { - return ctx && (ctx->features & UFFD_FEATURE_RWP_ASYNC); + return ctx && (userfaultfd_features(ctx) & UFFD_FEATURE_RWP_ASYNC); } /* @@ -2505,7 +2515,7 @@ bool userfaultfd_wp_unpopulated(struct vm_area_struct *vma) if (!ctx) return false; - return ctx->features & UFFD_FEATURE_WP_UNPOPULATED; + return userfaultfd_features(ctx) & UFFD_FEATURE_WP_UNPOPULATED; } static int userfaultfd_wake_function(wait_queue_entry_t *wq, unsigned mode, @@ -3742,7 +3752,7 @@ static int userfaultfd_register(struct userfaultfd_ctx *ctx, if (uffdio_register.mode & UFFDIO_REGISTER_MODE_RWP) { if (!pgtable_supports_uffd() || VM_UFFD_RWP == VM_NONE) goto out; - if (!(ctx->features & UFFD_FEATURE_RWP)) + if (!(userfaultfd_features(ctx) & UFFD_FEATURE_RWP)) goto out; vm_flags |= VM_UFFD_RWP; } @@ -4302,6 +4312,109 @@ static int userfaultfd_rwprotect(struct userfaultfd_ctx *ctx, return ret; } +/* Subset of UFFD_API_FEATURES actually supported by this kernel/arch */ +static __u64 uffd_api_available_features(void) +{ + __u64 f = UFFD_API_FEATURES; + + if (!IS_ENABLED(CONFIG_HAVE_ARCH_USERFAULTFD_MINOR)) + f &= ~(UFFD_FEATURE_MINOR_HUGETLBFS | UFFD_FEATURE_MINOR_SHMEM); + if (!pgtable_supports_uffd()) + f &= ~UFFD_FEATURE_PAGEFAULT_FLAG_WP; + if (!uffd_supports_wp_marker()) + f &= ~(UFFD_FEATURE_WP_HUGETLBFS_SHMEM | + UFFD_FEATURE_WP_UNPOPULATED | + UFFD_FEATURE_WP_ASYNC); + /* + * RWP needs both PROT_NONE support and the uffd PTE bit. The + * VM_UFFD_RWP check covers compile-time unavailability; the + * pgtable_supports_uffd() check covers runtime (e.g. riscv + * without the SVRSW60T59B extension) where the PTE bit is declared + * but not actually usable. + */ + if (VM_UFFD_RWP == VM_NONE || !pgtable_supports_uffd()) + f &= ~(UFFD_FEATURE_RWP | UFFD_FEATURE_RWP_ASYNC); + return f; +} + +/* Async features that can be toggled at runtime via UFFDIO_SET_MODE */ +#define UFFD_FEATURE_TOGGLEABLE UFFD_FEATURE_RWP_ASYNC + +static int userfaultfd_set_mode(struct userfaultfd_ctx *ctx, + unsigned long arg) +{ + struct uffdio_set_mode mode; + struct mm_struct *mm = ctx->mm; + + if (copy_from_user(&mode, (void __user *)arg, sizeof(mode))) + return -EFAULT; + + /* enable and disable must not overlap */ + if (mode.enable & mode.disable) + return -EINVAL; + + /* only toggleable features that this kernel/arch actually supports */ + if ((mode.enable | mode.disable) & + ~(uffd_api_available_features() & UFFD_FEATURE_TOGGLEABLE)) + return -EINVAL; + + /* RWP_ASYNC can only be enabled on contexts that negotiated RWP */ + if ((mode.enable & UFFD_FEATURE_RWP_ASYNC) && + !(userfaultfd_features(ctx) & UFFD_FEATURE_RWP)) + return -EINVAL; + + if (!mmget_not_zero(mm)) + return -ESRCH; + + /* + * Drain in-flight faults before flipping features. mmap_write_lock() + * blocks new mmap_read_lock() callers, but per-VMA locked faults + * (lock_vma_under_rcu() + FAULT_FLAG_VMA_LOCK) that acquired before + * this point keep running. Calling vma_start_write() on each UFFD- + * armed VMA waits for those readers to drop, so no in-flight fault + * can observe the old features after mmap_write_unlock(). + */ + mmap_write_lock(mm); + { + struct vm_area_struct *vma; + VMA_ITERATOR(vmi, mm, 0); + + for_each_vma(vmi, vma) { + if (vma->vm_userfaultfd_ctx.ctx == ctx) + vma_start_write(vma); + } + } + /* + * Single WRITE_ONCE so lockless readers (fdinfo, poll/read_iter + * via userfaultfd_is_initialized(), and the userfaultfd_features() + * helper used elsewhere) can't observe a mid-RMW intermediate + * value. Hot-path readers already serialise through the mmap lock + * + vma_start_write() drain above, so their load doesn't need an + * annotation. + */ + WRITE_ONCE(ctx->features, + (ctx->features | mode.enable) & ~mode.disable); + mmap_write_unlock(mm); + + /* + * If switching to async, wake threads blocked in handle_userfault(). + * They will retry the fault and auto-resolve under the new mode. + * len=0 means wake all pending faults on this context. + */ + if (mode.enable & UFFD_FEATURE_RWP_ASYNC) { + struct userfaultfd_wake_range range = { .len = 0 }; + + spin_lock_irq(&ctx->fault_pending_wqh.lock); + __wake_up_locked_key(&ctx->fault_pending_wqh, TASK_NORMAL, + &range); + __wake_up(&ctx->fault_wqh, TASK_NORMAL, 1, &range); + spin_unlock_irq(&ctx->fault_pending_wqh.lock); + } + + mmput(mm); + return 0; +} + static int userfaultfd_continue(struct userfaultfd_ctx *ctx, unsigned long arg) { __s64 ret; @@ -4540,29 +4653,7 @@ static int userfaultfd_api(struct userfaultfd_ctx *ctx, goto err_out; /* report all available features and ioctls to userland */ - uffdio_api.features = UFFD_API_FEATURES; -#ifndef CONFIG_HAVE_ARCH_USERFAULTFD_MINOR - uffdio_api.features &= - ~(UFFD_FEATURE_MINOR_HUGETLBFS | UFFD_FEATURE_MINOR_SHMEM); -#endif - if (!pgtable_supports_uffd()) - uffdio_api.features &= ~UFFD_FEATURE_PAGEFAULT_FLAG_WP; - - if (!uffd_supports_wp_marker()) { - uffdio_api.features &= ~UFFD_FEATURE_WP_HUGETLBFS_SHMEM; - uffdio_api.features &= ~UFFD_FEATURE_WP_UNPOPULATED; - uffdio_api.features &= ~UFFD_FEATURE_WP_ASYNC; - } - /* - * RWP needs both PROT_NONE support and the uffd-wp PTE bit. The - * VM_UFFD_RWP check covers compile-time unavailability; the - * pgtable_supports_uffd() check covers runtime (e.g. riscv - * without the SVRSW60T59B extension) where the PTE bit is declared - * but not actually usable. - */ - if (VM_UFFD_RWP == VM_NONE || !pgtable_supports_uffd()) - uffdio_api.features &= - ~(UFFD_FEATURE_RWP | UFFD_FEATURE_RWP_ASYNC); + uffdio_api.features = uffd_api_available_features(); ret = -EINVAL; if (features & ~uffdio_api.features) @@ -4632,6 +4723,9 @@ static long userfaultfd_ioctl(struct file *file, unsigned cmd, case UFFDIO_RWPROTECT: ret = userfaultfd_rwprotect(ctx, arg); break; + case UFFDIO_SET_MODE: + ret = userfaultfd_set_mode(ctx, arg); + break; } return ret; } @@ -4659,7 +4753,7 @@ static void userfaultfd_show_fdinfo(struct seq_file *m, struct file *f) * protocols: aa:... bb:... */ seq_printf(m, "pending:\t%lu\ntotal:\t%lu\nAPI:\t%Lx:%x:%Lx\n", - pending, total, UFFD_API, ctx->features, + pending, total, UFFD_API, userfaultfd_features(ctx), UFFD_API_IOCTLS|UFFD_API_RANGE_IOCTLS); } #endif -- cgit v1.2.3 From d26dcf73a8f15d1091ba0d31a3d2380f52bd5d92 Mon Sep 17 00:00:00 2001 From: Ilya Maximets Date: Tue, 4 Aug 2026 20:20:35 +0200 Subject: openvswitch: remove support for legacy tunnel types ovs-vswitchd doesn't use OVS_VPORT_TYPE_GRE/VXLAN/GENEVE with the Linux kernel module since adding support for standard tunnel devices with COLLECT_METADATA back in 2017. The code to use them was only activated as a fallback for old kernels, so not used in practice. And it is now fully removed in the upcoming OVS 4.0 release. Modern way to use tunnels with OVS is to create standard tunnel ports with RTM_NEWLINK + COLLECT_METADATA and add them as OVS_VPORT_TYPE_NETDEV. Device reference management and the netlink options parsing for these legacy port types is complicated and was a CVE magnet in the previous release cycles. Existence of these modules also makes locking analysis for geneve module and other core tunnel devices unnecessarily more complicated, especially in light of migration to per-netns locking. Since there are no actual users for these port types for a very long time, let's just remove the support entirely. There is no practical reason to run OVS from 2017 on a recent kernel. While it's technically a uAPI change in some sense, from the user's perspective this removal looks indistinguishable from the kernel built with CONFIG_OPENVSWITCH_GENEVE/VXLAN/GRE disabled. And it seems like removal of unused drivers/modules is not a rare event these days. A comment is added to the uAPI header noting that standard RTM_NEWLINK with COLLECT_METADATA followed by OVS_VPORT_CMD_NEW with the simple OVS_VPORT_TYPE_NETDEV should be used instead. Modules responsible for these tunnel ports are removed as well as selftests covering this functionality. Further cleanups will follow. Signed-off-by: Ilya Maximets Link: https://patch.msgid.link/20260804182049.2289754-2-i.maximets@ovn.org Signed-off-by: Paolo Abeni --- include/uapi/linux/openvswitch.h | 31 +++- net/openvswitch/Kconfig | 35 ----- net/openvswitch/Makefile | 4 - net/openvswitch/datapath.c | 6 +- net/openvswitch/vport-geneve.c | 143 ----------------- net/openvswitch/vport-gre.c | 106 ------------- net/openvswitch/vport-netdev.c | 29 +--- net/openvswitch/vport-netdev.h | 2 - net/openvswitch/vport-vxlan.c | 172 --------------------- tools/testing/selftests/net/config | 3 - tools/testing/selftests/net/openvswitch/config | 3 - .../selftests/net/openvswitch/openvswitch.sh | 38 ----- .../testing/selftests/net/openvswitch/ovs-dpctl.py | 93 +++-------- 13 files changed, 52 insertions(+), 613 deletions(-) delete mode 100644 net/openvswitch/vport-geneve.c delete mode 100644 net/openvswitch/vport-gre.c delete mode 100644 net/openvswitch/vport-vxlan.c (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/openvswitch.h b/include/uapi/linux/openvswitch.h index aa2acdbda8f8..440825e65837 100644 --- a/include/uapi/linux/openvswitch.h +++ b/include/uapi/linux/openvswitch.h @@ -244,13 +244,33 @@ enum ovs_vport_cmd { OVS_VPORT_CMD_SET }; +/** + * enum ovs_vport_type - OVS vport types for %OVS_VPORT_ATTR_TYPE. + * @OVS_VPORT_TYPE_NETDEV: Existing network device attached as a vport. + * @OVS_VPORT_TYPE_INTERNAL: Network device implemented by the OVS datapath. + * @OVS_VPORT_TYPE_GRE: Legacy GRE tunnel. Not supported, see below. + * @OVS_VPORT_TYPE_VXLAN: Legacy VXLAN tunnel. Not supported, see below. + * @OVS_VPORT_TYPE_GENEVE: Legacy Geneve tunnel. Not supported, see below. + * + * The tunnel vport types are not supported. Instead, create the tunnel device + * using %RTM_NEWLINK with the appropriate %IFLA_INFO_KIND (e.g. ``gre``, + * ``gretap``, ``vxlan``, ``geneve``, or other tunnel types) and add it as + * %OVS_VPORT_TYPE_NETDEV. To match and set tunnel parameters on a per-flow + * basis, the tunnel device should collect metadata. To do that, some tunnel + * types require an explicit flag such as %IFLA_VXLAN_COLLECT_METADATA for + * ``vxlan``, while others such as ``bareudp`` collect metadata + * unconditionally. + */ enum ovs_vport_type { + /* private: */ OVS_VPORT_TYPE_UNSPEC, + /* public: */ OVS_VPORT_TYPE_NETDEV, /* network device */ OVS_VPORT_TYPE_INTERNAL, /* network device implemented by datapath */ - OVS_VPORT_TYPE_GRE, /* GRE tunnel. */ - OVS_VPORT_TYPE_VXLAN, /* VXLAN tunnel. */ - OVS_VPORT_TYPE_GENEVE, /* Geneve tunnel. */ + OVS_VPORT_TYPE_GRE, /* GRE tunnel (legacy, not supported). */ + OVS_VPORT_TYPE_VXLAN, /* VXLAN tunnel (legacy, not supported). */ + OVS_VPORT_TYPE_GENEVE, /* Geneve tunnel (legacy, not supported). */ + /* private: */ __OVS_VPORT_TYPE_MAX }; @@ -284,7 +304,7 @@ enum ovs_vport_type { * %OVS_VPORT_ATTR_NAME attributes are required. %OVS_VPORT_ATTR_PORT_NO is * optional; if not specified a free port number is automatically selected. * Whether %OVS_VPORT_ATTR_OPTIONS is required or optional depends on the type - * of vport. + * of vport. None of currently supported vport types support options. * * For other requests, if %OVS_VPORT_ATTR_NAME is specified then it is used to * look up the vport to operate on; otherwise dp_idx from the &struct @@ -336,7 +356,8 @@ enum { #define OVS_VXLAN_EXT_MAX (__OVS_VXLAN_EXT_MAX - 1) -/* OVS_VPORT_ATTR_OPTIONS attributes for tunnels. +/* OVS_VPORT_ATTR_OPTIONS attributes for legacy tunnel vports. + * Not supported, see the note for enum ovs_vport_type. */ enum { OVS_TUNNEL_ATTR_UNSPEC, diff --git a/net/openvswitch/Kconfig b/net/openvswitch/Kconfig index e6aaee92dba4..19ac9ae18f1e 100644 --- a/net/openvswitch/Kconfig +++ b/net/openvswitch/Kconfig @@ -40,38 +40,3 @@ config OPENVSWITCH called openvswitch. If unsure, say N. - -config OPENVSWITCH_GRE - tristate "Open vSwitch GRE tunneling support" - depends on OPENVSWITCH - depends on NET_IPGRE - default OPENVSWITCH - help - If you say Y here, then the Open vSwitch will be able create GRE - vport. - - Say N to exclude this support and reduce the binary size. - - If unsure, say Y. - -config OPENVSWITCH_VXLAN - tristate "Open vSwitch VXLAN tunneling support" - depends on OPENVSWITCH - depends on VXLAN - default OPENVSWITCH - help - If you say Y here, then the Open vSwitch will be able create vxlan vport. - - Say N to exclude this support and reduce the binary size. - - If unsure, say Y. - -config OPENVSWITCH_GENEVE - tristate "Open vSwitch Geneve tunneling support" - depends on OPENVSWITCH - depends on GENEVE - default OPENVSWITCH - help - If you say Y here, then the Open vSwitch will be able create geneve vport. - - Say N to exclude this support and reduce the binary size. diff --git a/net/openvswitch/Makefile b/net/openvswitch/Makefile index 28982630bef3..46a27ab369f9 100644 --- a/net/openvswitch/Makefile +++ b/net/openvswitch/Makefile @@ -22,8 +22,4 @@ ifneq ($(CONFIG_NF_CONNTRACK),) openvswitch-y += conntrack.o endif -obj-$(CONFIG_OPENVSWITCH_VXLAN)+= vport-vxlan.o -obj-$(CONFIG_OPENVSWITCH_GENEVE)+= vport-geneve.o -obj-$(CONFIG_OPENVSWITCH_GRE) += vport-gre.o - CFLAGS_openvswitch_trace.o = -I$(src) diff --git a/net/openvswitch/datapath.c b/net/openvswitch/datapath.c index eaf332b156d7..0506770341af 100644 --- a/net/openvswitch/datapath.c +++ b/net/openvswitch/datapath.c @@ -2210,11 +2210,9 @@ static size_t ovs_vport_cmd_msg_size(void) /* OVS_VPORT_ATTR_UPCALL_PID */ msgsize += nla_total_size(nr_cpu_ids * sizeof(u32)); - /* OVS_VPORT_ATTR_OPTIONS(OVS_TUNNEL_ATTR_DST_PORT + - * OVS_TUNNEL_ATTR_EXTENSION(OVS_VXLAN_EXT_GBP)) + /* There are no vports supporting OVS_VPORT_ATTR_OPTIONS, so it is + * not included in the message size calculation. */ - msgsize += nla_total_size(nla_total_size(sizeof(u16)) + - nla_total_size(nla_total_size(0))); return msgsize; } diff --git a/net/openvswitch/vport-geneve.c b/net/openvswitch/vport-geneve.c deleted file mode 100644 index cb5ea4424ffc..000000000000 --- a/net/openvswitch/vport-geneve.c +++ /dev/null @@ -1,143 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (c) 2014 Nicira, Inc. - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "datapath.h" -#include "vport.h" -#include "vport-netdev.h" - -static struct vport_ops ovs_geneve_vport_ops; -/** - * struct geneve_port - Keeps track of open UDP ports - * @dst_port: destination port. - */ -struct geneve_port { - u16 dst_port; -}; - -static inline struct geneve_port *geneve_vport(const struct vport *vport) -{ - return vport_priv(vport); -} - -static int geneve_get_options(const struct vport *vport, - struct sk_buff *skb) -{ - struct geneve_port *geneve_port = geneve_vport(vport); - - if (nla_put_u16(skb, OVS_TUNNEL_ATTR_DST_PORT, geneve_port->dst_port)) - return -EMSGSIZE; - return 0; -} - -static struct vport *geneve_tnl_create(const struct vport_parms *parms) -{ - struct net *net = ovs_dp_get_net(parms->dp); - struct nlattr *options = parms->options; - struct geneve_port *geneve_port; - struct net_device *dev; - struct vport *vport; - struct nlattr *a; - u16 dst_port; - int err; - - if (!options) { - err = -EINVAL; - goto error; - } - - a = nla_find_nested(options, OVS_TUNNEL_ATTR_DST_PORT); - if (a && nla_len(a) == sizeof(u16)) { - dst_port = nla_get_u16(a); - } else { - /* Require destination port from userspace. */ - err = -EINVAL; - goto error; - } - - vport = ovs_vport_alloc(sizeof(struct geneve_port), - &ovs_geneve_vport_ops, parms); - if (IS_ERR(vport)) - return vport; - - geneve_port = geneve_vport(vport); - geneve_port->dst_port = dst_port; - - rtnl_lock(); - dev = geneve_dev_create_fb(net, parms->name, NET_NAME_USER, dst_port); - if (IS_ERR(dev)) { - rtnl_unlock(); - ovs_vport_free(vport); - return ERR_CAST(dev); - } - - err = dev_change_flags(dev, dev->flags | IFF_UP, NULL); - if (err < 0) { - rtnl_delete_link(dev, 0, NULL); - rtnl_unlock(); - ovs_vport_free(vport); - goto error; - } - - vport->dev = dev; - netdev_hold(vport->dev, &vport->dev_tracker, GFP_KERNEL); - - rtnl_unlock(); - return vport; -error: - return ERR_PTR(err); -} - -static struct vport *geneve_create(const struct vport_parms *parms) -{ - struct vport *vport; - - vport = geneve_tnl_create(parms); - if (IS_ERR(vport)) - return vport; - - return ovs_netdev_link(vport, true); -} - -static struct vport_ops ovs_geneve_vport_ops = { - .type = OVS_VPORT_TYPE_GENEVE, - .create = geneve_create, - .destroy = ovs_netdev_tunnel_destroy, - .get_options = geneve_get_options, - .send = dev_queue_xmit, -}; - -static int __init ovs_geneve_tnl_init(void) -{ - return ovs_vport_ops_register(&ovs_geneve_vport_ops); -} - -static void __exit ovs_geneve_tnl_exit(void) -{ - ovs_vport_ops_unregister(&ovs_geneve_vport_ops); -} - -module_init(ovs_geneve_tnl_init); -module_exit(ovs_geneve_tnl_exit); - -MODULE_DESCRIPTION("OVS: Geneve switching port"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("vport-type-5"); diff --git a/net/openvswitch/vport-gre.c b/net/openvswitch/vport-gre.c deleted file mode 100644 index 6cb5a697b396..000000000000 --- a/net/openvswitch/vport-gre.c +++ /dev/null @@ -1,106 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (c) 2007-2014 Nicira, Inc. - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "datapath.h" -#include "vport.h" -#include "vport-netdev.h" - -static struct vport_ops ovs_gre_vport_ops; - -static struct vport *gre_tnl_create(const struct vport_parms *parms) -{ - struct net *net = ovs_dp_get_net(parms->dp); - struct net_device *dev; - struct vport *vport; - int err; - - vport = ovs_vport_alloc(0, &ovs_gre_vport_ops, parms); - if (IS_ERR(vport)) - return vport; - - rtnl_lock(); - dev = gretap_fb_dev_create(net, parms->name, NET_NAME_USER); - if (IS_ERR(dev)) { - rtnl_unlock(); - ovs_vport_free(vport); - return ERR_CAST(dev); - } - - err = dev_change_flags(dev, dev->flags | IFF_UP, NULL); - if (err < 0) { - rtnl_delete_link(dev, 0, NULL); - rtnl_unlock(); - ovs_vport_free(vport); - return ERR_PTR(err); - } - - vport->dev = dev; - netdev_hold(vport->dev, &vport->dev_tracker, GFP_KERNEL); - - rtnl_unlock(); - return vport; -} - -static struct vport *gre_create(const struct vport_parms *parms) -{ - struct vport *vport; - - vport = gre_tnl_create(parms); - if (IS_ERR(vport)) - return vport; - - return ovs_netdev_link(vport, true); -} - -static struct vport_ops ovs_gre_vport_ops = { - .type = OVS_VPORT_TYPE_GRE, - .create = gre_create, - .send = dev_queue_xmit, - .destroy = ovs_netdev_tunnel_destroy, -}; - -static int __init ovs_gre_tnl_init(void) -{ - return ovs_vport_ops_register(&ovs_gre_vport_ops); -} - -static void __exit ovs_gre_tnl_exit(void) -{ - ovs_vport_ops_unregister(&ovs_gre_vport_ops); -} - -module_init(ovs_gre_tnl_init); -module_exit(ovs_gre_tnl_exit); - -MODULE_DESCRIPTION("OVS: GRE switching port"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("vport-type-3"); diff --git a/net/openvswitch/vport-netdev.c b/net/openvswitch/vport-netdev.c index e7e8490a53d8..44808cd0fcff 100644 --- a/net/openvswitch/vport-netdev.c +++ b/net/openvswitch/vport-netdev.c @@ -73,7 +73,7 @@ static struct net_device *get_dpdev(const struct datapath *dp) return local->dev; } -struct vport *ovs_netdev_link(struct vport *vport, bool tunnel) +static struct vport *ovs_netdev_link(struct vport *vport) { int err; @@ -112,15 +112,12 @@ struct vport *ovs_netdev_link(struct vport *vport, bool tunnel) error_master_upper_dev_unlink: netdev_upper_dev_unlink(vport->dev, get_dpdev(vport->dp)); error_put_unlock: - if (tunnel && vport->dev->reg_state == NETREG_REGISTERED) - rtnl_delete_link(vport->dev, 0, NULL); netdev_put(vport->dev, &vport->dev_tracker); rtnl_unlock(); error_free_vport: ovs_vport_free(vport); return ERR_PTR(err); } -EXPORT_SYMBOL_GPL(ovs_netdev_link); static struct vport *netdev_create(const struct vport_parms *parms) { @@ -152,7 +149,7 @@ static struct vport *netdev_create(const struct vport_parms *parms) goto error_put; } - return ovs_netdev_link(vport, false); + return ovs_netdev_link(vport); error_put: netdev_put(vport->dev, &vport->dev_tracker); error_free_vport: @@ -204,28 +201,6 @@ static void netdev_destroy(struct vport *vport) call_rcu(&vport->rcu, vport_netdev_free); } -void ovs_netdev_tunnel_destroy(struct vport *vport) -{ - rtnl_lock(); - if (netif_is_ovs_port(vport->dev)) - ovs_netdev_detach_dev(vport); - - /* We can be invoked by both explicit vport deletion and - * underlying netdev deregistration; delete the link only - * if it's not already shutting down. - */ - if (vport->dev->reg_state == NETREG_REGISTERED) - rtnl_delete_link(vport->dev, 0, NULL); - - /* We can't put the device reference yet, since it can still be in - * use, but rtnl_unlock()->netdev_run_todo() will block until all - * the references are released, so the RCU call must be before it. - */ - call_rcu(&vport->rcu, vport_netdev_free); - rtnl_unlock(); -} -EXPORT_SYMBOL_GPL(ovs_netdev_tunnel_destroy); - /* Returns null if this device is not attached to a datapath. */ struct vport *ovs_netdev_get_vport(struct net_device *dev) { diff --git a/net/openvswitch/vport-netdev.h b/net/openvswitch/vport-netdev.h index 6c0d7366f986..880506f8317e 100644 --- a/net/openvswitch/vport-netdev.h +++ b/net/openvswitch/vport-netdev.h @@ -13,11 +13,9 @@ struct vport *ovs_netdev_get_vport(struct net_device *dev); -struct vport *ovs_netdev_link(struct vport *vport, bool tunnel); void ovs_netdev_detach_dev(struct vport *); int __init ovs_netdev_init(void); void ovs_netdev_exit(void); -void ovs_netdev_tunnel_destroy(struct vport *vport); #endif /* vport_netdev.h */ diff --git a/net/openvswitch/vport-vxlan.c b/net/openvswitch/vport-vxlan.c deleted file mode 100644 index c1b37b50d29e..000000000000 --- a/net/openvswitch/vport-vxlan.c +++ /dev/null @@ -1,172 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (c) 2014 Nicira, Inc. - * Copyright (c) 2013 Cisco Systems, Inc. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "datapath.h" -#include "vport.h" -#include "vport-netdev.h" - -static struct vport_ops ovs_vxlan_netdev_vport_ops; - -static int vxlan_get_options(const struct vport *vport, struct sk_buff *skb) -{ - struct vxlan_dev *vxlan = netdev_priv(vport->dev); - __be16 dst_port = vxlan->cfg.dst_port; - - if (nla_put_u16(skb, OVS_TUNNEL_ATTR_DST_PORT, ntohs(dst_port))) - return -EMSGSIZE; - - if (vxlan->cfg.flags & VXLAN_F_GBP) { - struct nlattr *exts; - - exts = nla_nest_start_noflag(skb, OVS_TUNNEL_ATTR_EXTENSION); - if (!exts) - return -EMSGSIZE; - - if (vxlan->cfg.flags & VXLAN_F_GBP && - nla_put_flag(skb, OVS_VXLAN_EXT_GBP)) - return -EMSGSIZE; - - nla_nest_end(skb, exts); - } - - return 0; -} - -static const struct nla_policy exts_policy[OVS_VXLAN_EXT_MAX + 1] = { - [OVS_VXLAN_EXT_GBP] = { .type = NLA_FLAG, }, -}; - -static int vxlan_configure_exts(struct vport *vport, struct nlattr *attr, - struct vxlan_config *conf) -{ - struct nlattr *exts[OVS_VXLAN_EXT_MAX + 1]; - int err; - - if (nla_len(attr) < sizeof(struct nlattr)) - return -EINVAL; - - err = nla_parse_nested_deprecated(exts, OVS_VXLAN_EXT_MAX, attr, - exts_policy, NULL); - if (err < 0) - return err; - - if (exts[OVS_VXLAN_EXT_GBP]) - conf->flags |= VXLAN_F_GBP; - - return 0; -} - -static struct vport *vxlan_tnl_create(const struct vport_parms *parms) -{ - struct net *net = ovs_dp_get_net(parms->dp); - struct nlattr *options = parms->options; - struct net_device *dev; - struct vport *vport; - struct nlattr *a; - int err; - struct vxlan_config conf = { - .no_share = true, - .flags = VXLAN_F_COLLECT_METADATA | VXLAN_F_UDP_ZERO_CSUM6_RX, - /* Don't restrict the packets that can be sent by MTU */ - .mtu = IP_MAX_MTU, - }; - - if (!options) { - err = -EINVAL; - goto error; - } - - a = nla_find_nested(options, OVS_TUNNEL_ATTR_DST_PORT); - if (a && nla_len(a) == sizeof(u16)) { - conf.dst_port = htons(nla_get_u16(a)); - } else { - /* Require destination port from userspace. */ - err = -EINVAL; - goto error; - } - - vport = ovs_vport_alloc(0, &ovs_vxlan_netdev_vport_ops, parms); - if (IS_ERR(vport)) - return vport; - - a = nla_find_nested(options, OVS_TUNNEL_ATTR_EXTENSION); - if (a) { - err = vxlan_configure_exts(vport, a, &conf); - if (err) { - ovs_vport_free(vport); - goto error; - } - } - - rtnl_lock(); - dev = vxlan_dev_create(net, parms->name, NET_NAME_USER, &conf); - if (IS_ERR(dev)) { - rtnl_unlock(); - ovs_vport_free(vport); - return ERR_CAST(dev); - } - - err = dev_change_flags(dev, dev->flags | IFF_UP, NULL); - if (err < 0) { - rtnl_delete_link(dev, 0, NULL); - rtnl_unlock(); - ovs_vport_free(vport); - goto error; - } - - vport->dev = dev; - netdev_hold(vport->dev, &vport->dev_tracker, GFP_KERNEL); - - rtnl_unlock(); - return vport; -error: - return ERR_PTR(err); -} - -static struct vport *vxlan_create(const struct vport_parms *parms) -{ - struct vport *vport; - - vport = vxlan_tnl_create(parms); - if (IS_ERR(vport)) - return vport; - - return ovs_netdev_link(vport, true); -} - -static struct vport_ops ovs_vxlan_netdev_vport_ops = { - .type = OVS_VPORT_TYPE_VXLAN, - .create = vxlan_create, - .destroy = ovs_netdev_tunnel_destroy, - .get_options = vxlan_get_options, - .send = dev_queue_xmit, -}; - -static int __init ovs_vxlan_tnl_init(void) -{ - return ovs_vport_ops_register(&ovs_vxlan_netdev_vport_ops); -} - -static void __exit ovs_vxlan_tnl_exit(void) -{ - ovs_vport_ops_unregister(&ovs_vxlan_netdev_vport_ops); -} - -module_init(ovs_vxlan_tnl_init); -module_exit(ovs_vxlan_tnl_exit); - -MODULE_DESCRIPTION("OVS: VXLAN switching port"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("vport-type-4"); diff --git a/tools/testing/selftests/net/config b/tools/testing/selftests/net/config index a2d14ec9df1a..d83e11298b01 100644 --- a/tools/testing/selftests/net/config +++ b/tools/testing/selftests/net/config @@ -117,9 +117,6 @@ CONFIG_NFT_COMPAT=m CONFIG_NFT_NAT=m CONFIG_NUMA=y CONFIG_OPENVSWITCH=m -CONFIG_OPENVSWITCH_GENEVE=m -CONFIG_OPENVSWITCH_GRE=m -CONFIG_OPENVSWITCH_VXLAN=m CONFIG_PAGE_POOL_STATS=y CONFIG_PROC_SYSCTL=y CONFIG_PSAMPLE=m diff --git a/tools/testing/selftests/net/openvswitch/config b/tools/testing/selftests/net/openvswitch/config index c659749cd086..05ca6affb510 100644 --- a/tools/testing/selftests/net/openvswitch/config +++ b/tools/testing/selftests/net/openvswitch/config @@ -7,9 +7,6 @@ CONFIG_NET_IPGRE_DEMUX=m CONFIG_NF_CONNTRACK=m CONFIG_NF_CONNTRACK_OVS=y CONFIG_OPENVSWITCH=m -CONFIG_OPENVSWITCH_GENEVE=m -CONFIG_OPENVSWITCH_GRE=m -CONFIG_OPENVSWITCH_VXLAN=m CONFIG_PSAMPLE=m CONFIG_VETH=y CONFIG_VLAN_8021Q=y diff --git a/tools/testing/selftests/net/openvswitch/openvswitch.sh b/tools/testing/selftests/net/openvswitch/openvswitch.sh index 853dbc1b00d7..f63001dc2510 100755 --- a/tools/testing/selftests/net/openvswitch/openvswitch.sh +++ b/tools/testing/selftests/net/openvswitch/openvswitch.sh @@ -26,7 +26,6 @@ tests=" netlink_checks ovsnl: validate netlink attrs and settings upcall_interfaces ovs: test the upcall interfaces tunnel_metadata ovs: test extraction of tunnel metadata - tunnel_refcount ovs: test tunnel vport reference cleanup drop_reason drop: test drop reasons are emitted pop_vlan vlan: POP_VLAN action strips tag dec_ttl ttl: dec_ttl decrements IP TTL @@ -1210,43 +1209,6 @@ test_tunnel_metadata() { return 0 } -test_tunnel_refcount() { - sbxname="test_tunnel_refcount" - sbx_add "${sbxname}" || return 1 - - ovs_sbx "${sbxname}" ip netns add trefns || return 1 - on_exit "ovs_sbx ${sbxname} ip netns del trefns" - - for tun_type in gre vxlan geneve; do - info "testing ${tun_type} tunnel vport refcount" - - ovs_sbx "${sbxname}" ip netns exec trefns \ - python3 $ovs_base/ovs-dpctl.py \ - add-dp dp-${tun_type} || return 1 - - ovs_sbx "${sbxname}" ip netns exec trefns \ - python3 $ovs_base/ovs-dpctl.py \ - add-if --no-lwt -t ${tun_type} \ - dp-${tun_type} ovs-${tun_type}0 || return 1 - - ovs_wait ip -netns trefns link show \ - ovs-${tun_type}0 >/dev/null 2>&1 || return 1 - - info "deleting dp - may hang if reference counting is broken" - ovs_sbx "${sbxname}" ip netns exec trefns \ - python3 $ovs_base/ovs-dpctl.py \ - del-dp dp-${tun_type} & - - dev_removed() { - ! ip -netns trefns link show "$1" >/dev/null 2>&1 - } - ovs_wait dev_removed dp-${tun_type} || return 1 - ovs_wait dev_removed ovs-${tun_type}0 || return 1 - done - - return 0 -} - test_pop_vlan() { local sbx="test_pop_vlan" sbx_add "$sbx" || return $? diff --git a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py index f3edd198223f..3ece07d47281 100644 --- a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py +++ b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py @@ -2364,9 +2364,6 @@ class OvsDatapath(GenericNetlinkSocket): class OvsVport(GenericNetlinkSocket): OVS_VPORT_TYPE_NETDEV = 1 OVS_VPORT_TYPE_INTERNAL = 2 - OVS_VPORT_TYPE_GRE = 3 - OVS_VPORT_TYPE_VXLAN = 4 - OVS_VPORT_TYPE_GENEVE = 5 class ovs_vport_msg(ovs_dp_msg): nla_map = ( @@ -2374,7 +2371,7 @@ class OvsVport(GenericNetlinkSocket): ("OVS_VPORT_ATTR_PORT_NO", "uint32"), ("OVS_VPORT_ATTR_TYPE", "uint32"), ("OVS_VPORT_ATTR_NAME", "asciiz"), - ("OVS_VPORT_ATTR_OPTIONS", "vportopts"), + ("OVS_VPORT_ATTR_OPTIONS", "none"), ("OVS_VPORT_ATTR_UPCALL_PID", "array(uint32)"), ("OVS_VPORT_ATTR_STATS", "vportstats"), ("OVS_VPORT_ATTR_PAD", "none"), @@ -2382,13 +2379,6 @@ class OvsVport(GenericNetlinkSocket): ("OVS_VPORT_ATTR_NETNSID", "uint32"), ) - class vportopts(nla): - nla_map = ( - ("OVS_TUNNEL_ATTR_UNSPEC", "none"), - ("OVS_TUNNEL_ATTR_DST_PORT", "uint16"), - ("OVS_TUNNEL_ATTR_EXTENSION", "none"), - ) - class vportstats(nla): fields = ( ("rx_packets", "=Q"), @@ -2406,25 +2396,13 @@ class OvsVport(GenericNetlinkSocket): return "netdev" elif vport_type == OvsVport.OVS_VPORT_TYPE_INTERNAL: return "internal" - elif vport_type == OvsVport.OVS_VPORT_TYPE_GRE: - return "gre" - elif vport_type == OvsVport.OVS_VPORT_TYPE_VXLAN: - return "vxlan" - elif vport_type == OvsVport.OVS_VPORT_TYPE_GENEVE: - return "geneve" raise ValueError("Unknown vport type:%d" % vport_type) def str_to_type(vport_type): - if vport_type == "netdev": + if vport_type in ["netdev", "gre", "vxlan", "geneve"]: return OvsVport.OVS_VPORT_TYPE_NETDEV elif vport_type == "internal": return OvsVport.OVS_VPORT_TYPE_INTERNAL - elif vport_type == "gre": - return OvsVport.OVS_VPORT_TYPE_GRE - elif vport_type == "vxlan": - return OvsVport.OVS_VPORT_TYPE_VXLAN - elif vport_type == "geneve": - return OvsVport.OVS_VPORT_TYPE_GENEVE raise ValueError("Unknown vport type: '%s'" % vport_type) def __init__(self, packet=OvsPacket()): @@ -2457,16 +2435,18 @@ class OvsVport(GenericNetlinkSocket): raise ne return reply - def attach(self, dpindex, vport_ifname, ptype, dport, lwt): + def attach(self, dpindex, vport_ifname, ptype, dport): msg = OvsVport.ovs_vport_msg() msg["cmd"] = OVS_VPORT_CMD_NEW msg["version"] = OVS_DATAPATH_VERSION msg["reserved"] = 0 msg["dpifindex"] = dpindex - port_type = OvsVport.str_to_type(ptype) msg["attrs"].append(["OVS_VPORT_ATTR_NAME", vport_ifname]) + msg["attrs"].append( + ["OVS_VPORT_ATTR_TYPE", OvsVport.str_to_type(ptype)] + ) msg["attrs"].append( ["OVS_VPORT_ATTR_UPCALL_PID", [self.upcall_packet.epid]] ) @@ -2480,36 +2460,21 @@ class OvsVport(GenericNetlinkSocket): if not dport: dport = tnl[1] - if not lwt: - if tnl[0] == "gre": - # GRE tunnels have no options. - break - - vportopt = OvsVport.ovs_vport_msg.vportopts() - vportopt["attrs"].append( - ["OVS_TUNNEL_ATTR_DST_PORT", dport] - ) - msg["attrs"].append( - ["OVS_VPORT_ATTR_OPTIONS", vportopt] - ) - else: - port_type = OvsVport.OVS_VPORT_TYPE_NETDEV - ipr = pyroute2.iproute.IPRoute() - - if tnl[0] == "geneve": - ipr.link("add", ifname=vport_ifname, kind=tnl[0], - geneve_port=dport, - geneve_collect_metadata=True, - geneve_udp_zero_csum6_rx=1) - elif tnl[0] == "gre": - ipr.link("add", ifname=vport_ifname, kind="gretap", - gre_collect_metadata=True) - elif tnl[0] == "vxlan": - ipr.link("add", ifname=vport_ifname, kind=tnl[0], - vxlan_learning=0, vxlan_collect_metadata=1, - vxlan_udp_zero_csum6_rx=1, vxlan_port=dport) + ipr = pyroute2.iproute.IPRoute() + + if tnl[0] == "geneve": + ipr.link("add", ifname=vport_ifname, kind=tnl[0], + geneve_port=dport, + geneve_collect_metadata=True, + geneve_udp_zero_csum6_rx=1) + elif tnl[0] == "gre": + ipr.link("add", ifname=vport_ifname, kind="gretap", + gre_collect_metadata=True) + elif tnl[0] == "vxlan": + ipr.link("add", ifname=vport_ifname, kind=tnl[0], + vxlan_learning=0, vxlan_collect_metadata=1, + vxlan_udp_zero_csum6_rx=1, vxlan_port=dport) break - msg["attrs"].append(["OVS_VPORT_ATTR_TYPE", port_type]) try: reply = self.nlm_request( @@ -2937,19 +2902,12 @@ def print_ovsdp_full(dp_lookup_rep, ifindex, ndb=NDB(), vpl=OvsVport()): for iface in ndb.interfaces: rep = vpl.info(iface.ifname, ifindex) if rep is not None: - opts = "" - vpo = rep.get_attr("OVS_VPORT_ATTR_OPTIONS") - if vpo: - dpo = vpo.get_attr("OVS_TUNNEL_ATTR_DST_PORT") - if dpo: - opts += " tnl-dport:%s" % dpo print( - " port %d: %s (%s%s)" + " port %d: %s (%s)" % ( rep.get_attr("OVS_VPORT_ATTR_PORT_NO"), rep.get_attr("OVS_VPORT_ATTR_NAME"), OvsVport.type_to_str(rep.get_attr("OVS_VPORT_ATTR_TYPE")), - opts, ) ) @@ -3022,13 +2980,6 @@ def main(argv): default=0, help="Destination port (0 for default)" ) - addifcmd.add_argument( - "-l", - "--lwt", - action=argparse.BooleanOptionalAction, - default=True, - help="Use LWT infrastructure instead of vport (default true)." - ) delifcmd = subparsers.add_parser("del-if") delifcmd.add_argument("dpname", help="Datapath Name") delifcmd.add_argument("delif", help="Interface name for adding") @@ -3108,7 +3059,7 @@ def main(argv): return 1 dpindex = rep["dpifindex"] rep = ovsvp.attach(rep["dpifindex"], args.addif, args.ptype, - args.dport, args.lwt) + args.dport) msg = "vport '%s'" % args.addif if rep and rep["header"]["error"] is None: msg += " added." -- cgit v1.2.3 From 485e38995cc3b39f1aa91c1d340cc66affcea39a Mon Sep 17 00:00:00 2001 From: Simon Schippers Date: Mon, 3 Aug 2026 20:36:37 +0200 Subject: tun/tap: add IFF_BACKPRESSURE flag Add the IFF_BACKPRESSURE flag to the UAPI header and to its tools/ copy. The flag has no effect yet, it is the opt-in switch for the qdisc backpressure logic added by the following patches. It is added to TUN_FEATURES only in the last patch of the series, once the implementation is complete. Until then TUNSETIFF silently masks it off, as it does for any flag outside TUN_FEATURES. Keeping the flag and its users in separate patches would either leave a window where backpressure is unconditional, or make the opt-in a later add-on. Adding the flag first lets every following patch be a no-op unless it is set. Signed-off-by: Simon Schippers Link: https://patch.msgid.link/20260803183641.96882-2-simon.schippers@tu-dortmund.de Signed-off-by: Jakub Kicinski --- include/uapi/linux/if_tun.h | 4 ++++ tools/include/uapi/linux/if_tun.h | 4 ++++ 2 files changed, 8 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/if_tun.h b/include/uapi/linux/if_tun.h index 79d53c7a1ebd..a0ddc50a7534 100644 --- a/include/uapi/linux/if_tun.h +++ b/include/uapi/linux/if_tun.h @@ -69,6 +69,10 @@ #define IFF_NAPI_FRAGS 0x0020 /* Used in TUNSETIFF to bring up tun/tap without carrier */ #define IFF_NO_CARRIER 0x0040 +/* Stop the queue instead of dropping when the internal ring is full, so an + * attached qdisc applies backpressure instead of being bypassed. + */ +#define IFF_BACKPRESSURE 0x0080 #define IFF_NO_PI 0x1000 /* This flag has no real effect */ #define IFF_ONE_QUEUE 0x2000 diff --git a/tools/include/uapi/linux/if_tun.h b/tools/include/uapi/linux/if_tun.h index 2ec07de1d73b..2c85525704c1 100644 --- a/tools/include/uapi/linux/if_tun.h +++ b/tools/include/uapi/linux/if_tun.h @@ -67,6 +67,10 @@ #define IFF_TAP 0x0002 #define IFF_NAPI 0x0010 #define IFF_NAPI_FRAGS 0x0020 +/* Stop the queue instead of dropping when the internal ring is full, so an + * attached qdisc applies backpressure instead of being bypassed. + */ +#define IFF_BACKPRESSURE 0x0080 #define IFF_NO_PI 0x1000 /* This flag has no real effect */ #define IFF_ONE_QUEUE 0x2000 -- cgit v1.2.3 From b27a8560eec935df48eae4d4fb9cc79fc02cb0e0 Mon Sep 17 00:00:00 2001 From: Bobby Eshleman Date: Wed, 5 Aug 2026 13:42:43 -0700 Subject: net: devmem: allow rx-page-size > PAGE_SIZE per dmabuf binding Every devmem dmabuf binding today hands the page_pool PAGE_SIZE niovs. This caps a single RX descriptor at PAGE_SIZE, burning CPU on buffer churn for large flows. Add a bind-time netlink attribute, NETDEV_A_DMABUF_RX_PAGE_SIZE, that lets userspace request a larger niov size. The value must be a power of two >= PAGE_SIZE. The TX path is changed to always pass PAGE_SIZE. Measurements: Setup: kperf in devmem RX/TX cuda mode, 4 flows, 64 MB messages, 60s, dctcp, num-rx-queues=4, dmabuf-rx/tx-size-mb=2048, 10 runs per niov size, mlx5. CPU Util: niov net sirq % net idle % app sys % app idle % ----- ---------------- ---------------- ---------------- ---------------- 4K 62.38 +/- 8.27 33.40 +/- 7.51 54.15 +/- 10.23 43.67 +/- 10.53 16K 58.91 +/- 5.35 35.23 +/- 5.88 41.05 +/- 8.87 56.42 +/- 9.24 32K 64.12 +/- 0.68 31.09 +/- 1.48 44.54 +/- 3.51 52.63 +/- 3.65 64K 54.69 +/- 5.54 39.67 +/- 5.81 35.47 +/- 3.11 61.97 +/- 3.27 RX app sys % drops ~19% from 4K to 64K. Throughput: niov RX dev Gbps RX flow avg Gbps ----- ---------------- ----------------- 4K 300.63 +/- 53.21 75.16 +/- 13.30 16K 321.35 +/- 28.20 80.34 +/- 7.05 32K 347.63 +/- 2.20 86.91 +/- 0.55 64K 332.11 +/- 14.26 83.03 +/- 3.56 Throughput seems to increase, but the stdev is pretty wide so could just be noise. kperf support (not yet merged): https://github.com/facebookexperimental/kperf/commit/8837577f920876bce6986ec18869ac04439ebcd2 Acked-by: Stanislav Fomichev Reviewed-by: Mina Almasry Reviewed-by: Nikolay Aleksandrov Signed-off-by: Bobby Eshleman Link: https://patch.msgid.link/20260805-tcpdm-large-niovs-v8-1-3e0225e2808c@meta.com Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/netdev.yaml | 19 +++++++++++++++++ include/uapi/linux/netdev.h | 1 + net/core/devmem.c | 37 ++++++++++++++++++++------------- net/core/devmem.h | 13 ++++++++---- net/core/netdev-genl-gen.c | 11 ++++++++-- net/core/netdev-genl-gen.h | 1 + net/core/netdev-genl.c | 18 ++++++++++++++-- tools/include/uapi/linux/netdev.h | 1 + 8 files changed, 78 insertions(+), 23 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/netdev.yaml b/Documentation/netlink/specs/netdev.yaml index 5f143da7458c..3e3f03bd5c29 100644 --- a/Documentation/netlink/specs/netdev.yaml +++ b/Documentation/netlink/specs/netdev.yaml @@ -6,6 +6,14 @@ doc: >- netdev configuration over generic netlink. definitions: + - + type: const + name: page-size + # Dummy value, codegen needs a number. The real value comes from + # the PAGE_SIZE macro in the header below. + value: 0 + header: asm/page.h + scope: kernel - type: flags name: xdp-act @@ -598,6 +606,16 @@ attribute-sets: type: u32 checks: min: 1 + - + name: rx-page-size + doc: | + Size in bytes of each device page the NIC writes into from the bound + dmabuf. Must be a power of two and >= PAGE_SIZE; defaults to + PAGE_SIZE. + type: u32 + checks: + min: page-size + max: u32-max operations: list: @@ -812,6 +830,7 @@ operations: - ifindex - fd - queues + - rx-page-size reply: attributes: - id diff --git a/include/uapi/linux/netdev.h b/include/uapi/linux/netdev.h index 2f3ab75e8cc0..35ff083221c7 100644 --- a/include/uapi/linux/netdev.h +++ b/include/uapi/linux/netdev.h @@ -219,6 +219,7 @@ enum { NETDEV_A_DMABUF_QUEUES, NETDEV_A_DMABUF_FD, NETDEV_A_DMABUF_ID, + NETDEV_A_DMABUF_RX_PAGE_SIZE, __NETDEV_A_DMABUF_MAX, NETDEV_A_DMABUF_MAX = (__NETDEV_A_DMABUF_MAX - 1) diff --git a/net/core/devmem.c b/net/core/devmem.c index 957d6b96216b..f4d60654ce7f 100644 --- a/net/core/devmem.c +++ b/net/core/devmem.c @@ -46,7 +46,7 @@ static dma_addr_t net_devmem_get_dma_addr(const struct net_iov *niov) owner = net_devmem_iov_to_chunk_owner(niov); return owner->base_dma_addr + - ((dma_addr_t)net_iov_idx(niov) << PAGE_SHIFT); + ((dma_addr_t)net_iov_idx(niov) << owner->binding->niov_shift); } static void net_devmem_dmabuf_binding_release(struct percpu_ref *ref) @@ -93,13 +93,14 @@ net_devmem_alloc_dmabuf(struct net_devmem_dmabuf_binding *binding) ssize_t offset; ssize_t index; - dma_addr = gen_pool_alloc_owner(binding->chunk_pool, PAGE_SIZE, + dma_addr = gen_pool_alloc_owner(binding->chunk_pool, + 1UL << binding->niov_shift, (void **)&owner); if (!dma_addr) return NULL; offset = dma_addr - owner->base_dma_addr; - index = offset / PAGE_SIZE; + index = offset >> binding->niov_shift; niov = &owner->area.niovs[index]; niov->desc.pp_magic = 0; @@ -113,12 +114,13 @@ void net_devmem_free_dmabuf(struct net_iov *niov) { struct net_devmem_dmabuf_binding *binding = net_devmem_iov_binding(niov); unsigned long dma_addr = net_devmem_get_dma_addr(niov); + size_t niov_size = 1UL << binding->niov_shift; if (WARN_ON(!gen_pool_has_addr(binding->chunk_pool, dma_addr, - PAGE_SIZE))) + niov_size))) return; - gen_pool_free(binding->chunk_pool, dma_addr, PAGE_SIZE); + gen_pool_free(binding->chunk_pool, dma_addr, niov_size); } void net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding) @@ -163,6 +165,9 @@ int net_devmem_bind_dmabuf_to_queue(struct net_device *dev, u32 rxq_idx, u32 xa_idx; int err; + if (binding->niov_shift != PAGE_SHIFT) + mp_params.rx_page_size = 1U << binding->niov_shift; + err = netif_mp_open_rxq(dev, rxq_idx, &mp_params, extack); if (err) return err; @@ -184,10 +189,12 @@ struct net_devmem_dmabuf_binding * net_devmem_bind_dmabuf(struct net_device *dev, void *vdev, struct device *dma_dev, enum dma_data_direction direction, - unsigned int dmabuf_fd, struct netdev_nl_sock *priv, + unsigned int dmabuf_fd, unsigned int niov_shift, + struct netdev_nl_sock *priv, struct netlink_ext_ack *extack) { struct net_devmem_dmabuf_binding *binding; + size_t niov_size = 1UL << niov_shift; static u32 id_alloc_next; struct scatterlist *sg; struct dma_buf *dmabuf; @@ -213,6 +220,7 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev, binding->dev = dev; binding->vdev = vdev; + binding->niov_shift = niov_shift; xa_init_flags(&binding->bound_rxqs, XA_FLAGS_ALLOC); err = percpu_ref_init(&binding->ref, @@ -255,11 +263,7 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev, } } - /* For simplicity we expect to make PAGE_SIZE allocations, but the - * binding can be much more flexible than that. We may be able to - * allocate MTU sized chunks here. Leave that for future work... - */ - binding->chunk_pool = gen_pool_create(PAGE_SHIFT, + binding->chunk_pool = gen_pool_create(niov_shift, dev_to_node(&dev->dev)); if (!binding->chunk_pool) { err = -ENOMEM; @@ -273,9 +277,12 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev, size_t len = sg_dma_len(sg); struct net_iov *niov; - if (!IS_ALIGNED(len, PAGE_SIZE)) { + if (!IS_ALIGNED(dma_addr, niov_size) || + !IS_ALIGNED(len, niov_size)) { err = -EINVAL; - NL_SET_ERR_MSG(extack, "dma-buf SG length must be PAGE_SIZE aligned"); + NL_SET_ERR_MSG_FMT(extack, + "dmabuf sg entry (addr=%pad, len=%zu) not aligned to niov size %zu", + &dma_addr, len, niov_size); goto err_free_chunks; } @@ -288,7 +295,7 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev, owner->area.base_virtual = virtual; owner->base_dma_addr = dma_addr; - owner->area.num_niovs = len / PAGE_SIZE; + owner->area.num_niovs = len >> niov_shift; owner->binding = binding; err = gen_pool_add_owner(binding->chunk_pool, dma_addr, @@ -454,7 +461,7 @@ int mp_dmabuf_devmem_init(struct page_pool *pool) pool->dma_sync = false; pool->dma_sync_for_cpu = false; - if (pool->p.order != 0) + if (pool->p.order != binding->niov_shift - PAGE_SHIFT) return -E2BIG; net_devmem_dmabuf_binding_get(binding); diff --git a/net/core/devmem.h b/net/core/devmem.h index 3852a56036cb..4a293a7d1149 100644 --- a/net/core/devmem.h +++ b/net/core/devmem.h @@ -71,6 +71,8 @@ struct net_devmem_dmabuf_binding { */ struct net_iov **tx_vec; + unsigned int niov_shift; + struct work_struct unbind_w; }; @@ -93,7 +95,8 @@ struct net_devmem_dmabuf_binding * net_devmem_bind_dmabuf(struct net_device *dev, void *vdev, struct device *dma_dev, enum dma_data_direction direction, - unsigned int dmabuf_fd, struct netdev_nl_sock *priv, + unsigned int dmabuf_fd, unsigned int niov_shift, + struct netdev_nl_sock *priv, struct netlink_ext_ack *extack); struct net_devmem_dmabuf_binding *net_devmem_lookup_dmabuf(u32 id); void net_devmem_unbind_dmabuf(struct net_devmem_dmabuf_binding *binding); @@ -122,10 +125,11 @@ static inline u32 net_devmem_iov_binding_id(const struct net_iov *niov) static inline unsigned long net_iov_virtual_addr(const struct net_iov *niov) { - struct net_iov_area *owner = net_iov_owner(niov); + struct dmabuf_genpool_chunk_owner *co = + net_devmem_iov_to_chunk_owner(niov); - return owner->base_virtual + - ((unsigned long)net_iov_idx(niov) << PAGE_SHIFT); + return net_iov_owner(niov)->base_virtual + + ((unsigned long)net_iov_idx(niov) << co->binding->niov_shift); } static inline bool @@ -175,6 +179,7 @@ net_devmem_bind_dmabuf(struct net_device *dev, void *vdev, struct device *dma_dev, enum dma_data_direction direction, unsigned int dmabuf_fd, + unsigned int niov_shift, struct netdev_nl_sock *priv, struct netlink_ext_ack *extack) { diff --git a/net/core/netdev-genl-gen.c b/net/core/netdev-genl-gen.c index d18c89b5a6c7..f83790341eae 100644 --- a/net/core/netdev-genl-gen.c +++ b/net/core/netdev-genl-gen.c @@ -11,6 +11,7 @@ #include #include +#include /* Integer value ranges */ static const struct netlink_range_validation netdev_a_page_pool_id_range = { @@ -27,6 +28,11 @@ static const struct netlink_range_validation netdev_a_napi_defer_hard_irqs_range .max = S32_MAX, }; +static const struct netlink_range_validation netdev_a_dmabuf_rx_page_size_range = { + .min = PAGE_SIZE, + .max = U32_MAX, +}; + /* Common nested types */ const struct nla_policy netdev_lease_nl_policy[NETDEV_A_LEASE_NETNS_ID + 1] = { [NETDEV_A_LEASE_IFINDEX] = NLA_POLICY_MIN(NLA_U32, 1), @@ -106,10 +112,11 @@ static const struct nla_policy netdev_qstats_get_nl_policy[NETDEV_A_QSTATS_SCOPE }; /* NETDEV_CMD_BIND_RX - do */ -static const struct nla_policy netdev_bind_rx_nl_policy[NETDEV_A_DMABUF_FD + 1] = { +static const struct nla_policy netdev_bind_rx_nl_policy[NETDEV_A_DMABUF_RX_PAGE_SIZE + 1] = { [NETDEV_A_DMABUF_IFINDEX] = NLA_POLICY_MIN(NLA_U32, 1), [NETDEV_A_DMABUF_FD] = { .type = NLA_U32, }, [NETDEV_A_DMABUF_QUEUES] = NLA_POLICY_NESTED(netdev_queue_id_nl_policy), + [NETDEV_A_DMABUF_RX_PAGE_SIZE] = NLA_POLICY_FULL_RANGE(NLA_U32, &netdev_a_dmabuf_rx_page_size_range), }; /* NETDEV_CMD_NAPI_SET - do */ @@ -219,7 +226,7 @@ static const struct genl_split_ops netdev_nl_ops[] = { .cmd = NETDEV_CMD_BIND_RX, .doit = netdev_nl_bind_rx_doit, .policy = netdev_bind_rx_nl_policy, - .maxattr = NETDEV_A_DMABUF_FD, + .maxattr = NETDEV_A_DMABUF_RX_PAGE_SIZE, .flags = GENL_UNS_ADMIN_PERM | GENL_CMD_CAP_DO, }, { diff --git a/net/core/netdev-genl-gen.h b/net/core/netdev-genl-gen.h index d71b435d72c1..3fae88e8f5c5 100644 --- a/net/core/netdev-genl-gen.h +++ b/net/core/netdev-genl-gen.h @@ -12,6 +12,7 @@ #include #include +#include /* Common nested types */ extern const struct nla_policy netdev_lease_nl_policy[NETDEV_A_LEASE_NETNS_ID + 1]; diff --git a/net/core/netdev-genl.c b/net/core/netdev-genl.c index c15d8d4ca1f8..0eea4ee22f24 100644 --- a/net/core/netdev-genl.c +++ b/net/core/netdev-genl.c @@ -1013,6 +1013,7 @@ netdev_nl_get_dma_dev(struct net_device *netdev, unsigned long *rxq_bitmap, int netdev_nl_bind_rx_doit(struct sk_buff *skb, struct genl_info *info) { struct net_devmem_dmabuf_binding *binding; + unsigned int niov_shift = PAGE_SHIFT; u32 ifindex, dmabuf_fd, rxq_idx; struct netdev_nl_sock *priv; struct net_device *netdev; @@ -1030,6 +1031,18 @@ int netdev_nl_bind_rx_doit(struct sk_buff *skb, struct genl_info *info) ifindex = nla_get_u32(info->attrs[NETDEV_A_DEV_IFINDEX]); dmabuf_fd = nla_get_u32(info->attrs[NETDEV_A_DMABUF_FD]); + if (info->attrs[NETDEV_A_DMABUF_RX_PAGE_SIZE]) { + u32 rx_page_size = nla_get_u32(info->attrs[NETDEV_A_DMABUF_RX_PAGE_SIZE]); + + if (!is_power_of_2(rx_page_size)) { + NL_SET_ERR_MSG_ATTR(info->extack, + info->attrs[NETDEV_A_DMABUF_RX_PAGE_SIZE], + "rx-page-size must be a power of 2"); + return -EINVAL; + } + niov_shift = ilog2(rx_page_size); + } + priv = genl_sk_priv_get(&netdev_nl_family, NETLINK_CB(skb).sk); if (IS_ERR(priv)) return PTR_ERR(priv); @@ -1080,7 +1093,8 @@ int netdev_nl_bind_rx_doit(struct sk_buff *skb, struct genl_info *info) } binding = net_devmem_bind_dmabuf(netdev, NULL, dma_dev, DMA_FROM_DEVICE, - dmabuf_fd, priv, info->extack); + dmabuf_fd, niov_shift, priv, + info->extack); if (IS_ERR(binding)) { err = PTR_ERR(binding); goto err_rxq_bitmap; @@ -1221,7 +1235,7 @@ int netdev_nl_bind_tx_doit(struct sk_buff *skb, struct genl_info *info) binding = net_devmem_bind_dmabuf(bind_dev, bind_dev != netdev ? netdev : NULL, dma_dev, DMA_TO_DEVICE, dmabuf_fd, - priv, info->extack); + PAGE_SHIFT, priv, info->extack); if (IS_ERR(binding)) { err = PTR_ERR(binding); goto err_unlock_bind_dev; diff --git a/tools/include/uapi/linux/netdev.h b/tools/include/uapi/linux/netdev.h index 2f3ab75e8cc0..35ff083221c7 100644 --- a/tools/include/uapi/linux/netdev.h +++ b/tools/include/uapi/linux/netdev.h @@ -219,6 +219,7 @@ enum { NETDEV_A_DMABUF_QUEUES, NETDEV_A_DMABUF_FD, NETDEV_A_DMABUF_ID, + NETDEV_A_DMABUF_RX_PAGE_SIZE, __NETDEV_A_DMABUF_MAX, NETDEV_A_DMABUF_MAX = (__NETDEV_A_DMABUF_MAX - 1) -- cgit v1.2.3 From ac3e65ddddf3128c1f0c2187889fab458fcf4a70 Mon Sep 17 00:00:00 2001 From: Amit Machhiwal Date: Sat, 8 Aug 2026 21:41:45 +0530 Subject: KVM: PPC: Introduce KVM_CAP_PPC_COMPAT_CAPS and wire up ioctl Introduce a new capability and ioctl to expose CPU compatibility modes supported by the host processor for nested guests. On IBM POWER systems, newer processor generations (N) can operate in compatibility modes corresponding to earlier generations, like (N-1) and (N-2). This is particularly relevant for nested virtualization, where nested KVM guests may need to run with a specific processor compatibility level. Introduce KVM_CAP_PPC_COMPAT_CAPS capability and the corresponding KVM_PPC_GET_COMPAT_CAPS vm ioctl. The ioctl returns a bitmap describing the compatibility modes supported by the host in respective bit numbers, allowing userspace (e.g., QEMU) to select an appropriate compatibility level when configuring nested KVM guests. The ioctl handling is added in kvm_arch_vm_ioctl() and retrieves host CPU compatibility capabilities via a PowerPC-specific backend implementation when available. The struct kvm_ppc_compat_caps places the 'size' field first so it can be read alone via get_user() before copy_struct_from_user() is called, avoiding pointer arithmetic to locate the size field. The ioctl is defined using _IO so the ioctl number remains stable even if the struct grows in future versions. It uses copy_struct_from_user() and copy_struct_to_user() to provide forward- and backward-compatible extensibility: older userspace passing a smaller struct to a newer kernel gets zero-padded trailing fields. Newer userspace passing a larger struct to an older kernel (usize > ksize) succeeds if trailing bytes are zero (the kernel reports back min(usize, ksize) as the filled size); if trailing bytes are non-zero, the kernel writes back ksize into host_caps.size and returns -E2BIG so userspace can retry with the correct size. KVM_PPC_COMPAT_CAPS_SIZE_VER0 is defined as a frozen integer constant (24) marking the size of the initial struct version, used as the minimum floor for size field validation, similar to other versioned struct interfaces in the kernel. The 'flags' field is reserved for future use. The kernel rejects any call where flags is non-zero with -EINVAL, preventing garbage values from being baked into ABI permanently. The ioctl returns appropriate error codes: E2BIG if usize exceeds PAGE_SIZE, or if new userspace provides a larger struct with non-zero trailing bytes (with ksize written back into host_caps.size for the retry); EINVAL for an invalid size or non-zero reserved fields; EFAULT for failed copy operations; and ENOTTY if the backend doesn't implement get_compat_caps. Suggested-by: Vaibhav Jain Tested-by: Gautam Menghani Reviewed-by: Gautam Menghani Tested-by: Anushree Mathur Reviewed-by: Ritesh Harjani (IBM) Signed-off-by: Amit Machhiwal Signed-off-by: Madhavan Srinivasan Link: https://patch.msgid.link/20260808161148.66673-2-amachhiw@linux.ibm.com --- arch/powerpc/include/asm/kvm_ppc.h | 1 + arch/powerpc/include/uapi/asm/kvm.h | 8 ++++ arch/powerpc/kvm/powerpc.c | 78 +++++++++++++++++++++++++++++++++++++ include/uapi/linux/kvm.h | 3 ++ 4 files changed, 90 insertions(+) (limited to 'include/uapi/linux') diff --git a/arch/powerpc/include/asm/kvm_ppc.h b/arch/powerpc/include/asm/kvm_ppc.h index 0953f2daa466..169ea6a7fbad 100644 --- a/arch/powerpc/include/asm/kvm_ppc.h +++ b/arch/powerpc/include/asm/kvm_ppc.h @@ -319,6 +319,7 @@ struct kvmppc_ops { bool (*hash_v3_possible)(void); int (*create_vm_debugfs)(struct kvm *kvm); int (*create_vcpu_debugfs)(struct kvm_vcpu *vcpu, struct dentry *debugfs_dentry); + int (*get_compat_caps)(struct kvm_ppc_compat_caps *host_caps); }; extern struct kvmppc_ops *kvmppc_hv_ops; diff --git a/arch/powerpc/include/uapi/asm/kvm.h b/arch/powerpc/include/uapi/asm/kvm.h index 077c5437f521..19e53d5ae540 100644 --- a/arch/powerpc/include/uapi/asm/kvm.h +++ b/arch/powerpc/include/uapi/asm/kvm.h @@ -437,6 +437,14 @@ struct kvm_ppc_cpu_char { __u64 behaviour_mask; /* valid bits in behaviour */ }; +/* For KVM_PPC_GET_COMPAT_CAPS */ +struct kvm_ppc_compat_caps { + __u64 size; /* Size of this structure */ + __u64 flags; /* Reserved for future use */ + __u64 compat_capabilities; /* Capabilities supported by the host */ +}; +#define KVM_PPC_COMPAT_CAPS_SIZE_VER0 24 /* sizeof first published struct */ + /* * Values for character and character_mask. * These are identical to the values used by H_GET_CPU_CHARACTERISTICS. diff --git a/arch/powerpc/kvm/powerpc.c b/arch/powerpc/kvm/powerpc.c index 51c48fbce55f..9194cf492d1c 100644 --- a/arch/powerpc/kvm/powerpc.c +++ b/arch/powerpc/kvm/powerpc.c @@ -722,6 +722,13 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext) } } break; +#if defined(CONFIG_KVM_BOOK3S_HV_POSSIBLE) + case KVM_CAP_PPC_COMPAT_CAPS: + r = 0; + if (hv_enabled && kvmhv_on_pseries()) + r = 1; + break; +#endif /* CONFIG_KVM_BOOK3S_HV_POSSIBLE */ default: r = 0; break; @@ -2488,6 +2495,77 @@ int kvm_arch_vm_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg) r = kvm->arch.kvm_ops->svm_off(kvm); break; } + case KVM_PPC_GET_COMPAT_CAPS: { + struct kvm_ppc_compat_caps host_caps = {}; + u64 usize; + + /* + * Read the size field first to drive copy_struct_from_user. + * size must be the first field of the struct. + */ + r = -EFAULT; + if (get_user(usize, (__u64 __user *)argp)) + goto out; + + r = -E2BIG; + if (unlikely(usize > PAGE_SIZE)) + goto out; + + /* + * Enforce a minimum: reject buffers smaller than the initial + * struct version (VER0). This allows old userspace compiled + * against the original struct to still work on a newer kernel + * that has grown the struct with appended fields. + */ + r = -EINVAL; + if (usize < KVM_PPC_COMPAT_CAPS_SIZE_VER0) + goto out; + + /* + * copy_struct_from_user() handles forward/backward compat: + * usize == ksize: verbatim copy + * usize < ksize: zero-pad trailing (old userspace, new kernel) + * usize > ksize: succeed iff trailing bytes are zero, else -E2BIG + */ + r = copy_struct_from_user(&host_caps, sizeof(host_caps), + argp, usize); + if (r) { + /* + * New userspace with a larger struct called an older + * kernel. Write back ksize in host_caps.size so + * userspace knows which older struct to retry with, + * then fail with -E2BIG. + */ + if (r == -E2BIG) + if (put_user((__u64)sizeof(host_caps), + (__u64 __user *)argp)) + r = -EFAULT; + goto out; + } + + /* Reserved fields must be zero */ + r = -EINVAL; + if (host_caps.flags) + goto out; + + r = -ENOTTY; + if (!kvm->arch.kvm_ops->get_compat_caps) + goto out; + + r = kvm->arch.kvm_ops->get_compat_caps(&host_caps); + if (r) + goto out; + + /* + * Report the number of bytes actually populated by the kernel, + * not usize: if new userspace passed a larger struct with zero + * trailing bytes, we only filled sizeof(host_caps) bytes. + */ + host_caps.size = min_t(u64, usize, sizeof(host_caps)); + r = copy_struct_to_user(argp, usize, &host_caps, + sizeof(host_caps), NULL); + break; + } default: { struct kvm *kvm = filp->private_data; r = kvm->arch.kvm_ops->arch_vm_ioctl(filp, ioctl, arg); diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h index 419011097fa8..70e36e6a0ad4 100644 --- a/include/uapi/linux/kvm.h +++ b/include/uapi/linux/kvm.h @@ -997,6 +997,7 @@ struct kvm_enable_cap { #define KVM_CAP_S390_KEYOP 247 #define KVM_CAP_S390_VSIE_ESAMODE 248 #define KVM_CAP_S390_HPAGE_2G 249 +#define KVM_CAP_PPC_COMPAT_CAPS 250 struct kvm_irq_routing_irqchip { __u32 irqchip; @@ -1341,6 +1342,8 @@ struct kvm_s390_keyop { /* Available with KVM_CAP_COUNTER_OFFSET */ #define KVM_ARM_SET_COUNTER_OFFSET _IOW(KVMIO, 0xb5, struct kvm_arm_counter_offset) #define KVM_ARM_GET_REG_WRITABLE_MASKS _IOR(KVMIO, 0xb6, struct reg_mask_range) +/* Available with KVM_CAP_PPC_COMPAT_CAPS */ +#define KVM_PPC_GET_COMPAT_CAPS _IO(KVMIO, 0xb8) /* ioctl for vm fd */ #define KVM_CREATE_DEVICE _IOWR(KVMIO, 0xe0, struct kvm_create_device) -- cgit v1.2.3 From 389a3c294ae914efd1681f5355b07ed8b439678d Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Wed, 29 Jul 2026 22:17:10 -0700 Subject: crypto: ccp - don't abuse kernel-doc comment format Use plain C "/*" notation for comments that are not in kernel-doc format to avoid kernel-doc warnings: Warning: include/uapi/linux/psp-sfs.h:18 expecting prototype for SFS(). Prototype was for PAYLOAD_NAME_SIZE() instead Warning: include/uapi/linux/psp-sfs.h:46 This comment starts with '/**', but isn't a kernel-doc comment. * Seamless Firmware Support (SFS) IOC Fixes: 648dbccc03a0 ("crypto: ccp - Add AMD Seamless Firmware Servicing (SFS) driver") Signed-off-by: Randy Dunlap Acked-by: Tom Lendacky Signed-off-by: Herbert Xu --- include/uapi/linux/psp-sfs.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/psp-sfs.h b/include/uapi/linux/psp-sfs.h index 94e51670383c..fe9402c8a575 100644 --- a/include/uapi/linux/psp-sfs.h +++ b/include/uapi/linux/psp-sfs.h @@ -12,7 +12,7 @@ #include -/** +/* * SFS: AMD Seamless Firmware Support (SFS) interface */ @@ -43,7 +43,7 @@ struct sfs_user_update_package { __u32 sfs_extended_status; } __packed; -/** +/* * Seamless Firmware Support (SFS) IOC * * possible return codes for all SFS IOCTLs: -- cgit v1.2.3 From a0ac2a63d4b21455985436fbc70192da14a163fd Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Tue, 16 Jun 2026 07:58:45 -0400 Subject: nfsd: add protocol support for CB_NOTIFY Add the necessary bits to nfs4_1.x and remove the duplicate definitions from nfs4.h and the uapi nfs4 header. Regenerate the xdr files. Note that regenerating these files caused conflicts with the definitions of NFS4_VERIFIER_SIZE and NFS4_FHSIZE in include/uapi/linux/nfs4.h. These constants are defined by the RFC, and are not part of the kernel API. They have been removed. Userspace consumers who require those constants should plan to get them from more authoritative sources. The nfsstat4 enum defined in the .x is fed to the xdrgen-generated wire encoder and decoder, which treat every enumerated value as legal on the wire. Do not carry the NFS4ERR_FIRST_FREE sentinel (which is not a protocol error code) into the .x; keeping it would make 10097 a value that could leak onto the wire. Instead base nfsd's internal error codes (NFSERR_EOF and friends) at an impossible nfsstat4 value, as lockd does for its nlm__int__* status codes. Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260616-dir-deleg-v7-2-6cbc7eac0ade@kernel.org Signed-off-by: Chuck Lever --- Documentation/sunrpc/xdr/nfs4_1.x | 241 +++++++++++++- fs/nfsd/nfs4xdr_gen.c | 589 ++++++++++++++++++++++++++++++++++- fs/nfsd/nfs4xdr_gen.h | 2 +- fs/nfsd/nfsd.h | 9 +- fs/nfsd/trace.h | 1 + include/linux/nfs4.h | 127 -------- include/linux/sunrpc/xdrgen/nfs4_1.h | 279 ++++++++++++++++- include/uapi/linux/nfs4.h | 2 - 8 files changed, 1105 insertions(+), 145 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/sunrpc/xdr/nfs4_1.x b/Documentation/sunrpc/xdr/nfs4_1.x index 5b45547b2ebc..72d439b71735 100644 --- a/Documentation/sunrpc/xdr/nfs4_1.x +++ b/Documentation/sunrpc/xdr/nfs4_1.x @@ -45,19 +45,162 @@ pragma header nfs4; /* * Basic typedefs for RFC 1832 data type definitions */ -typedef hyper int64_t; -typedef unsigned int uint32_t; +typedef int int32_t; +typedef unsigned int uint32_t; +typedef hyper int64_t; +typedef unsigned hyper uint64_t; + +const NFS4_VERIFIER_SIZE = 8; +const NFS4_FHSIZE = 128; + +enum nfsstat4 { + NFS4_OK = 0, /* everything is okay */ + NFS4ERR_PERM = 1, /* caller not privileged */ + NFS4ERR_NOENT = 2, /* no such file/directory */ + NFS4ERR_IO = 5, /* hard I/O error */ + NFS4ERR_NXIO = 6, /* no such device */ + NFS4ERR_ACCESS = 13, /* access denied */ + NFS4ERR_EXIST = 17, /* file already exists */ + NFS4ERR_XDEV = 18, /* different filesystems */ + + /* + * Please do not allocate value 19; it was used in NFSv3 + * and we do not want a value in NFSv3 to have a different + * meaning in NFSv4.x. + */ + + NFS4ERR_NOTDIR = 20, /* should be a directory */ + NFS4ERR_ISDIR = 21, /* should not be directory */ + NFS4ERR_INVAL = 22, /* invalid argument */ + NFS4ERR_FBIG = 27, /* file exceeds server max */ + NFS4ERR_NOSPC = 28, /* no space on filesystem */ + NFS4ERR_ROFS = 30, /* read-only filesystem */ + NFS4ERR_MLINK = 31, /* too many hard links */ + NFS4ERR_NAMETOOLONG = 63, /* name exceeds server max */ + NFS4ERR_NOTEMPTY = 66, /* directory not empty */ + NFS4ERR_DQUOT = 69, /* hard quota limit reached*/ + NFS4ERR_STALE = 70, /* file no longer exists */ + NFS4ERR_BADHANDLE = 10001,/* Illegal filehandle */ + NFS4ERR_BAD_COOKIE = 10003,/* READDIR cookie is stale */ + NFS4ERR_NOTSUPP = 10004,/* operation not supported */ + NFS4ERR_TOOSMALL = 10005,/* response limit exceeded */ + NFS4ERR_SERVERFAULT = 10006,/* undefined server error */ + NFS4ERR_BADTYPE = 10007,/* type invalid for CREATE */ + NFS4ERR_DELAY = 10008,/* file "busy" - retry */ + NFS4ERR_SAME = 10009,/* nverify says attrs same */ + NFS4ERR_DENIED = 10010,/* lock unavailable */ + NFS4ERR_EXPIRED = 10011,/* lock lease expired */ + NFS4ERR_LOCKED = 10012,/* I/O failed due to lock */ + NFS4ERR_GRACE = 10013,/* in grace period */ + NFS4ERR_FHEXPIRED = 10014,/* filehandle expired */ + NFS4ERR_SHARE_DENIED = 10015,/* share reserve denied */ + NFS4ERR_WRONGSEC = 10016,/* wrong security flavor */ + NFS4ERR_CLID_INUSE = 10017,/* clientid in use */ + + /* NFS4ERR_RESOURCE is not a valid error in NFSv4.1 */ + NFS4ERR_RESOURCE = 10018,/* resource exhaustion */ + + NFS4ERR_MOVED = 10019,/* filesystem relocated */ + NFS4ERR_NOFILEHANDLE = 10020,/* current FH is not set */ + NFS4ERR_MINOR_VERS_MISMATCH= 10021,/* minor vers not supp */ + NFS4ERR_STALE_CLIENTID = 10022,/* server has rebooted */ + NFS4ERR_STALE_STATEID = 10023,/* server has rebooted */ + NFS4ERR_OLD_STATEID = 10024,/* state is out of sync */ + NFS4ERR_BAD_STATEID = 10025,/* incorrect stateid */ + NFS4ERR_BAD_SEQID = 10026,/* request is out of seq. */ + NFS4ERR_NOT_SAME = 10027,/* verify - attrs not same */ + NFS4ERR_LOCK_RANGE = 10028,/* overlapping lock range */ + NFS4ERR_SYMLINK = 10029,/* should be file/directory*/ + NFS4ERR_RESTOREFH = 10030,/* no saved filehandle */ + NFS4ERR_LEASE_MOVED = 10031,/* some filesystem moved */ + NFS4ERR_ATTRNOTSUPP = 10032,/* recommended attr not sup*/ + NFS4ERR_NO_GRACE = 10033,/* reclaim outside of grace*/ + NFS4ERR_RECLAIM_BAD = 10034,/* reclaim error at server */ + NFS4ERR_RECLAIM_CONFLICT= 10035,/* conflict on reclaim */ + NFS4ERR_BADXDR = 10036,/* XDR decode failed */ + NFS4ERR_LOCKS_HELD = 10037,/* file locks held at CLOSE*/ + NFS4ERR_OPENMODE = 10038,/* conflict in OPEN and I/O*/ + NFS4ERR_BADOWNER = 10039,/* owner translation bad */ + NFS4ERR_BADCHAR = 10040,/* utf-8 char not supported*/ + NFS4ERR_BADNAME = 10041,/* name not supported */ + NFS4ERR_BAD_RANGE = 10042,/* lock range not supported*/ + NFS4ERR_LOCK_NOTSUPP = 10043,/* no atomic up/downgrade */ + NFS4ERR_OP_ILLEGAL = 10044,/* undefined operation */ + NFS4ERR_DEADLOCK = 10045,/* file locking deadlock */ + NFS4ERR_FILE_OPEN = 10046,/* open file blocks op. */ + NFS4ERR_ADMIN_REVOKED = 10047,/* lockowner state revoked */ + NFS4ERR_CB_PATH_DOWN = 10048,/* callback path down */ + + /* NFSv4.1 errors start here. */ + + NFS4ERR_BADIOMODE = 10049, + NFS4ERR_BADLAYOUT = 10050, + NFS4ERR_BAD_SESSION_DIGEST = 10051, + NFS4ERR_BADSESSION = 10052, + NFS4ERR_BADSLOT = 10053, + NFS4ERR_COMPLETE_ALREADY = 10054, + NFS4ERR_CONN_NOT_BOUND_TO_SESSION = 10055, + NFS4ERR_DELEG_ALREADY_WANTED = 10056, + NFS4ERR_BACK_CHAN_BUSY = 10057,/*backchan reqs outstanding*/ + NFS4ERR_LAYOUTTRYLATER = 10058, + NFS4ERR_LAYOUTUNAVAILABLE = 10059, + NFS4ERR_NOMATCHING_LAYOUT = 10060, + NFS4ERR_RECALLCONFLICT = 10061, + NFS4ERR_UNKNOWN_LAYOUTTYPE = 10062, + NFS4ERR_SEQ_MISORDERED = 10063,/* unexpected seq.ID in req*/ + NFS4ERR_SEQUENCE_POS = 10064,/* [CB_]SEQ. op not 1st op */ + NFS4ERR_REQ_TOO_BIG = 10065,/* request too big */ + NFS4ERR_REP_TOO_BIG = 10066,/* reply too big */ + NFS4ERR_REP_TOO_BIG_TO_CACHE =10067,/* rep. not all cached*/ + NFS4ERR_RETRY_UNCACHED_REP =10068,/* retry & rep. uncached*/ + NFS4ERR_UNSAFE_COMPOUND =10069,/* retry/recovery too hard */ + NFS4ERR_TOO_MANY_OPS = 10070,/*too many ops in [CB_]COMP*/ + NFS4ERR_OP_NOT_IN_SESSION =10071,/* op needs [CB_]SEQ. op */ + NFS4ERR_HASH_ALG_UNSUPP = 10072, /* hash alg. not supp. */ + /* Error 10073 is unused. */ + NFS4ERR_CLIENTID_BUSY = 10074,/* clientid has state */ + NFS4ERR_PNFS_IO_HOLE = 10075,/* IO to _SPARSE file hole */ + NFS4ERR_SEQ_FALSE_RETRY= 10076,/* Retry != original req. */ + NFS4ERR_BAD_HIGH_SLOT = 10077,/* req has bad highest_slot*/ + NFS4ERR_DEADSESSION = 10078,/*new req sent to dead sess*/ + NFS4ERR_ENCR_ALG_UNSUPP= 10079,/* encr alg. not supp. */ + NFS4ERR_PNFS_NO_LAYOUT = 10080,/* I/O without a layout */ + NFS4ERR_NOT_ONLY_OP = 10081,/* addl ops not allowed */ + NFS4ERR_WRONG_CRED = 10082,/* op done by wrong cred */ + NFS4ERR_WRONG_TYPE = 10083,/* op on wrong type object */ + NFS4ERR_DIRDELEG_UNAVAIL=10084,/* delegation not avail. */ + NFS4ERR_REJECT_DELEG = 10085,/* cb rejected delegation */ + NFS4ERR_RETURNCONFLICT = 10086,/* layout get before return*/ + NFS4ERR_DELEG_REVOKED = 10087, /* deleg./layout revoked */ + NFS4ERR_PARTNER_NOTSUPP = 10088, + NFS4ERR_PARTNER_NO_AUTH = 10089, + NFS4ERR_UNION_NOTSUPP = 10090, + NFS4ERR_OFFLOAD_DENIED = 10091, + NFS4ERR_WRONG_LFS = 10092, + NFS4ERR_BADLABEL = 10093, + NFS4ERR_OFFLOAD_NO_REQS = 10094, + NFS4ERR_NOXATTR = 10095, + NFS4ERR_XATTR2BIG = 10096 +}; /* * Basic data types */ +typedef opaque attrlist4<>; typedef uint32_t bitmap4<>; +typedef opaque verifier4[NFS4_VERIFIER_SIZE]; +typedef uint64_t nfs_cookie4; +typedef opaque nfs_fh4; typedef opaque utf8string<>; typedef utf8string utf8str_cis; typedef utf8string utf8str_cs; typedef utf8string utf8str_mixed; +typedef utf8str_cs component4; +typedef utf8str_cs linktext4; +typedef component4 pathname4<>; + /* * Timeval */ @@ -66,6 +209,21 @@ struct nfstime4 { uint32_t nseconds; }; +/* + * File attribute container + */ +struct fattr4 { + bitmap4 attrmask; + attrlist4 attr_vals; +}; + +/* + * Stateid + */ +struct stateid4 { + uint32_t seqid; + opaque other[12]; +}; /* * The following content was extracted from draft-ietf-nfsv4-delstid @@ -245,3 +403,82 @@ const FATTR4_ACL_TRUEFORM = 89; const FATTR4_ACL_TRUEFORM_SCOPE = 90; const FATTR4_POSIX_DEFAULT_ACL = 91; const FATTR4_POSIX_ACCESS_ACL = 92; + +/* + * Directory notification types. + */ +enum notify_type4 { + NOTIFY4_CHANGE_CHILD_ATTRS = 0, + NOTIFY4_CHANGE_DIR_ATTRS = 1, + NOTIFY4_REMOVE_ENTRY = 2, + NOTIFY4_ADD_ENTRY = 3, + NOTIFY4_RENAME_ENTRY = 4, + NOTIFY4_CHANGE_COOKIE_VERIFIER = 5 +}; + +/* Changed entry information. */ +struct notify_entry4 { + component4 ne_file; + fattr4 ne_attrs; +}; + +/* Previous entry information */ +struct prev_entry4 { + notify_entry4 pe_prev_entry; + /* what READDIR returned for this entry */ + nfs_cookie4 pe_prev_entry_cookie; +}; + +struct notify_remove4 { + notify_entry4 nrm_old_entry; + nfs_cookie4 nrm_old_entry_cookie; +}; + +struct notify_add4 { + /* + * Information on object + * possibly renamed over. + */ + notify_remove4 nad_old_entry<1>; + notify_entry4 nad_new_entry; + /* what READDIR would have returned for this entry */ + nfs_cookie4 nad_new_entry_cookie<1>; + prev_entry4 nad_prev_entry<1>; + bool nad_last_entry; +}; + +struct notify_attr4 { + notify_entry4 na_changed_entry; +}; + +struct notify_rename4 { + notify_remove4 nrn_old_entry; + notify_add4 nrn_new_entry; +}; + +struct notify_verifier4 { + verifier4 nv_old_cookieverf; + verifier4 nv_new_cookieverf; +}; + +/* + * Objects of type notify_<>4 and + * notify_device_<>4 are encoded in this. + */ +typedef opaque notifylist4<>; + +struct notify4 { + /* composed from notify_type4 or notify_deviceid_type4 */ + bitmap4 notify_mask; + notifylist4 notify_vals; +}; + +struct CB_NOTIFY4args { + stateid4 cna_stateid; + nfs_fh4 cna_fh; + notify4 cna_changes<>; +}; + +struct CB_NOTIFY4res { + nfsstat4 cnr_status; +}; diff --git a/fs/nfsd/nfs4xdr_gen.c b/fs/nfsd/nfs4xdr_gen.c index 824497051b87..3a9c82418223 100644 --- a/fs/nfsd/nfs4xdr_gen.c +++ b/fs/nfsd/nfs4xdr_gen.c @@ -1,16 +1,16 @@ // SPDX-License-Identifier: GPL-2.0 // Generated by xdrgen. Manual edits will be lost. // XDR specification file: ../../Documentation/sunrpc/xdr/nfs4_1.x -// XDR specification modification time: Thu Jan 8 23:12:07 2026 +// XDR specification modification time: Wed Mar 25 11:40:02 2026 #include #include "nfs4xdr_gen.h" static bool __maybe_unused -xdrgen_decode_int64_t(struct xdr_stream *xdr, int64_t *ptr) +xdrgen_decode_int32_t(struct xdr_stream *xdr, int32_t *ptr) { - return xdrgen_decode_hyper(xdr, ptr); + return xdrgen_decode_int(xdr, ptr); } static bool __maybe_unused @@ -19,6 +19,154 @@ xdrgen_decode_uint32_t(struct xdr_stream *xdr, uint32_t *ptr) return xdrgen_decode_unsigned_int(xdr, ptr); } +static bool __maybe_unused +xdrgen_decode_int64_t(struct xdr_stream *xdr, int64_t *ptr) +{ + return xdrgen_decode_hyper(xdr, ptr); +} + +static bool __maybe_unused +xdrgen_decode_uint64_t(struct xdr_stream *xdr, uint64_t *ptr) +{ + return xdrgen_decode_unsigned_hyper(xdr, ptr); +} + +static bool __maybe_unused +xdrgen_decode_nfsstat4(struct xdr_stream *xdr, nfsstat4 *ptr) +{ + u32 val; + + if (xdr_stream_decode_u32(xdr, &val) < 0) + return false; + /* Compiler may optimize to a range check for dense enums */ + switch (val) { + case NFS4_OK: + case NFS4ERR_PERM: + case NFS4ERR_NOENT: + case NFS4ERR_IO: + case NFS4ERR_NXIO: + case NFS4ERR_ACCESS: + case NFS4ERR_EXIST: + case NFS4ERR_XDEV: + case NFS4ERR_NOTDIR: + case NFS4ERR_ISDIR: + case NFS4ERR_INVAL: + case NFS4ERR_FBIG: + case NFS4ERR_NOSPC: + case NFS4ERR_ROFS: + case NFS4ERR_MLINK: + case NFS4ERR_NAMETOOLONG: + case NFS4ERR_NOTEMPTY: + case NFS4ERR_DQUOT: + case NFS4ERR_STALE: + case NFS4ERR_BADHANDLE: + case NFS4ERR_BAD_COOKIE: + case NFS4ERR_NOTSUPP: + case NFS4ERR_TOOSMALL: + case NFS4ERR_SERVERFAULT: + case NFS4ERR_BADTYPE: + case NFS4ERR_DELAY: + case NFS4ERR_SAME: + case NFS4ERR_DENIED: + case NFS4ERR_EXPIRED: + case NFS4ERR_LOCKED: + case NFS4ERR_GRACE: + case NFS4ERR_FHEXPIRED: + case NFS4ERR_SHARE_DENIED: + case NFS4ERR_WRONGSEC: + case NFS4ERR_CLID_INUSE: + case NFS4ERR_RESOURCE: + case NFS4ERR_MOVED: + case NFS4ERR_NOFILEHANDLE: + case NFS4ERR_MINOR_VERS_MISMATCH: + case NFS4ERR_STALE_CLIENTID: + case NFS4ERR_STALE_STATEID: + case NFS4ERR_OLD_STATEID: + case NFS4ERR_BAD_STATEID: + case NFS4ERR_BAD_SEQID: + case NFS4ERR_NOT_SAME: + case NFS4ERR_LOCK_RANGE: + case NFS4ERR_SYMLINK: + case NFS4ERR_RESTOREFH: + case NFS4ERR_LEASE_MOVED: + case NFS4ERR_ATTRNOTSUPP: + case NFS4ERR_NO_GRACE: + case NFS4ERR_RECLAIM_BAD: + case NFS4ERR_RECLAIM_CONFLICT: + case NFS4ERR_BADXDR: + case NFS4ERR_LOCKS_HELD: + case NFS4ERR_OPENMODE: + case NFS4ERR_BADOWNER: + case NFS4ERR_BADCHAR: + case NFS4ERR_BADNAME: + case NFS4ERR_BAD_RANGE: + case NFS4ERR_LOCK_NOTSUPP: + case NFS4ERR_OP_ILLEGAL: + case NFS4ERR_DEADLOCK: + case NFS4ERR_FILE_OPEN: + case NFS4ERR_ADMIN_REVOKED: + case NFS4ERR_CB_PATH_DOWN: + case NFS4ERR_BADIOMODE: + case NFS4ERR_BADLAYOUT: + case NFS4ERR_BAD_SESSION_DIGEST: + case NFS4ERR_BADSESSION: + case NFS4ERR_BADSLOT: + case NFS4ERR_COMPLETE_ALREADY: + case NFS4ERR_CONN_NOT_BOUND_TO_SESSION: + case NFS4ERR_DELEG_ALREADY_WANTED: + case NFS4ERR_BACK_CHAN_BUSY: + case NFS4ERR_LAYOUTTRYLATER: + case NFS4ERR_LAYOUTUNAVAILABLE: + case NFS4ERR_NOMATCHING_LAYOUT: + case NFS4ERR_RECALLCONFLICT: + case NFS4ERR_UNKNOWN_LAYOUTTYPE: + case NFS4ERR_SEQ_MISORDERED: + case NFS4ERR_SEQUENCE_POS: + case NFS4ERR_REQ_TOO_BIG: + case NFS4ERR_REP_TOO_BIG: + case NFS4ERR_REP_TOO_BIG_TO_CACHE: + case NFS4ERR_RETRY_UNCACHED_REP: + case NFS4ERR_UNSAFE_COMPOUND: + case NFS4ERR_TOO_MANY_OPS: + case NFS4ERR_OP_NOT_IN_SESSION: + case NFS4ERR_HASH_ALG_UNSUPP: + case NFS4ERR_CLIENTID_BUSY: + case NFS4ERR_PNFS_IO_HOLE: + case NFS4ERR_SEQ_FALSE_RETRY: + case NFS4ERR_BAD_HIGH_SLOT: + case NFS4ERR_DEADSESSION: + case NFS4ERR_ENCR_ALG_UNSUPP: + case NFS4ERR_PNFS_NO_LAYOUT: + case NFS4ERR_NOT_ONLY_OP: + case NFS4ERR_WRONG_CRED: + case NFS4ERR_WRONG_TYPE: + case NFS4ERR_DIRDELEG_UNAVAIL: + case NFS4ERR_REJECT_DELEG: + case NFS4ERR_RETURNCONFLICT: + case NFS4ERR_DELEG_REVOKED: + case NFS4ERR_PARTNER_NOTSUPP: + case NFS4ERR_PARTNER_NO_AUTH: + case NFS4ERR_UNION_NOTSUPP: + case NFS4ERR_OFFLOAD_DENIED: + case NFS4ERR_WRONG_LFS: + case NFS4ERR_BADLABEL: + case NFS4ERR_OFFLOAD_NO_REQS: + case NFS4ERR_NOXATTR: + case NFS4ERR_XATTR2BIG: + break; + default: + return false; + } + *ptr = val; + return true; +} + +static bool __maybe_unused +xdrgen_decode_attrlist4(struct xdr_stream *xdr, attrlist4 *ptr) +{ + return xdrgen_decode_opaque(xdr, ptr, 0); +} + static bool __maybe_unused xdrgen_decode_bitmap4(struct xdr_stream *xdr, bitmap4 *ptr) { @@ -30,6 +178,24 @@ xdrgen_decode_bitmap4(struct xdr_stream *xdr, bitmap4 *ptr) return true; } +static bool __maybe_unused +xdrgen_decode_verifier4(struct xdr_stream *xdr, verifier4 *ptr) +{ + return xdr_stream_decode_opaque_fixed(xdr, ptr, NFS4_VERIFIER_SIZE) == 0; +} + +static bool __maybe_unused +xdrgen_decode_nfs_cookie4(struct xdr_stream *xdr, nfs_cookie4 *ptr) +{ + return xdrgen_decode_uint64_t(xdr, ptr); +} + +static bool __maybe_unused +xdrgen_decode_nfs_fh4(struct xdr_stream *xdr, nfs_fh4 *ptr) +{ + return xdrgen_decode_opaque(xdr, ptr, NFS4_FHSIZE); +} + static bool __maybe_unused xdrgen_decode_utf8string(struct xdr_stream *xdr, utf8string *ptr) { @@ -54,6 +220,29 @@ xdrgen_decode_utf8str_mixed(struct xdr_stream *xdr, utf8str_mixed *ptr) return xdrgen_decode_utf8string(xdr, ptr); } +static bool __maybe_unused +xdrgen_decode_component4(struct xdr_stream *xdr, component4 *ptr) +{ + return xdrgen_decode_utf8str_cs(xdr, ptr); +} + +static bool __maybe_unused +xdrgen_decode_linktext4(struct xdr_stream *xdr, linktext4 *ptr) +{ + return xdrgen_decode_utf8str_cs(xdr, ptr); +} + +static bool __maybe_unused +xdrgen_decode_pathname4(struct xdr_stream *xdr, pathname4 *ptr) +{ + if (xdr_stream_decode_u32(xdr, &ptr->count) < 0) + return false; + for (u32 i = 0; i < ptr->count; i++) + if (!xdrgen_decode_component4(xdr, &ptr->element[i])) + return false; + return true; +} + static bool __maybe_unused xdrgen_decode_nfstime4(struct xdr_stream *xdr, struct nfstime4 *ptr) { @@ -64,6 +253,26 @@ xdrgen_decode_nfstime4(struct xdr_stream *xdr, struct nfstime4 *ptr) return true; } +static bool __maybe_unused +xdrgen_decode_fattr4(struct xdr_stream *xdr, struct fattr4 *ptr) +{ + if (!xdrgen_decode_bitmap4(xdr, &ptr->attrmask)) + return false; + if (!xdrgen_decode_attrlist4(xdr, &ptr->attr_vals)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_decode_stateid4(struct xdr_stream *xdr, struct stateid4 *ptr) +{ + if (!xdrgen_decode_uint32_t(xdr, &ptr->seqid)) + return false; + if (xdr_stream_decode_opaque_fixed(xdr, ptr->other, 12) < 0) + return false; + return true; +} + static bool __maybe_unused xdrgen_decode_fattr4_offline(struct xdr_stream *xdr, fattr4_offline *ptr) { @@ -366,9 +575,160 @@ xdrgen_decode_fattr4_posix_access_acl(struct xdr_stream *xdr, fattr4_posix_acces */ static bool __maybe_unused -xdrgen_encode_int64_t(struct xdr_stream *xdr, const int64_t value) +xdrgen_decode_notify_type4(struct xdr_stream *xdr, notify_type4 *ptr) { - return xdrgen_encode_hyper(xdr, value); + u32 val; + + if (xdr_stream_decode_u32(xdr, &val) < 0) + return false; + /* Compiler may optimize to a range check for dense enums */ + switch (val) { + case NOTIFY4_CHANGE_CHILD_ATTRS: + case NOTIFY4_CHANGE_DIR_ATTRS: + case NOTIFY4_REMOVE_ENTRY: + case NOTIFY4_ADD_ENTRY: + case NOTIFY4_RENAME_ENTRY: + case NOTIFY4_CHANGE_COOKIE_VERIFIER: + break; + default: + return false; + } + *ptr = val; + return true; +} + +static bool __maybe_unused +xdrgen_decode_notify_entry4(struct xdr_stream *xdr, struct notify_entry4 *ptr) +{ + if (!xdrgen_decode_component4(xdr, &ptr->ne_file)) + return false; + if (!xdrgen_decode_fattr4(xdr, &ptr->ne_attrs)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_decode_prev_entry4(struct xdr_stream *xdr, struct prev_entry4 *ptr) +{ + if (!xdrgen_decode_notify_entry4(xdr, &ptr->pe_prev_entry)) + return false; + if (!xdrgen_decode_nfs_cookie4(xdr, &ptr->pe_prev_entry_cookie)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_decode_notify_remove4(struct xdr_stream *xdr, struct notify_remove4 *ptr) +{ + if (!xdrgen_decode_notify_entry4(xdr, &ptr->nrm_old_entry)) + return false; + if (!xdrgen_decode_nfs_cookie4(xdr, &ptr->nrm_old_entry_cookie)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_decode_notify_add4(struct xdr_stream *xdr, struct notify_add4 *ptr) +{ + if (xdr_stream_decode_u32(xdr, &ptr->nad_old_entry.count) < 0) + return false; + if (ptr->nad_old_entry.count > 1) + return false; + for (u32 i = 0; i < ptr->nad_old_entry.count; i++) + if (!xdrgen_decode_notify_remove4(xdr, &ptr->nad_old_entry.element[i])) + return false; + if (!xdrgen_decode_notify_entry4(xdr, &ptr->nad_new_entry)) + return false; + if (xdr_stream_decode_u32(xdr, &ptr->nad_new_entry_cookie.count) < 0) + return false; + if (ptr->nad_new_entry_cookie.count > 1) + return false; + for (u32 i = 0; i < ptr->nad_new_entry_cookie.count; i++) + if (!xdrgen_decode_nfs_cookie4(xdr, &ptr->nad_new_entry_cookie.element[i])) + return false; + if (xdr_stream_decode_u32(xdr, &ptr->nad_prev_entry.count) < 0) + return false; + if (ptr->nad_prev_entry.count > 1) + return false; + for (u32 i = 0; i < ptr->nad_prev_entry.count; i++) + if (!xdrgen_decode_prev_entry4(xdr, &ptr->nad_prev_entry.element[i])) + return false; + if (!xdrgen_decode_bool(xdr, &ptr->nad_last_entry)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_decode_notify_attr4(struct xdr_stream *xdr, struct notify_attr4 *ptr) +{ + if (!xdrgen_decode_notify_entry4(xdr, &ptr->na_changed_entry)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_decode_notify_rename4(struct xdr_stream *xdr, struct notify_rename4 *ptr) +{ + if (!xdrgen_decode_notify_remove4(xdr, &ptr->nrn_old_entry)) + return false; + if (!xdrgen_decode_notify_add4(xdr, &ptr->nrn_new_entry)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_decode_notify_verifier4(struct xdr_stream *xdr, struct notify_verifier4 *ptr) +{ + if (!xdrgen_decode_verifier4(xdr, &ptr->nv_old_cookieverf)) + return false; + if (!xdrgen_decode_verifier4(xdr, &ptr->nv_new_cookieverf)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_decode_notifylist4(struct xdr_stream *xdr, notifylist4 *ptr) +{ + return xdrgen_decode_opaque(xdr, ptr, 0); +} + +static bool __maybe_unused +xdrgen_decode_notify4(struct xdr_stream *xdr, struct notify4 *ptr) +{ + if (!xdrgen_decode_bitmap4(xdr, &ptr->notify_mask)) + return false; + if (!xdrgen_decode_notifylist4(xdr, &ptr->notify_vals)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_decode_CB_NOTIFY4args(struct xdr_stream *xdr, struct CB_NOTIFY4args *ptr) +{ + if (!xdrgen_decode_stateid4(xdr, &ptr->cna_stateid)) + return false; + if (!xdrgen_decode_nfs_fh4(xdr, &ptr->cna_fh)) + return false; + if (xdr_stream_decode_u32(xdr, &ptr->cna_changes.count) < 0) + return false; + for (u32 i = 0; i < ptr->cna_changes.count; i++) + if (!xdrgen_decode_notify4(xdr, &ptr->cna_changes.element[i])) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_decode_CB_NOTIFY4res(struct xdr_stream *xdr, struct CB_NOTIFY4res *ptr) +{ + if (!xdrgen_decode_nfsstat4(xdr, &ptr->cnr_status)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_encode_int32_t(struct xdr_stream *xdr, const int32_t value) +{ + return xdrgen_encode_int(xdr, value); } static bool __maybe_unused @@ -377,6 +737,30 @@ xdrgen_encode_uint32_t(struct xdr_stream *xdr, const uint32_t value) return xdrgen_encode_unsigned_int(xdr, value); } +static bool __maybe_unused +xdrgen_encode_int64_t(struct xdr_stream *xdr, const int64_t value) +{ + return xdrgen_encode_hyper(xdr, value); +} + +static bool __maybe_unused +xdrgen_encode_uint64_t(struct xdr_stream *xdr, const uint64_t value) +{ + return xdrgen_encode_unsigned_hyper(xdr, value); +} + +static bool __maybe_unused +xdrgen_encode_nfsstat4(struct xdr_stream *xdr, nfsstat4 value) +{ + return xdr_stream_encode_u32(xdr, value) == XDR_UNIT; +} + +static bool __maybe_unused +xdrgen_encode_attrlist4(struct xdr_stream *xdr, const attrlist4 value) +{ + return xdr_stream_encode_opaque(xdr, value.data, value.len) >= 0; +} + static bool __maybe_unused xdrgen_encode_bitmap4(struct xdr_stream *xdr, const bitmap4 value) { @@ -388,6 +772,24 @@ xdrgen_encode_bitmap4(struct xdr_stream *xdr, const bitmap4 value) return true; } +static bool __maybe_unused +xdrgen_encode_verifier4(struct xdr_stream *xdr, const verifier4 value) +{ + return xdr_stream_encode_opaque_fixed(xdr, value, NFS4_VERIFIER_SIZE) >= 0; +} + +static bool __maybe_unused +xdrgen_encode_nfs_cookie4(struct xdr_stream *xdr, const nfs_cookie4 value) +{ + return xdrgen_encode_uint64_t(xdr, value); +} + +static bool __maybe_unused +xdrgen_encode_nfs_fh4(struct xdr_stream *xdr, const nfs_fh4 value) +{ + return xdr_stream_encode_opaque(xdr, value.data, value.len) >= 0; +} + static bool __maybe_unused xdrgen_encode_utf8string(struct xdr_stream *xdr, const utf8string value) { @@ -412,6 +814,29 @@ xdrgen_encode_utf8str_mixed(struct xdr_stream *xdr, const utf8str_mixed value) return xdrgen_encode_utf8string(xdr, value); } +static bool __maybe_unused +xdrgen_encode_component4(struct xdr_stream *xdr, const component4 value) +{ + return xdrgen_encode_utf8str_cs(xdr, value); +} + +static bool __maybe_unused +xdrgen_encode_linktext4(struct xdr_stream *xdr, const linktext4 value) +{ + return xdrgen_encode_utf8str_cs(xdr, value); +} + +static bool __maybe_unused +xdrgen_encode_pathname4(struct xdr_stream *xdr, const pathname4 value) +{ + if (xdr_stream_encode_u32(xdr, value.count) != XDR_UNIT) + return false; + for (u32 i = 0; i < value.count; i++) + if (!xdrgen_encode_component4(xdr, value.element[i])) + return false; + return true; +} + static bool __maybe_unused xdrgen_encode_nfstime4(struct xdr_stream *xdr, const struct nfstime4 *value) { @@ -422,6 +847,26 @@ xdrgen_encode_nfstime4(struct xdr_stream *xdr, const struct nfstime4 *value) return true; } +static bool __maybe_unused +xdrgen_encode_fattr4(struct xdr_stream *xdr, const struct fattr4 *value) +{ + if (!xdrgen_encode_bitmap4(xdr, value->attrmask)) + return false; + if (!xdrgen_encode_attrlist4(xdr, value->attr_vals)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_encode_stateid4(struct xdr_stream *xdr, const struct stateid4 *value) +{ + if (!xdrgen_encode_uint32_t(xdr, value->seqid)) + return false; + if (xdr_stream_encode_opaque_fixed(xdr, value->other, 12) < 0) + return false; + return true; +} + static bool __maybe_unused xdrgen_encode_fattr4_offline(struct xdr_stream *xdr, const fattr4_offline value) { @@ -567,3 +1012,137 @@ xdrgen_encode_fattr4_posix_access_acl(struct xdr_stream *xdr, const fattr4_posix return false; return true; } + +static bool __maybe_unused +xdrgen_encode_notify_type4(struct xdr_stream *xdr, notify_type4 value) +{ + return xdr_stream_encode_u32(xdr, value) == XDR_UNIT; +} + +static bool __maybe_unused +xdrgen_encode_notify_entry4(struct xdr_stream *xdr, const struct notify_entry4 *value) +{ + if (!xdrgen_encode_component4(xdr, value->ne_file)) + return false; + if (!xdrgen_encode_fattr4(xdr, &value->ne_attrs)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_encode_prev_entry4(struct xdr_stream *xdr, const struct prev_entry4 *value) +{ + if (!xdrgen_encode_notify_entry4(xdr, &value->pe_prev_entry)) + return false; + if (!xdrgen_encode_nfs_cookie4(xdr, value->pe_prev_entry_cookie)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_encode_notify_remove4(struct xdr_stream *xdr, const struct notify_remove4 *value) +{ + if (!xdrgen_encode_notify_entry4(xdr, &value->nrm_old_entry)) + return false; + if (!xdrgen_encode_nfs_cookie4(xdr, value->nrm_old_entry_cookie)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_encode_notify_add4(struct xdr_stream *xdr, const struct notify_add4 *value) +{ + if (value->nad_old_entry.count > 1) + return false; + if (xdr_stream_encode_u32(xdr, value->nad_old_entry.count) != XDR_UNIT) + return false; + for (u32 i = 0; i < value->nad_old_entry.count; i++) + if (!xdrgen_encode_notify_remove4(xdr, &value->nad_old_entry.element[i])) + return false; + if (!xdrgen_encode_notify_entry4(xdr, &value->nad_new_entry)) + return false; + if (value->nad_new_entry_cookie.count > 1) + return false; + if (xdr_stream_encode_u32(xdr, value->nad_new_entry_cookie.count) != XDR_UNIT) + return false; + for (u32 i = 0; i < value->nad_new_entry_cookie.count; i++) + if (!xdrgen_encode_nfs_cookie4(xdr, value->nad_new_entry_cookie.element[i])) + return false; + if (value->nad_prev_entry.count > 1) + return false; + if (xdr_stream_encode_u32(xdr, value->nad_prev_entry.count) != XDR_UNIT) + return false; + for (u32 i = 0; i < value->nad_prev_entry.count; i++) + if (!xdrgen_encode_prev_entry4(xdr, &value->nad_prev_entry.element[i])) + return false; + if (!xdrgen_encode_bool(xdr, value->nad_last_entry)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_encode_notify_attr4(struct xdr_stream *xdr, const struct notify_attr4 *value) +{ + if (!xdrgen_encode_notify_entry4(xdr, &value->na_changed_entry)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_encode_notify_rename4(struct xdr_stream *xdr, const struct notify_rename4 *value) +{ + if (!xdrgen_encode_notify_remove4(xdr, &value->nrn_old_entry)) + return false; + if (!xdrgen_encode_notify_add4(xdr, &value->nrn_new_entry)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_encode_notify_verifier4(struct xdr_stream *xdr, const struct notify_verifier4 *value) +{ + if (!xdrgen_encode_verifier4(xdr, value->nv_old_cookieverf)) + return false; + if (!xdrgen_encode_verifier4(xdr, value->nv_new_cookieverf)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_encode_notifylist4(struct xdr_stream *xdr, const notifylist4 value) +{ + return xdr_stream_encode_opaque(xdr, value.data, value.len) >= 0; +} + +static bool __maybe_unused +xdrgen_encode_notify4(struct xdr_stream *xdr, const struct notify4 *value) +{ + if (!xdrgen_encode_bitmap4(xdr, value->notify_mask)) + return false; + if (!xdrgen_encode_notifylist4(xdr, value->notify_vals)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_encode_CB_NOTIFY4args(struct xdr_stream *xdr, const struct CB_NOTIFY4args *value) +{ + if (!xdrgen_encode_stateid4(xdr, &value->cna_stateid)) + return false; + if (!xdrgen_encode_nfs_fh4(xdr, value->cna_fh)) + return false; + if (xdr_stream_encode_u32(xdr, value->cna_changes.count) != XDR_UNIT) + return false; + for (u32 i = 0; i < value->cna_changes.count; i++) + if (!xdrgen_encode_notify4(xdr, &value->cna_changes.element[i])) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_encode_CB_NOTIFY4res(struct xdr_stream *xdr, const struct CB_NOTIFY4res *value) +{ + if (!xdrgen_encode_nfsstat4(xdr, value->cnr_status)) + return false; + return true; +} diff --git a/fs/nfsd/nfs4xdr_gen.h b/fs/nfsd/nfs4xdr_gen.h index 1c487f1a11ab..b989f37cdee8 100644 --- a/fs/nfsd/nfs4xdr_gen.h +++ b/fs/nfsd/nfs4xdr_gen.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* Generated by xdrgen. Manual edits will be lost. */ /* XDR specification file: ../../Documentation/sunrpc/xdr/nfs4_1.x */ -/* XDR specification modification time: Thu Jan 8 23:12:07 2026 */ +/* XDR specification modification time: Wed Mar 25 11:40:02 2026 */ #ifndef _LINUX_XDRGEN_NFS4_1_DECL_H #define _LINUX_XDRGEN_NFS4_1_DECL_H diff --git a/fs/nfsd/nfsd.h b/fs/nfsd/nfsd.h index 11bce03b9031..d33d49b61e6a 100644 --- a/fs/nfsd/nfsd.h +++ b/fs/nfsd/nfsd.h @@ -332,13 +332,14 @@ void nfsd_lockd_shutdown(void); #define nfserr_noxattr cpu_to_be32(NFS4ERR_NOXATTR) /* - * Error codes for internal use. We use enum to choose numbers that are - * not already assigned, then covert to be32 resulting in a number that - * cannot conflict with any existing be32 nfserr value. + * Error codes for internal use. These are based at an impossible + * nfsstat4 value so that, once converted to be32, they cannot conflict + * with any value defined by the protocol (compare the nlm__int__* codes + * in fs/lockd/lockd.h). */ enum { /* end-of-file indicator in readdir */ - NFSERR_EOF = NFS4ERR_FIRST_FREE, + NFSERR_EOF = 30000, #define nfserr_eof cpu_to_be32(NFSERR_EOF) /* replay detected */ diff --git a/fs/nfsd/trace.h b/fs/nfsd/trace.h index 33953d38314e..171e8fdbafb6 100644 --- a/fs/nfsd/trace.h +++ b/fs/nfsd/trace.h @@ -1677,6 +1677,7 @@ TRACE_EVENT(nfsd_cb_setup_err, { OP_CB_RECALL, "CB_RECALL" }, \ { OP_CB_LAYOUTRECALL, "CB_LAYOUTRECALL" }, \ { OP_CB_RECALL_ANY, "CB_RECALL_ANY" }, \ + { OP_CB_NOTIFY, "CB_NOTIFY" }, \ { OP_CB_NOTIFY_LOCK, "CB_NOTIFY_LOCK" }, \ { OP_CB_OFFLOAD, "CB_OFFLOAD" }) diff --git a/include/linux/nfs4.h b/include/linux/nfs4.h index d87be1f25273..44e5e9fa12e1 100644 --- a/include/linux/nfs4.h +++ b/include/linux/nfs4.h @@ -171,133 +171,6 @@ Needs to be updated if more operations are defined in future.*/ #define LAST_NFS42_OP OP_REMOVEXATTR #define LAST_NFS4_OP LAST_NFS42_OP -enum nfsstat4 { - NFS4_OK = 0, - NFS4ERR_PERM = 1, - NFS4ERR_NOENT = 2, - NFS4ERR_IO = 5, - NFS4ERR_NXIO = 6, - NFS4ERR_ACCESS = 13, - NFS4ERR_EXIST = 17, - NFS4ERR_XDEV = 18, - /* Unused/reserved 19 */ - NFS4ERR_NOTDIR = 20, - NFS4ERR_ISDIR = 21, - NFS4ERR_INVAL = 22, - NFS4ERR_FBIG = 27, - NFS4ERR_NOSPC = 28, - NFS4ERR_ROFS = 30, - NFS4ERR_MLINK = 31, - NFS4ERR_NAMETOOLONG = 63, - NFS4ERR_NOTEMPTY = 66, - NFS4ERR_DQUOT = 69, - NFS4ERR_STALE = 70, - NFS4ERR_BADHANDLE = 10001, - NFS4ERR_BAD_COOKIE = 10003, - NFS4ERR_NOTSUPP = 10004, - NFS4ERR_TOOSMALL = 10005, - NFS4ERR_SERVERFAULT = 10006, - NFS4ERR_BADTYPE = 10007, - NFS4ERR_DELAY = 10008, - NFS4ERR_SAME = 10009, - NFS4ERR_DENIED = 10010, - NFS4ERR_EXPIRED = 10011, - NFS4ERR_LOCKED = 10012, - NFS4ERR_GRACE = 10013, - NFS4ERR_FHEXPIRED = 10014, - NFS4ERR_SHARE_DENIED = 10015, - NFS4ERR_WRONGSEC = 10016, - NFS4ERR_CLID_INUSE = 10017, - NFS4ERR_RESOURCE = 10018, - NFS4ERR_MOVED = 10019, - NFS4ERR_NOFILEHANDLE = 10020, - NFS4ERR_MINOR_VERS_MISMATCH = 10021, - NFS4ERR_STALE_CLIENTID = 10022, - NFS4ERR_STALE_STATEID = 10023, - NFS4ERR_OLD_STATEID = 10024, - NFS4ERR_BAD_STATEID = 10025, - NFS4ERR_BAD_SEQID = 10026, - NFS4ERR_NOT_SAME = 10027, - NFS4ERR_LOCK_RANGE = 10028, - NFS4ERR_SYMLINK = 10029, - NFS4ERR_RESTOREFH = 10030, - NFS4ERR_LEASE_MOVED = 10031, - NFS4ERR_ATTRNOTSUPP = 10032, - NFS4ERR_NO_GRACE = 10033, - NFS4ERR_RECLAIM_BAD = 10034, - NFS4ERR_RECLAIM_CONFLICT = 10035, - NFS4ERR_BADXDR = 10036, - NFS4ERR_LOCKS_HELD = 10037, - NFS4ERR_OPENMODE = 10038, - NFS4ERR_BADOWNER = 10039, - NFS4ERR_BADCHAR = 10040, - NFS4ERR_BADNAME = 10041, - NFS4ERR_BAD_RANGE = 10042, - NFS4ERR_LOCK_NOTSUPP = 10043, - NFS4ERR_OP_ILLEGAL = 10044, - NFS4ERR_DEADLOCK = 10045, - NFS4ERR_FILE_OPEN = 10046, - NFS4ERR_ADMIN_REVOKED = 10047, - NFS4ERR_CB_PATH_DOWN = 10048, - - /* nfs41 */ - NFS4ERR_BADIOMODE = 10049, - NFS4ERR_BADLAYOUT = 10050, - NFS4ERR_BAD_SESSION_DIGEST = 10051, - NFS4ERR_BADSESSION = 10052, - NFS4ERR_BADSLOT = 10053, - NFS4ERR_COMPLETE_ALREADY = 10054, - NFS4ERR_CONN_NOT_BOUND_TO_SESSION = 10055, - NFS4ERR_DELEG_ALREADY_WANTED = 10056, - NFS4ERR_BACK_CHAN_BUSY = 10057, /* backchan reqs outstanding */ - NFS4ERR_LAYOUTTRYLATER = 10058, - NFS4ERR_LAYOUTUNAVAILABLE = 10059, - NFS4ERR_NOMATCHING_LAYOUT = 10060, - NFS4ERR_RECALLCONFLICT = 10061, - NFS4ERR_UNKNOWN_LAYOUTTYPE = 10062, - NFS4ERR_SEQ_MISORDERED = 10063, /* unexpected seq.id in req */ - NFS4ERR_SEQUENCE_POS = 10064, /* [CB_]SEQ. op not 1st op */ - NFS4ERR_REQ_TOO_BIG = 10065, /* request too big */ - NFS4ERR_REP_TOO_BIG = 10066, /* reply too big */ - NFS4ERR_REP_TOO_BIG_TO_CACHE = 10067, /* rep. not all cached */ - NFS4ERR_RETRY_UNCACHED_REP = 10068, /* retry & rep. uncached */ - NFS4ERR_UNSAFE_COMPOUND = 10069, /* retry/recovery too hard */ - NFS4ERR_TOO_MANY_OPS = 10070, /* too many ops in [CB_]COMP */ - NFS4ERR_OP_NOT_IN_SESSION = 10071, /* op needs [CB_]SEQ. op */ - NFS4ERR_HASH_ALG_UNSUPP = 10072, /* hash alg. not supp. */ - /* Error 10073 is unused. */ - NFS4ERR_CLIENTID_BUSY = 10074, /* clientid has state */ - NFS4ERR_PNFS_IO_HOLE = 10075, /* IO to _SPARSE file hole */ - NFS4ERR_SEQ_FALSE_RETRY = 10076, /* retry not original */ - NFS4ERR_BAD_HIGH_SLOT = 10077, /* sequence arg bad */ - NFS4ERR_DEADSESSION = 10078, /* persistent session dead */ - NFS4ERR_ENCR_ALG_UNSUPP = 10079, /* SSV alg mismatch */ - NFS4ERR_PNFS_NO_LAYOUT = 10080, /* direct I/O with no layout */ - NFS4ERR_NOT_ONLY_OP = 10081, /* bad compound */ - NFS4ERR_WRONG_CRED = 10082, /* permissions:state change */ - NFS4ERR_WRONG_TYPE = 10083, /* current operation mismatch */ - NFS4ERR_DIRDELEG_UNAVAIL = 10084, /* no directory delegation */ - NFS4ERR_REJECT_DELEG = 10085, /* on callback */ - NFS4ERR_RETURNCONFLICT = 10086, /* outstanding layoutreturn */ - NFS4ERR_DELEG_REVOKED = 10087, /* deleg./layout revoked */ - - /* nfs42 */ - NFS4ERR_PARTNER_NOTSUPP = 10088, - NFS4ERR_PARTNER_NO_AUTH = 10089, - NFS4ERR_UNION_NOTSUPP = 10090, - NFS4ERR_OFFLOAD_DENIED = 10091, - NFS4ERR_WRONG_LFS = 10092, - NFS4ERR_BADLABEL = 10093, - NFS4ERR_OFFLOAD_NO_REQS = 10094, - - /* xattr (RFC8276) */ - NFS4ERR_NOXATTR = 10095, - NFS4ERR_XATTR2BIG = 10096, - - /* can be used for internal errors */ - NFS4ERR_FIRST_FREE -}; - /* error codes for internal client use */ #define NFS4ERR_RESET_TO_MDS 12001 #define NFS4ERR_RESET_TO_PNFS 12002 diff --git a/include/linux/sunrpc/xdrgen/nfs4_1.h b/include/linux/sunrpc/xdrgen/nfs4_1.h index 4ac54bdbd335..bce993132bc0 100644 --- a/include/linux/sunrpc/xdrgen/nfs4_1.h +++ b/include/linux/sunrpc/xdrgen/nfs4_1.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* Generated by xdrgen. Manual edits will be lost. */ /* XDR specification file: ../../Documentation/sunrpc/xdr/nfs4_1.x */ -/* XDR specification modification time: Thu Jan 8 23:12:07 2026 */ +/* XDR specification modification time: Wed Mar 25 11:40:02 2026 */ #ifndef _LINUX_XDRGEN_NFS4_1_DEF_H #define _LINUX_XDRGEN_NFS4_1_DEF_H @@ -9,15 +9,149 @@ #include #include -typedef s64 int64_t; +typedef s32 int32_t; typedef u32 uint32_t; +typedef s64 int64_t; + +typedef u64 uint64_t; + +enum { NFS4_VERIFIER_SIZE = 8 }; + +enum { NFS4_FHSIZE = 128 }; + +enum nfsstat4 { + NFS4_OK = 0, + NFS4ERR_PERM = 1, + NFS4ERR_NOENT = 2, + NFS4ERR_IO = 5, + NFS4ERR_NXIO = 6, + NFS4ERR_ACCESS = 13, + NFS4ERR_EXIST = 17, + NFS4ERR_XDEV = 18, + NFS4ERR_NOTDIR = 20, + NFS4ERR_ISDIR = 21, + NFS4ERR_INVAL = 22, + NFS4ERR_FBIG = 27, + NFS4ERR_NOSPC = 28, + NFS4ERR_ROFS = 30, + NFS4ERR_MLINK = 31, + NFS4ERR_NAMETOOLONG = 63, + NFS4ERR_NOTEMPTY = 66, + NFS4ERR_DQUOT = 69, + NFS4ERR_STALE = 70, + NFS4ERR_BADHANDLE = 10001, + NFS4ERR_BAD_COOKIE = 10003, + NFS4ERR_NOTSUPP = 10004, + NFS4ERR_TOOSMALL = 10005, + NFS4ERR_SERVERFAULT = 10006, + NFS4ERR_BADTYPE = 10007, + NFS4ERR_DELAY = 10008, + NFS4ERR_SAME = 10009, + NFS4ERR_DENIED = 10010, + NFS4ERR_EXPIRED = 10011, + NFS4ERR_LOCKED = 10012, + NFS4ERR_GRACE = 10013, + NFS4ERR_FHEXPIRED = 10014, + NFS4ERR_SHARE_DENIED = 10015, + NFS4ERR_WRONGSEC = 10016, + NFS4ERR_CLID_INUSE = 10017, + NFS4ERR_RESOURCE = 10018, + NFS4ERR_MOVED = 10019, + NFS4ERR_NOFILEHANDLE = 10020, + NFS4ERR_MINOR_VERS_MISMATCH = 10021, + NFS4ERR_STALE_CLIENTID = 10022, + NFS4ERR_STALE_STATEID = 10023, + NFS4ERR_OLD_STATEID = 10024, + NFS4ERR_BAD_STATEID = 10025, + NFS4ERR_BAD_SEQID = 10026, + NFS4ERR_NOT_SAME = 10027, + NFS4ERR_LOCK_RANGE = 10028, + NFS4ERR_SYMLINK = 10029, + NFS4ERR_RESTOREFH = 10030, + NFS4ERR_LEASE_MOVED = 10031, + NFS4ERR_ATTRNOTSUPP = 10032, + NFS4ERR_NO_GRACE = 10033, + NFS4ERR_RECLAIM_BAD = 10034, + NFS4ERR_RECLAIM_CONFLICT = 10035, + NFS4ERR_BADXDR = 10036, + NFS4ERR_LOCKS_HELD = 10037, + NFS4ERR_OPENMODE = 10038, + NFS4ERR_BADOWNER = 10039, + NFS4ERR_BADCHAR = 10040, + NFS4ERR_BADNAME = 10041, + NFS4ERR_BAD_RANGE = 10042, + NFS4ERR_LOCK_NOTSUPP = 10043, + NFS4ERR_OP_ILLEGAL = 10044, + NFS4ERR_DEADLOCK = 10045, + NFS4ERR_FILE_OPEN = 10046, + NFS4ERR_ADMIN_REVOKED = 10047, + NFS4ERR_CB_PATH_DOWN = 10048, + NFS4ERR_BADIOMODE = 10049, + NFS4ERR_BADLAYOUT = 10050, + NFS4ERR_BAD_SESSION_DIGEST = 10051, + NFS4ERR_BADSESSION = 10052, + NFS4ERR_BADSLOT = 10053, + NFS4ERR_COMPLETE_ALREADY = 10054, + NFS4ERR_CONN_NOT_BOUND_TO_SESSION = 10055, + NFS4ERR_DELEG_ALREADY_WANTED = 10056, + NFS4ERR_BACK_CHAN_BUSY = 10057, + NFS4ERR_LAYOUTTRYLATER = 10058, + NFS4ERR_LAYOUTUNAVAILABLE = 10059, + NFS4ERR_NOMATCHING_LAYOUT = 10060, + NFS4ERR_RECALLCONFLICT = 10061, + NFS4ERR_UNKNOWN_LAYOUTTYPE = 10062, + NFS4ERR_SEQ_MISORDERED = 10063, + NFS4ERR_SEQUENCE_POS = 10064, + NFS4ERR_REQ_TOO_BIG = 10065, + NFS4ERR_REP_TOO_BIG = 10066, + NFS4ERR_REP_TOO_BIG_TO_CACHE = 10067, + NFS4ERR_RETRY_UNCACHED_REP = 10068, + NFS4ERR_UNSAFE_COMPOUND = 10069, + NFS4ERR_TOO_MANY_OPS = 10070, + NFS4ERR_OP_NOT_IN_SESSION = 10071, + NFS4ERR_HASH_ALG_UNSUPP = 10072, + NFS4ERR_CLIENTID_BUSY = 10074, + NFS4ERR_PNFS_IO_HOLE = 10075, + NFS4ERR_SEQ_FALSE_RETRY = 10076, + NFS4ERR_BAD_HIGH_SLOT = 10077, + NFS4ERR_DEADSESSION = 10078, + NFS4ERR_ENCR_ALG_UNSUPP = 10079, + NFS4ERR_PNFS_NO_LAYOUT = 10080, + NFS4ERR_NOT_ONLY_OP = 10081, + NFS4ERR_WRONG_CRED = 10082, + NFS4ERR_WRONG_TYPE = 10083, + NFS4ERR_DIRDELEG_UNAVAIL = 10084, + NFS4ERR_REJECT_DELEG = 10085, + NFS4ERR_RETURNCONFLICT = 10086, + NFS4ERR_DELEG_REVOKED = 10087, + NFS4ERR_PARTNER_NOTSUPP = 10088, + NFS4ERR_PARTNER_NO_AUTH = 10089, + NFS4ERR_UNION_NOTSUPP = 10090, + NFS4ERR_OFFLOAD_DENIED = 10091, + NFS4ERR_WRONG_LFS = 10092, + NFS4ERR_BADLABEL = 10093, + NFS4ERR_OFFLOAD_NO_REQS = 10094, + NFS4ERR_NOXATTR = 10095, + NFS4ERR_XATTR2BIG = 10096, +}; + +typedef enum nfsstat4 nfsstat4; + +typedef opaque attrlist4; + typedef struct { u32 count; uint32_t *element; } bitmap4; +typedef u8 verifier4[NFS4_VERIFIER_SIZE]; + +typedef uint64_t nfs_cookie4; + +typedef opaque nfs_fh4; + typedef opaque utf8string; typedef utf8string utf8str_cis; @@ -26,11 +160,30 @@ typedef utf8string utf8str_cs; typedef utf8string utf8str_mixed; +typedef utf8str_cs component4; + +typedef utf8str_cs linktext4; + +typedef struct { + u32 count; + component4 *element; +} pathname4; + struct nfstime4 { int64_t seconds; uint32_t nseconds; }; +struct fattr4 { + bitmap4 attrmask; + attrlist4 attr_vals; +}; + +struct stateid4 { + uint32_t seqid; + u8 other[12]; +}; + typedef bool fattr4_offline; enum { FATTR4_OFFLINE = 83 }; @@ -216,11 +369,98 @@ enum { FATTR4_POSIX_DEFAULT_ACL = 91 }; enum { FATTR4_POSIX_ACCESS_ACL = 92 }; -#define NFS4_int64_t_sz \ - (XDR_hyper) +enum notify_type4 { + NOTIFY4_CHANGE_CHILD_ATTRS = 0, + NOTIFY4_CHANGE_DIR_ATTRS = 1, + NOTIFY4_REMOVE_ENTRY = 2, + NOTIFY4_ADD_ENTRY = 3, + NOTIFY4_RENAME_ENTRY = 4, + NOTIFY4_CHANGE_COOKIE_VERIFIER = 5, +}; + +typedef enum notify_type4 notify_type4; + +struct notify_entry4 { + component4 ne_file; + struct fattr4 ne_attrs; +}; + +struct prev_entry4 { + struct notify_entry4 pe_prev_entry; + nfs_cookie4 pe_prev_entry_cookie; +}; + +struct notify_remove4 { + struct notify_entry4 nrm_old_entry; + nfs_cookie4 nrm_old_entry_cookie; +}; + +struct notify_add4 { + struct { + u32 count; + struct notify_remove4 *element; + } nad_old_entry; + struct notify_entry4 nad_new_entry; + struct { + u32 count; + nfs_cookie4 *element; + } nad_new_entry_cookie; + struct { + u32 count; + struct prev_entry4 *element; + } nad_prev_entry; + bool nad_last_entry; +}; + +struct notify_attr4 { + struct notify_entry4 na_changed_entry; +}; + +struct notify_rename4 { + struct notify_remove4 nrn_old_entry; + struct notify_add4 nrn_new_entry; +}; + +struct notify_verifier4 { + verifier4 nv_old_cookieverf; + verifier4 nv_new_cookieverf; +}; + +typedef opaque notifylist4; + +struct notify4 { + bitmap4 notify_mask; + notifylist4 notify_vals; +}; + +struct CB_NOTIFY4args { + struct stateid4 cna_stateid; + nfs_fh4 cna_fh; + struct { + u32 count; + struct notify4 *element; + } cna_changes; +}; + +struct CB_NOTIFY4res { + nfsstat4 cnr_status; +}; + +#define NFS4_int32_t_sz \ + (XDR_int) #define NFS4_uint32_t_sz \ (XDR_unsigned_int) +#define NFS4_int64_t_sz \ + (XDR_hyper) +#define NFS4_uint64_t_sz \ + (XDR_unsigned_hyper) +#define NFS4_nfsstat4_sz (XDR_int) +#define NFS4_attrlist4_sz (XDR_unsigned_int) #define NFS4_bitmap4_sz (XDR_unsigned_int) +#define NFS4_verifier4_sz (XDR_QUADLEN(NFS4_VERIFIER_SIZE)) +#define NFS4_nfs_cookie4_sz \ + (NFS4_uint64_t_sz) +#define NFS4_nfs_fh4_sz (XDR_unsigned_int + XDR_QUADLEN(NFS4_FHSIZE)) #define NFS4_utf8string_sz (XDR_unsigned_int) #define NFS4_utf8str_cis_sz \ (NFS4_utf8string_sz) @@ -228,8 +468,17 @@ enum { FATTR4_POSIX_ACCESS_ACL = 92 }; (NFS4_utf8string_sz) #define NFS4_utf8str_mixed_sz \ (NFS4_utf8string_sz) +#define NFS4_component4_sz \ + (NFS4_utf8str_cs_sz) +#define NFS4_linktext4_sz \ + (NFS4_utf8str_cs_sz) +#define NFS4_pathname4_sz (XDR_unsigned_int) #define NFS4_nfstime4_sz \ (NFS4_int64_t_sz + NFS4_uint32_t_sz) +#define NFS4_fattr4_sz \ + (NFS4_bitmap4_sz + NFS4_attrlist4_sz) +#define NFS4_stateid4_sz \ + (NFS4_uint32_t_sz + XDR_QUADLEN(12)) #define NFS4_fattr4_offline_sz \ (XDR_bool) #define NFS4_open_arguments4_sz \ @@ -259,5 +508,27 @@ enum { FATTR4_POSIX_ACCESS_ACL = 92 }; (NFS4_aclscope4_sz) #define NFS4_fattr4_posix_default_acl_sz (XDR_unsigned_int) #define NFS4_fattr4_posix_access_acl_sz (XDR_unsigned_int) +#define NFS4_notify_type4_sz (XDR_int) +#define NFS4_notify_entry4_sz \ + (NFS4_component4_sz + NFS4_fattr4_sz) +#define NFS4_prev_entry4_sz \ + (NFS4_notify_entry4_sz + NFS4_nfs_cookie4_sz) +#define NFS4_notify_remove4_sz \ + (NFS4_notify_entry4_sz + NFS4_nfs_cookie4_sz) +#define NFS4_notify_add4_sz \ + (XDR_unsigned_int + (1 * (NFS4_notify_remove4_sz)) + NFS4_notify_entry4_sz + XDR_unsigned_int + (1 * (NFS4_nfs_cookie4_sz)) + XDR_unsigned_int + (1 * (NFS4_prev_entry4_sz)) + XDR_bool) +#define NFS4_notify_attr4_sz \ + (NFS4_notify_entry4_sz) +#define NFS4_notify_rename4_sz \ + (NFS4_notify_remove4_sz + NFS4_notify_add4_sz) +#define NFS4_notify_verifier4_sz \ + (NFS4_verifier4_sz + NFS4_verifier4_sz) +#define NFS4_notifylist4_sz (XDR_unsigned_int) +#define NFS4_notify4_sz \ + (NFS4_bitmap4_sz + NFS4_notifylist4_sz) +#define NFS4_CB_NOTIFY4args_sz \ + (NFS4_stateid4_sz + NFS4_nfs_fh4_sz + XDR_unsigned_int) +#define NFS4_CB_NOTIFY4res_sz \ + (NFS4_nfsstat4_sz) #endif /* _LINUX_XDRGEN_NFS4_1_DEF_H */ diff --git a/include/uapi/linux/nfs4.h b/include/uapi/linux/nfs4.h index 4273e0249fcb..289205b53a08 100644 --- a/include/uapi/linux/nfs4.h +++ b/include/uapi/linux/nfs4.h @@ -17,11 +17,9 @@ #include #define NFS4_BITMAP_SIZE 3 -#define NFS4_VERIFIER_SIZE 8 #define NFS4_STATEID_SEQID_SIZE 4 #define NFS4_STATEID_OTHER_SIZE 12 #define NFS4_STATEID_SIZE (NFS4_STATEID_SEQID_SIZE + NFS4_STATEID_OTHER_SIZE) -#define NFS4_FHSIZE 128 #define NFS4_MAXPATHLEN PATH_MAX #define NFS4_MAXNAMLEN NAME_MAX #define NFS4_OPAQUE_LIMIT 1024 -- cgit v1.2.3 From 36ed32f46e2aa35084fa0e6edecc3f0a6178489f Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Fri, 17 Jul 2026 07:08:55 -0400 Subject: nfsd: implement server-stats-get netlink handler Implement nfsd_nl_server_stats_get_dumpit() which exposes the NFS server statistics currently available via /proc/net/rpc/nfsd through the nfsd generic netlink family. The handler uses a dump operation to stream statistics across one or more netlink messages. The reply is divided into sections that are emitted in order: - scalar stats (reply cache, filehandle, IO, network, RPC), emitted once in the first message, then - per-version procedure counts (proc2/3/4-ops) and the NFSv4 per-operation counts (proc4ops-ops), using the per-netns vs_count arrays. cb->args[0] tracks the current section and cb->args[1] the entry index within it, so a section that does not fit in the current message is closed and resumed in the next one. This matters because the first dump message is allocated at NLMSG_GOODSIZE (a single page on most architectures) regardless of the client's receive buffer; packing every counter into one message would overflow it and fail the dump with -EMSGSIZE. Userspace merges the attributes from every message. This allows nfsstat to retrieve server statistics via netlink with a procfs fallback for older kernels. Assisted-by: LLM Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260717-exportd-netlink-v7-3-b7ce17b83b60@kernel.org Signed-off-by: Chuck Lever --- Documentation/netlink/specs/nfsd.yaml | 105 ++++++++++++++++ fs/nfsd/netlink.c | 5 + fs/nfsd/netlink.h | 2 + fs/nfsd/nfsctl.c | 222 ++++++++++++++++++++++++++++++++++ include/uapi/linux/nfsd_netlink.h | 35 ++++++ 5 files changed, 369 insertions(+) (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/nfsd.yaml b/Documentation/netlink/specs/nfsd.yaml index 8f36fadd68f7..2a89d355ee7b 100644 --- a/Documentation/netlink/specs/nfsd.yaml +++ b/Documentation/netlink/specs/nfsd.yaml @@ -330,6 +330,86 @@ attribute-sets: of which client holds the state. Intended for use after all clients have been unexported from a given path, enabling the underlying filesystem to be unmounted. + - + name: server-proc-entry + attributes: + - + name: op + type: u32 + - + name: count + type: u64 + - + name: pad + type: pad + - + name: server-stats + attributes: + - + name: rc-hits + type: u64 + - + name: rc-misses + type: u64 + - + name: rc-nocache + type: u64 + - + name: pad + type: pad + - + name: fh-stale + type: u64 + - + name: io-read + type: u64 + - + name: io-write + type: u64 + - + name: netcnt + type: u32 + - + name: netudpcnt + type: u32 + - + name: nettcpcnt + type: u32 + - + name: nettcpconn + type: u32 + - + name: rpccnt + type: u32 + - + name: rpcbadfmt + type: u32 + - + name: rpcbadauth + type: u32 + - + name: rpcbadclnt + type: u32 + - + name: proc2-ops + type: nest + nested-attributes: server-proc-entry + multi-attr: true + - + name: proc3-ops + type: nest + nested-attributes: server-proc-entry + multi-attr: true + - + name: proc4-ops + type: nest + nested-attributes: server-proc-entry + multi-attr: true + - + name: proc4ops-ops + type: nest + nested-attributes: server-proc-entry + multi-attr: true operations: list: @@ -516,6 +596,31 @@ operations: request: attributes: - path + - + name: server-stats-get + doc: dump NFS server statistics + attribute-set: server-stats + dump: + reply: + attributes: + - rc-hits + - rc-misses + - rc-nocache + - fh-stale + - io-read + - io-write + - netcnt + - netudpcnt + - nettcpcnt + - nettcpconn + - rpccnt + - rpcbadfmt + - rpcbadauth + - rpcbadclnt + - proc2-ops + - proc3-ops + - proc4-ops + - proc4ops-ops mcast-groups: list: diff --git a/fs/nfsd/netlink.c b/fs/nfsd/netlink.c index fbee3676d253..eba8b353f412 100644 --- a/fs/nfsd/netlink.c +++ b/fs/nfsd/netlink.c @@ -225,6 +225,11 @@ static const struct genl_split_ops nfsd_nl_ops[] = { .maxattr = NFSD_A_UNLOCK_EXPORT_PATH, .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, }, + { + .cmd = NFSD_CMD_SERVER_STATS_GET, + .dumpit = nfsd_nl_server_stats_get_dumpit, + .flags = GENL_CMD_CAP_DUMP, + }, }; static const struct genl_multicast_group nfsd_nl_mcgrps[] = { diff --git a/fs/nfsd/netlink.h b/fs/nfsd/netlink.h index af41aa0d4a65..027e2953db26 100644 --- a/fs/nfsd/netlink.h +++ b/fs/nfsd/netlink.h @@ -42,6 +42,8 @@ int nfsd_nl_cache_flush_doit(struct sk_buff *skb, struct genl_info *info); int nfsd_nl_unlock_ip_doit(struct sk_buff *skb, struct genl_info *info); int nfsd_nl_unlock_filesystem_doit(struct sk_buff *skb, struct genl_info *info); int nfsd_nl_unlock_export_doit(struct sk_buff *skb, struct genl_info *info); +int nfsd_nl_server_stats_get_dumpit(struct sk_buff *skb, + struct netlink_callback *cb); enum { NFSD_NLGRP_NONE, diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c index ae5a7f1ad917..7731051105c9 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c @@ -2331,6 +2331,228 @@ int nfsd_nl_cache_flush_doit(struct sk_buff *skb, struct genl_info *info) return 0; } +/* Emit a single server-proc-entry nest: { op, count }. */ +static int nfsd_nl_put_proc_entry(struct sk_buff *skb, int attr, + u32 op, u64 count) +{ + struct nlattr *nest; + + nest = nla_nest_start(skb, attr); + if (!nest) + return -EMSGSIZE; + if (nla_put_u32(skb, NFSD_A_SERVER_PROC_ENTRY_OP, op) || + nla_put_u64_64bit(skb, NFSD_A_SERVER_PROC_ENTRY_COUNT, + count, NFSD_A_SERVER_PROC_ENTRY_PAD)) { + nla_nest_cancel(skb, nest); + return -EMSGSIZE; + } + nla_nest_end(skb, nest); + return 0; +} + +/* Emit the scalar server-stats counters. Only ever called on a fresh skb. */ +static int nfsd_nl_server_stats_scalars(struct sk_buff *skb, + struct nfsd_net *nn, + struct svc_stat *statp) +{ + if (nla_put_u64_64bit(skb, NFSD_A_SERVER_STATS_RC_HITS, + percpu_counter_sum_positive(&nn->counter[NFSD_STATS_RC_HITS]), + NFSD_A_SERVER_STATS_PAD) || + nla_put_u64_64bit(skb, NFSD_A_SERVER_STATS_RC_MISSES, + percpu_counter_sum_positive(&nn->counter[NFSD_STATS_RC_MISSES]), + NFSD_A_SERVER_STATS_PAD) || + nla_put_u64_64bit(skb, NFSD_A_SERVER_STATS_RC_NOCACHE, + percpu_counter_sum_positive(&nn->counter[NFSD_STATS_RC_NOCACHE]), + NFSD_A_SERVER_STATS_PAD)) + return -EMSGSIZE; + + if (nla_put_u64_64bit(skb, NFSD_A_SERVER_STATS_FH_STALE, + percpu_counter_sum_positive(&nn->counter[NFSD_STATS_FH_STALE]), + NFSD_A_SERVER_STATS_PAD)) + return -EMSGSIZE; + + if (nla_put_u64_64bit(skb, NFSD_A_SERVER_STATS_IO_READ, + percpu_counter_sum_positive(&nn->counter[NFSD_STATS_IO_READ]), + NFSD_A_SERVER_STATS_PAD) || + nla_put_u64_64bit(skb, NFSD_A_SERVER_STATS_IO_WRITE, + percpu_counter_sum_positive(&nn->counter[NFSD_STATS_IO_WRITE]), + NFSD_A_SERVER_STATS_PAD)) + return -EMSGSIZE; + + if (nla_put_u32(skb, NFSD_A_SERVER_STATS_NETCNT, statp->netcnt) || + nla_put_u32(skb, NFSD_A_SERVER_STATS_NETUDPCNT, statp->netudpcnt) || + nla_put_u32(skb, NFSD_A_SERVER_STATS_NETTCPCNT, statp->nettcpcnt) || + nla_put_u32(skb, NFSD_A_SERVER_STATS_NETTCPCONN, statp->nettcpconn)) + return -EMSGSIZE; + + if (nla_put_u32(skb, NFSD_A_SERVER_STATS_RPCCNT, statp->rpccnt) || + nla_put_u32(skb, NFSD_A_SERVER_STATS_RPCBADFMT, statp->rpcbadfmt) || + nla_put_u32(skb, NFSD_A_SERVER_STATS_RPCBADAUTH, statp->rpcbadauth) || + nla_put_u32(skb, NFSD_A_SERVER_STATS_RPCBADCLNT, statp->rpcbadclnt)) + return -EMSGSIZE; + + return 0; +} + +/* + * Emit per-version procedure counts for one NFS version, resuming at *idx. + * Returns 0 when the version has been fully emitted (or is not present), or + * -EMSGSIZE when @skb filled up, leaving *idx at the entry still to emit. + */ +static int nfsd_nl_server_stats_proc(struct sk_buff *skb, + struct svc_stat *statp, + struct svc_program *prog, + unsigned int ver, int attr, int *idx) +{ + unsigned long __percpu *counts; + unsigned int nproc; + + if (!statp->vs_count || ver >= prog->pg_nvers || + !prog->pg_vers[ver] || !statp->vs_count[ver]) + return 0; + + counts = statp->vs_count[ver]; + nproc = prog->pg_vers[ver]->vs_nproc; + + for (; *idx < nproc; (*idx)++) { + unsigned long count = 0; + int cpu; + + for_each_possible_cpu(cpu) + count += per_cpu(counts[*idx], cpu); + + if (!count) + continue; + if (nfsd_nl_put_proc_entry(skb, attr, *idx, count)) + return -EMSGSIZE; + } + + return 0; +} + +#ifdef CONFIG_NFSD_V4 +/* + * Emit NFSv4 per-operation counts, resuming at *idx. Same return convention + * as nfsd_nl_server_stats_proc(). + */ +static int nfsd_nl_server_stats_nfs4ops(struct sk_buff *skb, + struct nfsd_net *nn, int *idx) +{ + for (; *idx <= LAST_NFS4_OP; (*idx)++) { + u64 cnt = percpu_counter_sum_positive( + &nn->counter[NFSD_STATS_NFS4_OP(*idx)]); + + if (!cnt) + continue; + if (nfsd_nl_put_proc_entry(skb, NFSD_A_SERVER_STATS_PROC4OPS_OPS, + *idx, cnt)) + return -EMSGSIZE; + } + + return 0; +} +#endif + +/* Sections of the server-stats dump, emitted in order across messages. */ +enum { + NFSD_SERVER_STATS_SCALARS = 0, + NFSD_SERVER_STATS_PROC2, + NFSD_SERVER_STATS_PROC3, + NFSD_SERVER_STATS_PROC4, + NFSD_SERVER_STATS_PROC4OPS, + NFSD_SERVER_STATS_DONE, +}; + +/** + * nfsd_nl_server_stats_get_dumpit - dump NFS server statistics + * @skb: reply buffer + * @cb: netlink metadata and command arguments + * + * The server-stats object is emitted across one or more netlink messages. + * cb->args[0] tracks the current section and cb->args[1] the entry index + * within it, so a section that does not fit in the current message is resumed + * in the next one. The scalar counters are small and emitted once, in the + * first message; userspace merges the attributes from every message. + * + * Returns the size of the reply or a negative errno. + */ +int nfsd_nl_server_stats_get_dumpit(struct sk_buff *skb, + struct netlink_callback *cb) +{ + struct net *net = sock_net(skb->sk); + struct nfsd_net *nn = net_generic(net, nfsd_net_id); + struct svc_stat *statp = &nn->nfsd_svcstats; + struct svc_program *prog = statp->program; + int section = cb->args[0]; + int idx = cb->args[1]; + void *hdr; + + if (section >= NFSD_SERVER_STATS_DONE) + return 0; + + hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, + cb->nlh->nlmsg_seq, &nfsd_nl_family, + NLM_F_MULTI, NFSD_CMD_SERVER_STATS_GET); + if (!hdr) + return -ENOBUFS; + + /* Scalar stats fit easily and are emitted in the first message. */ + if (section == NFSD_SERVER_STATS_SCALARS) { + if (nfsd_nl_server_stats_scalars(skb, nn, statp)) + goto err_cancel; + section = NFSD_SERVER_STATS_PROC2; + idx = 0; + } + + /* + * Emit as many of the remaining sections as fit. A section returning + * -EMSGSIZE means the message is full: close it and resume from the + * same section/index on the next call with a fresh skb. Each entry is + * small enough to fit in a fresh skb, so forward progress is assured. + */ + while (section < NFSD_SERVER_STATS_DONE) { + int ret = 0; + + switch (section) { + case NFSD_SERVER_STATS_PROC2: + ret = nfsd_nl_server_stats_proc(skb, statp, prog, 2, + NFSD_A_SERVER_STATS_PROC2_OPS, &idx); + break; + case NFSD_SERVER_STATS_PROC3: + ret = nfsd_nl_server_stats_proc(skb, statp, prog, 3, + NFSD_A_SERVER_STATS_PROC3_OPS, &idx); + break; + case NFSD_SERVER_STATS_PROC4: + ret = nfsd_nl_server_stats_proc(skb, statp, prog, 4, + NFSD_A_SERVER_STATS_PROC4_OPS, &idx); + break; +#ifdef CONFIG_NFSD_V4 + case NFSD_SERVER_STATS_PROC4OPS: + ret = nfsd_nl_server_stats_nfs4ops(skb, nn, &idx); + break; +#endif + } + + if (ret == -EMSGSIZE) + goto out; + if (ret) + goto err_cancel; + + section++; + idx = 0; + } + +out: + genlmsg_end(skb, hdr); + cb->args[0] = section; + cb->args[1] = idx; + return skb->len; + +err_cancel: + genlmsg_cancel(skb, hdr); + return -EMSGSIZE; +} + int nfsd_cache_notify(struct cache_detail *cd, struct cache_head *h, u32 cache_type) { struct genlmsghdr *hdr; diff --git a/include/uapi/linux/nfsd_netlink.h b/include/uapi/linux/nfsd_netlink.h index f5b75d5caba9..3d076d173b1d 100644 --- a/include/uapi/linux/nfsd_netlink.h +++ b/include/uapi/linux/nfsd_netlink.h @@ -225,6 +225,40 @@ enum { NFSD_A_UNLOCK_EXPORT_MAX = (__NFSD_A_UNLOCK_EXPORT_MAX - 1) }; +enum { + NFSD_A_SERVER_PROC_ENTRY_OP = 1, + NFSD_A_SERVER_PROC_ENTRY_COUNT, + NFSD_A_SERVER_PROC_ENTRY_PAD, + + __NFSD_A_SERVER_PROC_ENTRY_MAX, + NFSD_A_SERVER_PROC_ENTRY_MAX = (__NFSD_A_SERVER_PROC_ENTRY_MAX - 1) +}; + +enum { + NFSD_A_SERVER_STATS_RC_HITS = 1, + NFSD_A_SERVER_STATS_RC_MISSES, + NFSD_A_SERVER_STATS_RC_NOCACHE, + NFSD_A_SERVER_STATS_PAD, + NFSD_A_SERVER_STATS_FH_STALE, + NFSD_A_SERVER_STATS_IO_READ, + NFSD_A_SERVER_STATS_IO_WRITE, + NFSD_A_SERVER_STATS_NETCNT, + NFSD_A_SERVER_STATS_NETUDPCNT, + NFSD_A_SERVER_STATS_NETTCPCNT, + NFSD_A_SERVER_STATS_NETTCPCONN, + NFSD_A_SERVER_STATS_RPCCNT, + NFSD_A_SERVER_STATS_RPCBADFMT, + NFSD_A_SERVER_STATS_RPCBADAUTH, + NFSD_A_SERVER_STATS_RPCBADCLNT, + NFSD_A_SERVER_STATS_PROC2_OPS, + NFSD_A_SERVER_STATS_PROC3_OPS, + NFSD_A_SERVER_STATS_PROC4_OPS, + NFSD_A_SERVER_STATS_PROC4OPS_OPS, + + __NFSD_A_SERVER_STATS_MAX, + NFSD_A_SERVER_STATS_MAX = (__NFSD_A_SERVER_STATS_MAX - 1) +}; + enum { NFSD_CMD_RPC_STATUS_GET = 1, NFSD_CMD_THREADS_SET, @@ -244,6 +278,7 @@ enum { NFSD_CMD_UNLOCK_IP, NFSD_CMD_UNLOCK_FILESYSTEM, NFSD_CMD_UNLOCK_EXPORT, + NFSD_CMD_SERVER_STATS_GET, __NFSD_CMD_MAX, NFSD_CMD_MAX = (__NFSD_CMD_MAX - 1) -- cgit v1.2.3 From 46db3c8a1be96a354758b44b1d4fbb4b70d09a20 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Fri, 17 Jul 2026 07:08:58 -0400 Subject: nfsd: export NFSv4 callback op stats via netlink Add a proc4cb-ops nested attribute to the server-stats netlink dump, reusing the existing server-proc-entry (op/count) layout. The dump gains a callback section that emits one entry per callback opcode (OP_CB_GETATTR..OP_CB_OFFLOAD) from the per-netns callback counters, paged across messages like the other per-operation sections. This lets nfsstat report NFSv4 backchannel operation counts over netlink, including CB_GETATTR which corresponds to the procfs wdeleg_getattr line. Assisted-by: LLM Signed-off-by: Jeff Layton Link: https://patch.msgid.link/20260717-exportd-netlink-v7-6-b7ce17b83b60@kernel.org Signed-off-by: Chuck Lever --- Documentation/netlink/specs/nfsd.yaml | 6 ++++++ fs/nfsd/nfsctl.c | 27 +++++++++++++++++++++++++++ include/uapi/linux/nfsd_netlink.h | 1 + 3 files changed, 34 insertions(+) (limited to 'include/uapi/linux') diff --git a/Documentation/netlink/specs/nfsd.yaml b/Documentation/netlink/specs/nfsd.yaml index 2a89d355ee7b..642268819c6f 100644 --- a/Documentation/netlink/specs/nfsd.yaml +++ b/Documentation/netlink/specs/nfsd.yaml @@ -410,6 +410,11 @@ attribute-sets: type: nest nested-attributes: server-proc-entry multi-attr: true + - + name: proc4cb-ops + type: nest + nested-attributes: server-proc-entry + multi-attr: true operations: list: @@ -621,6 +626,7 @@ operations: - proc3-ops - proc4-ops - proc4ops-ops + - proc4cb-ops mcast-groups: list: diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c index 93e18426039d..adb032b7311a 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c @@ -2451,6 +2451,29 @@ static int nfsd_nl_server_stats_nfs4ops(struct sk_buff *skb, return 0; } + +/* + * Emit NFSv4 callback (backchannel) per-operation counts, resuming at *idx, + * which counts from OP_CB_GETATTR. Same return convention as + * nfsd_nl_server_stats_proc(). + */ +static int nfsd_nl_server_stats_cbops(struct sk_buff *skb, + struct nfsd_net *nn, int *idx) +{ + int op; + + for (op = OP_CB_GETATTR + *idx; op <= OP_CB_OFFLOAD; op++, (*idx)++) { + u64 cnt = percpu_counter_sum_positive(&nn->cb_counter[op]); + + if (!cnt) + continue; + if (nfsd_nl_put_proc_entry(skb, NFSD_A_SERVER_STATS_PROC4CB_OPS, + op, cnt)) + return -EMSGSIZE; + } + + return 0; +} #endif /* Sections of the server-stats dump, emitted in order across messages. */ @@ -2459,6 +2482,7 @@ enum { NFSD_SERVER_STATS_PROC2, NFSD_SERVER_STATS_PROC3, NFSD_SERVER_STATS_PROC4, + NFSD_SERVER_STATS_PROC4CB, NFSD_SERVER_STATS_PROC4OPS, NFSD_SERVER_STATS_DONE, }; @@ -2527,6 +2551,9 @@ int nfsd_nl_server_stats_get_dumpit(struct sk_buff *skb, NFSD_A_SERVER_STATS_PROC4_OPS, &idx); break; #ifdef CONFIG_NFSD_V4 + case NFSD_SERVER_STATS_PROC4CB: + ret = nfsd_nl_server_stats_cbops(skb, nn, &idx); + break; case NFSD_SERVER_STATS_PROC4OPS: ret = nfsd_nl_server_stats_nfs4ops(skb, nn, &idx); break; diff --git a/include/uapi/linux/nfsd_netlink.h b/include/uapi/linux/nfsd_netlink.h index 3d076d173b1d..87da1d0bb21e 100644 --- a/include/uapi/linux/nfsd_netlink.h +++ b/include/uapi/linux/nfsd_netlink.h @@ -254,6 +254,7 @@ enum { NFSD_A_SERVER_STATS_PROC3_OPS, NFSD_A_SERVER_STATS_PROC4_OPS, NFSD_A_SERVER_STATS_PROC4OPS_OPS, + NFSD_A_SERVER_STATS_PROC4CB_OPS, __NFSD_A_SERVER_STATS_MAX, NFSD_A_SERVER_STATS_MAX = (__NFSD_A_SERVER_STATS_MAX - 1) -- cgit v1.2.3 From cdcc4e46180df8161f4d2f3c6fd6beaf6990133d Mon Sep 17 00:00:00 2001 From: Yizhou Zhao Date: Fri, 31 Jul 2026 22:27:43 +0800 Subject: ipvs: separate destination availability state IPVS configuration paths update destination availability while connection accounting updates destination overload state. The two independent states share dest->flags, so their read-modify-write updates can race and lose one another. Keep OVERLOAD in flags, where the preceding patch serializes its updates with dst_lock, and move AVAILABLE to cflags. This keeps configuration- controlled availability out of the scheduler hot cacheline until a scheduler needs to check it. It also prevents availability updates from clobbering overload state. The destination status bits are not exposed through the IPVS sockopt or netlink interfaces, so keep their definitions in the internal IPVS header. Readers can still observe stale destination state; this does not provide a cross-field snapshot. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Reported-by: Yizhou Zhao Reported-by: Yuxiang Yang Reported-by: Ao Wang Reported-by: Xuewei Feng Reported-by: Qi Li Reported-by: Ke Xu Link: https://lore.kernel.org/all/8913381c-1e02-35c7-0ec4-61de5a12fd35@ssi.bg/ Assisted-by: Claude-Code:GLM-5.2 Suggested-by: Julian Anastasov Signed-off-by: Yizhou Zhao Acked-by: Julian Anastasov Signed-off-by: Pablo Neira Ayuso --- include/net/ip_vs.h | 7 +++++++ include/uapi/linux/ip_vs.h | 6 ------ net/netfilter/ipvs/ip_vs_conn.c | 4 ++-- net/netfilter/ipvs/ip_vs_core.c | 6 +++--- net/netfilter/ipvs/ip_vs_ctl.c | 4 ++-- net/netfilter/ipvs/ip_vs_dh.c | 4 ++-- net/netfilter/ipvs/ip_vs_lblc.c | 2 +- net/netfilter/ipvs/ip_vs_lblcr.c | 8 ++++---- net/netfilter/ipvs/ip_vs_xmit.c | 4 ++-- 9 files changed, 23 insertions(+), 22 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h index e99382930617..fc2ef5ef31a6 100644 --- a/include/net/ip_vs.h +++ b/include/net/ip_vs.h @@ -36,6 +36,12 @@ #define IP_VS_HDR_INVERSE 1 #define IP_VS_HDR_ICMP 2 +/* Destination Server Flags */ +#define IP_VS_DEST_F_OVERLOAD 0x0002 /* server is overloaded */ + +/* Destination Server Config Flags */ +#define IP_VS_DEST_CF_AVAILABLE 0x0001 /* server is available */ + /* conn_tab limits (as per Kconfig) */ #define IP_VS_CONN_TAB_MIN_BITS 8 #if BITS_PER_LONG > 32 @@ -976,6 +982,7 @@ struct ip_vs_dest { volatile unsigned int flags; /* dest status flags */ atomic_t conn_flags; /* flags to copy to conn */ atomic_t weight; /* server weight */ + unsigned long cflags; /* config flags */ atomic_t last_weight; /* server latest weight */ __u16 tun_type; /* tunnel type */ __be16 tun_port; /* tunnel port */ diff --git a/include/uapi/linux/ip_vs.h b/include/uapi/linux/ip_vs.h index 1ed234e7f251..2c37c6ac7525 100644 --- a/include/uapi/linux/ip_vs.h +++ b/include/uapi/linux/ip_vs.h @@ -28,12 +28,6 @@ #define IP_VS_SVC_F_SCHED_SH_FALLBACK IP_VS_SVC_F_SCHED1 /* SH fallback */ #define IP_VS_SVC_F_SCHED_SH_PORT IP_VS_SVC_F_SCHED2 /* SH use port */ -/* - * Destination Server Flags - */ -#define IP_VS_DEST_F_AVAILABLE 0x0001 /* server is available */ -#define IP_VS_DEST_F_OVERLOAD 0x0002 /* server is overloaded */ - /* * IPVS sync daemon states */ diff --git a/net/netfilter/ipvs/ip_vs_conn.c b/net/netfilter/ipvs/ip_vs_conn.c index abf52a226fee..6fa3e1dc534c 100644 --- a/net/netfilter/ipvs/ip_vs_conn.c +++ b/net/netfilter/ipvs/ip_vs_conn.c @@ -1279,7 +1279,7 @@ int ip_vs_check_template(struct ip_vs_conn *ct, struct ip_vs_dest *cdest) * Checking the dest server status. */ if ((dest == NULL) || - !(dest->flags & IP_VS_DEST_F_AVAILABLE) || + !(dest->cflags & IP_VS_DEST_CF_AVAILABLE) || expire_quiescent_template(ipvs, dest) || (cdest && (dest != cdest))) { IP_VS_DBG_BUF(9, "check_template: dest not available for " @@ -2020,7 +2020,7 @@ repeat: cp = ip_vs_hn0_to_conn(hn); resched_score++; dest = cp->dest; - if (!dest || (dest->flags & IP_VS_DEST_F_AVAILABLE)) + if (!dest || (dest->cflags & IP_VS_DEST_CF_AVAILABLE)) continue; if (atomic_read(&cp->n_control)) diff --git a/net/netfilter/ipvs/ip_vs_core.c b/net/netfilter/ipvs/ip_vs_core.c index 0bdaeb4ed61e..95af77b68851 100644 --- a/net/netfilter/ipvs/ip_vs_core.c +++ b/net/netfilter/ipvs/ip_vs_core.c @@ -302,7 +302,7 @@ ip_vs_in_stats(struct ip_vs_conn *cp, struct sk_buff *skb) struct ip_vs_dest *dest = cp->dest; struct netns_ipvs *ipvs = cp->ipvs; - if (dest && (dest->flags & IP_VS_DEST_F_AVAILABLE)) { + if (dest && (dest->cflags & IP_VS_DEST_CF_AVAILABLE)) { struct ip_vs_cpu_stats *s; struct ip_vs_service *svc; @@ -338,7 +338,7 @@ ip_vs_out_stats(struct ip_vs_conn *cp, struct sk_buff *skb) struct ip_vs_dest *dest = cp->dest; struct netns_ipvs *ipvs = cp->ipvs; - if (dest && (dest->flags & IP_VS_DEST_F_AVAILABLE)) { + if (dest && (dest->cflags & IP_VS_DEST_CF_AVAILABLE)) { struct ip_vs_cpu_stats *s; struct ip_vs_service *svc; @@ -2210,7 +2210,7 @@ ip_vs_in_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state } /* Check the server status */ - if (cp && cp->dest && !(cp->dest->flags & IP_VS_DEST_F_AVAILABLE)) { + if (cp && cp->dest && !(cp->dest->cflags & IP_VS_DEST_CF_AVAILABLE)) { /* the destination server is not available */ if (sysctl_expire_nodest_conn(ipvs)) { bool old_ct = ip_vs_conn_uses_old_conntrack(cp, skb); diff --git a/net/netfilter/ipvs/ip_vs_ctl.c b/net/netfilter/ipvs/ip_vs_ctl.c index 974773642af8..8f9a8e491ad6 100644 --- a/net/netfilter/ipvs/ip_vs_ctl.c +++ b/net/netfilter/ipvs/ip_vs_ctl.c @@ -1402,7 +1402,7 @@ __ip_vs_update_dest(struct ip_vs_service *svc, struct ip_vs_dest *dest, } /* set the dest status flags */ - dest->flags |= IP_VS_DEST_F_AVAILABLE; + dest->cflags |= IP_VS_DEST_CF_AVAILABLE; if (READ_ONCE(dest->u_threshold) != udest->u_threshold || READ_ONCE(dest->l_threshold) != udest->l_threshold) { @@ -1662,7 +1662,7 @@ static void __ip_vs_unlink_dest(struct ip_vs_service *svc, struct ip_vs_dest *dest, int svcupd) { - dest->flags &= ~IP_VS_DEST_F_AVAILABLE; + dest->cflags &= ~IP_VS_DEST_CF_AVAILABLE; spin_lock_bh(&dest->dst_lock); __ip_vs_dst_cache_reset(dest); diff --git a/net/netfilter/ipvs/ip_vs_dh.c b/net/netfilter/ipvs/ip_vs_dh.c index e1f62f6b25e2..43abed7a26a6 100644 --- a/net/netfilter/ipvs/ip_vs_dh.c +++ b/net/netfilter/ipvs/ip_vs_dh.c @@ -219,8 +219,8 @@ ip_vs_dh_schedule(struct ip_vs_service *svc, const struct sk_buff *skb, s = (struct ip_vs_dh_state *) svc->sched_data; dest = ip_vs_dh_get(svc->af, s, &iph->daddr); - if (!dest - || !(dest->flags & IP_VS_DEST_F_AVAILABLE) + if (!dest || + !(dest->cflags & IP_VS_DEST_CF_AVAILABLE) || atomic_read(&dest->weight) <= 0 || is_overloaded(dest)) { ip_vs_scheduler_err(svc, "no destination available"); diff --git a/net/netfilter/ipvs/ip_vs_lblc.c b/net/netfilter/ipvs/ip_vs_lblc.c index 15ccb2b2fa1f..693bcc82ccb7 100644 --- a/net/netfilter/ipvs/ip_vs_lblc.c +++ b/net/netfilter/ipvs/ip_vs_lblc.c @@ -502,7 +502,7 @@ ip_vs_lblc_schedule(struct ip_vs_service *svc, const struct sk_buff *skb, */ dest = en->dest; - if ((dest->flags & IP_VS_DEST_F_AVAILABLE) && + if ((dest->cflags & IP_VS_DEST_CF_AVAILABLE) && atomic_read(&dest->weight) > 0 && !is_overloaded(dest, svc)) goto out; } diff --git a/net/netfilter/ipvs/ip_vs_lblcr.c b/net/netfilter/ipvs/ip_vs_lblcr.c index c90ea897c3f7..f53f05ceea36 100644 --- a/net/netfilter/ipvs/ip_vs_lblcr.c +++ b/net/netfilter/ipvs/ip_vs_lblcr.c @@ -169,8 +169,8 @@ static inline struct ip_vs_dest *ip_vs_dest_set_min(struct ip_vs_dest_set *set) if (least->flags & IP_VS_DEST_F_OVERLOAD) continue; - if ((atomic_read(&least->weight) > 0) - && (least->flags & IP_VS_DEST_F_AVAILABLE)) { + if ((atomic_read(&least->weight) > 0) && + (least->cflags & IP_VS_DEST_CF_AVAILABLE)) { loh = ip_vs_dest_conn_overhead(least); goto nextstage; } @@ -186,8 +186,8 @@ static inline struct ip_vs_dest *ip_vs_dest_set_min(struct ip_vs_dest_set *set) doh = ip_vs_dest_conn_overhead(dest); if (((__s64)loh * atomic_read(&dest->weight) > - (__s64)doh * atomic_read(&least->weight)) - && (dest->flags & IP_VS_DEST_F_AVAILABLE)) { + (__s64)doh * atomic_read(&least->weight)) && + (dest->cflags & IP_VS_DEST_CF_AVAILABLE)) { least = dest; loh = doh; } diff --git a/net/netfilter/ipvs/ip_vs_xmit.c b/net/netfilter/ipvs/ip_vs_xmit.c index c4508f3f43dd..fc7403186394 100644 --- a/net/netfilter/ipvs/ip_vs_xmit.c +++ b/net/netfilter/ipvs/ip_vs_xmit.c @@ -351,7 +351,7 @@ __ip_vs_get_out_rt(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb, * stored in dest_trash. */ if (!rt_dev_is_down(dst_dev_rcu(&rt->dst)) && - dest->flags & IP_VS_DEST_F_AVAILABLE) + dest->cflags & IP_VS_DEST_CF_AVAILABLE) __ip_vs_dst_set(dest, dest_dst, &rt->dst, 0); else noref = 0; @@ -530,7 +530,7 @@ __ip_vs_get_out_rt_v6(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb, * stored in dest_trash. */ if (!rt_dev_is_down(dst_dev_rcu(&rt->dst)) && - dest->flags & IP_VS_DEST_F_AVAILABLE) + dest->cflags & IP_VS_DEST_CF_AVAILABLE) __ip_vs_dst_set(dest, dest_dst, &rt->dst, cookie); else noref = 0; -- cgit v1.2.3 From eae3faf210bdc69181be717ce90437eadc6b3c80 Mon Sep 17 00:00:00 2001 From: Daeho Jeong Date: Wed, 5 Aug 2026 18:48:13 -0700 Subject: f2fs: support dynamic reserve/release for device aliasing This patch adds a dynamic management feature to the existing device aliasing functionality. It allows users to dynamically reserve or release specific devices from the filesystem's free pool at runtime through new ioctls. To support this, three new ioctls are introduced: - F2FS_IOC_RESERVE_DEV_ALIAS: This reclaims the space occupied by a device aliasing file. It first performs a capacity check, resets GC victim information for the target range, marks the segments as in-use to prevent new allocations, and then triggers GC to migrate existing valid data out of the range. Finally, it reserves these blocks in the SIT to effectively exclude the device from the usable capacity. - F2FS_IOC_RELEASE_DEV_ALIAS: This releases the reserved space of a previously reserved device aliasing file. It truncates the blocks associated with the file, which makes them available for general filesystem allocation again. - F2FS_IOC_GET_DEV_ALIAS_STATUS: This retrieves the current aliasing status of a device aliasing file, returning whether the file is released (inactive alias) or reserved (active alias, with blocks fully allocated on the device). Signed-off-by: Daeho Jeong Reviewed-by: Chao Yu Signed-off-by: Jaegeuk Kim --- Documentation/filesystems/f2fs.rst | 35 +++++ fs/f2fs/data.c | 4 +- fs/f2fs/extent_cache.c | 9 ++ fs/f2fs/f2fs.h | 19 ++- fs/f2fs/file.c | 273 ++++++++++++++++++++++++++++++++++++- fs/f2fs/gc.c | 30 ++-- fs/f2fs/namei.c | 14 ++ fs/f2fs/segment.c | 176 +++++++++++++++++------- fs/f2fs/segment.h | 22 +++ fs/f2fs/super.c | 36 +++++ include/uapi/linux/f2fs.h | 7 + 11 files changed, 557 insertions(+), 68 deletions(-) (limited to 'include/uapi/linux') diff --git a/Documentation/filesystems/f2fs.rst b/Documentation/filesystems/f2fs.rst index 8c4a14ae444f..1a5fd4afe609 100644 --- a/Documentation/filesystems/f2fs.rst +++ b/Documentation/filesystems/f2fs.rst @@ -1045,6 +1045,41 @@ So, the key idea is, user can do any file operations on /dev/vdc, and reclaim the space after the use, while the space is counted as /data. That doesn't require modifying partition size and filesystem format. +Dynamic Device Aliasing Management +---------------------------------- + +In addition to static device aliasing by deleting the aliasing file, F2FS +supports dynamic management of device aliasing. This mechanism allows the system +to dynamically transition partition ownership between F2FS userdata and external +entities (e.g., zRAM, raw partition) based on system requirements without +deleting the master aliasing file or requiring unmount/remount. + +The master aliasing file is created during the initial format of the file system +and remains as a persistent control entity (ioctl gateway) in the root directory. + +- Partition Reservation (In-service to Aliased) + When a specific partition needs to be dedicated to external services (e.g., zRAM), + a user can reserve the device alias range via ioctl. The kernel resets GC victim + information for the target range, marks segments as in-use to prevent new + allocations, and triggers forced GC to migrate existing valid data out of the + range. Finally, it reserves these blocks in the SIT to effectively exclude the + device from the usable capacity. + +- Partition Release (Aliased to In-service) + When external usage concludes, the space is reclaimed not by deleting the file, + but through the release ioctl. The kernel truncates blocks associated with + the file, releasing them back to general filesystem allocation. + +.. code-block:: + + # f2fs_io dev_alias release /mnt/f2fs/vdc.file + # df -h + /dev/vdb 64G 753M 64G 2% /mnt/f2fs + + # f2fs_io dev_alias reserve /mnt/f2fs/vdc.file + # df -h + /dev/vdb 64G 33G 32G 52% /mnt/f2fs + Per-file Read-Only Large Folio Support -------------------------------------- diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c index 30e8084da313..c219ea76a3a7 100644 --- a/fs/f2fs/data.c +++ b/fs/f2fs/data.c @@ -1270,7 +1270,7 @@ int f2fs_reserve_new_blocks(struct dnode_of_data *dn, blkcnt_t count) if (unlikely(is_inode_flag_set(dn->inode, FI_NO_ALLOC))) return -EPERM; - err = inc_valid_block_count(sbi, dn->inode, &count, true); + err = inc_valid_block_count(sbi, dn->inode, &count, true, false); if (unlikely(err)) return err; @@ -1543,7 +1543,7 @@ static int __allocate_data_block(struct dnode_of_data *dn, int seg_type) dn->data_blkaddr = f2fs_data_blkaddr(dn); if (dn->data_blkaddr == NULL_ADDR) { - err = inc_valid_block_count(sbi, dn->inode, &count, true); + err = inc_valid_block_count(sbi, dn->inode, &count, true, false); if (unlikely(err)) return err; } diff --git a/fs/f2fs/extent_cache.c b/fs/f2fs/extent_cache.c index 61f6b9714366..37cf9fa8d537 100644 --- a/fs/f2fs/extent_cache.c +++ b/fs/f2fs/extent_cache.c @@ -17,6 +17,7 @@ #include "f2fs.h" #include "node.h" +#include "segment.h" #include bool sanity_check_extent_cache(struct inode *inode, struct folio *ifolio) @@ -62,6 +63,14 @@ bool sanity_check_extent_cache(struct inode *inode, struct folio *ifolio) __func__, inode->i_ino, ei.blk, ei.fofs, ei.len); return false; } + + if ((GET_SEGOFF_FROM_SEG0(sbi, ei.blk) % BLKS_PER_SEC(sbi)) || + (ei.len % BLKS_PER_SEC(sbi))) { + f2fs_warn(sbi, "%s: device alias inode (ino=%llx)'s extent info [%u, %u, %u] is not aligned to section size %u", + __func__, inode->i_ino, ei.blk, ei.fofs, ei.len, + BLKS_PER_SEC(sbi)); + return false; + } return true; } diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h index 8e2fb0bda467..a380b8be8819 100644 --- a/fs/f2fs/f2fs.h +++ b/fs/f2fs/f2fs.h @@ -1404,6 +1404,8 @@ struct f2fs_dev_info { unsigned int total_segments; block_t start_blk; block_t end_blk; + bool has_alias; + bool is_reserving; #ifdef CONFIG_BLK_DEV_ZONED unsigned int nr_blkz; /* Total number of zones */ unsigned long *blkz_seq; /* Bitmap indicating sequential zones */ @@ -1884,6 +1886,7 @@ struct f2fs_sb_info { block_t last_valid_block_count; /* for recovery */ block_t reserved_blocks; /* configurable reserved blocks */ block_t current_reserved_blocks; /* current reserved blocks */ + block_t alias_reserved_blocks; /* reserved blocks for device alias */ /* Additional tracking for no checkpoint mode */ block_t unusable_block_count; /* # of blocks saved by last cp */ @@ -2586,7 +2589,8 @@ static inline unsigned int get_available_block_count(struct f2fs_sb_info *sbi, block_t avail_user_block_count; avail_user_block_count = sbi->user_block_count - - sbi->current_reserved_blocks; + sbi->current_reserved_blocks - + sbi->alias_reserved_blocks; if (test_opt(sbi, RESERVE_ROOT) && !__allow_reserved_root(sbi, inode, cap)) avail_user_block_count -= F2FS_OPTION(sbi).root_reserved_blocks; @@ -2603,7 +2607,8 @@ static inline unsigned int get_available_block_count(struct f2fs_sb_info *sbi, static inline void f2fs_i_blocks_write(struct inode *, block_t, bool, bool); static inline int inc_valid_block_count(struct f2fs_sb_info *sbi, - struct inode *inode, blkcnt_t *count, bool partial) + struct inode *inode, blkcnt_t *count, + bool partial, bool alias_reserved) { long long diff = 0, release = 0; block_t avail_user_block_count; @@ -2626,10 +2631,16 @@ static inline int inc_valid_block_count(struct f2fs_sb_info *sbi, spin_lock(&sbi->stat_lock); + if (alias_reserved) + sbi->alias_reserved_blocks -= *count; + avail_user_block_count = get_available_block_count(sbi, inode, true); diff = (long long)sbi->total_valid_block_count + *count - avail_user_block_count; if (unlikely(diff > 0)) { + if (alias_reserved) + sbi->alias_reserved_blocks += *count; + if (!partial) { spin_unlock(&sbi->stat_lock); release = *count; @@ -4037,6 +4048,8 @@ int f2fs_flush_device_cache(struct f2fs_sb_info *sbi); void f2fs_destroy_flush_cmd_control(struct f2fs_sb_info *sbi, bool free); void f2fs_invalidate_blocks(struct f2fs_sb_info *sbi, block_t addr, unsigned int len); +void f2fs_reserve_device_alias(struct f2fs_sb_info *sbi, block_t addr, + unsigned int len); bool f2fs_is_checkpointed_data(struct f2fs_sb_info *sbi, block_t blkaddr); int f2fs_start_discard_thread(struct f2fs_sb_info *sbi); void f2fs_drop_discard_cmd(struct f2fs_sb_info *sbi); @@ -4258,6 +4271,8 @@ void f2fs_build_gc_manager(struct f2fs_sb_info *sbi); int f2fs_gc_range(struct f2fs_sb_info *sbi, unsigned int start_seg, unsigned int end_seg, bool dry_run, unsigned int dry_run_sections); +void f2fs_reset_gc_victim_resource(struct f2fs_sb_info *sbi, + unsigned int start, unsigned int end); int f2fs_resize_fs(struct file *filp, __u64 block_count); int __init f2fs_create_garbage_collection_cache(void); void f2fs_destroy_garbage_collection_cache(void); diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index b99d9cdf9ba7..56529a82e027 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -813,13 +813,19 @@ int f2fs_do_truncate_blocks(struct inode *inode, u64 from, bool lock) if (IS_DEVICE_ALIASING(inode)) { struct extent_tree *et = F2FS_I(inode)->extent_tree[EX_READ]; - struct extent_info ei = et->largest; + struct extent_info ei; + + read_lock(&et->lock); + ei = et->largest; + read_unlock(&et->lock); f2fs_invalidate_blocks(sbi, ei.blk, ei.len); dec_valid_block_count(sbi, inode, ei.len); f2fs_update_time(sbi, REQ_TIME); + f2fs_drop_extent_tree(inode); + f2fs_folio_put(ifolio, true); goto out; } @@ -1100,8 +1106,9 @@ int f2fs_setattr(struct mnt_idmap *idmap, struct dentry *dentry, if ((attr->ia_valid & ATTR_SIZE)) { if (mapping_large_folio_support(inode->i_mapping)) return -EOPNOTSUPP; - if (!f2fs_is_compress_backend_ready(inode) || - IS_DEVICE_ALIASING(inode)) + if (IS_DEVICE_ALIASING(inode)) + return -EPERM; + if (!f2fs_is_compress_backend_ready(inode)) return -EOPNOTSUPP; if (is_inode_flag_set(inode, FI_COMPRESS_RELEASED) && !IS_ALIGNED(attr->ia_size, @@ -2139,6 +2146,9 @@ static int f2fs_setflags_common(struct inode *inode, u32 iflags, u32 mask) if (IS_NOQUOTA(inode)) return -EPERM; + if (IS_DEVICE_ALIASING(inode)) + return -EPERM; + if ((iflags ^ masked_flags) & F2FS_CASEFOLD_FL) { if (!f2fs_sb_has_casefold(F2FS_I_SB(inode))) return -EOPNOTSUPP; @@ -2687,6 +2697,17 @@ static int f2fs_ioc_get_encryption_policy(struct file *filp, unsigned long arg) return fscrypt_ioctl_get_policy(filp, (void __user *)arg); } +static int f2fs_ioc_get_dev_alias_status(struct file *filp, unsigned long arg) +{ + struct inode *inode = file_inode(filp); + + if (!IS_DEVICE_ALIASING(inode)) + return -EINVAL; + + return put_user(F2FS_HAS_BLOCKS(inode) ? F2FS_DEV_ALIAS_STATUS_RESERVED : + F2FS_DEV_ALIAS_STATUS_RELEASED, (u32 __user *)arg); +} + static int f2fs_ioc_get_encryption_pwsalt(struct file *filp, unsigned long arg) { struct inode *inode = file_inode(filp); @@ -3637,6 +3658,241 @@ static int f2fs_ioc_get_dev_alias_file(struct file *filp, unsigned long arg) (u32 __user *)arg); } +static bool f2fs_get_dev_alias_extent(struct f2fs_sb_info *sbi, + struct dentry *dentry, + struct extent_info *ei) +{ + int i; + + for (i = 1; i < sbi->s_ndevs; i++) { + char *name = strrchr(FDEV(i).path, '/'); + + name = name ? name + 1 : FDEV(i).path; + if (strcmp(name, dentry->d_name.name)) + continue; + + ei->blk = FDEV(i).start_blk; + ei->len = FDEV(i).total_segments << sbi->log_blocks_per_seg; + ei->fofs = 0; + return true; + } + return false; +} + +static int f2fs_ioc_reserve_dev_alias(struct file *filp) +{ + struct inode *inode = file_inode(filp); + struct f2fs_sb_info *sbi = F2FS_I_SB(inode); + struct extent_tree *et = F2FS_I(inode)->extent_tree[EX_READ]; + struct extent_info ei; + struct cp_control cpc = { CP_SYNC, 0, 0, 0 }; + struct f2fs_lock_context lc, glc; + blkcnt_t count; + unsigned int start, end; + int type, err; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (unlikely(is_sbi_flag_set(sbi, SBI_CP_DISABLED))) + return -EINVAL; + + err = mnt_want_write_file(filp); + if (err) + return err; + + inode_lock(inode); + + if (!IS_DEVICE_ALIASING(inode)) { + err = -EINVAL; + goto out_inode_unlock; + } + + if (F2FS_HAS_BLOCKS(inode)) { + err = 0; + goto out_inode_unlock; + } + + if (!f2fs_get_dev_alias_extent(sbi, filp->f_path.dentry, &ei)) { + f2fs_warn(sbi, "device alias file (%s, ino=%llu) has no matching device", + filp->f_path.dentry->d_name.name, + (unsigned long long)inode->i_ino); + set_sbi_flag(sbi, SBI_NEED_FSCK); + f2fs_handle_error(sbi, ERROR_CORRUPTED_INODE); + err = -EFSCORRUPTED; + goto out_inode_unlock; + } + + spin_lock(&sbi->stat_lock); + if (sbi->total_valid_block_count + ei.len > + get_available_block_count(sbi, inode, true)) { + spin_unlock(&sbi->stat_lock); + err = -ENOSPC; + goto out_inode_unlock; + } + sbi->alias_reserved_blocks += ei.len; + spin_unlock(&sbi->stat_lock); + + spin_lock(&FREE_I(sbi)->segmap_lock); + FDEV(f2fs_target_device_index(sbi, ei.blk)).is_reserving = true; + spin_unlock(&FREE_I(sbi)->segmap_lock); + + start = GET_SEGNO(sbi, ei.blk); + end = GET_SEGNO(sbi, ei.blk + ei.len - 1); + + /* Acquire gc_lock for victim reset, curseg resize, and range GC */ + f2fs_down_write_trace(&sbi->gc_lock, &glc); + + /* Reset the victim information to prevent GC from targeting the range */ + f2fs_reset_gc_victim_resource(sbi, start, end); + + /* Move out cursegs from the target range */ + for (type = CURSEG_HOT_DATA; type < NR_CURSEG_PERSIST_TYPE; type++) { + err = f2fs_allocate_segment_for_resize(sbi, type, start, end); + if (err) + goto out_gc_unlock; + } + + f2fs_lock_op(sbi, &lc); + + if (unlikely(is_sbi_flag_set(sbi, SBI_CP_DISABLED))) { + err = -EINVAL; + f2fs_unlock_op(sbi, &lc); + goto out_gc_unlock; + } + + /* do GC to move out valid blocks in the range all at once! */ + err = f2fs_gc_range(sbi, start, end, false, 0); + if (err) { + f2fs_unlock_op(sbi, &lc); + goto out_gc_unlock; + } + + count = ei.len; + err = inc_valid_block_count(sbi, inode, &count, false, true); + if (err) { + f2fs_unlock_op(sbi, &lc); + goto out_gc_unlock; + } + + write_lock(&et->lock); + et->largest = ei; + write_unlock(&et->lock); + clear_inode_flag(inode, FI_NO_EXTENT); + + f2fs_reserve_device_alias(sbi, ei.blk, ei.len); + + i_size_write(inode, (loff_t)ei.len << sbi->log_blocksize); + f2fs_update_inode_page(inode); + + spin_lock(&FREE_I(sbi)->segmap_lock); + FDEV(f2fs_target_device_index(sbi, ei.blk)).is_reserving = false; + spin_unlock(&FREE_I(sbi)->segmap_lock); + + f2fs_unlock_op(sbi, &lc); + f2fs_up_write_trace(&sbi->gc_lock, &glc); + + inode_unlock(inode); + mnt_drop_write_file(filp); + + return f2fs_write_checkpoint(sbi, &cpc); + +out_gc_unlock: + spin_lock(&sbi->stat_lock); + sbi->alias_reserved_blocks -= ei.len; + spin_unlock(&sbi->stat_lock); + + spin_lock(&FREE_I(sbi)->segmap_lock); + FDEV(f2fs_target_device_index(sbi, ei.blk)).is_reserving = false; + spin_unlock(&FREE_I(sbi)->segmap_lock); + f2fs_up_write_trace(&sbi->gc_lock, &glc); + +out_inode_unlock: + inode_unlock(inode); + mnt_drop_write_file(filp); + return err; +} + +static int f2fs_ioc_release_dev_alias(struct file *filp) +{ + struct inode *inode = file_inode(filp); + struct f2fs_sb_info *sbi = F2FS_I_SB(inode); + struct extent_tree *et = F2FS_I(inode)->extent_tree[EX_READ]; + struct extent_info ei = {0, }; + struct cp_control cpc = { CP_SYNC, 0, 0, 0 }; + struct f2fs_lock_context lc, glc; + int err; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (unlikely(is_sbi_flag_set(sbi, SBI_CP_DISABLED))) + return -EINVAL; + + err = mnt_want_write_file(filp); + if (err) + return err; + + inode_lock(inode); + + if (!IS_DEVICE_ALIASING(inode)) { + err = -EINVAL; + goto out_inode_unlock; + } + + if (!F2FS_HAS_BLOCKS(inode)) { + err = 0; + goto out_inode_unlock; + } + + err = filemap_write_and_wait(inode->i_mapping); + if (err) + goto out_inode_unlock; + + read_lock(&et->lock); + ei = et->largest; + read_unlock(&et->lock); + + f2fs_down_write_trace(&sbi->gc_lock, &glc); + f2fs_lock_op(sbi, &lc); + + if (unlikely(is_sbi_flag_set(sbi, SBI_CP_DISABLED))) { + err = -EINVAL; + f2fs_unlock_op(sbi, &lc); + f2fs_up_write_trace(&sbi->gc_lock, &glc); + goto out_inode_unlock; + } + + filemap_invalidate_lock(inode->i_mapping); + truncate_setsize(inode, 0); + + err = f2fs_truncate_blocks(inode, 0, false); + if (err) + i_size_write(inode, (loff_t)ei.len << sbi->log_blocksize); + filemap_invalidate_unlock(inode->i_mapping); + + if (err) { + f2fs_unlock_op(sbi, &lc); + f2fs_up_write_trace(&sbi->gc_lock, &glc); + goto out_inode_unlock; + } + + f2fs_update_inode_page(inode); + + f2fs_unlock_op(sbi, &lc); + f2fs_up_write_trace(&sbi->gc_lock, &glc); + + inode_unlock(inode); + mnt_drop_write_file(filp); + + return f2fs_write_checkpoint(sbi, &cpc); + +out_inode_unlock: + inode_unlock(inode); + mnt_drop_write_file(filp); + return err; +} + static int f2fs_ioc_io_prio(struct file *filp, unsigned long arg) { struct inode *inode = file_inode(filp); @@ -4062,7 +4318,7 @@ static int reserve_compress_blocks(struct dnode_of_data *dn, pgoff_t count, } ret = inc_valid_block_count(sbi, dn->inode, - &to_reserved, false); + &to_reserved, false, false); if (unlikely(ret)) return ret; @@ -4763,8 +5019,14 @@ static long __f2fs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) return f2fs_ioc_compress_file(filp); case F2FS_IOC_GET_DEV_ALIAS_FILE: return f2fs_ioc_get_dev_alias_file(filp, arg); + case F2FS_IOC_GET_DEV_ALIAS_STATUS: + return f2fs_ioc_get_dev_alias_status(filp, arg); case F2FS_IOC_IO_PRIO: return f2fs_ioc_io_prio(filp, arg); + case F2FS_IOC_RESERVE_DEV_ALIAS: + return f2fs_ioc_reserve_dev_alias(filp); + case F2FS_IOC_RELEASE_DEV_ALIAS: + return f2fs_ioc_release_dev_alias(filp); default: return -ENOTTY; } @@ -5551,7 +5813,10 @@ long f2fs_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) case F2FS_IOC_DECOMPRESS_FILE: case F2FS_IOC_COMPRESS_FILE: case F2FS_IOC_GET_DEV_ALIAS_FILE: + case F2FS_IOC_GET_DEV_ALIAS_STATUS: case F2FS_IOC_IO_PRIO: + case F2FS_IOC_RESERVE_DEV_ALIAS: + case F2FS_IOC_RELEASE_DEV_ALIAS: break; default: return -ENOIOCTLCMD; diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c index d04633f872ef..51fc8c69eb25 100644 --- a/fs/f2fs/gc.c +++ b/fs/f2fs/gc.c @@ -2191,29 +2191,37 @@ int f2fs_gc_range(struct f2fs_sb_info *sbi, return 0; } +void f2fs_reset_gc_victim_resource(struct f2fs_sb_info *sbi, + unsigned int start, unsigned int end) +{ + int i; + + mutex_lock(&DIRTY_I(sbi)->seglist_lock); + for (i = 0; i < MAX_GC_POLICY; i++) + if (SIT_I(sbi)->last_victim[i] >= start && + SIT_I(sbi)->last_victim[i] <= end) + SIT_I(sbi)->last_victim[i] = 0; + + for (i = BG_GC; i <= FG_GC; i++) + if (sbi->next_victim_seg[i] >= start && + sbi->next_victim_seg[i] <= end) + sbi->next_victim_seg[i] = NULL_SEGNO; + mutex_unlock(&DIRTY_I(sbi)->seglist_lock); +} + static int free_segment_range(struct f2fs_sb_info *sbi, unsigned int secs, bool dry_run) { unsigned int next_inuse, start, end; struct cp_control cpc = { CP_RESIZE, 0, 0, 0 }; - int gc_mode, gc_type; int err = 0; int type; - /* Force block allocation for GC */ MAIN_SECS(sbi) -= secs; start = MAIN_SECS(sbi) * SEGS_PER_SEC(sbi); end = MAIN_SEGS(sbi) - 1; - mutex_lock(&DIRTY_I(sbi)->seglist_lock); - for (gc_mode = 0; gc_mode < MAX_GC_POLICY; gc_mode++) - if (SIT_I(sbi)->last_victim[gc_mode] >= start) - SIT_I(sbi)->last_victim[gc_mode] = 0; - - for (gc_type = BG_GC; gc_type <= FG_GC; gc_type++) - if (sbi->next_victim_seg[gc_type] >= start) - sbi->next_victim_seg[gc_type] = NULL_SEGNO; - mutex_unlock(&DIRTY_I(sbi)->seglist_lock); + f2fs_reset_gc_victim_resource(sbi, start, end); /* Move out cursegs from the target range */ for (type = CURSEG_HOT_DATA; type < NR_CURSEG_PERSIST_TYPE; type++) { diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c index 7ffdf23cea5e..784f636244e1 100644 --- a/fs/f2fs/namei.c +++ b/fs/f2fs/namei.c @@ -425,6 +425,9 @@ static int f2fs_link(struct dentry *old_dentry, struct inode *dir, if (!f2fs_is_checkpoint_ready(sbi)) return -ENOSPC; + if (IS_DEVICE_ALIASING(inode)) + return -EPERM; + err = fscrypt_prepare_link(old_dentry, dir, dentry); if (err) return err; @@ -568,6 +571,9 @@ static int f2fs_unlink(struct inode *dir, struct dentry *dentry) trace_f2fs_unlink_enter(dir, dentry); + if (IS_DEVICE_ALIASING(inode)) + return -EPERM; + if (unlikely(f2fs_cp_error(sbi))) { err = -EIO; goto out; @@ -946,6 +952,9 @@ static int f2fs_rename(struct mnt_idmap *idmap, struct inode *old_dir, bool old_is_dir = S_ISDIR(old_inode->i_mode); int err; + if (IS_DEVICE_ALIASING(old_inode)) + return -EPERM; + if (unlikely(f2fs_cp_error(sbi))) return -EIO; if (!f2fs_is_checkpoint_ready(sbi)) @@ -1016,6 +1025,8 @@ static int f2fs_rename(struct mnt_idmap *idmap, struct inode *old_dir, } if (new_inode) { + if (IS_DEVICE_ALIASING(new_inode)) + return -EPERM; err = -ENOTEMPTY; if (old_is_dir && !f2fs_empty_dir(new_inode)) @@ -1143,6 +1154,9 @@ static int f2fs_cross_rename(struct inode *old_dir, struct dentry *old_dentry, int old_nlink = 0, new_nlink = 0; int err; + if (IS_DEVICE_ALIASING(old_inode) || IS_DEVICE_ALIASING(new_inode)) + return -EPERM; + if (unlikely(f2fs_cp_error(sbi))) return -EIO; if (!f2fs_is_checkpoint_ready(sbi)) diff --git a/fs/f2fs/segment.c b/fs/f2fs/segment.c index 00cc2af45ff9..b81245ddd490 100644 --- a/fs/f2fs/segment.c +++ b/fs/f2fs/segment.c @@ -261,7 +261,7 @@ retry: } else { blkcnt_t count = 1; - err = inc_valid_block_count(sbi, inode, &count, true); + err = inc_valid_block_count(sbi, inode, &count, true, false); if (err) { f2fs_put_dnode(&dn); return err; @@ -2539,35 +2539,42 @@ static int update_sit_entry_for_alloc(struct f2fs_sb_info *sbi, struct seg_entry unsigned int segno, block_t blkaddr, unsigned int offset, int del) { bool exist; + int del_count = del; + int i; - exist = f2fs_test_and_set_bit(offset, se->cur_valid_map); - if (unlikely(exist)) { - f2fs_err(sbi, "Bitmap was wrongly set, blk:%u", blkaddr); - f2fs_bug_on(sbi, 1); - se->valid_blocks--; - del = 0; - } + f2fs_bug_on(sbi, GET_SEGNO(sbi, blkaddr) != GET_SEGNO(sbi, blkaddr + del_count - 1)); - if (f2fs_block_unit_discard(sbi) && - !f2fs_test_and_set_bit(offset, se->discard_map)) - sbi->discard_blks--; + for (i = 0; i < del_count; i++) { + exist = f2fs_test_and_set_bit(offset + i, se->cur_valid_map); + if (unlikely(exist)) { + f2fs_err(sbi, "Bitmap was wrongly set, blk:%u", blkaddr + i); + f2fs_bug_on(sbi, 1); + se->valid_blocks--; + del -= 1; + continue; + } - /* - * SSR should never reuse block which is checkpointed - * or newly invalidated. - */ - if (!is_sbi_flag_set(sbi, SBI_CP_DISABLED)) { - if (!f2fs_test_and_set_bit(offset, se->ckpt_valid_map)) { - se->ckpt_valid_blocks++; - if (__is_large_section(sbi)) - get_sec_entry(sbi, segno)->ckpt_valid_blocks++; + if (f2fs_block_unit_discard(sbi) && + !f2fs_test_and_set_bit(offset + i, se->discard_map)) + sbi->discard_blks--; + + /* + * SSR should never reuse block which is checkpointed + * or newly invalidated. + */ + if (!is_sbi_flag_set(sbi, SBI_CP_DISABLED)) { + if (!f2fs_test_and_set_bit(offset + i, se->ckpt_valid_map)) { + se->ckpt_valid_blocks++; + if (__is_large_section(sbi)) + get_sec_entry(sbi, segno)->ckpt_valid_blocks++; + } } - } - if (!f2fs_test_bit(offset, se->ckpt_valid_map)) { - se->ckpt_valid_blocks += del; - if (__is_large_section(sbi)) - get_sec_entry(sbi, segno)->ckpt_valid_blocks += del; + if (!f2fs_test_bit(offset + i, se->ckpt_valid_map)) { + se->ckpt_valid_blocks += 1; + if (__is_large_section(sbi)) + get_sec_entry(sbi, segno)->ckpt_valid_blocks += 1; + } } if (__is_large_section(sbi)) @@ -2622,9 +2629,14 @@ void f2fs_invalidate_blocks(struct f2fs_sb_info *sbi, block_t addr, unsigned int segno = GET_SEGNO(sbi, addr); struct sit_info *sit_i = SIT_I(sbi); block_t addr_start = addr, addr_end = addr + len - 1; - unsigned int seg_num = GET_SEGNO(sbi, addr_end) - segno + 1; + unsigned int seg_num; unsigned int i = 1, max_blocks = sbi->blocks_per_seg, cnt; + if (len == 0) + return; + + seg_num = GET_SEGNO(sbi, addr_end) - segno + 1; + f2fs_bug_on(sbi, addr == NULL_ADDR); if (addr == NEW_ADDR || addr == COMPRESS_ADDR) return; @@ -2657,6 +2669,52 @@ void f2fs_invalidate_blocks(struct f2fs_sb_info *sbi, block_t addr, up_write(&sit_i->sentry_lock); } +void f2fs_reserve_device_alias(struct f2fs_sb_info *sbi, block_t addr, + unsigned int len) +{ + unsigned int segno = GET_SEGNO(sbi, addr); + struct sit_info *sit_i = SIT_I(sbi); + block_t addr_start = addr, addr_end = addr + len - 1; + unsigned int seg_num; + unsigned int i = 1, max_blocks = sbi->blocks_per_seg, cnt; + + if (len == 0) + return; + + seg_num = GET_SEGNO(sbi, addr_end) - segno + 1; + + down_write(&sit_i->sentry_lock); + + if (seg_num == 1) + cnt = len; + else + cnt = max_blocks - GET_BLKOFF_FROM_SEG0(sbi, addr); + + do { + update_segment_mtime(sbi, addr_start, 0); + update_sit_entry(sbi, addr_start, cnt); + __set_test_and_inuse(sbi, segno); + + /* Remove the segment from PRE (prefree) to prevent checkpoint from freeing it! */ + mutex_lock(&DIRTY_I(sbi)->seglist_lock); + if (test_and_clear_bit(segno, DIRTY_I(sbi)->dirty_segmap[PRE])) + DIRTY_I(sbi)->nr_dirty[PRE]--; + mutex_unlock(&DIRTY_I(sbi)->seglist_lock); + + /* add it into dirty seglist */ + locate_dirty_segment(sbi, segno); + + /* update @addr_start and @cnt and @segno */ + addr_start = START_BLOCK(sbi, ++segno); + if (++i == seg_num) + cnt = GET_BLKOFF_FROM_SEG0(sbi, addr_end) + 1; + else + cnt = max_blocks; + } while (i <= seg_num); + + up_write(&sit_i->sentry_lock); +} + bool f2fs_is_checkpointed_data(struct f2fs_sb_info *sbi, block_t blkaddr) { struct sit_info *sit_i = SIT_I(sbi); @@ -2795,8 +2853,13 @@ static int is_next_segment_free(struct f2fs_sb_info *sbi, unsigned int segno = curseg->segno + 1; struct free_segmap_info *free_i = FREE_I(sbi); - if (segno < MAIN_SEGS(sbi) && segno % SEGS_PER_SEC(sbi)) + if (segno < MAIN_SEGS(sbi) && segno % SEGS_PER_SEC(sbi)) { + int devi = f2fs_target_device_index(sbi, START_BLOCK(sbi, segno)); + + if (f2fs_dev_is_reserving(sbi, devi)) + return 0; return !test_bit(segno, free_i->free_segmap); + } return 0; } @@ -2815,7 +2878,8 @@ static int get_new_segment(struct f2fs_sb_info *sbi, unsigned int alloc_policy = sbi->allocate_section_policy; unsigned int alloc_hint = sbi->allocate_section_hint; bool init = true; - int i; + bool looped = false; + int i, devi; int ret = 0; spin_lock(&free_i->segmap_lock); @@ -2828,8 +2892,13 @@ static int get_new_segment(struct f2fs_sb_info *sbi, if (!new_sec && ((*newseg + 1) % SEGS_PER_SEC(sbi))) { segno = find_next_zero_bit(free_i->free_segmap, GET_SEG_FROM_SEC(sbi, hint + 1), *newseg + 1); - if (segno < GET_SEG_FROM_SEC(sbi, hint + 1)) + if (segno < GET_SEG_FROM_SEC(sbi, hint + 1)) { + devi = f2fs_target_device_index(sbi, START_BLOCK(sbi, segno)); + + if (f2fs_dev_is_alloc_blocked(sbi, devi, pinning)) + goto find_other_zone; goto got_it; + } } #ifdef CONFIG_BLK_DEV_ZONED @@ -2865,33 +2934,42 @@ static int get_new_segment(struct f2fs_sb_info *sbi, find_other_zone: secno = find_next_zero_bit(free_i->free_secmap, MAIN_SECS(sbi), hint); -#ifdef CONFIG_BLK_DEV_ZONED - if (secno >= MAIN_SECS(sbi) && f2fs_sb_has_blkzoned(sbi)) { - /* Write only to sequential zones */ - if (sbi->blkzone_alloc_policy == BLKZONE_ALLOC_ONLY_SEQ) { - hint = GET_SEC_FROM_SEG(sbi, sbi->first_seq_zone_segno); - secno = find_next_zero_bit(free_i->free_secmap, MAIN_SECS(sbi), hint); - } else - secno = find_first_zero_bit(free_i->free_secmap, - MAIN_SECS(sbi)); - if (secno >= MAIN_SECS(sbi)) { - ret = -ENOSPC; - f2fs_bug_on(sbi, 1); - goto out_unlock; - } - } -#endif - if (secno >= MAIN_SECS(sbi)) { - secno = find_first_zero_bit(free_i->free_secmap, - MAIN_SECS(sbi)); - if (secno >= MAIN_SECS(sbi)) { + if (looped) { ret = -ENOSPC; f2fs_bug_on(sbi, !pinning); goto out_unlock; } + hint = 0; +#ifdef CONFIG_BLK_DEV_ZONED + /* Write only to sequential zones */ + if (f2fs_sb_has_blkzoned(sbi) && + sbi->blkzone_alloc_policy == BLKZONE_ALLOC_ONLY_SEQ) + hint = GET_SEC_FROM_SEG(sbi, sbi->first_seq_zone_segno); +#endif + looped = true; + goto find_other_zone; } + segno = GET_SEG_FROM_SEC(sbi, secno); + + devi = f2fs_target_device_index(sbi, START_BLOCK(sbi, segno)); + + if (f2fs_dev_is_alloc_blocked(sbi, devi, pinning)) { + while (devi < sbi->s_ndevs && + f2fs_dev_is_alloc_blocked(sbi, devi, pinning)) { + unsigned int end_segno = GET_SEGNO(sbi, FDEV(devi).end_blk); + + hint = GET_SEC_FROM_SEG(sbi, end_segno) + 1; + devi++; + } + goto find_other_zone; + } + + if (sec_usage_check(sbi, secno)) { + hint = secno + 1; + goto find_other_zone; + } zoneno = GET_ZONE_FROM_SEC(sbi, secno); /* give up on finding another zone */ diff --git a/fs/f2fs/segment.h b/fs/f2fs/segment.h index 33a2257da1e6..db1079169a23 100644 --- a/fs/f2fs/segment.h +++ b/fs/f2fs/segment.h @@ -940,10 +940,32 @@ static inline block_t sum_blk_addr(struct f2fs_sb_info *sbi, int base, int type) - (base + 1) + type; } +static inline bool f2fs_dev_is_reserving(struct f2fs_sb_info *sbi, int devi) +{ + if (!f2fs_sb_has_device_alias(sbi)) + return false; + return FDEV(devi).is_reserving; +} + +static inline bool f2fs_dev_is_alloc_blocked(struct f2fs_sb_info *sbi, + int devi, bool pinning) +{ + if (!f2fs_sb_has_device_alias(sbi)) + return false; + return (pinning && FDEV(devi).has_alias) || FDEV(devi).is_reserving; +} + static inline bool sec_usage_check(struct f2fs_sb_info *sbi, unsigned int secno) { if (is_cursec(sbi, secno) || (sbi->cur_victim_sec == secno)) return true; + if (f2fs_sb_has_device_alias(sbi)) { + block_t start_blk = START_BLOCK(sbi, GET_SEG_FROM_SEC(sbi, secno)); + int devi = f2fs_target_device_index(sbi, start_blk); + + if (f2fs_dev_is_reserving(sbi, devi)) + return true; + } return false; } diff --git a/fs/f2fs/super.c b/fs/f2fs/super.c index 5902b2da7ea4..ed9e0ba9cf0b 100644 --- a/fs/f2fs/super.c +++ b/fs/f2fs/super.c @@ -5005,6 +5005,39 @@ static void f2fs_tuning_parameters(struct f2fs_sb_info *sbi) sbi->readdir_ra = true; } +static void f2fs_restore_device_alias(struct f2fs_sb_info *sbi) +{ + struct inode *root = d_inode(sbi->sb->s_root); + struct f2fs_dir_entry *de; + struct folio *folio; + int i; + + if (!f2fs_sb_has_device_alias(sbi)) + return; + + for (i = 1; i < sbi->s_ndevs; i++) { + char *name = strrchr(FDEV(i).path, '/'); + struct inode *inode; + struct qstr qstr; + + name = name ? name + 1 : FDEV(i).path; + qstr.name = name; + qstr.len = strlen(name); + + de = f2fs_find_entry(root, &qstr, &folio); + if (!de) + continue; + + inode = f2fs_iget(sbi->sb, le32_to_cpu(de->ino)); + if (!IS_ERR(inode)) { + if (IS_DEVICE_ALIASING(inode)) + FDEV(i).has_alias = true; + iput(inode); + } + f2fs_folio_put(folio, 0); + } +} + static int f2fs_fill_super(struct super_block *sb, struct fs_context *fc) { struct f2fs_fs_context *ctx = fc->fs_private; @@ -5209,6 +5242,7 @@ try_onemore: sbi->last_valid_block_count = sbi->total_valid_block_count; sbi->reserved_blocks = 0; sbi->current_reserved_blocks = 0; + sbi->alias_reserved_blocks = 0; limit_reserve_root(sbi); adjust_unusable_cap_perc(sbi); @@ -5436,6 +5470,8 @@ reset_checkpoint: f2fs_update_time(sbi, REQ_TIME); clear_sbi_flag(sbi, SBI_CP_DISABLED_QUICK); + f2fs_restore_device_alias(sbi); + sbi->umount_lock_holder = NULL; return 0; diff --git a/include/uapi/linux/f2fs.h b/include/uapi/linux/f2fs.h index 795e26258355..4409ada2fecb 100644 --- a/include/uapi/linux/f2fs.h +++ b/include/uapi/linux/f2fs.h @@ -45,6 +45,9 @@ #define F2FS_IOC_START_ATOMIC_REPLACE _IO(F2FS_IOCTL_MAGIC, 25) #define F2FS_IOC_GET_DEV_ALIAS_FILE _IOR(F2FS_IOCTL_MAGIC, 26, __u32) #define F2FS_IOC_IO_PRIO _IOW(F2FS_IOCTL_MAGIC, 27, __u32) +#define F2FS_IOC_RESERVE_DEV_ALIAS _IO(F2FS_IOCTL_MAGIC, 28) +#define F2FS_IOC_RELEASE_DEV_ALIAS _IO(F2FS_IOCTL_MAGIC, 29) +#define F2FS_IOC_GET_DEV_ALIAS_STATUS _IOR(F2FS_IOCTL_MAGIC, 30, __u32) /* * should be same as XFS_IOC_GOINGDOWN. @@ -70,6 +73,10 @@ enum { F2FS_IOPRIO_MAX, }; +/* for F2FS_IOC_GET_DEV_ALIAS_STATUS */ +#define F2FS_DEV_ALIAS_STATUS_RELEASED 0 +#define F2FS_DEV_ALIAS_STATUS_RESERVED 1 + struct f2fs_gc_range { __u32 sync; __u64 start; -- cgit v1.2.3 From 5b1541c7b0dae224bec29c626ca31a0c31b57d6b Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Thu, 30 Jul 2026 12:47:47 -0700 Subject: drm/amdkfd: kfd_ioctl.h: fix most kernel-doc warnings Make corrections to kernel-doc comments: - use the struct keyword when describing structs - use the enum keyword when describing enums - insert colons (':') as needed in struct member descriptions - add missing short descriptions - convert some comments to kernel-doc format to prevent these warnings: Warning: include/uapi/linux/kfd_ioctl.h:708 cannot understand function prototype: 'struct kfd_ioctl_criu_args' Warning: include/uapi/linux/kfd_ioctl.h:771 cannot understand function prototype: 'enum kfd_ioctl_svm_op' Warning: include/uapi/linux/kfd_ioctl.h:805 cannot understand function prototype: 'enum kfd_ioctl_svm_attr_type' Warning: include/uapi/linux/kfd_ioctl.h:824 cannot understand function prototype: 'struct kfd_ioctl_svm_attribute' Warning: include/uapi/linux/kfd_ioctl.h:867 cannot understand function prototype: 'struct kfd_ioctl_svm_args' Warning: include/uapi/linux/kfd_ioctl.h:910 cannot understand function prototype: 'struct kfd_ioctl_set_xnack_mode_args' Warning: include/uapi/linux/kfd_ioctl.h:1075 cannot understand function prototype: 'struct kfd_ioctl_runtime_enable_args' Warning: include/uapi/linux/kfd_ioctl.h:1160 missing initial short description on line: * kfd_ioctl_dbg_trap_enable_args Warning: include/uapi/linux/kfd_ioctl.h:1182 cannot understand function prototype: 'struct kfd_ioctl_dbg_trap_enable_args' Warning: include/uapi/linux/kfd_ioctl.h:1190 missing initial short description on line: * kfd_ioctl_dbg_trap_send_runtime_event_args Warning: include/uapi/linux/kfd_ioctl.h:1208 cannot understand function prototype: 'struct kfd_ioctl_dbg_trap_send_runtime_event_args' Warning: include/uapi/linux/kfd_ioctl.h:1215 missing initial short description on line: * kfd_ioctl_dbg_trap_set_exceptions_enabled_args Warning: include/uapi/linux/kfd_ioctl.h:1225 cannot understand function prototype: 'struct kfd_ioctl_dbg_trap_set_exceptions_enabled_args' Warning: include/uapi/linux/kfd_ioctl.h:1230 missing initial short description on line: * kfd_ioctl_dbg_trap_set_wave_launch_override_args Warning: include/uapi/linux/kfd_ioctl.h:1252 cannot understand function prototype: 'struct kfd_ioctl_dbg_trap_set_wave_launch_override_args' Warning: include/uapi/linux/kfd_ioctl.h:1260 missing initial short description on line: * kfd_ioctl_dbg_trap_set_wave_launch_mode_args Warning: include/uapi/linux/kfd_ioctl.h:1270 cannot understand function prototype: 'struct kfd_ioctl_dbg_trap_set_wave_launch_mode_args' Warning: include/uapi/linux/kfd_ioctl.h:1276 missing initial short description on line: * kfd_ioctl_dbg_trap_suspend_queues_ags Warning: include/uapi/linux/kfd_ioctl.h:1305 cannot understand function prototype: 'struct kfd_ioctl_dbg_trap_suspend_queues_args' Warning: include/uapi/linux/kfd_ioctl.h:1313 missing initial short description on line: * kfd_ioctl_dbg_trap_resume_queues_args Warning: include/uapi/linux/kfd_ioctl.h:1330 cannot understand function prototype: 'struct kfd_ioctl_dbg_trap_resume_queues_args' Warning: include/uapi/linux/kfd_ioctl.h:1337 missing initial short description on line: * kfd_ioctl_dbg_trap_set_node_address_watch_args Warning: include/uapi/linux/kfd_ioctl.h:1354 cannot understand function prototype: 'struct kfd_ioctl_dbg_trap_set_node_address_watch_args' Warning: include/uapi/linux/kfd_ioctl.h:1363 missing initial short description on line: * kfd_ioctl_dbg_trap_clear_node_address_watch_args Warning: include/uapi/linux/kfd_ioctl.h:1376 cannot understand function prototype: 'struct kfd_ioctl_dbg_trap_clear_node_address_watch_args' Warning: include/uapi/linux/kfd_ioctl.h:1382 missing initial short description on line: * kfd_ioctl_dbg_trap_set_flags_args Warning: include/uapi/linux/kfd_ioctl.h:1393 cannot understand function prototype: 'struct kfd_ioctl_dbg_trap_set_flags_args' Warning: include/uapi/linux/kfd_ioctl.h:1399 missing initial short description on line: * kfd_ioctl_dbg_trap_query_debug_event_args Warning: include/uapi/linux/kfd_ioctl.h:1421 cannot understand function prototype: 'struct kfd_ioctl_dbg_trap_query_debug_event_args' Warning: include/uapi/linux/kfd_ioctl.h:1428 missing initial short description on line: * kfd_ioctl_dbg_trap_query_exception_info_args Warning: include/uapi/linux/kfd_ioctl.h:1448 cannot understand function prototype: 'struct kfd_ioctl_dbg_trap_query_exception_info_args' Warning: include/uapi/linux/kfd_ioctl.h:1457 missing initial short description on line: * kfd_ioctl_dbg_trap_get_queue_snapshot_args Warning: include/uapi/linux/kfd_ioctl.h:1485 cannot understand function prototype: 'struct kfd_ioctl_dbg_trap_queue_snapshot_args' Warning: include/uapi/linux/kfd_ioctl.h:1493 missing initial short description on line: * kfd_ioctl_dbg_trap_get_device_snapshot_args Warning: include/uapi/linux/kfd_ioctl.h:1521 cannot understand function prototype: 'struct kfd_ioctl_dbg_trap_device_snapshot_args' Warning: include/uapi/linux/kfd_ioctl.h:1529 missing initial short description on line: * kfd_ioctl_dbg_trap_args Warning: include/uapi/linux/kfd_ioctl.h:1539 cannot understand function prototype: 'struct kfd_ioctl_dbg_trap_args' Warning: include/uapi/linux/kfd_ioctl.h:1569 This comment starts with '/**', but isn't a kernel-doc comment. * Enables/Disables GPU Specific profiler settings Warning: include/uapi/linux/kfd_ioctl.h:718 struct member 'num_bos' not described in 'kfd_ioctl_criu_args' Warning: include/uapi/linux/kfd_ioctl.h:718 struct member 'op' not described in 'kfd_ioctl_criu_args' Warning: include/uapi/linux/kfd_ioctl.h:874 struct member 'start_addr' not described in 'kfd_ioctl_svm_args' Warning: include/uapi/linux/kfd_ioctl.h:874 struct member 'size' not described in 'kfd_ioctl_svm_args' Warning: include/uapi/linux/kfd_ioctl.h:874 struct member 'op' not described in 'kfd_ioctl_svm_args' Warning: include/uapi/linux/kfd_ioctl.h:874 struct member 'nattr' not described in 'kfd_ioctl_svm_args' Warning: include/uapi/linux/kfd_ioctl.h:874 struct member 'attrs' not described in 'kfd_ioctl_svm_args' Warning: include/uapi/linux/kfd_ioctl.h:1079 struct member 'r_debug' not described in 'kfd_ioctl_runtime_enable_args' Warning: include/uapi/linux/kfd_ioctl.h:1079 struct member 'mode_mask' not described in 'kfd_ioctl_runtime_enable_args' Warning: include/uapi/linux/kfd_ioctl.h:1079 struct member 'capabilities_mask' not described in 'kfd_ioctl_runtime_enable_args' Warning: include/uapi/linux/kfd_ioctl.h:1254 struct member 'pad' not described in 'kfd_ioctl_dbg_trap_set_wave_launch_override_args' Warning: include/uapi/linux/kfd_ioctl.h:1267 cannot understand function prototype: 'struct kfd_ioctl_dbg_trap_set_wave_launch_mode_args' Warning: include/uapi/linux/kfd_ioctl.h:1308 expecting prototype for struct kfd_ioctl_dbg_trap_suspend_queues_ags. Prototype was for struct kfd_ioctl_dbg_trap_suspend_queues_args instead Warning: include/uapi/linux/kfd_ioctl.h:1332 struct member 'pad' not described in 'kfd_ioctl_dbg_trap_resume_queues_args' Warning: include/uapi/linux/kfd_ioctl.h:1488 expecting prototype for struct kfd_ioctl_dbg_trap_get_queue_snapshot_args. Prototype was for struct kfd_ioctl_dbg_trap_queue_snapshot_args instead Warning: include/uapi/linux/kfd_ioctl.h:1524 expecting prototype for struct kfd_ioctl_dbg_trap_get_device_snapshot_args. Prototype was for struct kfd_ioctl_dbg_trap_device_snapshot_args instead * This leaves the following struct members undescribed in kernel-doc comments: Warning: include/uapi/linux/kfd_ioctl.h:1555 struct member 'enable' not described in 'kfd_ioctl_dbg_trap_args' Warning: include/uapi/linux/kfd_ioctl.h:1555 struct member 'send_runtime_event' not described in 'kfd_ioctl_dbg_trap_args' Warning: include/uapi/linux/kfd_ioctl.h:1555 struct member 'set_exceptions_enabled' not described in 'kfd_ioctl_dbg_trap_args' Warning: include/uapi/linux/kfd_ioctl.h:1555 struct member 'launch_override' not described in 'kfd_ioctl_dbg_trap_args' Warning: include/uapi/linux/kfd_ioctl.h:1555 struct member 'launch_mode' not described in 'kfd_ioctl_dbg_trap_args' Warning: include/uapi/linux/kfd_ioctl.h:1555 struct member 'suspend_queues' not described in 'kfd_ioctl_dbg_trap_args' Warning: include/uapi/linux/kfd_ioctl.h:1555 struct member 'resume_queues' not described in 'kfd_ioctl_dbg_trap_args' Warning: include/uapi/linux/kfd_ioctl.h:1555 struct member 'set_node_address_watch' not described in 'kfd_ioctl_dbg_trap_args' Warning: include/uapi/linux/kfd_ioctl.h:1555 struct member 'clear_node_address_watch' not described in 'kfd_ioctl_dbg_trap_args' Warning: include/uapi/linux/kfd_ioctl.h:1555 struct member 'set_flags' not described in 'kfd_ioctl_dbg_trap_args' Warning: include/uapi/linux/kfd_ioctl.h:1555 struct member 'query_debug_event' not described in 'kfd_ioctl_dbg_trap_args' Warning: include/uapi/linux/kfd_ioctl.h:1555 struct member 'query_exception_info' not described in 'kfd_ioctl_dbg_trap_args' Warning: include/uapi/linux/kfd_ioctl.h:1555 struct member 'queue_snapshot' not described in 'kfd_ioctl_dbg_trap_args' Warning: include/uapi/linux/kfd_ioctl.h:1555 struct member 'device_snapshot' not described in 'kfd_ioctl_dbg_trap_args' Warning: include/uapi/linux/kfd_ioctl.h:1571 struct member 'gpu_id' not described in 'kfd_ioctl_pmc_settings' Warning: include/uapi/linux/kfd_ioctl.h:1571 struct member 'lock' not described in 'kfd_ioctl_pmc_settings' Warning: include/uapi/linux/kfd_ioctl.h:1571 struct member 'perfcount_enable' not described in 'kfd_ioctl_pmc_settings' All amdgpu object files before/after compare equal after this change. Signed-off-by: Randy Dunlap Signed-off-by: Alex Deucher --- include/uapi/linux/kfd_ioctl.h | 180 ++++++++++++++++++++--------------------- 1 file changed, 89 insertions(+), 91 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/kfd_ioctl.h b/include/uapi/linux/kfd_ioctl.h index 9584b5aab727..749d196e2114 100644 --- a/include/uapi/linux/kfd_ioctl.h +++ b/include/uapi/linux/kfd_ioctl.h @@ -690,7 +690,7 @@ enum kfd_criu_op { }; /** - * kfd_ioctl_criu_args - Arguments perform CRIU operation + * struct kfd_ioctl_criu_args - Arguments perform CRIU operation * @devices: [in/out] User pointer to memory location for devices information. * This is an array of type kfd_criu_device_bucket. * @bos: [in/out] User pointer to memory location for BOs information @@ -698,11 +698,11 @@ enum kfd_criu_op { * @priv_data: [in/out] User pointer to memory location for private data * @priv_data_size: [in/out] Size of priv_data in bytes * @num_devices: [in/out] Number of GPUs used by process. Size of @devices array. - * @num_bos [in/out] Number of BOs used by process. Size of @bos array. + * @num_bos: [in/out] Number of BOs used by process. Size of @bos array. * @num_objects: [in/out] Number of objects used by process. Objects are opaque to * user application. * @pid: [in/out] PID of the process being checkpointed - * @op [in] Type of operation (kfd_criu_op) + * @op: [in] Type of operation (kfd_criu_op) * * Return: 0 on success, -errno on failure */ @@ -764,7 +764,7 @@ enum kfd_mmio_remap { #define KFD_IOCTL_SVM_FLAG_EXT_COHERENT 0x00000080 /** - * kfd_ioctl_svm_op - SVM ioctl operations + * enum kfd_ioctl_svm_op - SVM ioctl operations * * @KFD_IOCTL_SVM_OP_SET_ATTR: Modify one or more attributes * @KFD_IOCTL_SVM_OP_GET_ATTR: Query one or more attributes @@ -786,7 +786,7 @@ enum kfd_ioctl_svm_location { }; /** - * kfd_ioctl_svm_attr_type - SVM attribute types + * enum kfd_ioctl_svm_attr_type - SVM attribute types * * @KFD_IOCTL_SVM_ATTR_PREFERRED_LOC: gpuid of the preferred location, 0 for * system memory @@ -815,7 +815,7 @@ enum kfd_ioctl_svm_attr_type { }; /** - * kfd_ioctl_svm_attribute - Attributes as pairs of type and value + * struct kfd_ioctl_svm_attribute - Attributes as pairs of type and value * * The meaning of the @value depends on the attribute type. * @@ -828,7 +828,7 @@ struct kfd_ioctl_svm_attribute { }; /** - * kfd_ioctl_svm_args - Arguments for SVM ioctl + * struct kfd_ioctl_svm_args - Arguments for SVM ioctl * * @op specifies the operation to perform (see enum * @kfd_ioctl_svm_op). @start_addr and @size are common for all @@ -875,7 +875,7 @@ struct kfd_ioctl_svm_args { }; /** - * kfd_ioctl_set_xnack_mode_args - Arguments for set_xnack_mode + * struct kfd_ioctl_set_xnack_mode_args - Arguments for set_xnack_mode * * @xnack_enabled: [in/out] Whether to enable XNACK mode for this process * @@ -1055,15 +1055,15 @@ struct kfd_runtime_info { #define KFD_RUNTIME_ENABLE_MODE_TTMP_SAVE_MASK 2 /** - * kfd_ioctl_runtime_enable_args - Arguments for runtime enable + * struct kfd_ioctl_runtime_enable_args - Arguments for runtime enable * * Coordinates debug exception signalling and debug device enablement with runtime. * - * @r_debug - pointer to user struct for sharing information between ROCr and the debuggger - * @mode_mask - mask to set mode + * @r_debug: pointer to user struct for sharing information between ROCr and the debuggger + * @mode_mask: mask to set mode * KFD_RUNTIME_ENABLE_MODE_ENABLE_MASK - enable runtime for debugging, otherwise disable * KFD_RUNTIME_ENABLE_MODE_TTMP_SAVE_MASK - enable trap temporary setup (ignore on disable) - * @capabilities_mask - mask to notify runtime on what KFD supports + * @capabilities_mask: mask to notify runtime on what KFD supports * * Return - 0 on SUCCESS. * - EBUSY if runtime enable call already pending. @@ -1158,17 +1158,15 @@ enum kfd_dbg_trap_operations { }; /** - * kfd_ioctl_dbg_trap_enable_args - * - * Arguments for KFD_IOC_DBG_TRAP_ENABLE. + * struct kfd_ioctl_dbg_trap_enable_args - Arguments for KFD_IOC_DBG_TRAP_ENABLE. * * Enables debug session for target process. Call @op KFD_IOC_DBG_TRAP_DISABLE in * kfd_ioctl_dbg_trap_args to disable debug session. * - * @exception_mask (IN) - exceptions to raise to the debugger - * @rinfo_ptr (IN) - pointer to runtime info buffer (see kfd_runtime_info) - * @rinfo_size (IN/OUT) - size of runtime info buffer in bytes - * @dbg_fd (IN) - fd the KFD will nofify the debugger with of raised + * @exception_mask: (IN) - exceptions to raise to the debugger + * @rinfo_ptr: (IN) - pointer to runtime info buffer (see kfd_runtime_info) + * @rinfo_size: (IN/OUT) - size of runtime info buffer in bytes + * @dbg_fd: (IN) - fd the KFD will nofify the debugger with of raised * exceptions set in exception_mask. * * Generic errors apply (see kfd_dbg_trap_operations). @@ -1188,15 +1186,14 @@ struct kfd_ioctl_dbg_trap_enable_args { }; /** - * kfd_ioctl_dbg_trap_send_runtime_event_args - * + * struct kfd_ioctl_dbg_trap_send_runtime_event_args - Arguments for + * KFD_IOC_DBG_TRAP_SEND_RUNTIME_EVENT. * - * Arguments for KFD_IOC_DBG_TRAP_SEND_RUNTIME_EVENT. * Raises exceptions to runtime. * - * @exception_mask (IN) - exceptions to raise to runtime - * @gpu_id (IN) - target device id - * @queue_id (IN) - target queue id + * @exception_mask: (IN) - exceptions to raise to runtime + * @gpu_id: (IN) - target device id + * @queue_id: (IN) - target queue id * * Generic errors apply (see kfd_dbg_trap_operations). * Return - 0 on SUCCESS. @@ -1213,12 +1210,12 @@ struct kfd_ioctl_dbg_trap_send_runtime_event_args { }; /** - * kfd_ioctl_dbg_trap_set_exceptions_enabled_args + * struct kfd_ioctl_dbg_trap_set_exceptions_enabled_args - Arguments for + * KFD_IOC_SET_EXCEPTIONS_ENABLED * - * Arguments for KFD_IOC_SET_EXCEPTIONS_ENABLED * Set new exceptions to be raised to the debugger. * - * @exception_mask (IN) - new exceptions to raise the debugger + * @exception_mask: (IN) - new exceptions to raise the debugger * * Generic errors apply (see kfd_dbg_trap_operations). * Return - 0 on SUCCESS. @@ -1228,16 +1225,16 @@ struct kfd_ioctl_dbg_trap_set_exceptions_enabled_args { }; /** - * kfd_ioctl_dbg_trap_set_wave_launch_override_args + * struct kfd_ioctl_dbg_trap_set_wave_launch_override_args - Arguments for + * KFD_IOC_DBG_TRAP_SET_WAVE_LAUNCH_OVERRIDE * - * Arguments for KFD_IOC_DBG_TRAP_SET_WAVE_LAUNCH_OVERRIDE * Enable HW exceptions to raise trap. * - * @override_mode (IN) - see kfd_dbg_trap_override_mode - * @enable_mask (IN/OUT) - reference kfd_dbg_trap_mask. + * @override_mode: (IN) - see kfd_dbg_trap_override_mode + * @enable_mask: (IN/OUT) - reference kfd_dbg_trap_mask. * IN is the override modes requested to be enabled. * OUT is referenced in Return below. - * @support_request_mask (IN/OUT) - reference kfd_dbg_trap_mask. + * @support_request_mask: (IN/OUT) - reference kfd_dbg_trap_mask. * IN is the override modes requested for support check. * OUT is referenced in Return below. * @@ -1254,36 +1251,38 @@ struct kfd_ioctl_dbg_trap_set_wave_launch_override_args { __u32 override_mode; __u32 enable_mask; __u32 support_request_mask; + /* private: */ __u32 pad; }; /** - * kfd_ioctl_dbg_trap_set_wave_launch_mode_args + * struct kfd_ioctl_dbg_trap_set_wave_launch_mode_args - Arguments for + * KFD_IOC_DBG_TRAP_SET_WAVE_LAUNCH_MODE * - * Arguments for KFD_IOC_DBG_TRAP_SET_WAVE_LAUNCH_MODE * Set wave launch mode. * - * @mode (IN) - see kfd_dbg_trap_wave_launch_mode + * @launch_mode: (IN) - see kfd_dbg_trap_wave_launch_mode * * Generic errors apply (see kfd_dbg_trap_operations). * Return - 0 on SUCCESS. */ struct kfd_ioctl_dbg_trap_set_wave_launch_mode_args { __u32 launch_mode; + /* private: */ __u32 pad; }; /** - * kfd_ioctl_dbg_trap_suspend_queues_ags + * struct kfd_ioctl_dbg_trap_suspend_queues_args - Arguments for + * KFD_IOC_DBG_TRAP_SUSPEND_QUEUES * - * Arguments for KFD_IOC_DBG_TRAP_SUSPEND_QUEUES * Suspend queues. * - * @exception_mask (IN) - raised exceptions to clear - * @queue_array_ptr (IN) - pointer to array of queue ids (u32 per queue id) + * @exception_mask: (IN) - raised exceptions to clear + * @queue_array_ptr: (IN) - pointer to array of queue ids (u32 per queue id) * to suspend - * @num_queues (IN) - number of queues to suspend in @queue_array_ptr - * @grace_period (IN) - wave time allowance before preemption + * @num_queues: (IN) - number of queues to suspend in @queue_array_ptr + * @grace_period: (IN) - wave time allowance before preemption * per 1K GPU clock cycle unit * * Generic errors apply (see kfd_dbg_trap_operations). @@ -1311,14 +1310,14 @@ struct kfd_ioctl_dbg_trap_suspend_queues_args { }; /** - * kfd_ioctl_dbg_trap_resume_queues_args + * struct kfd_ioctl_dbg_trap_resume_queues_args - Arguments for + * KFD_IOC_DBG_TRAP_RESUME_QUEUES * - * Arguments for KFD_IOC_DBG_TRAP_RESUME_QUEUES * Resume queues. * - * @queue_array_ptr (IN) - pointer to array of queue ids (u32 per queue id) + * @queue_array_ptr: (IN) - pointer to array of queue ids (u32 per queue id) * to resume - * @num_queues (IN) - number of queues to resume in @queue_array_ptr + * @num_queues: (IN) - number of queues to resume in @queue_array_ptr * * Generic errors apply (see kfd_dbg_trap_operations). * Return - Number of queues resumed on SUCCESS. @@ -1331,20 +1330,21 @@ struct kfd_ioctl_dbg_trap_suspend_queues_args { struct kfd_ioctl_dbg_trap_resume_queues_args { __u64 queue_array_ptr; __u32 num_queues; + /* private: */ __u32 pad; }; /** - * kfd_ioctl_dbg_trap_set_node_address_watch_args + * struct kfd_ioctl_dbg_trap_set_node_address_watch_args - Arguments for + * KFD_IOC_DBG_TRAP_SET_NODE_ADDRESS_WATCH * - * Arguments for KFD_IOC_DBG_TRAP_SET_NODE_ADDRESS_WATCH * Sets address watch for device. * - * @address (IN) - watch address to set - * @mode (IN) - see kfd_dbg_trap_address_watch_mode - * @mask (IN) - watch address mask - * @gpu_id (IN) - target gpu to set watch point - * @id (OUT) - watch id allocated + * @address: (IN) - watch address to set + * @mode: (IN) - see kfd_dbg_trap_address_watch_mode + * @mask: (IN) - watch address mask + * @gpu_id: (IN) - target gpu to set watch point + * @id: (OUT) - watch id allocated * * Generic errors apply (see kfd_dbg_trap_operations). * Return - 0 on SUCCESS. @@ -1361,13 +1361,13 @@ struct kfd_ioctl_dbg_trap_set_node_address_watch_args { }; /** - * kfd_ioctl_dbg_trap_clear_node_address_watch_args + * struct kfd_ioctl_dbg_trap_clear_node_address_watch_args - Arguments for + * KFD_IOC_DBG_TRAP_CLEAR_NODE_ADDRESS_WATCH * - * Arguments for KFD_IOC_DBG_TRAP_CLEAR_NODE_ADDRESS_WATCH * Clear address watch for device. * - * @gpu_id (IN) - target device to clear watch point - * @id (IN) - allocated watch id to clear + * @gpu_id: (IN) - target device to clear watch point + * @id: (IN) - allocated watch id to clear * * Generic errors apply (see kfd_dbg_trap_operations). * Return - 0 on SUCCESS. @@ -1380,12 +1380,12 @@ struct kfd_ioctl_dbg_trap_clear_node_address_watch_args { }; /** - * kfd_ioctl_dbg_trap_set_flags_args + * struct kfd_ioctl_dbg_trap_set_flags_args - Arguments for + * KFD_IOC_DBG_TRAP_SET_FLAGS * - * Arguments for KFD_IOC_DBG_TRAP_SET_FLAGS * Sets flags for wave behaviour. * - * @flags (IN/OUT) - IN = flags to enable, OUT = flags previously enabled + * @flags: (IN/OUT) - IN = flags to enable, OUT = flags previously enabled * * Generic errors apply (see kfd_dbg_trap_operations). * Return - 0 on SUCCESS. @@ -1393,13 +1393,13 @@ struct kfd_ioctl_dbg_trap_clear_node_address_watch_args { */ struct kfd_ioctl_dbg_trap_set_flags_args { __u32 flags; + /* private: */ __u32 pad; }; /** - * kfd_ioctl_dbg_trap_query_debug_event_args - * - * Arguments for KFD_IOC_DBG_TRAP_QUERY_DEBUG_EVENT + * struct kfd_ioctl_dbg_trap_query_debug_event_args - Arguments for + * KFD_IOC_DBG_TRAP_QUERY_DEBUG_EVENT * * Find one or more raised exceptions. This function can return multiple * exceptions from a single queue or a single device with one call. To find @@ -1409,9 +1409,9 @@ struct kfd_ioctl_dbg_trap_set_flags_args { * However, clearing an exception prevents retrieving further information * about it with KFD_IOC_DBG_TRAP_QUERY_EXCEPTION_INFO. * - * @exception_mask (IN/OUT) - exception to clear (IN) and raised (OUT) - * @gpu_id (OUT) - gpu id of exceptions raised - * @queue_id (OUT) - queue id of exceptions raised + * @exception_mask: (IN/OUT) - exception to clear (IN) and raised (OUT) + * @gpu_id: (OUT) - gpu id of exceptions raised + * @queue_id: (OUT) - queue id of exceptions raised * * Generic errors apply (see kfd_dbg_trap_operations). * Return - 0 on raised exception found @@ -1426,16 +1426,16 @@ struct kfd_ioctl_dbg_trap_query_debug_event_args { }; /** - * kfd_ioctl_dbg_trap_query_exception_info_args + * struct kfd_ioctl_dbg_trap_query_exception_info_args - Arguments for + * KFD_IOC_DBG_TRAP_QUERY_EXCEPTION_INFO * - * Arguments KFD_IOC_DBG_TRAP_QUERY_EXCEPTION_INFO * Get additional info on raised exception. * - * @info_ptr (IN) - pointer to exception info buffer to copy to - * @info_size (IN/OUT) - exception info buffer size (bytes) - * @source_id (IN) - target gpu or queue id - * @exception_code (IN) - target exception - * @clear_exception (IN) - clear raised @exception_code exception + * @info_ptr: (IN) - pointer to exception info buffer to copy to + * @info_size: (IN/OUT) - exception info buffer size (bytes) + * @source_id: (IN) - target gpu or queue id + * @exception_code: (IN) - target exception + * @clear_exception: (IN) - clear raised @exception_code exception * (0 = false, 1 = true) * * Generic errors apply (see kfd_dbg_trap_operations). @@ -1455,20 +1455,20 @@ struct kfd_ioctl_dbg_trap_query_exception_info_args { }; /** - * kfd_ioctl_dbg_trap_get_queue_snapshot_args + * struct kfd_ioctl_dbg_trap_queue_snapshot_args - Arguments for + * KFD_IOC_DBG_TRAP_GET_QUEUE_SNAPSHOT * - * Arguments KFD_IOC_DBG_TRAP_GET_QUEUE_SNAPSHOT * Get queue information. * - * @exception_mask (IN) - exceptions raised to clear - * @snapshot_buf_ptr (IN) - queue snapshot entry buffer (see kfd_queue_snapshot_entry) - * @num_queues (IN/OUT) - number of queue snapshot entries + * @exception_mask: (IN) - exceptions raised to clear + * @snapshot_buf_ptr: (IN) - queue snapshot entry buffer (see kfd_queue_snapshot_entry) + * @num_queues: (IN/OUT) - number of queue snapshot entries * The debugger specifies the size of the array allocated in @num_queues. * KFD returns the number of queues that actually existed. If this is * larger than the size specified by the debugger, KFD will not overflow * the array allocated by the debugger. * - * @entry_size (IN/OUT) - size per entry in bytes + * @entry_size: (IN/OUT) - size per entry in bytes * The debugger specifies sizeof(struct kfd_queue_snapshot_entry) in * @entry_size. KFD returns the number of bytes actually populated per * entry. The debugger should use the KFD_IOCTL_MINOR_VERSION to determine, @@ -1491,20 +1491,20 @@ struct kfd_ioctl_dbg_trap_queue_snapshot_args { }; /** - * kfd_ioctl_dbg_trap_get_device_snapshot_args + * struct kfd_ioctl_dbg_trap_device_snapshot_args - Arguments for + * KFD_IOC_DBG_TRAP_GET_DEVICE_SNAPSHOT * - * Arguments for KFD_IOC_DBG_TRAP_GET_DEVICE_SNAPSHOT * Get device information. * - * @exception_mask (IN) - exceptions raised to clear - * @snapshot_buf_ptr (IN) - pointer to snapshot buffer (see kfd_dbg_device_info_entry) - * @num_devices (IN/OUT) - number of debug devices to snapshot + * @exception_mask: (IN) - exceptions raised to clear + * @snapshot_buf_ptr: (IN) - pointer to snapshot buffer (see kfd_dbg_device_info_entry) + * @num_devices: (IN/OUT) - number of debug devices to snapshot * The debugger specifies the size of the array allocated in @num_devices. * KFD returns the number of devices that actually existed. If this is * larger than the size specified by the debugger, KFD will not overflow * the array allocated by the debugger. * - * @entry_size (IN/OUT) - size per entry in bytes + * @entry_size: (IN/OUT) - size per entry in bytes * The debugger specifies sizeof(struct kfd_dbg_device_info_entry) in * @entry_size. KFD returns the number of bytes actually populated. The * debugger should use KFD_IOCTL_MINOR_VERSION to determine, which fields @@ -1527,12 +1527,10 @@ struct kfd_ioctl_dbg_trap_device_snapshot_args { }; /** - * kfd_ioctl_dbg_trap_args - * - * Arguments to debug target process. + * struct kfd_ioctl_dbg_trap_args - Arguments to debug target process. * - * @pid - target process to debug - * @op - debug operation (see kfd_dbg_trap_operations) + * @pid: target process to debug + * @op: debug operation (see kfd_dbg_trap_operations) * * @op determines which union struct args to use. * Refer to kern docs for each kfd_ioctl_dbg_trap_*_args struct. @@ -1567,7 +1565,7 @@ enum kfd_profiler_ops { }; /** - * Enables/Disables GPU Specific profiler settings + * struct kfd_ioctl_pmc_settings - Enables/Disables GPU Specific profiler settings */ struct kfd_ioctl_pmc_settings { __u32 gpu_id; /* This is the user_gpu_id */ -- cgit v1.2.3 From 949eb95d5bcc530e6ce74cc95897c1849c53ea89 Mon Sep 17 00:00:00 2001 From: Gao Xiang Date: Wed, 12 Aug 2026 15:15:27 +0100 Subject: cachefiles,netfs: sunset ondemand mode It was an effort to enhance fscache as a kernel cache for lazy pulling (at least according to previous Incremental FS discussion [1]) and EROFS over fscache was the in-tree user of this mode. fscache has since evolved to be netfslib-oriented, serving network filesystem inodes via the netfs library, but EROFS never acts as a network filesystem and we need to cache golden filesystem images rather than individual EROFS inodes. Since EROFS over fscache is now removed, clean up netfs/fscache/ cachefiles upstream too. [1] https://lore.kernel.org/r/CAOQ4uxi4dzxArY24YO=+kBCK2gGoq3Ptb8WkzCqSogPgU_R3dQ@mail.gmail.com [dh] Fixed up comments on: https://sashiko.dev/#/patchset/20260716103030.3065561-1-dhowells%40redhat.com https://sashiko.dev/#/patchset/20260722130218.78958-1-dhowells%40redhat.com Signed-off-by: Gao Xiang Signed-off-by: David Howells Link: https://patch.msgid.link/1046393.1786544127@warthog.procyon.org.uk cc: Paulo Alcantara cc: netfs@lists.linux.dev cc: linux-erofs@lists.ozlabs.org cc: bpf@vger.kernel.org cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner (Amutable) --- Documentation/filesystems/caching/cachefiles.rst | 179 ------ fs/cachefiles/Kconfig | 12 - fs/cachefiles/Makefile | 1 - fs/cachefiles/daemon.c | 96 +-- fs/cachefiles/interface.c | 8 - fs/cachefiles/internal.h | 134 ---- fs/cachefiles/io.c | 26 +- fs/cachefiles/namei.c | 8 - fs/cachefiles/ondemand.c | 761 ----------------------- include/linux/netfs.h | 9 - include/trace/events/cachefiles.h | 184 +----- include/uapi/linux/cachefiles.h | 68 -- kernel/bpf/btf.c | 7 - 13 files changed, 9 insertions(+), 1484 deletions(-) delete mode 100644 fs/cachefiles/ondemand.c delete mode 100644 include/uapi/linux/cachefiles.h (limited to 'include/uapi/linux') diff --git a/Documentation/filesystems/caching/cachefiles.rst b/Documentation/filesystems/caching/cachefiles.rst index b3ccc782cb3b..adfb7d079027 100644 --- a/Documentation/filesystems/caching/cachefiles.rst +++ b/Documentation/filesystems/caching/cachefiles.rst @@ -28,8 +28,6 @@ Cache on Already Mounted Filesystem (*) Debugging. - (*) On-demand Read. - Overview ======== @@ -483,180 +481,3 @@ the control file. For example:: echo $((1|4|8)) >/sys/module/cachefiles/parameters/debug will turn on all function entry debugging. - - -On-demand Read -============== - -When working in its original mode, CacheFiles serves as a local cache for a -remote networking fs - while in on-demand read mode, CacheFiles can boost the -scenario where on-demand read semantics are needed, e.g. container image -distribution. - -The essential difference between these two modes is seen when a cache miss -occurs: In the original mode, the netfs will fetch the data from the remote -server and then write it to the cache file; in on-demand read mode, fetching -the data and writing it into the cache is delegated to a user daemon. - -``CONFIG_CACHEFILES_ONDEMAND`` should be enabled to support on-demand read mode. - - -Protocol Communication ----------------------- - -The on-demand read mode uses a simple protocol for communication between kernel -and user daemon. The protocol can be modeled as:: - - kernel --[request]--> user daemon --[reply]--> kernel - -CacheFiles will send requests to the user daemon when needed. The user daemon -should poll the devnode ('/dev/cachefiles') to check if there's a pending -request to be processed. A POLLIN event will be returned when there's a pending -request. - -The user daemon then reads the devnode to fetch a request to process. It should -be noted that each read only gets one request. When it has finished processing -the request, the user daemon should write the reply to the devnode. - -Each request starts with a message header of the form:: - - struct cachefiles_msg { - __u32 msg_id; - __u32 opcode; - __u32 len; - __u32 object_id; - __u8 data[]; - }; - -where: - - * ``msg_id`` is a unique ID identifying this request among all pending - requests. - - * ``opcode`` indicates the type of this request. - - * ``object_id`` is a unique ID identifying the cache file operated on. - - * ``data`` indicates the payload of this request. - - * ``len`` indicates the whole length of this request, including the - header and following type-specific payload. - - -Turning on On-demand Mode -------------------------- - -An optional parameter becomes available to the "bind" command:: - - bind [ondemand] - -When the "bind" command is given no argument, it defaults to the original mode. -When it is given the "ondemand" argument, i.e. "bind ondemand", on-demand read -mode will be enabled. - - -The OPEN Request ----------------- - -When the netfs opens a cache file for the first time, a request with the -CACHEFILES_OP_OPEN opcode, a.k.a an OPEN request will be sent to the user -daemon. The payload format is of the form:: - - struct cachefiles_open { - __u32 volume_key_size; - __u32 cookie_key_size; - __u32 fd; - __u32 flags; - __u8 data[]; - }; - -where: - - * ``data`` contains the volume_key followed directly by the cookie_key. - The volume key is a NUL-terminated string; the cookie key is binary - data. - - * ``volume_key_size`` indicates the size of the volume key in bytes. - - * ``cookie_key_size`` indicates the size of the cookie key in bytes. - - * ``fd`` indicates an anonymous fd referring to the cache file, through - which the user daemon can perform write/llseek file operations on the - cache file. - - -The user daemon can use the given (volume_key, cookie_key) pair to distinguish -the requested cache file. With the given anonymous fd, the user daemon can -fetch the data and write it to the cache file in the background, even when -kernel has not triggered a cache miss yet. - -Be noted that each cache file has a unique object_id, while it may have multiple -anonymous fds. The user daemon may duplicate anonymous fds from the initial -anonymous fd indicated by the @fd field through dup(). Thus each object_id can -be mapped to multiple anonymous fds, while the usr daemon itself needs to -maintain the mapping. - -When implementing a user daemon, please be careful of RLIMIT_NOFILE, -``/proc/sys/fs/nr_open`` and ``/proc/sys/fs/file-max``. Typically these needn't -be huge since they're related to the number of open device blobs rather than -open files of each individual filesystem. - -The user daemon should reply the OPEN request by issuing a "copen" (complete -open) command on the devnode:: - - copen , - -where: - - * ``msg_id`` must match the msg_id field of the OPEN request. - - * When >= 0, ``cache_size`` indicates the size of the cache file; - when < 0, ``cache_size`` indicates any error code encountered by the - user daemon. - - -The CLOSE Request ------------------ - -When a cookie withdrawn, a CLOSE request (opcode CACHEFILES_OP_CLOSE) will be -sent to the user daemon. This tells the user daemon to close all anonymous fds -associated with the given object_id. The CLOSE request has no extra payload, -and shouldn't be replied. - - -The READ Request ----------------- - -When a cache miss is encountered in on-demand read mode, CacheFiles will send a -READ request (opcode CACHEFILES_OP_READ) to the user daemon. This tells the user -daemon to fetch the contents of the requested file range. The payload is of the -form:: - - struct cachefiles_read { - __u64 off; - __u64 len; - }; - -where: - - * ``off`` indicates the starting offset of the requested file range. - - * ``len`` indicates the length of the requested file range. - - -When it receives a READ request, the user daemon should fetch the requested data -and write it to the cache file identified by object_id. - -When it has finished processing the READ request, the user daemon should reply -by using the CACHEFILES_IOC_READ_COMPLETE ioctl on one of the anonymous fds -associated with the object_id given in the READ request. The ioctl is of the -form:: - - ioctl(fd, CACHEFILES_IOC_READ_COMPLETE, msg_id); - -where: - - * ``fd`` is one of the anonymous fds associated with the object_id - given. - - * ``msg_id`` must match the msg_id field of the READ request. diff --git a/fs/cachefiles/Kconfig b/fs/cachefiles/Kconfig index c5a070550ee3..afb25b6af5aa 100644 --- a/fs/cachefiles/Kconfig +++ b/fs/cachefiles/Kconfig @@ -26,15 +26,3 @@ config CACHEFILES_ERROR_INJECTION help This permits error injection to be enabled in cachefiles whilst a cache is in service. - -config CACHEFILES_ONDEMAND - bool "Support for on-demand read" - depends on CACHEFILES - default n - help - This permits userspace to enable the cachefiles on-demand read mode. - In this mode, when a cache miss occurs, responsibility for fetching - the data lies with the cachefiles backend instead of with the netfs - and is delegated to userspace. - - If unsure, say N. diff --git a/fs/cachefiles/Makefile b/fs/cachefiles/Makefile index c37a7a9af10b..16d811f1a2fa 100644 --- a/fs/cachefiles/Makefile +++ b/fs/cachefiles/Makefile @@ -16,6 +16,5 @@ cachefiles-y := \ xattr.o cachefiles-$(CONFIG_CACHEFILES_ERROR_INJECTION) += error_inject.o -cachefiles-$(CONFIG_CACHEFILES_ONDEMAND) += ondemand.o obj-$(CONFIG_CACHEFILES) := cachefiles.o diff --git a/fs/cachefiles/daemon.c b/fs/cachefiles/daemon.c index 4117b145ac94..1a66e0af2837 100644 --- a/fs/cachefiles/daemon.c +++ b/fs/cachefiles/daemon.c @@ -76,10 +76,6 @@ static const struct cachefiles_daemon_cmd cachefiles_daemon_cmds[] = { { "inuse", cachefiles_daemon_inuse }, { "secctx", cachefiles_daemon_secctx }, { "tag", cachefiles_daemon_tag }, -#ifdef CONFIG_CACHEFILES_ONDEMAND - { "copen", cachefiles_ondemand_copen }, - { "restore", cachefiles_ondemand_restore }, -#endif { "", NULL } }; @@ -113,9 +109,6 @@ static int cachefiles_daemon_open(struct inode *inode, struct file *file) INIT_LIST_HEAD(&cache->volumes); INIT_LIST_HEAD(&cache->object_list); spin_lock_init(&cache->object_list_lock); - refcount_set(&cache->unbind_pincount, 1); - xa_init_flags(&cache->reqs, XA_FLAGS_ALLOC); - xa_init_flags(&cache->ondemand_ids, XA_FLAGS_ALLOC1); /* set default caching limits * - limit at 1% free space and/or free files @@ -134,54 +127,6 @@ static int cachefiles_daemon_open(struct inode *inode, struct file *file) return 0; } -void cachefiles_flush_reqs(struct cachefiles_cache *cache) -{ - struct xarray *xa = &cache->reqs; - struct cachefiles_req *req; - unsigned long index; - - /* - * Make sure the following two operations won't be reordered. - * 1) set CACHEFILES_DEAD bit - * 2) flush requests in the xarray - * Otherwise the request may be enqueued after xarray has been - * flushed, leaving the orphan request never being completed. - * - * CPU 1 CPU 2 - * ===== ===== - * flush requests in the xarray - * test CACHEFILES_DEAD bit - * enqueue the request - * set CACHEFILES_DEAD bit - */ - smp_mb(); - - xa_lock(xa); - xa_for_each(xa, index, req) { - req->error = -EIO; - complete(&req->done); - __xa_erase(xa, index); - } - xa_unlock(xa); - - xa_destroy(&cache->reqs); - xa_destroy(&cache->ondemand_ids); -} - -void cachefiles_put_unbind_pincount(struct cachefiles_cache *cache) -{ - if (refcount_dec_and_test(&cache->unbind_pincount)) { - cachefiles_daemon_unbind(cache); - cachefiles_open = 0; - kfree(cache); - } -} - -void cachefiles_get_unbind_pincount(struct cachefiles_cache *cache) -{ - refcount_inc(&cache->unbind_pincount); -} - /* * Release a cache. */ @@ -194,16 +139,14 @@ static int cachefiles_daemon_release(struct inode *inode, struct file *file) ASSERT(cache); set_bit(CACHEFILES_DEAD, &cache->flags); - - if (cachefiles_in_ondemand_mode(cache)) - cachefiles_flush_reqs(cache); + cachefiles_daemon_unbind(cache); /* clean up the control file interface */ cache->cachefilesd = NULL; file->private_data = NULL; + cachefiles_open = 0; - cachefiles_put_unbind_pincount(cache); - + kfree(cache); _leave(""); return 0; } @@ -266,10 +209,7 @@ static ssize_t cachefiles_daemon_read(struct file *file, char __user *_buffer, if (!test_bit(CACHEFILES_READY, &cache->flags)) return 0; - if (cachefiles_in_ondemand_mode(cache)) - return cachefiles_ondemand_daemon_read(cache, _buffer, buflen); - else - return cachefiles_do_daemon_read(cache, _buffer, buflen); + return cachefiles_do_daemon_read(cache, _buffer, buflen); } /* @@ -358,28 +298,13 @@ static __poll_t cachefiles_daemon_poll(struct file *file, struct poll_table_struct *poll) { struct cachefiles_cache *cache = file->private_data; - XA_STATE(xas, &cache->reqs, 0); - struct cachefiles_req *req; __poll_t mask; poll_wait(file, &cache->daemon_pollwq, poll); mask = 0; - if (cachefiles_in_ondemand_mode(cache)) { - if (!xa_empty(&cache->reqs)) { - xas_lock(&xas); - xas_for_each_marked(&xas, req, ULONG_MAX, CACHEFILES_REQ_NEW) { - if (!cachefiles_ondemand_is_reopening_read(req)) { - mask |= EPOLLIN; - break; - } - } - xas_unlock(&xas); - } - } else { - if (test_bit(CACHEFILES_STATE_CHANGED, &cache->flags)) - mask |= EPOLLIN; - } + if (test_bit(CACHEFILES_STATE_CHANGED, &cache->flags)) + mask |= EPOLLIN; if (test_bit(CACHEFILES_CULLING, &cache->flags)) mask |= EPOLLOUT; @@ -779,14 +704,7 @@ static int cachefiles_daemon_bind(struct cachefiles_cache *cache, char *args) return -EBUSY; } - if (IS_ENABLED(CONFIG_CACHEFILES_ONDEMAND)) { - if (!strcmp(args, "ondemand")) { - set_bit(CACHEFILES_ONDEMAND_MODE, &cache->flags); - } else if (*args) { - pr_err("Invalid argument to the 'bind' command\n"); - return -EINVAL; - } - } else if (*args) { + if (*args) { pr_err("'bind' command doesn't take an argument\n"); return -EINVAL; } diff --git a/fs/cachefiles/interface.c b/fs/cachefiles/interface.c index a08250d244ea..50a000310a8c 100644 --- a/fs/cachefiles/interface.c +++ b/fs/cachefiles/interface.c @@ -32,11 +32,6 @@ struct cachefiles_object *cachefiles_alloc_object(struct fscache_cookie *cookie) if (!object) return NULL; - if (cachefiles_ondemand_init_obj_info(object, volume)) { - kmem_cache_free(cachefiles_object_jar, object); - return NULL; - } - refcount_set(&object->ref, 1); spin_lock_init(&object->lock); @@ -94,7 +89,6 @@ void cachefiles_put_object(struct cachefiles_object *object, ASSERTCMP(object->file, ==, NULL); kfree(object->d_name); - cachefiles_ondemand_deinit_obj_info(object); cache = object->volume->cache->cache; fscache_put_cookie(object->cookie, fscache_cookie_put_object); object->cookie = NULL; @@ -374,8 +368,6 @@ static void cachefiles_withdraw_cookie(struct fscache_cookie *cookie) spin_unlock(&cache->object_list_lock); } - cachefiles_ondemand_clean_object(object); - if (object->file) { cachefiles_begin_secure(cache, &saved_cred); cachefiles_clean_up_object(object, cache); diff --git a/fs/cachefiles/internal.h b/fs/cachefiles/internal.h index b62cd3e9a18e..c93324e0f98c 100644 --- a/fs/cachefiles/internal.h +++ b/fs/cachefiles/internal.h @@ -15,8 +15,6 @@ #include #include #include -#include -#include #define CACHEFILES_DIO_BLOCK_SIZE 4096 @@ -44,21 +42,6 @@ struct cachefiles_volume { struct dentry *fanout[256]; /* Fanout subdirs */ }; -enum cachefiles_object_state { - CACHEFILES_ONDEMAND_OBJSTATE_CLOSE, /* Anonymous fd closed by daemon or initial state */ - CACHEFILES_ONDEMAND_OBJSTATE_OPEN, /* Anonymous fd associated with object is available */ - CACHEFILES_ONDEMAND_OBJSTATE_REOPENING, /* Object that was closed and is being reopened. */ - CACHEFILES_ONDEMAND_OBJSTATE_DROPPING, /* Object is being dropped. */ -}; - -struct cachefiles_ondemand_info { - struct work_struct ondemand_work; - int ondemand_id; - enum cachefiles_object_state state; - struct cachefiles_object *object; - spinlock_t lock; -}; - /* * Backing file state. */ @@ -74,13 +57,8 @@ struct cachefiles_object { enum cachefiles_content content_info:8; /* Info about content presence */ unsigned long flags; #define CACHEFILES_OBJECT_USING_TMPFILE 0 /* Have an unlinked tmpfile */ -#ifdef CONFIG_CACHEFILES_ONDEMAND - struct cachefiles_ondemand_info *ondemand; -#endif }; -#define CACHEFILES_ONDEMAND_ID_CLOSED -1 - /* * Cache files cache definition */ @@ -119,35 +97,12 @@ struct cachefiles_cache { #define CACHEFILES_DEAD 1 /* T if cache dead */ #define CACHEFILES_CULLING 2 /* T if cull engaged */ #define CACHEFILES_STATE_CHANGED 3 /* T if state changed (poll trigger) */ -#define CACHEFILES_ONDEMAND_MODE 4 /* T if in on-demand read mode */ char *rootdirname; /* name of cache root directory */ char *tag; /* cache binding tag */ - refcount_t unbind_pincount;/* refcount to do daemon unbind */ - struct xarray reqs; /* xarray of pending on-demand requests */ - unsigned long req_id_next; - struct xarray ondemand_ids; /* xarray for ondemand_id allocation */ - u32 ondemand_id_next; - u32 msg_id_next; u32 secid; /* LSM security id */ bool have_secid; /* whether "secid" was set */ }; -static inline bool cachefiles_in_ondemand_mode(struct cachefiles_cache *cache) -{ - return IS_ENABLED(CONFIG_CACHEFILES_ONDEMAND) && - test_bit(CACHEFILES_ONDEMAND_MODE, &cache->flags); -} - -struct cachefiles_req { - struct cachefiles_object *object; - struct completion done; - refcount_t ref; - int error; - struct cachefiles_msg msg; -}; - -#define CACHEFILES_REQ_NEW XA_MARK_1 - #include static inline @@ -190,9 +145,6 @@ extern int cachefiles_has_space(struct cachefiles_cache *cache, * daemon.c */ extern const struct file_operations cachefiles_daemon_fops; -extern void cachefiles_flush_reqs(struct cachefiles_cache *cache); -extern void cachefiles_get_unbind_pincount(struct cachefiles_cache *cache); -extern void cachefiles_put_unbind_pincount(struct cachefiles_cache *cache); /* * error_inject.c @@ -298,90 +250,6 @@ extern struct file *cachefiles_create_tmpfile(struct cachefiles_object *object); extern bool cachefiles_commit_tmpfile(struct cachefiles_cache *cache, struct cachefiles_object *object); -/* - * ondemand.c - */ -#ifdef CONFIG_CACHEFILES_ONDEMAND -extern ssize_t cachefiles_ondemand_daemon_read(struct cachefiles_cache *cache, - char __user *_buffer, size_t buflen); - -extern int cachefiles_ondemand_copen(struct cachefiles_cache *cache, - char *args); - -extern int cachefiles_ondemand_restore(struct cachefiles_cache *cache, - char *args); - -extern int cachefiles_ondemand_init_object(struct cachefiles_object *object); -extern void cachefiles_ondemand_clean_object(struct cachefiles_object *object); - -extern int cachefiles_ondemand_read(struct cachefiles_object *object, - loff_t pos, size_t len); - -extern int cachefiles_ondemand_init_obj_info(struct cachefiles_object *obj, - struct cachefiles_volume *volume); -extern void cachefiles_ondemand_deinit_obj_info(struct cachefiles_object *obj); - -#define CACHEFILES_OBJECT_STATE_FUNCS(_state, _STATE) \ -static inline bool \ -cachefiles_ondemand_object_is_##_state(const struct cachefiles_object *object) \ -{ \ - return object->ondemand->state == CACHEFILES_ONDEMAND_OBJSTATE_##_STATE; \ -} \ - \ -static inline void \ -cachefiles_ondemand_set_object_##_state(struct cachefiles_object *object) \ -{ \ - object->ondemand->state = CACHEFILES_ONDEMAND_OBJSTATE_##_STATE; \ -} - -CACHEFILES_OBJECT_STATE_FUNCS(open, OPEN); -CACHEFILES_OBJECT_STATE_FUNCS(close, CLOSE); -CACHEFILES_OBJECT_STATE_FUNCS(reopening, REOPENING); -CACHEFILES_OBJECT_STATE_FUNCS(dropping, DROPPING); - -static inline bool cachefiles_ondemand_is_reopening_read(struct cachefiles_req *req) -{ - return cachefiles_ondemand_object_is_reopening(req->object) && - req->msg.opcode == CACHEFILES_OP_READ; -} - -#else -static inline ssize_t cachefiles_ondemand_daemon_read(struct cachefiles_cache *cache, - char __user *_buffer, size_t buflen) -{ - return -EOPNOTSUPP; -} - -static inline int cachefiles_ondemand_init_object(struct cachefiles_object *object) -{ - return 0; -} - -static inline void cachefiles_ondemand_clean_object(struct cachefiles_object *object) -{ -} - -static inline int cachefiles_ondemand_read(struct cachefiles_object *object, - loff_t pos, size_t len) -{ - return -EOPNOTSUPP; -} - -static inline int cachefiles_ondemand_init_obj_info(struct cachefiles_object *obj, - struct cachefiles_volume *volume) -{ - return 0; -} -static inline void cachefiles_ondemand_deinit_obj_info(struct cachefiles_object *obj) -{ -} - -static inline bool cachefiles_ondemand_is_reopening_read(struct cachefiles_req *req) -{ - return false; -} -#endif - /* * security.c */ @@ -430,8 +298,6 @@ do { \ pr_err("I/O Error: " FMT"\n", ##__VA_ARGS__); \ fscache_io_error((___cache)->cache); \ set_bit(CACHEFILES_DEAD, &(___cache)->flags); \ - if (cachefiles_in_ondemand_mode(___cache)) \ - cachefiles_flush_reqs(___cache); \ } while (0) #define cachefiles_io_error_obj(object, FMT, ...) \ diff --git a/fs/cachefiles/io.c b/fs/cachefiles/io.c index d879b80a0bed..9540ec25b3cb 100644 --- a/fs/cachefiles/io.c +++ b/fs/cachefiles/io.c @@ -390,7 +390,6 @@ cachefiles_do_prepare_read(struct netfs_cache_resources *cres, size_t len = *_len; loff_t off, to; ino_t ino = file ? file_inode(file)->i_ino : 0; - int rc; _enter("%zx @%llx/%llx", len, start, i_size); @@ -403,8 +402,7 @@ cachefiles_do_prepare_read(struct netfs_cache_resources *cres, if (test_bit(FSCACHE_COOKIE_NO_DATA_TO_READ, &cookie->flags)) { __set_bit(NETFS_SREQ_COPY_TO_CACHE, _flags); why = cachefiles_trace_read_no_data; - if (!test_bit(NETFS_SREQ_ONDEMAND, _flags)) - goto out_no_object; + goto out_no_object; } /* The object and the file may be being created in the background. */ @@ -421,7 +419,6 @@ cachefiles_do_prepare_read(struct netfs_cache_resources *cres, object = cachefiles_cres_object(cres); cache = object->volume->cache; cachefiles_begin_secure(cache, &saved_cred); -retry: off = cachefiles_inject_read_error(); if (off == 0) off = vfs_llseek(file, start, SEEK_DATA); @@ -474,14 +471,6 @@ retry: download_and_store: __set_bit(NETFS_SREQ_COPY_TO_CACHE, _flags); - if (test_bit(NETFS_SREQ_ONDEMAND, _flags)) { - rc = cachefiles_ondemand_read(object, start, len); - if (!rc) { - __clear_bit(NETFS_SREQ_ONDEMAND, _flags); - goto retry; - } - ret = NETFS_INVALID_READ; - } out: cachefiles_end_secure(cache, saved_cred); out_no_object: @@ -501,18 +490,6 @@ static enum netfs_io_source cachefiles_prepare_read(struct netfs_io_subrequest * &subreq->flags, subreq->rreq->inode->i_ino); } -/* - * Prepare an on-demand read operation, shortening it to a cached/uncached - * boundary as appropriate. - */ -static enum netfs_io_source -cachefiles_prepare_ondemand_read(struct netfs_cache_resources *cres, - loff_t start, size_t *_len, loff_t i_size, - unsigned long *_flags, ino_t ino) -{ - return cachefiles_do_prepare_read(cres, start, _len, i_size, _flags, ino); -} - /* * Prepare for a write to occur. */ @@ -731,7 +708,6 @@ static const struct netfs_cache_ops cachefiles_netfs_cache_ops = { .prepare_read = cachefiles_prepare_read, .prepare_write = cachefiles_prepare_write, .prepare_write_subreq = cachefiles_prepare_write_subreq, - .prepare_ondemand_read = cachefiles_prepare_ondemand_read, .query_occupancy = cachefiles_query_occupancy, }; diff --git a/fs/cachefiles/namei.c b/fs/cachefiles/namei.c index 8a9f6be15828..88955249a1a6 100644 --- a/fs/cachefiles/namei.c +++ b/fs/cachefiles/namei.c @@ -442,10 +442,6 @@ struct file *cachefiles_create_tmpfile(struct cachefiles_object *object) if (!cachefiles_mark_inode_in_use(object, file_inode(file))) WARN_ON(1); - ret = cachefiles_ondemand_init_object(object); - if (ret < 0) - goto err_unuse; - ni_size = object->cookie->object_size; ni_size = round_up(ni_size, CACHEFILES_DIO_BLOCK_SIZE); @@ -546,10 +542,6 @@ static bool cachefiles_open_file(struct cachefiles_object *object, } _debug("file -> %pd positive", dentry); - ret = cachefiles_ondemand_init_object(object); - if (ret < 0) - goto error_fput; - ret = cachefiles_check_auxdata(object, file); if (ret < 0) goto check_failed; diff --git a/fs/cachefiles/ondemand.c b/fs/cachefiles/ondemand.c deleted file mode 100644 index 0849eaf583cd..000000000000 --- a/fs/cachefiles/ondemand.c +++ /dev/null @@ -1,761 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -#include -#include -#include "internal.h" - -struct ondemand_anon_file { - struct file *file; - int fd; -}; - -static inline void cachefiles_req_put(struct cachefiles_req *req) -{ - if (refcount_dec_and_test(&req->ref)) - kfree(req); -} - -static int cachefiles_ondemand_fd_release(struct inode *inode, - struct file *file) -{ - struct cachefiles_object *object = file->private_data; - struct cachefiles_cache *cache; - struct cachefiles_ondemand_info *info; - int object_id; - struct cachefiles_req *req; - XA_STATE(xas, NULL, 0); - - if (!object) - return 0; - - info = object->ondemand; - cache = object->volume->cache; - xas.xa = &cache->reqs; - - xa_lock(&cache->reqs); - spin_lock(&info->lock); - object_id = info->ondemand_id; - info->ondemand_id = CACHEFILES_ONDEMAND_ID_CLOSED; - cachefiles_ondemand_set_object_close(object); - spin_unlock(&info->lock); - - /* Only flush CACHEFILES_REQ_NEW marked req to avoid race with daemon_read */ - xas_for_each_marked(&xas, req, ULONG_MAX, CACHEFILES_REQ_NEW) { - if (req->msg.object_id == object_id && - req->msg.opcode == CACHEFILES_OP_CLOSE) { - complete(&req->done); - xas_store(&xas, NULL); - } - } - xa_unlock(&cache->reqs); - - xa_erase(&cache->ondemand_ids, object_id); - trace_cachefiles_ondemand_fd_release(object, object_id); - cachefiles_put_object(object, cachefiles_obj_put_ondemand_fd); - cachefiles_put_unbind_pincount(cache); - return 0; -} - -static ssize_t cachefiles_ondemand_fd_write_iter(struct kiocb *kiocb, - struct iov_iter *iter) -{ - struct cachefiles_object *object = kiocb->ki_filp->private_data; - struct cachefiles_cache *cache = object->volume->cache; - struct file *file; - size_t len = iter->count, aligned_len = len; - loff_t pos = kiocb->ki_pos; - const struct cred *saved_cred; - int ret; - - spin_lock(&object->lock); - file = object->file; - if (!file) { - spin_unlock(&object->lock); - return -ENOBUFS; - } - get_file(file); - spin_unlock(&object->lock); - - cachefiles_begin_secure(cache, &saved_cred); - ret = __cachefiles_prepare_write(object, file, &pos, &aligned_len, len, true); - cachefiles_end_secure(cache, saved_cred); - if (ret < 0) - goto out; - - trace_cachefiles_ondemand_fd_write(object, file_inode(file), pos, len); - ret = __cachefiles_write(object, file, pos, iter, NULL, NULL); - if (ret > 0) - kiocb->ki_pos += ret; - -out: - fput(file); - return ret; -} - -static loff_t cachefiles_ondemand_fd_llseek(struct file *filp, loff_t pos, - int whence) -{ - struct cachefiles_object *object = filp->private_data; - struct file *file; - loff_t ret; - - spin_lock(&object->lock); - file = object->file; - if (!file) { - spin_unlock(&object->lock); - return -ENOBUFS; - } - get_file(file); - spin_unlock(&object->lock); - - ret = vfs_llseek(file, pos, whence); - fput(file); - - return ret; -} - -static long cachefiles_ondemand_fd_ioctl(struct file *filp, unsigned int ioctl, - unsigned long id) -{ - struct cachefiles_object *object = filp->private_data; - struct cachefiles_cache *cache = object->volume->cache; - struct cachefiles_req *req; - XA_STATE(xas, &cache->reqs, id); - - if (ioctl != CACHEFILES_IOC_READ_COMPLETE) - return -EINVAL; - - if (!test_bit(CACHEFILES_ONDEMAND_MODE, &cache->flags)) - return -EOPNOTSUPP; - - xa_lock(&cache->reqs); - req = xas_load(&xas); - if (!req || req->msg.opcode != CACHEFILES_OP_READ || - req->object != object) { - xa_unlock(&cache->reqs); - return -EINVAL; - } - xas_store(&xas, NULL); - xa_unlock(&cache->reqs); - - trace_cachefiles_ondemand_cread(object, id); - complete(&req->done); - return 0; -} - -static const struct file_operations cachefiles_ondemand_fd_fops = { - .owner = THIS_MODULE, - .release = cachefiles_ondemand_fd_release, - .write_iter = cachefiles_ondemand_fd_write_iter, - .llseek = cachefiles_ondemand_fd_llseek, - .unlocked_ioctl = cachefiles_ondemand_fd_ioctl, -}; - -/* - * OPEN request Completion (copen) - * - command: "copen ," - * indicates the object size if >=0, error code if negative - */ -int cachefiles_ondemand_copen(struct cachefiles_cache *cache, char *args) -{ - struct cachefiles_req *req; - struct fscache_cookie *cookie; - struct cachefiles_ondemand_info *info; - char *pid, *psize; - unsigned long id; - long size; - int ret; - XA_STATE(xas, &cache->reqs, 0); - - if (!test_bit(CACHEFILES_ONDEMAND_MODE, &cache->flags)) - return -EOPNOTSUPP; - - if (!*args) { - pr_err("Empty id specified\n"); - return -EINVAL; - } - - pid = args; - psize = strchr(args, ','); - if (!psize) { - pr_err("Cache size is not specified\n"); - return -EINVAL; - } - - *psize = 0; - psize++; - - ret = kstrtoul(pid, 0, &id); - if (ret) - return ret; - - xa_lock(&cache->reqs); - xas.xa_index = id; - req = xas_load(&xas); - if (!req || req->msg.opcode != CACHEFILES_OP_OPEN || - !req->object->ondemand->ondemand_id) { - xa_unlock(&cache->reqs); - return -EINVAL; - } - xas_store(&xas, NULL); - xa_unlock(&cache->reqs); - - info = req->object->ondemand; - /* fail OPEN request if copen format is invalid */ - ret = kstrtol(psize, 0, &size); - if (ret) { - req->error = ret; - goto out; - } - - /* fail OPEN request if daemon reports an error */ - if (size < 0) { - if (!IS_ERR_VALUE(size)) { - req->error = -EINVAL; - ret = -EINVAL; - } else { - req->error = size; - ret = 0; - } - goto out; - } - - spin_lock(&info->lock); - /* - * The anonymous fd was closed before copen ? Fail the request. - * - * t1 | t2 - * --------------------------------------------------------- - * cachefiles_ondemand_copen - * req = xa_erase(&cache->reqs, id) - * // Anon fd is maliciously closed. - * cachefiles_ondemand_fd_release - * xa_lock(&cache->reqs) - * cachefiles_ondemand_set_object_close(object) - * xa_unlock(&cache->reqs) - * cachefiles_ondemand_set_object_open - * // No one will ever close it again. - * cachefiles_ondemand_daemon_read - * cachefiles_ondemand_select_req - * - * Get a read req but its fd is already closed. The daemon can't - * issue a cread ioctl with an closed fd, then hung. - */ - if (info->ondemand_id == CACHEFILES_ONDEMAND_ID_CLOSED) { - spin_unlock(&info->lock); - req->error = -EBADFD; - goto out; - } - cookie = req->object->cookie; - cookie->object_size = size; - if (size) - clear_bit(FSCACHE_COOKIE_NO_DATA_TO_READ, &cookie->flags); - else - set_bit(FSCACHE_COOKIE_NO_DATA_TO_READ, &cookie->flags); - trace_cachefiles_ondemand_copen(req->object, id, size); - - cachefiles_ondemand_set_object_open(req->object); - spin_unlock(&info->lock); - wake_up_all(&cache->daemon_pollwq); - -out: - spin_lock(&info->lock); - /* Need to set object close to avoid reopen status continuing */ - if (info->ondemand_id == CACHEFILES_ONDEMAND_ID_CLOSED) - cachefiles_ondemand_set_object_close(req->object); - spin_unlock(&info->lock); - complete(&req->done); - return ret; -} - -int cachefiles_ondemand_restore(struct cachefiles_cache *cache, char *args) -{ - struct cachefiles_req *req; - - XA_STATE(xas, &cache->reqs, 0); - - if (!test_bit(CACHEFILES_ONDEMAND_MODE, &cache->flags)) - return -EOPNOTSUPP; - - /* - * Reset the requests to CACHEFILES_REQ_NEW state, so that the - * requests have been processed halfway before the crash of the - * user daemon could be reprocessed after the recovery. - */ - xas_lock(&xas); - xas_for_each(&xas, req, ULONG_MAX) - xas_set_mark(&xas, CACHEFILES_REQ_NEW); - xas_unlock(&xas); - - wake_up_all(&cache->daemon_pollwq); - return 0; -} - -static int cachefiles_ondemand_get_fd(struct cachefiles_req *req, - struct ondemand_anon_file *anon_file) -{ - struct cachefiles_object *object; - struct cachefiles_cache *cache; - struct cachefiles_open *load; - u32 object_id; - int ret; - - object = cachefiles_grab_object(req->object, - cachefiles_obj_get_ondemand_fd); - cache = object->volume->cache; - - ret = xa_alloc_cyclic(&cache->ondemand_ids, &object_id, NULL, - XA_LIMIT(1, INT_MAX), - &cache->ondemand_id_next, GFP_KERNEL); - if (ret < 0) - goto err; - - anon_file->fd = get_unused_fd_flags(O_WRONLY); - if (anon_file->fd < 0) { - ret = anon_file->fd; - goto err_free_id; - } - - anon_file->file = anon_inode_getfile_fmode("[cachefiles]", - &cachefiles_ondemand_fd_fops, object, - O_WRONLY, FMODE_PWRITE | FMODE_LSEEK); - if (IS_ERR(anon_file->file)) { - ret = PTR_ERR(anon_file->file); - goto err_put_fd; - } - - spin_lock(&object->ondemand->lock); - if (object->ondemand->ondemand_id > 0) { - spin_unlock(&object->ondemand->lock); - /* Pair with check in cachefiles_ondemand_fd_release(). */ - anon_file->file->private_data = NULL; - ret = -EEXIST; - goto err_put_file; - } - - load = (void *)req->msg.data; - load->fd = anon_file->fd; - object->ondemand->ondemand_id = object_id; - spin_unlock(&object->ondemand->lock); - - cachefiles_get_unbind_pincount(cache); - trace_cachefiles_ondemand_open(object, &req->msg, load); - return 0; - -err_put_file: - fput(anon_file->file); - anon_file->file = NULL; -err_put_fd: - put_unused_fd(anon_file->fd); - anon_file->fd = ret; -err_free_id: - xa_erase(&cache->ondemand_ids, object_id); -err: - spin_lock(&object->ondemand->lock); - /* Avoid marking an opened object as closed. */ - if (object->ondemand->ondemand_id <= 0) - cachefiles_ondemand_set_object_close(object); - spin_unlock(&object->ondemand->lock); - cachefiles_put_object(object, cachefiles_obj_put_ondemand_fd); - return ret; -} - -static void ondemand_object_worker(struct work_struct *work) -{ - struct cachefiles_ondemand_info *info = - container_of(work, struct cachefiles_ondemand_info, ondemand_work); - - cachefiles_ondemand_init_object(info->object); -} - -/* - * If there are any inflight or subsequent READ requests on the - * closed object, reopen it. - * Skip read requests whose related object is reopening. - */ -static struct cachefiles_req *cachefiles_ondemand_select_req(struct xa_state *xas, - unsigned long xa_max) -{ - struct cachefiles_req *req; - struct cachefiles_object *object; - struct cachefiles_ondemand_info *info; - - xas_for_each_marked(xas, req, xa_max, CACHEFILES_REQ_NEW) { - if (req->msg.opcode != CACHEFILES_OP_READ) - return req; - object = req->object; - info = object->ondemand; - if (cachefiles_ondemand_object_is_close(object)) { - cachefiles_ondemand_set_object_reopening(object); - queue_work(fscache_wq, &info->ondemand_work); - continue; - } - if (cachefiles_ondemand_object_is_reopening(object)) - continue; - return req; - } - return NULL; -} - -static inline bool cachefiles_ondemand_finish_req(struct cachefiles_req *req, - struct xa_state *xas, int err) -{ - if (unlikely(!xas || !req)) - return false; - - if (xa_cmpxchg(xas->xa, xas->xa_index, req, NULL, 0) != req) - return false; - - req->error = err; - complete(&req->done); - return true; -} - -ssize_t cachefiles_ondemand_daemon_read(struct cachefiles_cache *cache, - char __user *_buffer, size_t buflen) -{ - struct cachefiles_req *req; - struct cachefiles_msg *msg; - size_t n; - int ret = 0; - struct ondemand_anon_file anon_file; - XA_STATE(xas, &cache->reqs, cache->req_id_next); - - xa_lock(&cache->reqs); - /* - * Cyclically search for a request that has not ever been processed, - * to prevent requests from being processed repeatedly, and make - * request distribution fair. - */ - req = cachefiles_ondemand_select_req(&xas, ULONG_MAX); - if (!req && cache->req_id_next > 0) { - xas_set(&xas, 0); - req = cachefiles_ondemand_select_req(&xas, cache->req_id_next - 1); - } - if (!req) { - xa_unlock(&cache->reqs); - return 0; - } - - msg = &req->msg; - n = msg->len; - - if (n > buflen) { - xa_unlock(&cache->reqs); - return -EMSGSIZE; - } - - xas_clear_mark(&xas, CACHEFILES_REQ_NEW); - cache->req_id_next = xas.xa_index + 1; - refcount_inc(&req->ref); - cachefiles_grab_object(req->object, cachefiles_obj_get_read_req); - xa_unlock(&cache->reqs); - - if (msg->opcode == CACHEFILES_OP_OPEN) { - ret = cachefiles_ondemand_get_fd(req, &anon_file); - if (ret) - goto out; - } - - msg->msg_id = xas.xa_index; - msg->object_id = req->object->ondemand->ondemand_id; - - if (copy_to_user(_buffer, msg, n) != 0) - ret = -EFAULT; - - if (msg->opcode == CACHEFILES_OP_OPEN) { - if (ret < 0) { - fput(anon_file.file); - put_unused_fd(anon_file.fd); - goto out; - } - fd_install(anon_file.fd, anon_file.file); - } -out: - cachefiles_put_object(req->object, cachefiles_obj_put_read_req); - /* Remove error request and CLOSE request has no reply */ - if (ret || msg->opcode == CACHEFILES_OP_CLOSE) - cachefiles_ondemand_finish_req(req, &xas, ret); - cachefiles_req_put(req); - return ret ? ret : n; -} - -typedef int (*init_req_fn)(struct cachefiles_req *req, void *private); - -static int cachefiles_ondemand_send_req(struct cachefiles_object *object, - enum cachefiles_opcode opcode, - size_t data_len, - init_req_fn init_req, - void *private) -{ - struct cachefiles_cache *cache = object->volume->cache; - struct cachefiles_req *req = NULL; - XA_STATE(xas, &cache->reqs, 0); - int ret; - - if (!test_bit(CACHEFILES_ONDEMAND_MODE, &cache->flags)) - return 0; - - if (test_bit(CACHEFILES_DEAD, &cache->flags)) { - ret = -EIO; - goto out; - } - - req = kzalloc(sizeof(*req) + data_len, GFP_KERNEL); - if (!req) { - ret = -ENOMEM; - goto out; - } - - refcount_set(&req->ref, 1); - req->object = object; - init_completion(&req->done); - req->msg.opcode = opcode; - req->msg.len = sizeof(struct cachefiles_msg) + data_len; - - ret = init_req(req, private); - if (ret) - goto out; - - do { - /* - * Stop enqueuing the request when daemon is dying. The - * following two operations need to be atomic as a whole. - * 1) check cache state, and - * 2) enqueue request if cache is alive. - * Otherwise the request may be enqueued after xarray has been - * flushed, leaving the orphan request never being completed. - * - * CPU 1 CPU 2 - * ===== ===== - * test CACHEFILES_DEAD bit - * set CACHEFILES_DEAD bit - * flush requests in the xarray - * enqueue the request - */ - xas_lock(&xas); - - if (test_bit(CACHEFILES_DEAD, &cache->flags) || - cachefiles_ondemand_object_is_dropping(object)) { - xas_unlock(&xas); - ret = -EIO; - goto out; - } - - /* coupled with the barrier in cachefiles_flush_reqs() */ - smp_mb(); - - if (opcode == CACHEFILES_OP_CLOSE && - !cachefiles_ondemand_object_is_open(object)) { - WARN_ON_ONCE(object->ondemand->ondemand_id == 0); - xas_unlock(&xas); - ret = -EIO; - goto out; - } - - /* - * Cyclically find a free xas to avoid msg_id reuse that would - * cause the daemon to successfully copen a stale msg_id. - */ - xas.xa_index = cache->msg_id_next; - xas_find_marked(&xas, UINT_MAX, XA_FREE_MARK); - if (xas.xa_node == XAS_RESTART) { - xas.xa_index = 0; - xas_find_marked(&xas, cache->msg_id_next - 1, XA_FREE_MARK); - } - if (xas.xa_node == XAS_RESTART) - xas_set_err(&xas, -EBUSY); - - xas_store(&xas, req); - if (xas_valid(&xas)) { - cache->msg_id_next = xas.xa_index + 1; - xas_clear_mark(&xas, XA_FREE_MARK); - xas_set_mark(&xas, CACHEFILES_REQ_NEW); - } - xas_unlock(&xas); - } while (xas_nomem(&xas, GFP_KERNEL)); - - ret = xas_error(&xas); - if (ret) - goto out; - - wake_up_all(&cache->daemon_pollwq); -wait: - ret = wait_for_completion_killable(&req->done); - if (!ret) { - ret = req->error; - } else { - ret = -EINTR; - if (!cachefiles_ondemand_finish_req(req, &xas, ret)) { - /* Someone will complete it soon. */ - cpu_relax(); - goto wait; - } - } - cachefiles_req_put(req); - return ret; -out: - /* Reset the object to close state in error handling path. - * If error occurs after creating the anonymous fd, - * cachefiles_ondemand_fd_release() will set object to close. - */ - if (opcode == CACHEFILES_OP_OPEN && - !cachefiles_ondemand_object_is_dropping(object)) - cachefiles_ondemand_set_object_close(object); - kfree(req); - return ret; -} - -static int cachefiles_ondemand_init_open_req(struct cachefiles_req *req, - void *private) -{ - struct cachefiles_object *object = req->object; - struct fscache_cookie *cookie = object->cookie; - struct fscache_volume *volume = object->volume->vcookie; - struct cachefiles_open *load = (void *)req->msg.data; - size_t volume_key_size, cookie_key_size; - void *volume_key, *cookie_key; - - /* - * Volume key is a NUL-terminated string. key[0] stores strlen() of the - * string, followed by the content of the string (excluding '\0'). - */ - volume_key_size = volume->key[0] + 1; - volume_key = volume->key + 1; - - /* Cookie key is binary data, which is netfs specific. */ - cookie_key_size = cookie->key_len; - cookie_key = fscache_get_key(cookie); - - if (!(object->cookie->advice & FSCACHE_ADV_WANT_CACHE_SIZE)) { - pr_err("WANT_CACHE_SIZE is needed for on-demand mode\n"); - return -EINVAL; - } - - load->volume_key_size = volume_key_size; - load->cookie_key_size = cookie_key_size; - memcpy(load->data, volume_key, volume_key_size); - memcpy(load->data + volume_key_size, cookie_key, cookie_key_size); - - return 0; -} - -static int cachefiles_ondemand_init_close_req(struct cachefiles_req *req, - void *private) -{ - struct cachefiles_object *object = req->object; - - if (!cachefiles_ondemand_object_is_open(object)) - return -ENOENT; - - trace_cachefiles_ondemand_close(object, &req->msg); - return 0; -} - -struct cachefiles_read_ctx { - loff_t off; - size_t len; -}; - -static int cachefiles_ondemand_init_read_req(struct cachefiles_req *req, - void *private) -{ - struct cachefiles_object *object = req->object; - struct cachefiles_read *load = (void *)req->msg.data; - struct cachefiles_read_ctx *read_ctx = private; - - load->off = read_ctx->off; - load->len = read_ctx->len; - trace_cachefiles_ondemand_read(object, &req->msg, load); - return 0; -} - -int cachefiles_ondemand_init_object(struct cachefiles_object *object) -{ - struct fscache_cookie *cookie = object->cookie; - struct fscache_volume *volume = object->volume->vcookie; - size_t volume_key_size, cookie_key_size, data_len; - - if (!object->ondemand) - return 0; - - /* - * CacheFiles will firstly check the cache file under the root cache - * directory. If the coherency check failed, it will fallback to - * creating a new tmpfile as the cache file. Reuse the previously - * allocated object ID if any. - */ - if (cachefiles_ondemand_object_is_open(object)) - return 0; - - volume_key_size = volume->key[0] + 1; - cookie_key_size = cookie->key_len; - data_len = sizeof(struct cachefiles_open) + - volume_key_size + cookie_key_size; - - return cachefiles_ondemand_send_req(object, CACHEFILES_OP_OPEN, - data_len, cachefiles_ondemand_init_open_req, NULL); -} - -void cachefiles_ondemand_clean_object(struct cachefiles_object *object) -{ - unsigned long index; - struct cachefiles_req *req; - struct cachefiles_cache *cache; - - if (!object->ondemand) - return; - - cachefiles_ondemand_send_req(object, CACHEFILES_OP_CLOSE, 0, - cachefiles_ondemand_init_close_req, NULL); - - if (!object->ondemand->ondemand_id) - return; - - /* Cancel all requests for the object that is being dropped. */ - cache = object->volume->cache; - xa_lock(&cache->reqs); - cachefiles_ondemand_set_object_dropping(object); - xa_for_each(&cache->reqs, index, req) { - if (req->object == object) { - req->error = -EIO; - complete(&req->done); - __xa_erase(&cache->reqs, index); - } - } - xa_unlock(&cache->reqs); - - /* Wait for ondemand_object_worker() to finish to avoid UAF. */ - cancel_work_sync(&object->ondemand->ondemand_work); -} - -int cachefiles_ondemand_init_obj_info(struct cachefiles_object *object, - struct cachefiles_volume *volume) -{ - if (!cachefiles_in_ondemand_mode(volume->cache)) - return 0; - - object->ondemand = kzalloc_obj(struct cachefiles_ondemand_info); - if (!object->ondemand) - return -ENOMEM; - - object->ondemand->object = object; - spin_lock_init(&object->ondemand->lock); - INIT_WORK(&object->ondemand->ondemand_work, ondemand_object_worker); - return 0; -} - -void cachefiles_ondemand_deinit_obj_info(struct cachefiles_object *object) -{ - kfree(object->ondemand); - object->ondemand = NULL; -} - -int cachefiles_ondemand_read(struct cachefiles_object *object, - loff_t pos, size_t len) -{ - struct cachefiles_read_ctx read_ctx = {pos, len}; - - return cachefiles_ondemand_send_req(object, CACHEFILES_OP_READ, - sizeof(struct cachefiles_read), - cachefiles_ondemand_init_read_req, &read_ctx); -} diff --git a/include/linux/netfs.h b/include/linux/netfs.h index d0b62d53eea9..f837a501008c 100644 --- a/include/linux/netfs.h +++ b/include/linux/netfs.h @@ -191,7 +191,6 @@ struct netfs_io_subrequest { #define NETFS_SREQ_COPY_TO_CACHE 0 /* Set if should copy the data to the cache */ #define NETFS_SREQ_CLEAR_TAIL 1 /* Set if the rest of the read should be cleared */ #define NETFS_SREQ_MADE_PROGRESS 4 /* Set if we transferred at least some data */ -#define NETFS_SREQ_ONDEMAND 5 /* Set if it's from on-demand read mode */ #define NETFS_SREQ_BOUNDARY 6 /* Set if ends on hard boundary (eg. ceph object) */ #define NETFS_SREQ_HIT_EOF 7 /* Set if short due to EOF */ #define NETFS_SREQ_IN_PROGRESS 8 /* Unlocked when the subrequest completes */ @@ -374,14 +373,6 @@ struct netfs_cache_ops { loff_t *_start, size_t *_len, size_t upper_len, loff_t i_size, bool no_space_allocated_yet); - /* Prepare an on-demand read operation, shortening it to a cached/uncached - * boundary as appropriate. - */ - enum netfs_io_source (*prepare_ondemand_read)(struct netfs_cache_resources *cres, - loff_t start, size_t *_len, - loff_t i_size, - unsigned long *_flags, ino_t ino); - /* Query the occupancy of the cache in a region, returning where the * next chunk of data starts and how long it is. */ diff --git a/include/trace/events/cachefiles.h b/include/trace/events/cachefiles.h index 6e3b1424eea4..9259bc71049e 100644 --- a/include/trace/events/cachefiles.h +++ b/include/trace/events/cachefiles.h @@ -31,10 +31,6 @@ enum cachefiles_obj_ref_trace { cachefiles_obj_see_lookup_failed, cachefiles_obj_see_withdraw_cookie, cachefiles_obj_see_withdrawal, - cachefiles_obj_get_ondemand_fd, - cachefiles_obj_put_ondemand_fd, - cachefiles_obj_get_read_req, - cachefiles_obj_put_read_req, }; enum fscache_why_object_killed { @@ -129,11 +125,7 @@ enum cachefiles_error_trace { EM(cachefiles_obj_see_lookup_cookie, "SEE lookup_cookie") \ EM(cachefiles_obj_see_lookup_failed, "SEE lookup_failed") \ EM(cachefiles_obj_see_withdraw_cookie, "SEE withdraw_cookie") \ - EM(cachefiles_obj_see_withdrawal, "SEE withdrawal") \ - EM(cachefiles_obj_get_ondemand_fd, "GET ondemand_fd") \ - EM(cachefiles_obj_put_ondemand_fd, "PUT ondemand_fd") \ - EM(cachefiles_obj_get_read_req, "GET read_req") \ - E_(cachefiles_obj_put_read_req, "PUT read_req") + E_(cachefiles_obj_see_withdrawal, "SEE withdrawal") #define cachefiles_coherency_traces \ EM(cachefiles_coherency_check_aux, "BAD aux ") \ @@ -687,180 +679,6 @@ TRACE_EVENT(cachefiles_io_error, __entry->error) ); -TRACE_EVENT(cachefiles_ondemand_open, - TP_PROTO(struct cachefiles_object *obj, struct cachefiles_msg *msg, - struct cachefiles_open *load), - - TP_ARGS(obj, msg, load), - - TP_STRUCT__entry( - __field(unsigned int, obj) - __field(unsigned int, msg_id) - __field(unsigned int, object_id) - __field(unsigned int, fd) - __field(unsigned int, flags) - ), - - TP_fast_assign( - __entry->obj = obj ? obj->debug_id : 0; - __entry->msg_id = msg->msg_id; - __entry->object_id = msg->object_id; - __entry->fd = load->fd; - __entry->flags = load->flags; - ), - - TP_printk("o=%08x mid=%x oid=%x fd=%d f=%x", - __entry->obj, - __entry->msg_id, - __entry->object_id, - __entry->fd, - __entry->flags) - ); - -TRACE_EVENT(cachefiles_ondemand_copen, - TP_PROTO(struct cachefiles_object *obj, unsigned int msg_id, - long len), - - TP_ARGS(obj, msg_id, len), - - TP_STRUCT__entry( - __field(unsigned int, obj) - __field(unsigned int, msg_id) - __field(long, len) - ), - - TP_fast_assign( - __entry->obj = obj ? obj->debug_id : 0; - __entry->msg_id = msg_id; - __entry->len = len; - ), - - TP_printk("o=%08x mid=%x l=%lx", - __entry->obj, - __entry->msg_id, - __entry->len) - ); - -TRACE_EVENT(cachefiles_ondemand_close, - TP_PROTO(struct cachefiles_object *obj, struct cachefiles_msg *msg), - - TP_ARGS(obj, msg), - - TP_STRUCT__entry( - __field(unsigned int, obj) - __field(unsigned int, msg_id) - __field(unsigned int, object_id) - ), - - TP_fast_assign( - __entry->obj = obj ? obj->debug_id : 0; - __entry->msg_id = msg->msg_id; - __entry->object_id = msg->object_id; - ), - - TP_printk("o=%08x mid=%x oid=%x", - __entry->obj, - __entry->msg_id, - __entry->object_id) - ); - -TRACE_EVENT(cachefiles_ondemand_read, - TP_PROTO(struct cachefiles_object *obj, struct cachefiles_msg *msg, - struct cachefiles_read *load), - - TP_ARGS(obj, msg, load), - - TP_STRUCT__entry( - __field(unsigned int, obj) - __field(unsigned int, msg_id) - __field(unsigned int, object_id) - __field(loff_t, start) - __field(size_t, len) - ), - - TP_fast_assign( - __entry->obj = obj ? obj->debug_id : 0; - __entry->msg_id = msg->msg_id; - __entry->object_id = msg->object_id; - __entry->start = load->off; - __entry->len = load->len; - ), - - TP_printk("o=%08x mid=%x oid=%x s=%llx l=%zx", - __entry->obj, - __entry->msg_id, - __entry->object_id, - __entry->start, - __entry->len) - ); - -TRACE_EVENT(cachefiles_ondemand_cread, - TP_PROTO(struct cachefiles_object *obj, unsigned int msg_id), - - TP_ARGS(obj, msg_id), - - TP_STRUCT__entry( - __field(unsigned int, obj) - __field(unsigned int, msg_id) - ), - - TP_fast_assign( - __entry->obj = obj ? obj->debug_id : 0; - __entry->msg_id = msg_id; - ), - - TP_printk("o=%08x mid=%x", - __entry->obj, - __entry->msg_id) - ); - -TRACE_EVENT(cachefiles_ondemand_fd_write, - TP_PROTO(struct cachefiles_object *obj, struct inode *backer, - loff_t start, size_t len), - - TP_ARGS(obj, backer, start, len), - - TP_STRUCT__entry( - __field(unsigned int, obj) - __field(unsigned int, backer) - __field(loff_t, start) - __field(size_t, len) - ), - - TP_fast_assign( - __entry->obj = obj ? obj->debug_id : 0; - __entry->backer = backer->i_ino; - __entry->start = start; - __entry->len = len; - ), - - TP_printk("o=%08x iB=%x s=%llx l=%zx", - __entry->obj, - __entry->backer, - __entry->start, - __entry->len) - ); - -TRACE_EVENT(cachefiles_ondemand_fd_release, - TP_PROTO(struct cachefiles_object *obj, int object_id), - - TP_ARGS(obj, object_id), - - TP_STRUCT__entry( - __field(unsigned int, obj) - __field(unsigned int, object_id) - ), - - TP_fast_assign( - __entry->obj = obj ? obj->debug_id : 0; - __entry->object_id = object_id; - ), - - TP_printk("o=%08x oid=%x", - __entry->obj, - __entry->object_id) - ); - #endif /* _TRACE_CACHEFILES_H */ /* This part must be outside protection */ diff --git a/include/uapi/linux/cachefiles.h b/include/uapi/linux/cachefiles.h deleted file mode 100644 index 78caa73e5343..000000000000 --- a/include/uapi/linux/cachefiles.h +++ /dev/null @@ -1,68 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ -#ifndef _LINUX_CACHEFILES_H -#define _LINUX_CACHEFILES_H - -#include -#include - -/* - * Fscache ensures that the maximum length of cookie key is 255. The volume key - * is controlled by netfs, and generally no bigger than 255. - */ -#define CACHEFILES_MSG_MAX_SIZE 1024 - -enum cachefiles_opcode { - CACHEFILES_OP_OPEN, - CACHEFILES_OP_CLOSE, - CACHEFILES_OP_READ, -}; - -/* - * Message Header - * - * @msg_id a unique ID identifying this message - * @opcode message type, CACHEFILE_OP_* - * @len message length, including message header and following data - * @object_id a unique ID identifying a cache file - * @data message type specific payload - */ -struct cachefiles_msg { - __u32 msg_id; - __u32 opcode; - __u32 len; - __u32 object_id; - __u8 data[]; -}; - -/* - * @data contains the volume_key followed directly by the cookie_key. volume_key - * is a NUL-terminated string; @volume_key_size indicates the size of the volume - * key in bytes. cookie_key is binary data, which is netfs specific; - * @cookie_key_size indicates the size of the cookie key in bytes. - * - * @fd identifies an anon_fd referring to the cache file. - */ -struct cachefiles_open { - __u32 volume_key_size; - __u32 cookie_key_size; - __u32 fd; - __u32 flags; - __u8 data[]; -}; - -/* - * @off indicates the starting offset of the requested file range - * @len indicates the length of the requested file range - */ -struct cachefiles_read { - __u64 off; - __u64 len; -}; - -/* - * Reply for READ request - * @arg for this ioctl is the @id field of READ request. - */ -#define CACHEFILES_IOC_READ_COMPLETE _IOW(0x98, 1, int) - -#endif diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c index c4673a54c4ba..7e25051dc1ea 100644 --- a/kernel/bpf/btf.c +++ b/kernel/bpf/btf.c @@ -6676,13 +6676,6 @@ static const struct bpf_raw_tp_null_args raw_tp_null_args[] = { { "cachefiles_mark_inactive", 0x1 }, { "cachefiles_vfs_error", 0x1 }, { "cachefiles_io_error", 0x1 }, - { "cachefiles_ondemand_open", 0x1 }, - { "cachefiles_ondemand_copen", 0x1 }, - { "cachefiles_ondemand_close", 0x1 }, - { "cachefiles_ondemand_read", 0x1 }, - { "cachefiles_ondemand_cread", 0x1 }, - { "cachefiles_ondemand_fd_write", 0x1 }, - { "cachefiles_ondemand_fd_release", 0x1 }, /* ext4, from ext4__mballoc event class */ { "ext4_mballoc_discard", 0x10 }, { "ext4_mballoc_free", 0x10 }, -- cgit v1.2.3 From cc17e386f8a5fc23a3bf49a45d17cd45b49fbb01 Mon Sep 17 00:00:00 2001 From: Yuyang Huang Date: Sat, 8 Aug 2026 09:56:33 +0900 Subject: ipv6: add ip6_del_rt_reason() Add RTA_DEL_REASON and enum rt_del_reason to the rtnetlink uAPI, and add ip6_del_rt_reason(), which takes the reason a route is being deleted. It has no skip_notify argument: a caller that records a deletion reason wants the notification that carries it. The reason is unused for now. Subsequent patches propagate it to the deletion path and report it on RTM_DELROUTE. Signed-off-by: Yuyang Huang Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260808005642.26901-2-sigefriedhyy@gmail.com Signed-off-by: Paolo Abeni --- include/net/ip6_route.h | 2 ++ include/uapi/linux/rtnetlink.h | 25 +++++++++++++++++++++++++ net/ipv6/route.c | 8 ++++++++ 3 files changed, 35 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/net/ip6_route.h b/include/net/ip6_route.h index 09ffe0f13ce7..cc045705862d 100644 --- a/include/net/ip6_route.h +++ b/include/net/ip6_route.h @@ -126,6 +126,8 @@ int ipv6_route_ioctl(struct net *net, unsigned int cmd, int ip6_route_add(struct fib6_config *cfg, gfp_t gfp_flags, struct netlink_ext_ack *extack); int ip6_ins_rt(struct net *net, struct fib6_info *f6i); +int ip6_del_rt_reason(struct net *net, struct fib6_info *f6i, + enum rt_del_reason del_reason); #if IS_ENABLED(CONFIG_IPV6) int ip6_del_rt(struct net *net, struct fib6_info *f6i, bool skip_notify); #else diff --git a/include/uapi/linux/rtnetlink.h b/include/uapi/linux/rtnetlink.h index 27265fd31e5f..3988b51db539 100644 --- a/include/uapi/linux/rtnetlink.h +++ b/include/uapi/linux/rtnetlink.h @@ -399,6 +399,7 @@ enum rtattr_type_t { RTA_DPORT, RTA_NH_ID, RTA_FLOWLABEL, + RTA_DEL_REASON, __RTA_MAX }; @@ -407,6 +408,30 @@ enum rtattr_type_t { #define RTM_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct rtmsg)))) #define RTM_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct rtmsg)) +/* RTA_DEL_REASON: why the kernel deleted the route. u32. + * Emitted only on RTM_DELROUTE notifications, and only when the deletion + * path records a cause. Absence means either an older kernel or a + * deletion path that does not (yet) record its cause - consumers must + * treat "absent" and "unspec" identically. New causes may be appended. + * Currently only IPv6 deletion paths record a cause. + * + * The attribute is notification-only: the kernel rejects it in + * requests, so a notification must not be echoed back verbatim. + * + * The value space is family-agnostic: a value must never be + * reinterpreted per address family. A cause that only one family can + * produce still gets its own value rather than reusing another + * family's. + */ +enum rt_del_reason { + RT_DEL_REASON_UNSPEC, /* cause not recorded */ + RT_DEL_REASON_EXPIRED, /* RTF_EXPIRES lifetime ran out (GC) */ + RT_DEL_REASON_RA_WITHDRAWN, /* zero-lifetime RA / PIO / RIO */ + __RT_DEL_REASON_MAX +}; + +#define RT_DEL_REASON_MAX (__RT_DEL_REASON_MAX - 1) + /* RTM_MULTIPATH --- array of struct rtnexthop. * * "struct rtnexthop" describes all necessary nexthop information, diff --git a/net/ipv6/route.c b/net/ipv6/route.c index 5968ce5ad150..9898578cb418 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -4004,6 +4004,14 @@ int ip6_del_rt(struct net *net, struct fib6_info *rt, bool skip_notify) return __ip6_del_rt(rt, &info); } +int ip6_del_rt_reason(struct net *net, struct fib6_info *rt, + enum rt_del_reason del_reason) +{ + struct nl_info info = { .nl_net = net }; + + return __ip6_del_rt(rt, &info); +} + static int __ip6_del_rt_siblings(struct fib6_info *rt, struct fib6_config *cfg) { struct nl_info *info = &cfg->fc_nlinfo; -- cgit v1.2.3 From fa63a2e3f94709c83181a86440f3670e1df52ee8 Mon Sep 17 00:00:00 2001 From: Farhan Ali Date: Tue, 30 Jun 2026 09:55:52 -0700 Subject: vfio-pci/zdev: Add a device feature for error information For zPCI devices, we have platform specific error information. The platform firmware provides this error information to the operating system in an architecture specific mechanism. To enable recovery from userspace for these devices, we want to expose this error information to userspace. Add a new device feature to expose this information. Userspace needs to be provide a buffer of fixed size. This size is provided to userspace via the VFIO_DEVICE_INFO_CAP_ZPCI_BASE capability. Reviewed-by: Matthew Rosato Reviewed-by: Niklas Schnelle Signed-off-by: Farhan Ali Link: https://lore.kernel.org/r/20260630165553.725-3-alifm@linux.ibm.com Signed-off-by: Alex Williamson --- arch/s390/include/asm/pci.h | 2 ++ arch/s390/pci/pci_event.c | 19 +++++++++++++++++ drivers/vfio/pci/vfio_pci_core.c | 2 ++ drivers/vfio/pci/vfio_pci_priv.h | 9 ++++++++ drivers/vfio/pci/vfio_pci_zdev.c | 44 ++++++++++++++++++++++++++++++++++++++-- include/uapi/linux/vfio.h | 21 +++++++++++++++++++ include/uapi/linux/vfio_zdev.h | 2 ++ 7 files changed, 97 insertions(+), 2 deletions(-) (limited to 'include/uapi/linux') diff --git a/arch/s390/include/asm/pci.h b/arch/s390/include/asm/pci.h index 016386f7ef4a..88a125b92bdd 100644 --- a/arch/s390/include/asm/pci.h +++ b/arch/s390/include/asm/pci.h @@ -364,6 +364,8 @@ int zpci_clear_error_state(struct zpci_dev *zdev); int zpci_reset_load_store_blocked(struct zpci_dev *zdev); void zpci_start_mediated_recovery(struct zpci_dev *zdev); void zpci_stop_mediated_recovery(struct zpci_dev *zdev); +int zpci_get_pending_error(struct zpci_dev *zdev, + struct zpci_ccdf_err *ccdf); #ifdef CONFIG_NUMA diff --git a/arch/s390/pci/pci_event.c b/arch/s390/pci/pci_event.c index 8934b640a095..5829ad398800 100644 --- a/arch/s390/pci/pci_event.c +++ b/arch/s390/pci/pci_event.c @@ -77,6 +77,25 @@ static int zpci_store_pci_error(struct pci_dev *pdev, return 0; } +int zpci_get_pending_error(struct zpci_dev *zdev, + struct zpci_ccdf_err *ccdf) +{ + int head; + + guard(mutex)(&zdev->pending_errs_lock); + + if (!zdev->pending_errs.count) + return -ENOMSG; + + head = zdev->pending_errs.head % ZPCI_ERR_PENDING_MAX; + memcpy(ccdf, &zdev->pending_errs.err[head], + sizeof(struct zpci_ccdf_err)); + zdev->pending_errs.head++; + zdev->pending_errs.count--; + return 0; +} +EXPORT_SYMBOL_GPL(zpci_get_pending_error); + void zpci_start_mediated_recovery(struct zpci_dev *zdev) { guard(mutex)(&zdev->pending_errs_lock); diff --git a/drivers/vfio/pci/vfio_pci_core.c b/drivers/vfio/pci/vfio_pci_core.c index 6a184588ff23..d9dbddf25585 100644 --- a/drivers/vfio/pci/vfio_pci_core.c +++ b/drivers/vfio/pci/vfio_pci_core.c @@ -1628,6 +1628,8 @@ int vfio_pci_core_ioctl_feature(struct vfio_device *device, u32 flags, return vfio_pci_core_feature_token(vdev, flags, arg, argsz); case VFIO_DEVICE_FEATURE_DMA_BUF: return vfio_pci_core_feature_dma_buf(vdev, flags, arg, argsz); + case VFIO_DEVICE_FEATURE_ZPCI_ERROR: + return vfio_pci_zdev_feature_err(device, flags, arg, argsz); default: return -ENOTTY; } diff --git a/drivers/vfio/pci/vfio_pci_priv.h b/drivers/vfio/pci/vfio_pci_priv.h index fca9d0dfac90..4e7162234a2e 100644 --- a/drivers/vfio/pci/vfio_pci_priv.h +++ b/drivers/vfio/pci/vfio_pci_priv.h @@ -93,6 +93,8 @@ int vfio_pci_info_zdev_add_caps(struct vfio_pci_core_device *vdev, struct vfio_info_cap *caps); int vfio_pci_zdev_open_device(struct vfio_pci_core_device *vdev); void vfio_pci_zdev_close_device(struct vfio_pci_core_device *vdev); +int vfio_pci_zdev_feature_err(struct vfio_device *device, u32 flags, + void __user *arg, size_t argsz); #else static inline int vfio_pci_info_zdev_add_caps(struct vfio_pci_core_device *vdev, struct vfio_info_cap *caps) @@ -107,6 +109,13 @@ static inline int vfio_pci_zdev_open_device(struct vfio_pci_core_device *vdev) static inline void vfio_pci_zdev_close_device(struct vfio_pci_core_device *vdev) {} + +static inline int vfio_pci_zdev_feature_err(struct vfio_device *device, + u32 flags, void __user *arg, + size_t argsz) +{ + return -ENOTTY; +} #endif static inline bool vfio_pci_is_vga(struct pci_dev *pdev) diff --git a/drivers/vfio/pci/vfio_pci_zdev.c b/drivers/vfio/pci/vfio_pci_zdev.c index 78a28db00c6d..f47f36314a1c 100644 --- a/drivers/vfio/pci/vfio_pci_zdev.c +++ b/drivers/vfio/pci/vfio_pci_zdev.c @@ -24,7 +24,7 @@ static int zpci_base_cap(struct zpci_dev *zdev, struct vfio_info_cap *caps) { struct vfio_device_info_cap_zpci_base cap = { .header.id = VFIO_DEVICE_INFO_CAP_ZPCI_BASE, - .header.version = 2, + .header.version = 3, .start_dma = zdev->start_dma, .end_dma = zdev->end_dma, .pchid = zdev->pchid, @@ -32,7 +32,8 @@ static int zpci_base_cap(struct zpci_dev *zdev, struct vfio_info_cap *caps) .fmb_length = zdev->fmb_length, .pft = zdev->pft, .gid = zdev->pfgid, - .fh = zdev->fh + .fh = zdev->fh, + .ccdf_err_length = sizeof(struct zpci_ccdf_err) }; return vfio_info_add_capability(caps, &cap.header, sizeof(cap)); @@ -141,6 +142,45 @@ int vfio_pci_info_zdev_add_caps(struct vfio_pci_core_device *vdev, return ret; } +int vfio_pci_zdev_feature_err(struct vfio_device *device, u32 flags, + void __user *arg, size_t argsz) +{ + struct vfio_device_feature_zpci_err err; + struct vfio_pci_core_device *vdev; + struct zpci_ccdf_err ccdf = {}; + struct zpci_dev *zdev; + int ret; + + vdev = container_of(device, struct vfio_pci_core_device, vdev); + zdev = to_zpci(vdev->pdev); + if (!zdev) + return -ENODEV; + + ret = vfio_check_feature(flags, argsz, VFIO_DEVICE_FEATURE_GET, + sizeof(err)); + if (ret != 1) + return ret; + + if (copy_from_user(&err, arg, sizeof(err))) + return -EFAULT; + + if (!err.data) + return -EINVAL; + + ret = zpci_get_pending_error(zdev, &ccdf); + if (ret) + return ret; + + if (copy_to_user(u64_to_user_ptr(err.data), &ccdf, sizeof(ccdf))) { + dev_warn_ratelimited(device->dev, + "Failed to handle PCI error event for PCI function 0x%x", + zdev->fid); + return -EFAULT; + } + + return 0; +} + int vfio_pci_zdev_open_device(struct vfio_pci_core_device *vdev) { struct zpci_dev *zdev = to_zpci(vdev->pdev); diff --git a/include/uapi/linux/vfio.h b/include/uapi/linux/vfio.h index 5de618a3a5ee..e41437fa17ad 100644 --- a/include/uapi/linux/vfio.h +++ b/include/uapi/linux/vfio.h @@ -1534,6 +1534,27 @@ struct vfio_device_feature_dma_buf { */ #define VFIO_DEVICE_FEATURE_MIG_PRECOPY_INFOv2 12 +/** + * VFIO_DEVICE_FEATURE_ZPCI_ERROR feature provides PCI error information to + * userspace for vfio-pci devices on s390. On s390, PCI error recovery + * involves platform firmware and notification to operating systems is done + * by architecture specific mechanism. Exposing this information to + * userspace allows it to take appropriate actions to handle an + * error on the device. + * + * Userspace provides an opaque buffer of fixed length, and the kernel + * fills it with the zpci_ccdf_err data structure. The length of + * zpci_ccdf_err is provided to userspace via the + * VFIO_DEVICE_INFO_CAP_ZPCI_BASE capability. + * + * The ioctl returns -ENOMSG if there are no pending PCI errors. + */ +struct vfio_device_feature_zpci_err { + __aligned_u64 data; +}; + +#define VFIO_DEVICE_FEATURE_ZPCI_ERROR 13 + /* -------- API for Type1 VFIO IOMMU -------- */ /** diff --git a/include/uapi/linux/vfio_zdev.h b/include/uapi/linux/vfio_zdev.h index 77f2aff1f27e..d6c1175c884e 100644 --- a/include/uapi/linux/vfio_zdev.h +++ b/include/uapi/linux/vfio_zdev.h @@ -32,6 +32,8 @@ struct vfio_device_info_cap_zpci_base { /* End of version 1 */ __u32 fh; /* PCI function handle */ /* End of version 2 */ + __u32 ccdf_err_length; /* PCI CCDF length */ + /* End of version 3 */ }; /** -- cgit v1.2.3 From 6b88ff2b9a0eb0b3b6cfc68652cb9cd9765737bb Mon Sep 17 00:00:00 2001 From: Günther Noack Date: Sat, 16 May 2026 21:01:12 +0200 Subject: landlock: Documentation wording cleanups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Documentation cleanups suggested by Alejandro Colomar, which we have also applied in the man pages. Link: https://lore.kernel.org/all/agW4yMK6CinJGqXt@devuan/ Suggested-by: Alejandro Colomar Signed-off-by: Günther Noack Link: https://patch.msgid.link/20260516190112.4924-1-gnoack3000@gmail.com Signed-off-by: Mickaël Salaün --- include/uapi/linux/landlock.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h index 7ffe2ef127ee..272f047df438 100644 --- a/include/uapi/linux/landlock.h +++ b/include/uapi/linux/landlock.h @@ -315,16 +315,16 @@ struct landlock_net_port_attr { * :manpage:`connect(2)` as well as calls to :manpage:`sendmsg(2)` with an * explicit recipient address. * - * This access right only applies to connections to UNIX server sockets which + * This access right applies only to connections to UNIX server sockets which * were created outside of the newly created Landlock domain (e.g. from within * a parent domain or from an unrestricted process). Newly created UNIX * servers within the same Landlock domain continue to be accessible. In this * regard, %LANDLOCK_ACCESS_FS_RESOLVE_UNIX has the same semantics as the * ``LANDLOCK_SCOPE_*`` flags. * - * If a resolve attempt is denied, the operation returns an ``EACCES`` error, - * in line with other filesystem access rights (but different to denials for - * abstract UNIX domain sockets). + * If a resolution attempt is denied, the operation returns an ``EACCES`` + * error, in line with other filesystem access rights (but different to + * denials for abstract UNIX domain sockets). * * This access right is available since the ninth version of the Landlock ABI. * -- cgit v1.2.3 From 672fa082d48b21e1fb62cdb184fee41513e53421 Mon Sep 17 00:00:00 2001 From: Günther Noack Date: Thu, 13 Aug 2026 11:31:53 +0200 Subject: landlock: Require LANDLOCK_ACCESS_FS_MAKE_REG for whiteout creation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Whiteout objects are used in the upper layer of an OverlayFS to indicate that the file with this name does not exist in the unified view, even if it is present in one of the lower layer file systems. For the userspace implementations of OverlayFS (fuse-overlayfs), whiteout objects can be created from userspace as well: * mknod(2) with S_IFCHR and makedev(0, 0) * renameat2(2) with RENAME_WHITEOUT, creating the whiteout in the old place of the moved file. This commit guards whiteout creation in both of these cases with LANDLOCK_ACCESS_FS_MAKE_REG. Whiteout objects are *not* considered character devices and are not bound to a driver. LANDLOCK_ACCESS_FS_MAKE_REG describes the same permission class as a whiteout object: creating one is the only S_IFCHR creation that the VFS exempts from CAP_MKNOD, so it is as unprivileged as creating a regular file, while LANDLOCK_ACCESS_FS_MAKE_CHAR and LANDLOCK_ACCESS_FS_MAKE_BLOCK keep meaning the creation of devices that expose a kernel interface [1]. For the mknod(2) case, introduce a Landlock erratum. The creation of whiteout objects through mknod(2) was previously guarded using LANDLOCK_ACCESS_FS_MAKE_CHAR, and it is now guarded using LANDLOCK_ACCESS_FS_MAKE_REG. For the renameat2(2) case, fix a bug: Before this commit, renameat2(2) with RENAME_WHITEOUT would create a directory entry even when all LANDLOCK_ACCESS_FS_MAKE_* rights were denied. This does not affect normal renames within layered OverlayFS mounts: When doing a regular rename() on a mounted fuse-overlayfs, it is the fuse-overlayfs daemon that exercises renameat2() with RENAME_WHITEOUT, and only the Landlock domain of that daemon is checked there. Suggested-by: Christian Brauner Suggested-by: Mickaël Salaün Cc: stable@vger.kernel.org Fixes: cb2c7d1a1776 ("landlock: Support filesystem access-control") Depends-on: 49c9e09d9610 ("landlock: Fix handling of disconnected directories") Depends-on: fe72ce6710cb ("landlock: Add errata documentation section") Signed-off-by: Günther Noack Link: https://patch.msgid.link/20260720.chow9ohYie5b@digikod.net [1] Link: https://patch.msgid.link/20260813093157.1436894-3-gnoack@google.com [mic: Record why LANDLOCK_ACCESS_FS_MAKE_REG is the matching right, and add link(2) to the user doc] Signed-off-by: Mickaël Salaün --- include/uapi/linux/landlock.h | 1 + security/landlock/errata/abi-1.h | 23 ++++++++++++++++++++++ security/landlock/fs.c | 41 +++++++++++++++++++++++++++++++--------- 3 files changed, 56 insertions(+), 9 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h index 272f047df438..27ae3f39cafb 100644 --- a/include/uapi/linux/landlock.h +++ b/include/uapi/linux/landlock.h @@ -351,6 +351,7 @@ struct landlock_net_port_attr { * device. * - %LANDLOCK_ACCESS_FS_MAKE_DIR: Create (or rename) a directory. * - %LANDLOCK_ACCESS_FS_MAKE_REG: Create (or rename or link) a regular file. + * This also guards the creation of whiteout objects as used in OverlayFS. * - %LANDLOCK_ACCESS_FS_MAKE_SOCK: Create (or rename or link) a UNIX domain * socket. * - %LANDLOCK_ACCESS_FS_MAKE_FIFO: Create (or rename or link) a named pipe. diff --git a/security/landlock/errata/abi-1.h b/security/landlock/errata/abi-1.h index 3f099555f059..315ea7e0fe50 100644 --- a/security/landlock/errata/abi-1.h +++ b/security/landlock/errata/abi-1.h @@ -22,3 +22,26 @@ * from their original mount points. */ LANDLOCK_ERRATUM(3) + +/** + * DOC: erratum_4 + * + * Erratum 4: Creation of whiteout objects + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This fix changes the access rights required for the creation of whiteout + * objects through :manpage:`mknod(2)`, :manpage:`renameat2(2)`, or + * :manpage:`link(2)`. Creating whiteout objects is now guarded by + * ``LANDLOCK_ACCESS_FS_MAKE_REG`` instead of ``LANDLOCK_ACCESS_FS_MAKE_CHAR``. + * + * Whiteout objects are used in OverlayFS to mark the absence of a file in an + * upper file system. Despite being created with ``S_IFCHR``, whiteout objects + * do not count as character devices. + * + * Impact: + * + * Sandboxed programs that create OverlayFS whiteouts (such as fuse-overlayfs) + * now require ``LANDLOCK_ACCESS_FS_MAKE_REG`` instead of + * ``LANDLOCK_ACCESS_FS_MAKE_CHAR``. + */ +LANDLOCK_ERRATUM(4) diff --git a/security/landlock/fs.c b/security/landlock/fs.c index f7e5e4ef9eac..8fc7f82a374a 100644 --- a/security/landlock/fs.c +++ b/security/landlock/fs.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -983,7 +984,8 @@ static int current_check_access_path(const struct path *const path, return -EACCES; } -static __attribute_const__ access_mask_t get_mode_access(const umode_t mode) +static __attribute_const__ access_mask_t get_mode_access(const umode_t mode, + const dev_t dev) { switch (mode & S_IFMT) { case S_IFLNK: @@ -991,6 +993,9 @@ static __attribute_const__ access_mask_t get_mode_access(const umode_t mode) case S_IFDIR: return LANDLOCK_ACCESS_FS_MAKE_DIR; case S_IFCHR: + /* Whiteout objects are guarded with MAKE_REG. */ + if (dev == WHITEOUT_DEV) + return LANDLOCK_ACCESS_FS_MAKE_REG; return LANDLOCK_ACCESS_FS_MAKE_CHAR; case S_IFBLK: return LANDLOCK_ACCESS_FS_MAKE_BLOCK; @@ -1007,6 +1012,13 @@ static __attribute_const__ access_mask_t get_mode_access(const umode_t mode) } } +static access_mask_t get_dentry_access(const struct dentry *const dentry) +{ + const struct inode *const inode = d_backing_inode(dentry); + + return get_mode_access(inode->i_mode, inode->i_rdev); +} + static access_mask_t maybe_remove(const struct dentry *const dentry) { if (d_is_negative(dentry)) @@ -1093,6 +1105,7 @@ static bool collect_domain_accesses(const struct landlock_ruleset *const domain, * @new_dentry: Destination file or directory. * @removable: Sets to true if it is a rename operation. * @exchange: Sets to true if it is a rename operation with RENAME_EXCHANGE. + * @whiteout: Sets to true if it is a rename operation with RENAME_WHITEOUT. * * Because of its unprivileged constraints, Landlock relies on file hierarchies * (and not only inodes) to tie access rights to files. Being able to link or @@ -1140,7 +1153,8 @@ static bool collect_domain_accesses(const struct landlock_ruleset *const domain, static int current_check_refer_path(struct dentry *const old_dentry, const struct path *const new_dir, struct dentry *const new_dentry, - const bool removable, const bool exchange) + const bool removable, const bool exchange, + const bool whiteout) { const struct landlock_cred_security *const subject = landlock_get_applicable_subject(current_cred(), any_fs, NULL); @@ -1159,18 +1173,25 @@ static int current_check_refer_path(struct dentry *const old_dentry, if (exchange) { if (unlikely(d_is_negative(new_dentry))) return -ENOENT; - access_request_parent1 = - get_mode_access(d_backing_inode(new_dentry)->i_mode); + access_request_parent1 = get_dentry_access(new_dentry); } else { access_request_parent1 = 0; } - access_request_parent2 = - get_mode_access(d_backing_inode(old_dentry)->i_mode); + access_request_parent2 = get_dentry_access(old_dentry); if (removable) { access_request_parent1 |= maybe_remove(old_dentry); access_request_parent2 |= maybe_remove(new_dentry); } + /* + * In case of renameat2(2) with RENAME_WHITEOUT, a whiteout object is + * created in the source location, so we require an additional access + * right there. + */ + if (whiteout) + access_request_parent1 |= + get_mode_access(S_IFCHR | WHITEOUT_MODE, WHITEOUT_DEV); + /* The mount points are the same for old and new paths, cf. EXDEV. */ if (old_dentry->d_parent == new_dir->dentry) { /* @@ -1520,7 +1541,7 @@ static int hook_path_link(struct dentry *const old_dentry, struct dentry *const new_dentry) { return current_check_refer_path(old_dentry, new_dir, new_dentry, false, - false); + false, false); } static int hook_path_rename(const struct path *const old_dir, @@ -1531,7 +1552,8 @@ static int hook_path_rename(const struct path *const old_dir, { /* old_dir refers to old_dentry->d_parent and new_dir->mnt */ return current_check_refer_path(old_dentry, new_dir, new_dentry, true, - !!(flags & RENAME_EXCHANGE)); + !!(flags & RENAME_EXCHANGE), + !!(flags & RENAME_WHITEOUT)); } static int hook_path_mkdir(const struct path *const dir, @@ -1544,7 +1566,8 @@ static int hook_path_mknod(const struct path *const dir, struct dentry *const dentry, const umode_t mode, const unsigned int dev) { - return current_check_access_path(dir, get_mode_access(mode)); + return current_check_access_path( + dir, get_mode_access(mode, new_decode_dev(dev))); } static int hook_path_symlink(const struct path *const dir, -- cgit v1.2.3 From 6887c03bf2600beaa1a24e0a8c00053c236c1071 Mon Sep 17 00:00:00 2001 From: Justin Suess Date: Sun, 9 Aug 2026 17:24:59 -0400 Subject: landlock: Add LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a landlock_restrict_self(2) flag to set the no_new_privs attribute of the calling thread only after enforcement of the ruleset: no_new_privs is set if and only if the call succeeds. This removes the need for a prior prctl(2) PR_SET_NO_NEW_PRIVS call and guarantees that a failed enforcement leaves the attribute unchanged. Because no_new_privs is set by the call itself, the no_new_privs / CAP_SYS_ADMIN requirement of landlock_restrict_self(2) is fulfilled by construction, and the related EPERM check is skipped. Unlike LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF, this flag always requires a valid ruleset: with a ruleset_fd of -1, such a call would be nothing more than a Landlock-flavored prctl(2) PR_SET_NO_NEW_PRIVS, and there is no valid use case for setting no_new_privs (possibly with LANDLOCK_RESTRICT_SELF_TSYNC) without also enforcing Landlock restrictions. Rejecting these calls also keeps the option of giving them a meaning later. The attribute is only set past the last point of failure, just before committing the new credentials. When combined with LANDLOCK_RESTRICT_SELF_TSYNC, no_new_privs is set on the sibling threads as well, in their commit phase, with the same ordering. Bump the Landlock ABI version to 11, and include the minimal related test changes to keep the tests bisectable. Cc: Mickaël Salaün Signed-off-by: Justin Suess Link: https://patch.msgid.link/20260809212459.2427878-1-utilityemal77@gmail.com Signed-off-by: Mickaël Salaün --- include/uapi/linux/landlock.h | 13 +++++++++++++ security/landlock/limits.h | 2 +- security/landlock/syscalls.c | 27 ++++++++++++++++++++------- security/landlock/tsync.c | 8 ++++++-- security/landlock/tsync.h | 4 +++- tools/testing/selftests/landlock/base_test.c | 12 ++++++++++-- 6 files changed, 53 insertions(+), 13 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h index 27ae3f39cafb..cceda3b3b961 100644 --- a/include/uapi/linux/landlock.h +++ b/include/uapi/linux/landlock.h @@ -191,12 +191,25 @@ struct landlock_ruleset_attr { * * If the calling thread is running with no_new_privs, this operation * enables no_new_privs on the sibling threads as well. + * + * The following flag ties the no_new_privs attribute to the ruleset + * enforcement: + * + * %LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS + * Sets the no_new_privs attribute of the calling thread only once the + * enforcement of the ruleset succeeded: no_new_privs is set if and only + * if sys_landlock_restrict_self() succeeds. This removes the need for a + * prior :manpage:`prctl(2)` ``PR_SET_NO_NEW_PRIVS`` call (or + * %CAP_SYS_ADMIN use). This flag requires a ruleset. When + * combined with %LANDLOCK_RESTRICT_SELF_TSYNC, no_new_privs is set on the + * sibling threads as well. */ /* clang-format off */ #define LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF (1U << 0) #define LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON (1U << 1) #define LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF (1U << 2) #define LANDLOCK_RESTRICT_SELF_TSYNC (1U << 3) +#define LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS (1U << 4) /* clang-format on */ /** diff --git a/security/landlock/limits.h b/security/landlock/limits.h index 08d5f2f6d321..1a7c5fb8f6fd 100644 --- a/security/landlock/limits.h +++ b/security/landlock/limits.h @@ -34,7 +34,7 @@ #define LANDLOCK_NUM_ACCESS_MAX \ MAX(MAX(LANDLOCK_NUM_ACCESS_FS, LANDLOCK_NUM_ACCESS_NET), LANDLOCK_NUM_SCOPE) -#define LANDLOCK_LAST_RESTRICT_SELF LANDLOCK_RESTRICT_SELF_TSYNC +#define LANDLOCK_LAST_RESTRICT_SELF LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS #define LANDLOCK_MASK_RESTRICT_SELF ((LANDLOCK_LAST_RESTRICT_SELF << 1) - 1) /* clang-format on */ diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index e3ef7b980c82..e5f65a1c35ff 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -169,7 +169,7 @@ static const struct file_operations ruleset_fops = { * If the change involves a fix that requires userspace awareness, also update * the errata documentation in Documentation/userspace-api/landlock.rst . */ -const int landlock_abi_version = 10; +const int landlock_abi_version = 11; /** * sys_landlock_create_ruleset - Create a new ruleset @@ -502,21 +502,28 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd, * - %LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON * - %LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF * - %LANDLOCK_RESTRICT_SELF_TSYNC + * - %LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS * * This system call enforces a Landlock ruleset on the current thread. * Enforcing a ruleset requires that the task has %CAP_SYS_ADMIN in its * namespace or is running with no_new_privs. This avoids scenarios where * unprivileged tasks can affect the behavior of privileged children. * + * With %LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS, the no_new_privs attribute of the + * calling thread is set only once the enforcement of the ruleset succeeded, + * which fulfills the above requirement: no_new_privs is set if and only if the + * call succeeds. + * * Return: 0 on success, or -errno on failure. Possible returned errors are: * * - %EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time; * - %EINVAL: @flags contains an unknown bit. * - %EBADF: @ruleset_fd is not a file descriptor for the current thread; * - %EBADFD: @ruleset_fd is not a ruleset file descriptor; - * - %EPERM: @ruleset_fd has no read access to the underlying ruleset, or the - * current thread is not running with no_new_privs, or it doesn't have - * %CAP_SYS_ADMIN in its namespace. + * - %EPERM: @ruleset_fd has no read access to the underlying ruleset, or + * %LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS is not set while the current thread + * is not running with no_new_privs and doesn't have %CAP_SYS_ADMIN in its + * namespace. * - %E2BIG: The maximum number of stacked rulesets is reached for the current * thread. * @@ -541,9 +548,11 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32, /* * Similar checks as for seccomp(2), except that an -EPERM may be - * returned. + * returned. LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS fulfills this + * requirement. */ - if (!task_no_new_privs(current) && + if (!(flags & LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS) && + !task_no_new_privs(current) && !ns_capable_noaudit(current_user_ns(), CAP_SYS_ADMIN)) return -EPERM; @@ -620,12 +629,16 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32, if (flags & LANDLOCK_RESTRICT_SELF_TSYNC) { const int err = landlock_restrict_sibling_threads( - current_cred(), new_cred); + current_cred(), new_cred, flags); if (err) { abort_creds(new_cred); return err; } } + /* Sets no_new_privs past the last point of failure. */ + if (flags & LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS) + task_set_no_new_privs(current); + return commit_creds(new_cred); } diff --git a/security/landlock/tsync.c b/security/landlock/tsync.c index c5730bbd9ed3..0b71e158c3f5 100644 --- a/security/landlock/tsync.c +++ b/security/landlock/tsync.c @@ -17,6 +17,7 @@ #include #include #include +#include #include "cred.h" #include "tsync.h" @@ -466,7 +467,8 @@ static void cancel_tsync_works(const struct tsync_works *works, * restrict_sibling_threads - enables a Landlock policy for all sibling threads */ int landlock_restrict_sibling_threads(const struct cred *old_cred, - const struct cred *new_cred) + const struct cred *new_cred, + const u32 restrict_flags) { int err; struct tsync_shared_context shared_ctx; @@ -481,7 +483,9 @@ int landlock_restrict_sibling_threads(const struct cred *old_cred, init_completion(&shared_ctx.all_finished); shared_ctx.old_cred = old_cred; shared_ctx.new_cred = new_cred; - shared_ctx.set_no_new_privs = task_no_new_privs(current); + shared_ctx.set_no_new_privs = + (restrict_flags & LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS) || + task_no_new_privs(current); /* * Serialize concurrent TSYNC operations to prevent deadlocks when diff --git a/security/landlock/tsync.h b/security/landlock/tsync.h index ef86bb61c2f6..2ae4f938ca00 100644 --- a/security/landlock/tsync.h +++ b/security/landlock/tsync.h @@ -9,8 +9,10 @@ #define _SECURITY_LANDLOCK_TSYNC_H #include +#include int landlock_restrict_sibling_threads(const struct cred *old_cred, - const struct cred *new_cred); + const struct cred *new_cred, + u32 restrict_flags); #endif /* _SECURITY_LANDLOCK_TSYNC_H */ diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c index f3c126d5c003..288d6bc19232 100644 --- a/tools/testing/selftests/landlock/base_test.c +++ b/tools/testing/selftests/landlock/base_test.c @@ -76,7 +76,7 @@ TEST(abi_version) const struct landlock_ruleset_attr ruleset_attr = { .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE, }; - ASSERT_EQ(10, landlock_create_ruleset(NULL, 0, + ASSERT_EQ(11, landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION)); ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0, @@ -265,6 +265,14 @@ TEST(restrict_self_checks_ordering) ASSERT_EQ(EPERM, errno); ASSERT_EQ(-1, landlock_restrict_self(ruleset_fd, 0)); ASSERT_EQ(EPERM, errno); + /* + * LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS fulfills the no_new_privs / + * CAP_SYS_ADMIN requirement but requires a ruleset, so the FD is + * checked next. + */ + ASSERT_EQ(-1, landlock_restrict_self( + -1, LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS)); + ASSERT_EQ(EBADF, errno); ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); @@ -310,7 +318,7 @@ TEST(restrict_self_fd_logging_flags) TEST(restrict_self_logging_flags) { - const __u32 last_flag = LANDLOCK_RESTRICT_SELF_TSYNC; + const __u32 last_flag = LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS; /* Tests invalid flag combinations. */ -- cgit v1.2.3 From 3c8a5e271594f6ba6d2af40f229cbcd0bcb08c06 Mon Sep 17 00:00:00 2001 From: Pavel Begunkov Date: Fri, 7 Aug 2026 14:19:34 +0100 Subject: io_uring/zcrx: add dynamic area provisioning It's not always possible for the user to predict during registration how much memory zcrx will need to sustain the traffic. Allow to dynamically add more areas with a new ctrl code ZCRX_CTRL_ADD_AREA. Signed-off-by: Pavel Begunkov Link: https://patch.msgid.link/43495ca686713b3f0014c9f47c4c0276d704da4d.1786108672.git.asml.silence@gmail.com Signed-off-by: Jens Axboe --- include/uapi/linux/io_uring/zcrx.h | 7 +++++ io_uring/zcrx.c | 56 ++++++++++++++++++++++++++++++++++---- 2 files changed, 57 insertions(+), 6 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/io_uring/zcrx.h b/include/uapi/linux/io_uring/zcrx.h index 15c05c45ce36..08cdb173b04b 100644 --- a/include/uapi/linux/io_uring/zcrx.h +++ b/include/uapi/linux/io_uring/zcrx.h @@ -116,6 +116,7 @@ enum zcrx_ctrl_op { ZCRX_CTRL_FLUSH_RQ, ZCRX_CTRL_EXPORT, ZCRX_CTRL_ARM_NOTIFICATION, + ZCRX_CTRL_ADD_AREA, __ZCRX_CTRL_LAST, }; @@ -134,6 +135,11 @@ struct zcrx_ctrl_arm_notif { __u32 __resv[11]; }; +struct zcrx_ctrl_add_area { + __u64 area_ptr; /* pointer to struct io_uring_zcrx_area_reg */ + __u64 __resv[5]; +}; + struct zcrx_ctrl { __u32 zcrx_id; __u32 op; /* see enum zcrx_ctrl_op */ @@ -143,6 +149,7 @@ struct zcrx_ctrl { struct zcrx_ctrl_export zc_export; struct zcrx_ctrl_flush_rq zc_flush; struct zcrx_ctrl_arm_notif zc_arm_notif; + struct zcrx_ctrl_add_area zc_area; }; }; diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c index 22f04a9f2410..194418833b9c 100644 --- a/io_uring/zcrx.c +++ b/io_uring/zcrx.c @@ -36,9 +36,15 @@ #define ZCRX_REFILL_CAP MIN(64 * ZCRX_MAX_FRAGS_PER_PAGE, 1024) #define IO_ZCRX_AREA_SUPPORTED_FLAGS (IORING_ZCRX_AREA_DMABUF) +#define ZCRX_MAX_AREAS 1024 #define IO_DMA_ATTR (DMA_ATTR_SKIP_CPU_SYNC | DMA_ATTR_WEAK_ORDERING) +static inline u32 zcrx_next_area_id(struct io_zcrx_ifq *zcrx) +{ + return zcrx->nr_areas; +} + static inline u64 zcrx_area_id_to_token(u32 area_id) { return (u64)area_id << IORING_ZCRX_AREA_SHIFT; @@ -334,7 +340,7 @@ static void io_zcrx_unmap_areas(struct io_zcrx_ifq *ifq) { unsigned area_idx; - guard(mutex)(&ifq->pp_lock); + lockdep_assert_held(&ifq->pp_lock); for (area_idx = 0; area_idx < ifq->nr_areas; area_idx++) __io_zcrx_unmap_area(ifq, ifq->areas[area_idx]); @@ -469,7 +475,9 @@ static int io_zcrx_append_area(struct io_zcrx_ifq *ifq, struct io_zcrx_area **areas, **old_areas; unsigned old_nr; - if (WARN_ON_ONCE(ifq->kern_readable != kern_readable)) + if (ifq->kern_readable != kern_readable) + return -EINVAL; + if (ifq->nr_areas + 1 > ZCRX_MAX_AREAS) return -EINVAL; old_areas = ifq->areas; @@ -508,7 +516,7 @@ static int __zcrx_create_area(struct io_zcrx_ifq *ifq, return -EINVAL; buf_size_shift = ilog2(rx_buf_len); } - if (WARN_ON_ONCE(ifq->niov_shift)) + if (ifq->niov_shift && ifq->niov_shift != buf_size_shift) return -EINVAL; if (!ifq->dev && buf_size_shift != PAGE_SHIFT) return -EOPNOTSUPP; @@ -566,7 +574,7 @@ static int __zcrx_create_area(struct io_zcrx_ifq *ifq, area->free_count = nr_iovs; /* we're only supporting one area per ifq for now */ - area->area_id = 0; + area->area_id = zcrx_next_area_id(ifq); area_reg->rq_area_token = zcrx_area_id_to_token(area->area_id); ret = io_zcrx_append_area(ifq, area); @@ -608,7 +616,7 @@ static struct io_zcrx_ifq *io_zcrx_ifq_alloc(struct io_ring_ctx *ctx) static void io_zcrx_drop_netdev(struct io_zcrx_ifq *ifq) { - guard(mutex)(&ifq->pp_lock); + lockdep_assert_held(&ifq->pp_lock); if (!ifq->netdev) return; @@ -636,7 +644,8 @@ static void io_close_queue(struct io_zcrx_ifq *ifq) if (ifq->if_rxq != -1) netif_mp_close_rxq(netdev, ifq->if_rxq, &p); - io_zcrx_unmap_areas(ifq); + scoped_guard(mutex, &ifq->pp_lock) + io_zcrx_unmap_areas(ifq); netdev_unlock(netdev); netdev_put(netdev, &netdev_tracker); } @@ -994,6 +1003,8 @@ int io_register_zcrx(struct io_ring_ctx *ctx, if (copy_from_user(&area, u64_to_user_ptr(reg.area_ptr), sizeof(area))) return -EFAULT; + if (area.rq_area_token) + return -EINVAL; memset(¬if, 0, sizeof(notif)); if (reg.notif_desc && copy_from_user(¬if, u64_to_user_ptr(reg.notif_desc), @@ -1056,6 +1067,8 @@ int io_register_zcrx(struct io_ring_ctx *ctx, goto err; } + WARN_ON_ONCE(!ifq->niov_shift); + reg.zcrx_id = id; scoped_guard(mutex, &ctx->mmap_lock) { @@ -1447,6 +1460,7 @@ static void io_pp_uninstall(void *mp_priv, struct netdev_rx_queue *rxq) struct pp_memory_provider_params *p = &rxq->mp_params; struct io_zcrx_ifq *ifq = mp_priv; + guard(mutex)(&ifq->pp_lock); io_zcrx_unmap_areas(ifq); io_zcrx_drop_netdev(ifq); @@ -1549,6 +1563,34 @@ static int zcrx_arm_notif(struct io_ring_ctx *ctx, struct io_zcrx_ifq *zcrx, return 0; } +static int zcrx_ctrl_add_area(struct io_ring_ctx *ctx, struct io_zcrx_ifq *ifq, + struct zcrx_ctrl *ctrl) +{ + struct zcrx_ctrl_add_area *ctrl_add = &ctrl->zc_area; + struct io_uring_zcrx_area_reg __user *area_uptr; + struct io_uring_zcrx_area_reg area_reg; + + area_uptr = u64_to_user_ptr(ctrl_add->area_ptr); + if (copy_from_user(&area_reg, area_uptr, sizeof(area_reg))) + return -EFAULT; + if (!mem_is_zero(&ctrl_add->__resv, sizeof(ctrl_add->__resv))) + return -EINVAL; + if (area_reg.rq_area_token) + return -EINVAL; + + guard(mutex)(&ifq->pp_lock); + if (ifq->dev && !ifq->netdev) + return -EFAULT; + + /* we can't safely roll back area append, copy it out first */ + area_reg.rq_area_token = zcrx_area_id_to_token(zcrx_next_area_id(ifq)); + if (copy_to_user(area_uptr, &area_reg, sizeof(area_reg))) + return -EFAULT; + area_reg.rq_area_token = 0; + + return __zcrx_create_area(ifq, &area_reg, 1U << ifq->niov_shift); +} + int io_zcrx_ctrl(struct io_ring_ctx *ctx, void __user *arg, unsigned nr_args) { struct zcrx_ctrl ctrl; @@ -1575,6 +1617,8 @@ int io_zcrx_ctrl(struct io_ring_ctx *ctx, void __user *arg, unsigned nr_args) return zcrx_export(ctx, zcrx, &ctrl, arg); case ZCRX_CTRL_ARM_NOTIFICATION: return zcrx_arm_notif(ctx, zcrx, &ctrl); + case ZCRX_CTRL_ADD_AREA: + return zcrx_ctrl_add_area(ctx, zcrx, &ctrl); } return -EOPNOTSUPP; -- cgit v1.2.3 From 5e4bcad6171d4baf426e49a39580cdb79254ea36 Mon Sep 17 00:00:00 2001 From: Nick Hudson Date: Wed, 12 Aug 2026 09:31:10 +0100 Subject: bpf: Name the enum for BPF_FUNC_skb_adjust_room flags The existing anonymous enum for BPF_FUNC_skb_adjust_room flags is named to enum bpf_adj_room_flags to enable CO-RE (Compile Once - Run Everywhere) lookups in BPF programs. Co-developed-by: Max Tottenham Co-developed-by: Anna Glasgall Signed-off-by: Max Tottenham Signed-off-by: Anna Glasgall Signed-off-by: Nick Hudson Signed-off-by: Daniel Borkmann Reviewed-by: Willem de Bruijn Link: https://lore.kernel.org/bpf/20260812083115.73100-2-nhudson@akamai.com --- include/uapi/linux/bpf.h | 2 +- tools/include/uapi/linux/bpf.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index ffd96e8b920b..f6c9dc856858 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -6283,7 +6283,7 @@ enum { }; /* BPF_FUNC_skb_adjust_room flags. */ -enum { +enum bpf_adj_room_flags { BPF_F_ADJ_ROOM_FIXED_GSO = (1ULL << 0), BPF_F_ADJ_ROOM_ENCAP_L3_IPV4 = (1ULL << 1), BPF_F_ADJ_ROOM_ENCAP_L3_IPV6 = (1ULL << 2), diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index ffd96e8b920b..f6c9dc856858 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -6283,7 +6283,7 @@ enum { }; /* BPF_FUNC_skb_adjust_room flags. */ -enum { +enum bpf_adj_room_flags { BPF_F_ADJ_ROOM_FIXED_GSO = (1ULL << 0), BPF_F_ADJ_ROOM_ENCAP_L3_IPV4 = (1ULL << 1), BPF_F_ADJ_ROOM_ENCAP_L3_IPV6 = (1ULL << 2), -- cgit v1.2.3 From da199070bc6209bf5db970f8f84c7db4210fe511 Mon Sep 17 00:00:00 2001 From: Nick Hudson Date: Wed, 12 Aug 2026 09:31:12 +0100 Subject: bpf: Add BPF_F_ADJ_ROOM_DECAP_* flags for tunnel decapsulation Add new bpf_skb_adjust_room() decapsulation flags: - BPF_F_ADJ_ROOM_DECAP_L4_GRE - BPF_F_ADJ_ROOM_DECAP_L4_UDP - BPF_F_ADJ_ROOM_DECAP_IPXIP4 - BPF_F_ADJ_ROOM_DECAP_IPXIP6 These flags let BPF programs describe which tunnel layer is being removed, so later changes can update tunnel-related GSO state accordingly during decapsulation. This patch only introduces the UAPI flag definitions and helper documentation. Co-developed-by: Max Tottenham Co-developed-by: Anna Glasgall Signed-off-by: Max Tottenham Signed-off-by: Anna Glasgall Signed-off-by: Nick Hudson Signed-off-by: Daniel Borkmann Reviewed-by: Willem de Bruijn Link: https://lore.kernel.org/bpf/20260812083115.73100-4-nhudson@akamai.com --- include/uapi/linux/bpf.h | 34 ++++++++++++++++++++++++++++++++-- tools/include/uapi/linux/bpf.h | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 64 insertions(+), 4 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index f6c9dc856858..732b35cc08d1 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -3038,8 +3038,34 @@ union bpf_attr { * * * **BPF_F_ADJ_ROOM_DECAP_L3_IPV4**, * **BPF_F_ADJ_ROOM_DECAP_L3_IPV6**: - * Indicate the new IP header version after decapsulating the outer - * IP header. Used when the inner and outer IP versions are different. + * Indicate the new IP header version after decapsulating the + * outer IP header. Used when the inner and outer IP versions + * are different. These flags only trigger a protocol change + * without clearing any tunnel-specific GSO flags. + * + * * **BPF_F_ADJ_ROOM_DECAP_L4_GRE**: + * Clear GRE tunnel GSO flags (SKB_GSO_GRE and SKB_GSO_GRE_CSUM) + * when decapsulating a GRE tunnel. + * + * * **BPF_F_ADJ_ROOM_DECAP_L4_UDP**: + * Clear UDP tunnel GSO flags (SKB_GSO_UDP_TUNNEL and + * SKB_GSO_UDP_TUNNEL_CSUM) when decapsulating a UDP tunnel. + * + * * **BPF_F_ADJ_ROOM_DECAP_IPXIP4**: + * Clear IPIP/SIT tunnel GSO flag (SKB_GSO_IPXIP4) when decapsulating + * a tunnel with an outer IPv4 header (IPv4-in-IPv4 or IPv6-in-IPv4). + * + * * **BPF_F_ADJ_ROOM_DECAP_IPXIP6**: + * Clear IPv6 encapsulation tunnel GSO flag (SKB_GSO_IPXIP6) when + * decapsulating a tunnel with an outer IPv6 header (IPv6-in-IPv6 + * or IPv4-in-IPv6). + * + * When using the decapsulation flags above, the skb->encapsulation + * flag is automatically cleared if all tunnel-specific GSO flags + * (SKB_GSO_UDP_TUNNEL, SKB_GSO_UDP_TUNNEL_CSUM, SKB_GSO_GRE, + * SKB_GSO_GRE_CSUM, SKB_GSO_IPXIP4, SKB_GSO_IPXIP6) have been + * removed from the packet. This handles cases where all tunnel + * layers have been decapsulated. * * A call to this helper is susceptible to change the underlying * packet buffer. Therefore, at load time, all checks on pointers @@ -6293,6 +6319,10 @@ enum bpf_adj_room_flags { BPF_F_ADJ_ROOM_ENCAP_L2_ETH = (1ULL << 6), BPF_F_ADJ_ROOM_DECAP_L3_IPV4 = (1ULL << 7), BPF_F_ADJ_ROOM_DECAP_L3_IPV6 = (1ULL << 8), + BPF_F_ADJ_ROOM_DECAP_L4_GRE = (1ULL << 9), + BPF_F_ADJ_ROOM_DECAP_L4_UDP = (1ULL << 10), + BPF_F_ADJ_ROOM_DECAP_IPXIP4 = (1ULL << 11), + BPF_F_ADJ_ROOM_DECAP_IPXIP6 = (1ULL << 12), }; enum { diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index f6c9dc856858..732b35cc08d1 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -3038,8 +3038,34 @@ union bpf_attr { * * * **BPF_F_ADJ_ROOM_DECAP_L3_IPV4**, * **BPF_F_ADJ_ROOM_DECAP_L3_IPV6**: - * Indicate the new IP header version after decapsulating the outer - * IP header. Used when the inner and outer IP versions are different. + * Indicate the new IP header version after decapsulating the + * outer IP header. Used when the inner and outer IP versions + * are different. These flags only trigger a protocol change + * without clearing any tunnel-specific GSO flags. + * + * * **BPF_F_ADJ_ROOM_DECAP_L4_GRE**: + * Clear GRE tunnel GSO flags (SKB_GSO_GRE and SKB_GSO_GRE_CSUM) + * when decapsulating a GRE tunnel. + * + * * **BPF_F_ADJ_ROOM_DECAP_L4_UDP**: + * Clear UDP tunnel GSO flags (SKB_GSO_UDP_TUNNEL and + * SKB_GSO_UDP_TUNNEL_CSUM) when decapsulating a UDP tunnel. + * + * * **BPF_F_ADJ_ROOM_DECAP_IPXIP4**: + * Clear IPIP/SIT tunnel GSO flag (SKB_GSO_IPXIP4) when decapsulating + * a tunnel with an outer IPv4 header (IPv4-in-IPv4 or IPv6-in-IPv4). + * + * * **BPF_F_ADJ_ROOM_DECAP_IPXIP6**: + * Clear IPv6 encapsulation tunnel GSO flag (SKB_GSO_IPXIP6) when + * decapsulating a tunnel with an outer IPv6 header (IPv6-in-IPv6 + * or IPv4-in-IPv6). + * + * When using the decapsulation flags above, the skb->encapsulation + * flag is automatically cleared if all tunnel-specific GSO flags + * (SKB_GSO_UDP_TUNNEL, SKB_GSO_UDP_TUNNEL_CSUM, SKB_GSO_GRE, + * SKB_GSO_GRE_CSUM, SKB_GSO_IPXIP4, SKB_GSO_IPXIP6) have been + * removed from the packet. This handles cases where all tunnel + * layers have been decapsulated. * * A call to this helper is susceptible to change the underlying * packet buffer. Therefore, at load time, all checks on pointers @@ -6293,6 +6319,10 @@ enum bpf_adj_room_flags { BPF_F_ADJ_ROOM_ENCAP_L2_ETH = (1ULL << 6), BPF_F_ADJ_ROOM_DECAP_L3_IPV4 = (1ULL << 7), BPF_F_ADJ_ROOM_DECAP_L3_IPV6 = (1ULL << 8), + BPF_F_ADJ_ROOM_DECAP_L4_GRE = (1ULL << 9), + BPF_F_ADJ_ROOM_DECAP_L4_UDP = (1ULL << 10), + BPF_F_ADJ_ROOM_DECAP_IPXIP4 = (1ULL << 11), + BPF_F_ADJ_ROOM_DECAP_IPXIP6 = (1ULL << 12), }; enum { -- cgit v1.2.3 From ebed9ea5b469588c6074f3ed5b8d8ec63c4ccf48 Mon Sep 17 00:00:00 2001 From: Joanne Koong Date: Fri, 14 Aug 2026 11:59:42 -0700 Subject: fuse: add FUSE_IO_URING_CMD_ADD_QUEUE fuse-over-io-uring queues are currently created lazily, as a side effect of the first FUSE_IO_URING_CMD_REGISTER command for a given qid. This ties queue creation to entry registration. Add a FUSE_IO_URING_CMD_ADD_QUEUE command so a server can create a queue explicitly, decoupling queue setup from entry registration. This is additionally a prerequisite for FUSE_IO_URING_CMD_ADD_BUFPOOL, which attaches a buffer pool to an existing queue and therefore needs the queue to have been created first. Reviewed-by: Bernd Schubert Signed-off-by: Joanne Koong Signed-off-by: Miklos Szeredi --- fs/fuse/dev_uring.c | 45 ++++++++++++++++++++++++++++++++++++++------- include/uapi/linux/fuse.h | 8 +++++++- 2 files changed, 45 insertions(+), 8 deletions(-) (limited to 'include/uapi/linux') diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 9616778505ba..3b9fd0daef66 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -281,7 +281,8 @@ void fuse_uring_conn_init(struct fuse_chan *fch) } static struct fuse_ring_queue *fuse_uring_create_queue(struct fuse_ring *ring, - int qid) + int qid, + bool fail_if_exists) { struct fuse_chan *fch = ring->chan; struct fuse_ring_queue *queue; @@ -289,11 +290,11 @@ static struct fuse_ring_queue *fuse_uring_create_queue(struct fuse_ring *ring, queue = kzalloc_obj(*queue, GFP_KERNEL_ACCOUNT); if (!queue) - return NULL; + return ERR_PTR(-ENOMEM); pq = fuse_pqueue_alloc(); if (!pq) { kfree(queue); - return NULL; + return ERR_PTR(-ENOMEM); } queue->qid = qid; @@ -316,7 +317,7 @@ static struct fuse_ring_queue *fuse_uring_create_queue(struct fuse_ring *ring, spin_unlock(&fch->lock); kfree(queue->fpq.processing); kfree(queue); - return ring->queues[qid]; + return fail_if_exists ? ERR_PTR(-EEXIST) : ring->queues[qid]; } /* @@ -1189,9 +1190,9 @@ static int fuse_uring_register(struct io_uring_cmd *cmd, queue = READ_ONCE(ring->queues[qid]); if (!queue) { - queue = fuse_uring_create_queue(ring, qid); - if (!queue) - return -ENOMEM; + queue = fuse_uring_create_queue(ring, qid, false); + if (IS_ERR(queue)) + return PTR_ERR(queue); } /* @@ -1206,6 +1207,30 @@ static int fuse_uring_register(struct io_uring_cmd *cmd, return fuse_uring_do_register(ent, cmd, issue_flags); } +static int fuse_uring_add_queue(struct io_uring_cmd *cmd, struct fuse_chan *fch) +{ + const struct fuse_uring_cmd_req *cmd_req = + io_uring_sqe128_cmd(cmd->sqe, struct fuse_uring_cmd_req); + struct fuse_ring *ring = smp_load_acquire(&fch->ring); + unsigned int qid = READ_ONCE(cmd_req->qid); + uint64_t flags = READ_ONCE(cmd_req->flags); + struct fuse_ring_queue *queue; + + if (!ring || flags) + return -EINVAL; + + if (qid >= ring->nr_queues) { + pr_info_ratelimited("fuse: Invalid ring qid %u\n", qid); + return -EINVAL; + } + + queue = fuse_uring_create_queue(ring, qid, true); + if (IS_ERR(queue)) + return PTR_ERR(queue); + + return 0; +} + /* * Entry function from io_uring to handle the given passthrough command * (op code IORING_OP_URING_CMD) @@ -1272,6 +1297,12 @@ int fuse_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags) return err; } break; + case FUSE_IO_URING_CMD_ADD_QUEUE: + err = fuse_uring_add_queue(cmd, fch); + if (err) + pr_info_once("FUSE_IO_URING_CMD_ADD_QUEUE failed err=%d\n", + err); + return err; default: return -EINVAL; } diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h index c13e1f9a2f12..cfb055c0c764 100644 --- a/include/uapi/linux/fuse.h +++ b/include/uapi/linux/fuse.h @@ -240,6 +240,9 @@ * - add FUSE_COPY_FILE_RANGE_64 * - add struct fuse_copy_file_range_out * - add FUSE_NOTIFY_PRUNE + * + * 7.46 + * - add FUSE_IO_URING_CMD_ADD_QUEUE */ #ifndef _LINUX_FUSE_H @@ -275,7 +278,7 @@ #define FUSE_KERNEL_VERSION 7 /** Minor version number of this interface */ -#define FUSE_KERNEL_MINOR_VERSION 45 +#define FUSE_KERNEL_MINOR_VERSION 46 /** The node ID of the root inode */ #define FUSE_ROOT_ID 1 @@ -1292,6 +1295,9 @@ enum fuse_uring_cmd { /* commit fuse request result and fetch next request */ FUSE_IO_URING_CMD_COMMIT_AND_FETCH = 2, + + /* add a queue */ + FUSE_IO_URING_CMD_ADD_QUEUE = 3, }; /** -- cgit v1.2.3 From b45aaabc628bc7356e21bd2eb0c2ae9bdfa13894 Mon Sep 17 00:00:00 2001 From: Joanne Koong Date: Fri, 14 Aug 2026 11:59:43 -0700 Subject: fuse: add io-uring buffer pools Right now, ents and buffers are tightly coupled in fuse io-uring where each entry has its own dedicated payload buffer, requiring N buffers for N entries where each buffer must be large enough to accomodate the maximum payload size. This is suboptimal as most request types (lookup, open, release, getattr, etc) require vastly less bytes than the maximum payload size and some requests (unlink, rmdir, fsync, flush, etc) do not require payload buffers at all. Instead of requiring a 1:1 coupling between ents and payload buffers, allow the server to pass in a buffer pool (a contiguous chunk of memory) that the kernel will use as it wishes for servicing ents/requests. Entries only reserve a "buffer" from the pool while actively processing a request that requires a payload buffer. This decoupling and letting the kernel delegate memory from the pool for requests allows the kernel to optimize memory usage and reduces the memory usage requirements needed to use fuse-over-io-uring. A pool is registered per queue with the new FUSE_IO_URING_CMD_ADD_BUFPOOL command. The server passes the pool's base address and length in fuse_uring_cmd_req.bufpool.{uaddr,len}. Internally, the kernel splits the region into buffers of ring->max_payload_sz bytes each (nr_bufs = pool len / max_payload_sz). A queue commits to a payload mode on first use: registering an entry that carries its own payload selects the legacy per-entry mode, while ADD_BUFPOOL selects pool mode. The two are mutually exclusive, so ADD_BUFPOOL must be issued before any payload-carrying entries are registered on that queue. The queue must have been created before the bufpool is added, through the FUSE_IO_URING_CMD_ADD_QUEUE command. The kernel tracks free buffers with a bitmap (a set bit marks a free buffer). On dispatch, a request that needs a payload claims a free buffer (find_first_bit + clear). A request that needs none claims nothing. The buffer's byte offset within the pool is reported to the server in the new fuse_uring_ent_in_out.offset field so that the server can locate the payload. On completion the buffer is returned to the pool or reused directly if the next request on that entry also has a payload. The FUSE_HAS_IO_URING_BUFPOOL flag advertises kernel support to the server for bufpools. Buffer pool request flow ~~~~~~~~~~~~~~~~~~~~~~~~ | Kernel | FUSE daemon | | | [request arrives] | | [claim a free pool buffer] | | >fuse_uring_select_buffer() | | [copy headers to ring] | | [copy payload to buffer] | | [report buffer offset in ent_in_out] | | >io_uring_cmd_done() | | | [read headers] | | [read/write payload at offset] | | [process request] | | >io_uring_submit() | | COMMIT_AND_FETCH | >fuse_uring_commit_fetch() | | [copy reply from ring] | | [return buffer to the pool] | | >fuse_uring_recycle_buffer() | Reviewed-by: Bernd Schubert Signed-off-by: Joanne Koong Signed-off-by: Miklos Szeredi --- fs/fuse/dev_uring.c | 239 ++++++++++++++++++++++++++++++++++++++++------ fs/fuse/dev_uring_i.h | 37 ++++++- fs/fuse/inode.c | 2 +- include/uapi/linux/fuse.h | 21 +++- 4 files changed, 269 insertions(+), 30 deletions(-) (limited to 'include/uapi/linux') diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 3b9fd0daef66..d300c7f441c4 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -9,6 +9,7 @@ #include "dev_uring_i.h" #include "fuse_trace.h" +#include #include #include @@ -41,6 +42,11 @@ enum fuse_uring_header_type { FUSE_URING_HEADER_RING_ENT, }; +static inline bool bufpool_enabled(struct fuse_ring_queue *queue) +{ + return queue->payload_mode == FUSE_PAYLOAD_BUFPOOL; +} + static void uring_cmd_set_ring_ent(struct io_uring_cmd *cmd, struct fuse_ring_ent *ring_ent) { @@ -222,6 +228,7 @@ void fuse_uring_destruct(struct fuse_chan *fch) } kfree(queue->fpq.processing); + kfree(queue->bufpool); kfree(queue); WRITE_ONCE(ring->queues[qid], NULL); } @@ -316,6 +323,7 @@ static struct fuse_ring_queue *fuse_uring_create_queue(struct fuse_ring *ring, if (ring->queues[qid]) { spin_unlock(&fch->lock); kfree(queue->fpq.processing); + kfree(queue->bufpool); kfree(queue); return fail_if_exists ? ERR_PTR(-EEXIST) : ring->queues[qid]; } @@ -646,13 +654,14 @@ static int copy_header_from_ring(struct fuse_ring_ent *ent, } static int setup_fuse_copy_state(struct fuse_copy_state *cs, - struct fuse_ring *ring, struct fuse_req *req, + struct fuse_req *req, struct fuse_ring_ent *ent, int dir, struct iov_iter *iter) { int err; - err = import_ubuf(dir, ent->payload, ring->max_payload_sz, iter); + err = import_ubuf(dir, ent->payload.iov_base, ent->payload.iov_len, + iter); if (err) { pr_info_ratelimited("fuse: Import of user buffer failed\n"); return err; @@ -666,8 +675,7 @@ static int setup_fuse_copy_state(struct fuse_copy_state *cs, return 0; } -static int fuse_uring_copy_from_ring(struct fuse_ring *ring, - struct fuse_req *req, +static int fuse_uring_copy_from_ring(struct fuse_req *req, struct fuse_ring_ent *ent) { struct fuse_copy_state cs; @@ -681,7 +689,7 @@ static int fuse_uring_copy_from_ring(struct fuse_ring *ring, if (err) return err; - err = setup_fuse_copy_state(&cs, ring, req, ent, ITER_SOURCE, &iter); + err = setup_fuse_copy_state(&cs, req, ent, ITER_SOURCE, &iter); if (err) return err; @@ -693,7 +701,7 @@ static int fuse_uring_copy_from_ring(struct fuse_ring *ring, /* * Copy data from the req to the ring buffer */ -static int fuse_uring_args_to_ring(struct fuse_ring *ring, struct fuse_req *req, +static int fuse_uring_args_to_ring(struct fuse_req *req, struct fuse_ring_ent *ent) { struct fuse_copy_state cs; @@ -707,7 +715,7 @@ static int fuse_uring_args_to_ring(struct fuse_ring *ring, struct fuse_req *req, .commit_id = req->in.h.unique, }; - err = setup_fuse_copy_state(&cs, ring, req, ent, ITER_DEST, &iter); + err = setup_fuse_copy_state(&cs, req, ent, ITER_DEST, &iter); if (err) return err; @@ -737,6 +745,10 @@ static int fuse_uring_args_to_ring(struct fuse_ring *ring, struct fuse_req *req, } ent_in_out.payload_sz = cs.ring.copied_sz; + if (bufpool_enabled(ent->queue) && ent->payload.iov_base) + ent_in_out.offset = + (uintptr_t)ent->payload.iov_base - ent->queue->bufpool->base_uaddr; + return copy_header_to_ring(ent, FUSE_URING_HEADER_RING_ENT, &ent_in_out, sizeof(ent_in_out)); } @@ -745,7 +757,6 @@ static int fuse_uring_copy_to_ring(struct fuse_ring_ent *ent, struct fuse_req *req) { struct fuse_ring_queue *queue = ent->queue; - struct fuse_ring *ring = queue->ring; int err; err = -EIO; @@ -760,7 +771,7 @@ static int fuse_uring_copy_to_ring(struct fuse_ring_ent *ent, return err; /* copy the request */ - err = fuse_uring_args_to_ring(ring, req, ent); + err = fuse_uring_args_to_ring(req, ent); if (unlikely(err)) { pr_info_ratelimited("Copy to ring failed: %d\n", err); return err; @@ -771,6 +782,91 @@ static int fuse_uring_copy_to_ring(struct fuse_ring_ent *ent, sizeof(req->in.h)); } +static bool fuse_uring_req_has_payload(struct fuse_req *req) +{ + struct fuse_args *args = req->args; + + return args->in_numargs > 1 || args->out_numargs; +} + +static int fuse_uring_select_buffer(struct fuse_ring_ent *ent) +{ + struct fuse_ring_queue *queue = ent->queue; + struct fuse_bufpool *pool = queue->bufpool; + unsigned int id; + + lockdep_assert_held(&queue->lock); + + id = find_first_bit(pool->free_map, pool->nr_bufs); + if (id >= pool->nr_bufs) + return -ENOBUFS; + + WARN_ON_ONCE(ent->payload.iov_base); + __clear_bit(id, pool->free_map); + + ent->buf_id = id; + ent->payload.iov_base = + (void __user *)(pool->base_uaddr + id * pool->buf_size); + ent->payload.iov_len = pool->buf_size; + + return 0; +} + +static void fuse_uring_recycle_buffer(struct fuse_ring_ent *ent) +{ + struct iovec *ent_payload = &ent->payload; + struct fuse_ring_queue *queue = ent->queue; + struct fuse_bufpool *pool; + + lockdep_assert_held(&queue->lock); + + if (!bufpool_enabled(queue) || !ent_payload->iov_base) + return; + + pool = queue->bufpool; + + /* a buffer should never be recycled twice */ + WARN_ON_ONCE(test_bit(ent->buf_id, pool->free_map)); + __set_bit(ent->buf_id, pool->free_map); + + memset(ent_payload, 0, sizeof(*ent_payload)); + ent->buf_id = 0; +} + +static int fuse_uring_next_req_update_buffer(struct fuse_ring_ent *ent, + struct fuse_req *req) +{ + bool buffer_selected; + bool has_payload; + + if (!bufpool_enabled(ent->queue)) + return 0; + + buffer_selected = !!ent->payload.iov_base; + has_payload = fuse_uring_req_has_payload(req); + + if (has_payload && !buffer_selected) + return fuse_uring_select_buffer(ent); + + if (!has_payload && buffer_selected) + fuse_uring_recycle_buffer(ent); + + return 0; +} + +static int fuse_uring_prep_buffer(struct fuse_ring_ent *ent, + struct fuse_req *req) +{ + if (!bufpool_enabled(ent->queue)) + return 0; + + /* no payload to copy, can skip selecting a buffer */ + if (!fuse_uring_req_has_payload(req)) + return 0; + + return fuse_uring_select_buffer(ent); +} + static int fuse_uring_prepare_send(struct fuse_ring_ent *ent, struct fuse_req *req) { @@ -858,9 +954,12 @@ static struct fuse_req *fuse_uring_ent_assign_req(struct fuse_ring_ent *ent) /* get and assign the next entry while it is still holding the lock */ req = list_first_entry_or_null(req_queue, struct fuse_req, list); - if (req) - fuse_uring_add_req_to_ring_ent(ent, req); + if (!req || fuse_uring_next_req_update_buffer(ent, req)) { + fuse_uring_recycle_buffer(ent); + return NULL; + } + fuse_uring_add_req_to_ring_ent(ent, req); return req; } @@ -872,7 +971,6 @@ static struct fuse_req *fuse_uring_ent_assign_req(struct fuse_ring_ent *ent) static void fuse_uring_commit(struct fuse_ring_ent *ent, struct fuse_req *req, unsigned int issue_flags) { - struct fuse_ring *ring = ent->queue->ring; ssize_t err = -EFAULT; if (copy_header_from_ring(ent, FUSE_URING_HEADER_IN_OUT, &req->out.h, @@ -885,7 +983,7 @@ static void fuse_uring_commit(struct fuse_ring_ent *ent, struct fuse_req *req, goto out; } - err = fuse_uring_copy_from_ring(ring, req, ent); + err = fuse_uring_copy_from_ring(req, ent); out: fuse_uring_req_end(ent, req, err); } @@ -1004,6 +1102,7 @@ static int fuse_uring_commit_fetch(struct io_uring_cmd *cmd, int issue_flags, if (err != 0) { pr_info_ratelimited("qid=%d commit_id %llu state %d", queue->qid, commit_id, ent->state); + fuse_uring_recycle_buffer(ent); spin_unlock(&queue->lock); fuse_uring_req_end(ent, req, err); return err; @@ -1021,6 +1120,11 @@ static int fuse_uring_commit_fetch(struct io_uring_cmd *cmd, int issue_flags, * fuse requests would otherwise not get processed - committing * and fetching is done in one step vs legacy fuse, which has separated * read (fetch request) and write (commit result). + * + * If there is no next request or if all buffers are busy (if using a + * bufpool), the cmd is not returned to userspace. The entry is left + * available and the cmd only returns to userspace when there's a + * next request and an available buffer. */ if (fuse_uring_get_next_fuse_req(ent, queue)) fuse_uring_send(ent, cmd, 0, issue_flags); @@ -1145,11 +1249,23 @@ fuse_uring_create_ring_ent(struct io_uring_cmd *cmd, } payload = &iov[FUSE_URING_IOV_PAYLOAD]; - if (payload->iov_len < ring->max_payload_sz) { - pr_info_ratelimited("Invalid req payload len %zu\n", - payload->iov_len); - return ERR_PTR(err); + + spin_lock(&queue->lock); + if (bufpool_enabled(queue)) { + if (payload->iov_base || payload->iov_len) { + spin_unlock(&queue->lock); + return ERR_PTR(err); + } + } else { + if (payload->iov_len < ring->max_payload_sz) { + pr_info_ratelimited("Invalid req payload len %zu\n", + payload->iov_len); + spin_unlock(&queue->lock); + return ERR_PTR(err); + } + queue->payload_mode = FUSE_PAYLOAD_PER_ENT; } + spin_unlock(&queue->lock); err = -ENOMEM; ent = kzalloc_obj(*ent, GFP_KERNEL_ACCOUNT); @@ -1160,7 +1276,8 @@ fuse_uring_create_ring_ent(struct io_uring_cmd *cmd, ent->queue = queue; ent->headers = headers->iov_base; - ent->payload = payload->iov_base; + if (queue->payload_mode == FUSE_PAYLOAD_PER_ENT) + ent->payload = *payload; atomic_inc(&ring->queue_refs); return ent; @@ -1231,6 +1348,67 @@ static int fuse_uring_add_queue(struct io_uring_cmd *cmd, struct fuse_chan *fch) return 0; } +static int fuse_uring_add_bufpool(struct io_uring_cmd *cmd, + struct fuse_chan *fch) +{ + const struct fuse_uring_cmd_req *cmd_req = + io_uring_sqe128_cmd(cmd->sqe, struct fuse_uring_cmd_req); + unsigned int qid = READ_ONCE(cmd_req->qid); + uint64_t flags = READ_ONCE(cmd_req->flags); + /* paired with the smp_store_release() in fuse_uring_create */ + struct fuse_ring *ring = smp_load_acquire(&fch->ring); + struct fuse_ring_queue *queue; + struct fuse_bufpool *pool; + uintptr_t pool_uaddr; + unsigned int pool_len, nr_bufs; + size_t pool_size, buf_size; + + if (!ring || qid >= ring->nr_queues || flags) + return -EINVAL; + + /* reserved for future use, must be zero */ + if (READ_ONCE(cmd_req->bufpool.reserved)) + return -EINVAL; + + /* Pairs with smp_store_release() in fuse_uring_create_queue() */ + queue = smp_load_acquire(&ring->queues[qid]); + if (!queue) + return -EINVAL; + + pool_uaddr = READ_ONCE(cmd_req->bufpool.uaddr); + pool_len = READ_ONCE(cmd_req->bufpool.len); + + /* each buffer holds the max payload size */ + buf_size = queue->ring->max_payload_sz; + + nr_bufs = pool_len / buf_size; + if (!nr_bufs) + return -EINVAL; + + pool_size = struct_size(pool, free_map, BITS_TO_LONGS(nr_bufs)); + pool = kzalloc(pool_size, GFP_KERNEL_ACCOUNT); + if (!pool) + return -ENOMEM; + + pool->base_uaddr = pool_uaddr; + pool->buf_size = buf_size; + pool->nr_bufs = nr_bufs; + /* all buffers are free */ + bitmap_set(pool->free_map, 0, nr_bufs); + + spin_lock(&queue->lock); + if (queue->payload_mode != FUSE_PAYLOAD_UNSET) { + spin_unlock(&queue->lock); + kfree(pool); + return -EINVAL; + } + queue->bufpool = pool; + queue->payload_mode = FUSE_PAYLOAD_BUFPOOL; + spin_unlock(&queue->lock); + + return 0; +} + /* * Entry function from io_uring to handle the given passthrough command * (op code IORING_OP_URING_CMD) @@ -1303,6 +1481,12 @@ int fuse_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags) pr_info_once("FUSE_IO_URING_CMD_ADD_QUEUE failed err=%d\n", err); return err; + case FUSE_IO_URING_CMD_ADD_BUFPOOL: + err = fuse_uring_add_bufpool(cmd, fch); + if (err) + pr_info_once("FUSE_IO_URING_ADD_BUFPOOL failed err=%d\n", + err); + return err; default: return -EINVAL; } @@ -1336,6 +1520,7 @@ static void fuse_uring_send_in_task(struct io_tw_req tw_req, io_tw_token_t tw) spin_lock(&queue->lock); list_del_init(&ent->list); + fuse_uring_recycle_buffer(ent); spin_unlock(&queue->lock); io_uring_cmd_done(cmd, err, issue_flags); @@ -1397,15 +1582,16 @@ void fuse_uring_queue_fuse_req(struct fuse_iqueue *fiq, struct fuse_req *req) req->ring_queue = queue; ent = list_first_entry_or_null(&queue->ent_avail_queue, struct fuse_ring_ent, list); - if (ent) - fuse_uring_add_req_to_ring_ent(ent, req); - else - list_add_tail(&req->list, &queue->fuse_req_queue); - spin_unlock(&queue->lock); - if (ent) - fuse_uring_dispatch_ent(ent); + if (!ent || fuse_uring_prep_buffer(ent, req)) { + list_add_tail(&req->list, &queue->fuse_req_queue); + spin_unlock(&queue->lock); + return; + } + fuse_uring_add_req_to_ring_ent(ent, req); + spin_unlock(&queue->lock); + fuse_uring_dispatch_ent(ent); return; err_unlock: @@ -1453,10 +1639,9 @@ bool fuse_uring_queue_bq_req(struct fuse_req *req) */ req = list_first_entry_or_null(&queue->fuse_req_queue, struct fuse_req, list); - if (ent && req) { + if (ent && req && !fuse_uring_prep_buffer(ent, req)) { fuse_uring_add_req_to_ring_ent(ent, req); spin_unlock(&queue->lock); - fuse_uring_dispatch_ent(ent); } else { spin_unlock(&queue->lock); diff --git a/fs/fuse/dev_uring_i.h b/fs/fuse/dev_uring_i.h index d721a4fc0215..cdf56f8b38b5 100644 --- a/fs/fuse/dev_uring_i.h +++ b/fs/fuse/dev_uring_i.h @@ -7,6 +7,8 @@ #ifndef _FS_FUSE_DEV_URING_I_H #define _FS_FUSE_DEV_URING_I_H +#include + #include "fuse_dev_i.h" #ifdef CONFIG_FUSE_IO_URING @@ -36,11 +38,38 @@ enum fuse_ring_req_state { FRRS_RELEASED, }; +/* how a queue's payload buffers are provided */ +enum fuse_queue_payload_mode { + /* not yet committed (a bufpool may still be added) */ + FUSE_PAYLOAD_UNSET = 0, + /* each entry registers its own payload buffer */ + FUSE_PAYLOAD_PER_ENT, + /* each entry's payload buffer is assigned from a bufpool */ + FUSE_PAYLOAD_BUFPOOL, +}; + +struct fuse_bufpool { + /* starting uaddr of the bufpool */ + uintptr_t base_uaddr; + + /* size of each buffer in the pool */ + size_t buf_size; + + /* total number of buffers in the pool */ + unsigned int nr_bufs; + + /* bitmap tracking which buffers are free */ + unsigned long free_map[]; +}; + /** A fuse ring entry, part of the ring queue */ struct fuse_ring_ent { /* userspace buffer */ struct fuse_uring_req_header __user *headers; - void __user *payload; + struct iovec payload; + + /* buffer id in the pool, if bufpools are used. ignored otherwise */ + unsigned int buf_id; /* the ring queue that owns the request */ struct fuse_ring_queue *queue; @@ -99,6 +128,12 @@ struct fuse_ring_queue { unsigned int active_background; bool stopped; + + /* how this queue's payload buffers are provided */ + enum fuse_queue_payload_mode payload_mode; + + /* only allocated when payload_mode == FUSE_PAYLOAD_BUFPOOL */ + struct fuse_bufpool *bufpool; }; /* diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 33773c7d129a..9779adc98593 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -1482,7 +1482,7 @@ static struct fuse_init_args *fuse_new_init(struct fuse_mount *fm) * the reply - server is either sending IORING_OP_URING_CMD or not. */ if (fuse_uring_enabled()) - flags |= FUSE_OVER_IO_URING; + flags |= FUSE_OVER_IO_URING | FUSE_HAS_IO_URING_BUFPOOL; ia->in.flags = flags; ia->in.flags2 = flags >> 32; diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h index cfb055c0c764..538d844da099 100644 --- a/include/uapi/linux/fuse.h +++ b/include/uapi/linux/fuse.h @@ -243,6 +243,9 @@ * * 7.46 * - add FUSE_IO_URING_CMD_ADD_QUEUE + * - add FUSE_HAS_IO_URING_BUFPOOL + * - add fuse_uring_cmd_req bufpool struct + * - add bufpool offset field to fuse_uring_ent_in_out struct */ #ifndef _LINUX_FUSE_H @@ -451,6 +454,7 @@ struct fuse_file_lock { * FUSE_OVER_IO_URING: Indicate that client supports io-uring * FUSE_REQUEST_TIMEOUT: kernel supports timing out requests. * init_out.request_timeout contains the timeout (in secs) + * FUSE_HAS_IO_URING_BUFPOOL: kernel supports io-uring buffer pools */ #define FUSE_ASYNC_READ (1 << 0) #define FUSE_POSIX_LOCKS (1 << 1) @@ -498,6 +502,7 @@ struct fuse_file_lock { #define FUSE_ALLOW_IDMAP (1ULL << 40) #define FUSE_OVER_IO_URING (1ULL << 41) #define FUSE_REQUEST_TIMEOUT (1ULL << 42) +#define FUSE_HAS_IO_URING_BUFPOOL (1ULL << 43) /** * CUSE INIT request/reply flags @@ -1266,7 +1271,9 @@ struct fuse_uring_ent_in_out { /* size of user payload buffer */ uint32_t payload_sz; - uint32_t padding; + + /* Offset into the bufpool, if bufpools are used */ + uint32_t offset; uint64_t reserved; }; @@ -1298,6 +1305,9 @@ enum fuse_uring_cmd { /* add a queue */ FUSE_IO_URING_CMD_ADD_QUEUE = 3, + + /* add a bufpool to a queue */ + FUSE_IO_URING_CMD_ADD_BUFPOOL = 4, }; /** @@ -1312,6 +1322,15 @@ struct fuse_uring_cmd_req { /* queue the command is for (queue index) */ uint16_t qid; uint8_t padding[6]; + + union { + struct { + /* base address of bufpool */ + uint64_t uaddr; + uint32_t len; + uint32_t reserved; + } bufpool; + }; }; #endif /* _LINUX_FUSE_H */ -- cgit v1.2.3 From 43f8343858eb942d7f7c49964b31c54dcc314890 Mon Sep 17 00:00:00 2001 From: Joanne Koong Date: Fri, 14 Aug 2026 11:59:45 -0700 Subject: fuse: add zero-copy over io-uring Implement zero-copy in fuse io-uring to eliminate memory copies between the application, kernel, and server for read/write operations. The server can directly access client pages or page cache folios without copying data through an intermediary buffer. When a fuse request arrives, the kernel registers the relevant pages into a sparse slot in the server's io_uring registered buffer table. The server can then operate on these pages directly using io-uring fixed buffer operations (eg read_fixed/write_fixed) and the kernel unregisters these pages when the request completes. Non-page-backed args (eg op out headers) will go through the payload buffer as normal. The server can specify which open files should have their reads/writes go through zero-copy, by setting the FOPEN_IO_URING_ZERO_COPY flag when servicing opens. This requires CAP_SYS_ADMIN and bufpools. This is gated behind CAP_SYS_ADMIN because zero-copy allows the server direct access to the client's underlying pages, rather than operating on an intermediary buffer that the contents of the client's pages were copied into or on page cache folios. The request flow for the zero-copy direct-io write path (client writes data, server reads it) is as follows: ======================================================================= | Kernel | FUSE server | | | "write(fd, buf, 1MB)" | | | | >sys_write() | | >fuse_file_write_iter() | | >fuse_send_one() | | [req->args->in_pages = true] | | [folios hold client write data] | | | | >fuse_uring_copy_to_ring() | | >copy_header_to_ring(IN_OUT) | | [memcpy fuse_in_header] | | >copy_header_to_ring(OP) | | [memcpy write_in header] | | | | >fuse_uring_args_to_ring() | | >setup_fuse_copy_state() | | [skip_folio_copy = true] | | | | >fuse_uring_set_up_zero_copy() | | [folio_get for each client folio] | | [build bio_vec array from folios] | | >io_buffer_register_bvec() | | [register pages at ent->zero_copy_index] | | [ent->zero_copied = true] | | | | >fuse_copy_args() | | [skip_folio_copy => return 0 | | for page arg, skip data copy] | | | | >copy_header_to_ring(RING_ENT) | | [memcpy ent_in_out] | | >io_uring_cmd_done() | | | | | [CQE received] | | | | [issue io_uring READ at | | ent->zero_copy_index] | | [reads directly from | |client's pages (ZERO_COPY)] | | | | [write data to backing | | store] | | [submit COMMIT AND FETCH] | | | >fuse_uring_commit_fetch() | | >fuse_uring_commit() | | >fuse_uring_copy_from_ring() | | >fuse_uring_req_end() | | >io_buffer_unregister(ent->zero_copy_index) | | [unregister pages from index] | | >fuse_zero_copy_release() | | [folio_put for each folio] | | [ent->zero_copied = false] | | >fuse_request_end() | | [wake up client] | The zero-copy read path is analogous. Some requests may have both page-backed args and non-page-backed args. For these requests, the page-backed args are zero-copied while the non-page-backed args are copied to the buffer selected from the buffer pool: zero-copy: pages registered via io_buffer_register_bvec() non-page-backed: copied to payload buffer via fuse_copy_args() For a request whose payload is zero-copied, the registration/unregistration path looks like: register: fuse_uring_set_up_zero_copy() folio_get() for each folio io_buffer_register_bvec(ent->zero_copy_index) unregister: fuse_uring_req_end() io_buffer_unregister(ent->zero_copy_index) -> fuse_zero_copy_release() callback folio_put() for each folio Please note that on abort for in-flight zero-copied requests that have been sent to userspace, the registered bvec slot remains occupied and its folios remain pinned until the io-uring ring is destroyed, at which point io-uring unregisters all buffers and the fuse_zero_copy_release() callback drops the folio references. Unregistering at teardown would require operating on the ring context directly, whose validity is hard to ascertain; this is deemed not worth the complexity for the abort race, since everything is freed when the ring is torn down. The throughput improvement from zero-copy depends on how much of the per-request latency is spent on data copying vs backing I/O. The gain comes from eliminating the payload-buffer memcpy, but accessing the zero-copied pages requires the server to issue the read/write as an IORING_OP_READ/WRITE_FIXED operation. The benefit is largest when the mempcy is a meaningful fraction of per-request latency while backing i/o is still noticable enough that the extra io-uring op's overhead doesn't dominate. Benchmarked with passthrough_hp (--nopassthrough, q_depth=8) on a 2-socket Intel Xeon Gold 6138 (40 cores / 80 threads), using fio (sync engine, bs=1M, O_DIRECT, numjobs=2, 30s run + 10s ramp, 3 runs) where direct-I/O throughput is against a RAM-backed (tmpfs) source (backing I/O is not the bottleneck): baseline registered-buf zero-copy (zc vs base) direct read ~5.1 GB/s ~5.4 GB/s ~8.9 GB/s (+75%) direct write ~3.4 GB/s ~4.8 GB/s ~5.1 GB/s (+50%) Reads end up higher than writes because the backing store reads faster than it writes (the baseline shows the same read>write gap, and the raw device does too). On a device-bound NVMe (~2 GB/s reads) the read gain shrinks to ~10-16% (and no measurable gains for writes), as backing I/O rather than the eliminated copy dominates latency. The benefit overall scales with how much of the per-request latency is the data copy versus backing I/O. Signed-off-by: Joanne Koong Reviewed-by: Bernd Schubert Signed-off-by: Miklos Szeredi --- fs/fuse/args.h | 2 + fs/fuse/dev.c | 24 +++++- fs/fuse/dev_uring.c | 182 ++++++++++++++++++++++++++++++++++++++++++---- fs/fuse/dev_uring_i.h | 6 ++ fs/fuse/file.c | 2 + fs/fuse/fuse_dev_i.h | 2 + include/uapi/linux/fuse.h | 34 +++++++++ 7 files changed, 236 insertions(+), 16 deletions(-) (limited to 'include/uapi/linux') diff --git a/fs/fuse/args.h b/fs/fuse/args.h index ecfe51a192af..5173264a1261 100644 --- a/fs/fuse/args.h +++ b/fs/fuse/args.h @@ -42,6 +42,8 @@ struct fuse_args { bool is_pinned:1; bool invalidate_vmap:1; bool abort_on_kill:1; + /* server requested io-uring zero-copy for this op */ + bool zero_copy:1; struct fuse_in_arg in_args[4]; struct fuse_arg out_args[2]; void (*end)(struct fuse_args *args, int error); diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index d8f97943e973..90dceb7da571 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -1248,11 +1248,25 @@ int fuse_copy_folio(struct fuse_copy_state *cs, struct folio **foliop, if (folio) { size = folio_size(folio); - if (zeroing && count < size) - folio_zero_range(folio, 0, size); + if (zeroing && count < size) { + /* + * When the copy is skipped the folio already holds the + * payload, so only the bytes outside [offset, offset + + * count) may be zeroed. + * + * Otherwise, the whole folio is cleared first so that a + * failed copy leaves zeros rather than stale folio + * contents. + */ + if (cs->skip_folio_copy) + folio_zero_segments(folio, 0, offset, + offset + count, size); + else + folio_zero_range(folio, 0, size); + } } - while (count) { + while (!cs->skip_folio_copy && count) { if (cs->write && cs->pipebufs && folio) { /* * Can't control lifetime of pipe buffers, so always @@ -1345,6 +1359,10 @@ int fuse_copy_args(struct fuse_copy_state *cs, unsigned numargs, for (i = 0; !err && i < numargs; i++) { struct fuse_arg *arg = &args[i]; if (i == numargs - 1 && argpages) + /* + * if cs->skip_folio_copy is set, this just does any + * needed zeroing. No copying is involved. + */ err = fuse_copy_folios(cs, arg->size, zeroing); else err = fuse_copy_one(cs, arg->value, arg->size); diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c index 17806da93039..1547a4f0d9b9 100644 --- a/fs/fuse/dev_uring.c +++ b/fs/fuse/dev_uring.c @@ -22,6 +22,8 @@ MODULE_PARM_DESC(enable_uring, #define FUSE_URING_IOV_HEADERS 0 #define FUSE_URING_IOV_PAYLOAD 1 +#define FUSE_URING_ADD_QUEUE_FLAGS (FUSE_URING_ZERO_COPY) + bool fuse_uring_enabled(void) { return enable_uring; @@ -31,6 +33,11 @@ struct fuse_uring_pdu { struct fuse_ring_ent *ent; }; +struct fuse_zero_copy_bvs { + unsigned int nr_bvs; + struct bio_vec bvs[]; +}; + static const struct fuse_iqueue_ops fuse_io_uring_ops; enum fuse_uring_header_type { @@ -113,8 +120,36 @@ static void fuse_uring_flush_bg(struct fuse_ring_queue *queue) } } +static bool can_zero_copy_req(struct fuse_ring_ent *ent, struct fuse_req *req) +{ + struct fuse_args *args = req->args; + + if (!ent->queue->zero_copy || !args->zero_copy) + return false; + + if (args->opcode != FUSE_READ && args->opcode != FUSE_WRITE) + return false; + + return args->in_pages || args->out_pages; +} + +static void zero_copy_unregister(struct io_uring_cmd *cmd, + struct fuse_ring_ent *ent, + unsigned int issue_flags) +{ + if (ent->zero_copied) { + int err = io_buffer_unregister(cmd, ent->zero_copy_index, + issue_flags); + + if (err) + pr_warn_ratelimited("qid=%d zero-copy unregister failed: %d\n", + ent->queue->qid, err); + ent->zero_copied = false; + } +} + static void fuse_uring_req_end(struct fuse_ring_ent *ent, struct fuse_req *req, - int error) + int error, unsigned int issue_flags) { struct fuse_ring_queue *queue = ent->queue; struct fuse_ring *ring = queue->ring; @@ -134,6 +169,8 @@ static void fuse_uring_req_end(struct fuse_ring_ent *ent, struct fuse_req *req, spin_unlock(&queue->lock); + zero_copy_unregister(ent->cmd, ent, issue_flags); + if (error) req->out.h.error = error; @@ -309,7 +346,7 @@ void fuse_uring_conn_init(struct fuse_chan *fch) } static struct fuse_ring_queue *fuse_uring_create_queue(struct fuse_ring *ring, - int qid, + int qid, bool zero_copy, bool fail_if_exists) { struct fuse_chan *fch = ring->chan; @@ -328,6 +365,7 @@ static struct fuse_ring_queue *fuse_uring_create_queue(struct fuse_ring *ring, queue->qid = qid; queue->ring = ring; spin_lock_init(&queue->lock); + queue->zero_copy = zero_copy; INIT_LIST_HEAD(&queue->ent_avail_queue); INIT_LIST_HEAD(&queue->ent_commit_queue); @@ -713,6 +751,9 @@ static int setup_fuse_copy_state(struct fuse_copy_state *cs, fuse_copy_init(cs, dir == ITER_DEST, iter); + if (ent->zero_copied) + cs->skip_folio_copy = true; + cs->is_uring = true; cs->req = req; @@ -744,6 +785,62 @@ static int fuse_uring_copy_from_ring(struct fuse_req *req, return err; } +static void fuse_zero_copy_release(void *priv) +{ + struct fuse_zero_copy_bvs *zc_bvs = priv; + unsigned int i; + + for (i = 0; i < zc_bvs->nr_bvs; i++) + folio_put(page_folio(zc_bvs->bvs[i].bv_page)); + + kvfree(zc_bvs); +} + +static int fuse_uring_set_up_zero_copy(struct fuse_ring_ent *ent, + struct fuse_req *req, + unsigned int issue_flags) +{ + struct fuse_args_pages *ap; + int err, i, ddir = 0; + struct fuse_zero_copy_bvs *zc_bvs; + struct bio_vec *bvs; + + /* out_pages indicates a read, in_pages indicates a write */ + if (req->args->out_pages) + ddir |= IO_BUF_DEST; + if (req->args->in_pages) + ddir |= IO_BUF_SOURCE; + + ap = container_of(req->args, typeof(*ap), args); + + zc_bvs = kvmalloc_flex(*zc_bvs, bvs, ap->num_folios, + GFP_KERNEL_ACCOUNT); + if (!zc_bvs) + return -ENOMEM; + + zc_bvs->nr_bvs = ap->num_folios; + bvs = zc_bvs->bvs; + for (i = 0; i < ap->num_folios; i++) { + bvs[i].bv_page = folio_page(ap->folios[i], 0); + bvs[i].bv_offset = ap->descs[i].offset; + bvs[i].bv_len = ap->descs[i].length; + folio_get(ap->folios[i]); + } + + err = io_buffer_register_bvec(ent->cmd, bvs, ap->num_folios, + fuse_zero_copy_release, zc_bvs, + ddir, ent->zero_copy_index, + issue_flags); + if (err) { + fuse_zero_copy_release(zc_bvs); + return err; + } + + ent->zero_copied = true; + + return 0; +} + /* * Copy data from the req to the ring buffer */ @@ -762,6 +859,13 @@ static int fuse_uring_args_to_ring(struct fuse_req *req, .commit_id = req->in.h.unique, }; + if (can_zero_copy_req(ent, req)) { + ent_in_out.flags |= FUSE_URING_ENT_ZERO_COPY; + err = fuse_uring_set_up_zero_copy(ent, req, issue_flags); + if (err) + return err; + } + err = setup_fuse_copy_state(&cs, req, ent, ITER_DEST, &iter, issue_flags); if (err) @@ -793,6 +897,18 @@ static int fuse_uring_args_to_ring(struct fuse_req *req, } ent_in_out.payload_sz = cs.ring.copied_sz; + /* + * on a zero-copied write the pages are registered for the server to + * read via a fixed-buffer op rather than copied into the payload + * buffer, so copied_sz does not account for it. The server still needs + * the total inbound size to know how many bytes to read from the + * registered buffer, so add the page arg (always the last in-arg) back + * in + */ + if (cs.skip_folio_copy && args->in_pages) + ent_in_out.payload_sz += + args->in_args[args->in_numargs - 1].size; + if (bufpool_enabled(ent->queue) && ent->payload.iov_base) ent_in_out.offset = (uintptr_t)ent->payload.iov_base - ent->queue->bufpool->base_uaddr; @@ -831,11 +947,25 @@ static int fuse_uring_copy_to_ring(struct fuse_ring_ent *ent, sizeof(req->in.h)); } -static bool fuse_uring_req_has_payload(struct fuse_req *req) +static bool fuse_uring_req_has_copyable_payload(struct fuse_ring_ent *ent, + struct fuse_req *req) { struct fuse_args *args = req->args; - return args->in_numargs > 1 || args->out_numargs; + if (!can_zero_copy_req(ent, req)) + return args->in_numargs > 1 || args->out_numargs; + + /* + * the asymmetry between in_numargs > 2 and out_numargs > 1 is because + * the per-op header is extracted before fuse_copy_args() for inargs but + * not for outargs + */ + if ((args->in_numargs > 1) && (!args->in_pages || args->in_numargs > 2)) + return true; + if (args->out_numargs && (!args->out_pages || args->out_numargs > 1)) + return true; + + return false; } static int fuse_uring_select_buffer(struct fuse_ring_ent *ent) @@ -892,7 +1022,7 @@ static int fuse_uring_next_req_update_buffer(struct fuse_ring_ent *ent, return 0; buffer_selected = !!ent->payload.iov_base; - has_payload = fuse_uring_req_has_payload(req); + has_payload = fuse_uring_req_has_copyable_payload(ent, req); if (has_payload && !buffer_selected) return fuse_uring_select_buffer(ent); @@ -910,7 +1040,7 @@ static int fuse_uring_prep_buffer(struct fuse_ring_ent *ent, return 0; /* no payload to copy, can skip selecting a buffer */ - if (!fuse_uring_req_has_payload(req)) + if (!fuse_uring_req_has_copyable_payload(ent, req)) return 0; return fuse_uring_select_buffer(ent); @@ -936,7 +1066,7 @@ static int fuse_uring_prepare_send(struct fuse_ring_ent *ent, ent->state = FRRS_INVALID; spin_unlock(&ent->queue->lock); - fuse_uring_req_end(ent, req, err); + fuse_uring_req_end(ent, req, err, issue_flags); } return err; @@ -1035,7 +1165,7 @@ static void fuse_uring_commit(struct fuse_ring_ent *ent, struct fuse_req *req, err = fuse_uring_copy_from_ring(req, ent, issue_flags); out: - fuse_uring_req_end(ent, req, err); + fuse_uring_req_end(ent, req, err, issue_flags); } /* @@ -1160,7 +1290,12 @@ static int fuse_uring_commit_fetch(struct io_uring_cmd *cmd, int issue_flags, queue->qid, commit_id, ent->state); fuse_uring_recycle_buffer(ent); spin_unlock(&queue->lock); - fuse_uring_req_end(ent, req, err); + /* + * Unregister any zero copyable pages since ent->cmd is null + * when it hits fuse_uring_req_end() in this path + */ + zero_copy_unregister(cmd, ent, issue_flags); + fuse_uring_req_end(ent, req, err, issue_flags); return err; } @@ -1284,10 +1419,14 @@ static struct fuse_ring_ent * fuse_uring_create_ring_ent(struct io_uring_cmd *cmd, struct fuse_ring_queue *queue) { + const struct fuse_uring_cmd_req *cmd_req = + io_uring_sqe128_cmd(cmd->sqe, struct fuse_uring_cmd_req); struct fuse_ring *ring = queue->ring; struct fuse_ring_ent *ent; struct iovec iov[FUSE_URING_IOV_SEGS]; struct iovec *headers, *payload; + unsigned int zero_copy_index; + int err; err = fuse_uring_get_iovec_from_sqe(cmd->sqe, iov); @@ -1297,6 +1436,10 @@ fuse_uring_create_ring_ent(struct io_uring_cmd *cmd, return ERR_PTR(err); } + zero_copy_index = READ_ONCE(cmd_req->ent_zero_copy_buf_index); + if (zero_copy_index && !queue->zero_copy) + return ERR_PTR(-EINVAL); + err = -EINVAL; headers = &iov[FUSE_URING_IOV_HEADERS]; if (headers->iov_len < sizeof(struct fuse_uring_req_header)) { @@ -1315,9 +1458,14 @@ fuse_uring_create_ring_ent(struct io_uring_cmd *cmd, } } else { if (payload->iov_len < ring->max_payload_sz) { + spin_unlock(&queue->lock); pr_info_ratelimited("Invalid req payload len %zu\n", payload->iov_len); + return ERR_PTR(err); + } + if (queue->zero_copy) { spin_unlock(&queue->lock); + pr_info_ratelimited("Can only use zero copy with bufpools\n"); return ERR_PTR(err); } queue->payload_mode = FUSE_PAYLOAD_PER_ENT; @@ -1335,6 +1483,7 @@ fuse_uring_create_ring_ent(struct io_uring_cmd *cmd, ent->headers = headers->iov_base; if (queue->payload_mode == FUSE_PAYLOAD_PER_ENT) ent->payload = *payload; + ent->zero_copy_index = zero_copy_index; atomic_inc(&ring->queue_refs); return ent; @@ -1364,7 +1513,7 @@ static int fuse_uring_register(struct io_uring_cmd *cmd, queue = READ_ONCE(ring->queues[qid]); if (!queue) { - queue = fuse_uring_create_queue(ring, qid, false); + queue = fuse_uring_create_queue(ring, qid, false, false); if (IS_ERR(queue)) return PTR_ERR(queue); } @@ -1389,8 +1538,9 @@ static int fuse_uring_add_queue(struct io_uring_cmd *cmd, struct fuse_chan *fch) unsigned int qid = READ_ONCE(cmd_req->qid); uint64_t flags = READ_ONCE(cmd_req->flags); struct fuse_ring_queue *queue; + bool zero_copy = flags & FUSE_URING_ZERO_COPY; - if (!ring || flags) + if (!ring) return -EINVAL; if (qid >= ring->nr_queues) { @@ -1398,7 +1548,13 @@ static int fuse_uring_add_queue(struct io_uring_cmd *cmd, struct fuse_chan *fch) return -EINVAL; } - queue = fuse_uring_create_queue(ring, qid, true); + if (flags & ~FUSE_URING_ADD_QUEUE_FLAGS) + return -EINVAL; + + if (zero_copy && !capable(CAP_SYS_ADMIN)) + return -EPERM; + + queue = fuse_uring_create_queue(ring, qid, zero_copy, true); if (IS_ERR(queue)) return PTR_ERR(queue); @@ -1595,7 +1751,7 @@ static void fuse_uring_send_in_task(struct io_tw_req tw_req, io_tw_token_t tw) io_uring_cmd_done(cmd, err, issue_flags); - fuse_uring_req_end(ent, ent->fuse_req, err); + fuse_uring_req_end(ent, ent->fuse_req, err, issue_flags); kfree(ent); if (atomic_dec_and_test(&queue->ring->queue_refs)) wake_up_all(&queue->ring->stop_waitq); diff --git a/fs/fuse/dev_uring_i.h b/fs/fuse/dev_uring_i.h index e142cae43022..263d0f8b9714 100644 --- a/fs/fuse/dev_uring_i.h +++ b/fs/fuse/dev_uring_i.h @@ -79,6 +79,10 @@ struct fuse_ring_ent { /* buffer id in the pool, if bufpools are used. ignored otherwise */ unsigned int buf_id; + /* true if the request's pages are being zero-copied */ + bool zero_copied; + unsigned int zero_copy_index; + /* the ring queue that owns the request */ struct fuse_ring_queue *queue; @@ -142,6 +146,8 @@ struct fuse_ring_queue { /* only allocated when payload_mode == FUSE_PAYLOAD_BUFPOOL */ struct fuse_bufpool *bufpool; + + bool zero_copy; }; /* diff --git a/fs/fuse/file.c b/fs/fuse/file.c index da5859e8159d..7b883cf170ac 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c @@ -605,6 +605,7 @@ void fuse_read_args_fill(struct fuse_io_args *ia, struct file *file, loff_t pos, args->out_argvar = true; args->out_numargs = 1; args->out_args[0].size = count; + args->zero_copy = ff->open_flags & FOPEN_IO_URING_ZERO_COPY; } static void fuse_release_user_pages(struct fuse_args_pages *ap, ssize_t nres, @@ -1151,6 +1152,7 @@ static void fuse_write_args_fill(struct fuse_io_args *ia, struct fuse_file *ff, args->out_numargs = 1; args->out_args[0].size = sizeof(ia->write.out); args->out_args[0].value = &ia->write.out; + args->zero_copy = ff->open_flags & FOPEN_IO_URING_ZERO_COPY; } static unsigned int fuse_write_flags(struct kiocb *iocb) diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h index 668c8391d61c..f41749f484df 100644 --- a/fs/fuse/fuse_dev_i.h +++ b/fs/fuse/fuse_dev_i.h @@ -325,6 +325,8 @@ struct fuse_copy_state { bool write:1; bool move_folios:1; bool is_uring:1; + /* set when the payload is zero-copied. folios are filled in place */ + bool skip_folio_copy:1; struct { unsigned int copied_sz; /* copied size into the user buffer */ } ring; diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h index 538d844da099..7435e09c87fe 100644 --- a/include/uapi/linux/fuse.h +++ b/include/uapi/linux/fuse.h @@ -246,6 +246,8 @@ * - add FUSE_HAS_IO_URING_BUFPOOL * - add fuse_uring_cmd_req bufpool struct * - add bufpool offset field to fuse_uring_ent_in_out struct + * - add FUSE_URING_ZERO_COPY, FUSE_URING_ENT_ZERO_COPY, and + * FOPEN_IO_URING_ZERO_COPY flag */ #ifndef _LINUX_FUSE_H @@ -389,6 +391,12 @@ struct fuse_file_lock { * FOPEN_NOFLUSH: don't flush data cache on close (unless FUSE_WRITEBACK_CACHE) * FOPEN_PARALLEL_DIRECT_WRITES: Allow concurrent direct writes on the same inode * FOPEN_PASSTHROUGH: passthrough read/write io for this open file + * FOPEN_IO_URING_ZERO_COPY: use io-uring zero-copy for reads/writes on this + * open file. Honored only when the serving io-uring + * queue was set up for zero-copy + * (FUSE_URING_ZERO_COPY) and the request carries page + * payload. Otherwise reads/writes fall back to + * copying. */ #define FOPEN_DIRECT_IO (1 << 0) #define FOPEN_KEEP_CACHE (1 << 1) @@ -398,6 +406,7 @@ struct fuse_file_lock { #define FOPEN_NOFLUSH (1 << 5) #define FOPEN_PARALLEL_DIRECT_WRITES (1 << 6) #define FOPEN_PASSTHROUGH (1 << 7) +#define FOPEN_IO_URING_ZERO_COPY (1 << 8) /** * INIT request/reply flags @@ -1259,6 +1268,13 @@ struct fuse_supp_groups { #define FUSE_URING_IN_OUT_HEADER_SZ 128 #define FUSE_URING_OP_IN_OUT_SZ 128 +/** + * fuse_uring_ent_in_out flags + * + * FUSE_URING_ENT_ZERO_COPY: Set if the ent's payload is zero-copied + */ +#define FUSE_URING_ENT_ZERO_COPY (1 << 0) + /* Used as part of the fuse_uring_req_header */ struct fuse_uring_ent_in_out { uint64_t flags; @@ -1310,6 +1326,14 @@ enum fuse_uring_cmd { FUSE_IO_URING_CMD_ADD_BUFPOOL = 4, }; +/* + * fuse_uring_cmd_req flags for FUSE_IO_URING_CMD_ADD_QUEUE + * + * FUSE_URING_ZERO_COPY is only supported for queues with bufpools on privileged + * servers + */ +#define FUSE_URING_ZERO_COPY (1 << 0) + /** * In the 80B command area of the SQE. */ @@ -1330,6 +1354,16 @@ struct fuse_uring_cmd_req { uint32_t len; uint32_t reserved; } bufpool; + + /* + * Index of this entry's slot in the server's io_uring + * registered buffer table, where the kernel registers the + * request's pages for zero-copy. Set for + * FUSE_IO_URING_CMD_REGISTER cmds only, and only on queues + * created with FUSE_URING_ZERO_COPY. On a non-zero-copy queue + * this must be 0 + */ + uint16_t ent_zero_copy_buf_index; }; }; -- cgit v1.2.3 From 2187be212179243f33da1271248571aca3e99a3f Mon Sep 17 00:00:00 2001 From: Albert Esteve Date: Tue, 10 Mar 2026 09:40:46 +0100 Subject: virtio: Add ID for virtio media Add VIRTIO_ID_MEDIA definition for virtio-media. Signed-off-by: Albert Esteve Message-ID: <20260310-virtio-media-id-v1-1-be211bcf682b@redhat.com> Signed-off-by: Michael S. Tsirkin --- include/uapi/linux/virtio_ids.h | 1 + 1 file changed, 1 insertion(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/virtio_ids.h b/include/uapi/linux/virtio_ids.h index 6c12db16faa3..f9056af0c622 100644 --- a/include/uapi/linux/virtio_ids.h +++ b/include/uapi/linux/virtio_ids.h @@ -69,6 +69,7 @@ #define VIRTIO_ID_BT 40 /* virtio bluetooth */ #define VIRTIO_ID_GPIO 41 /* virtio gpio */ #define VIRTIO_ID_SPI 45 /* virtio spi */ +#define VIRTIO_ID_MEDIA 48 /* virtio media */ /* * Virtio Transitional IDs -- cgit v1.2.3 From a596238c2ab6944861404dc597133ffd4ad062d5 Mon Sep 17 00:00:00 2001 From: Eugenio Pérez Date: Tue, 7 Jul 2026 14:25:00 +0200 Subject: vduse: add VDUSE_GET_FEATURES ioctl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add an ioctl to allow VDUSE instances to query the available features supported by the kernel module. Signed-off-by: Eugenio Pérez Signed-off-by: Michael S. Tsirkin Message-ID: <20260707122502.239022-3-eperezma@redhat.com> --- drivers/vdpa/vdpa_user/vduse_dev.c | 7 +++++++ include/uapi/linux/vduse.h | 3 +++ 2 files changed, 10 insertions(+) (limited to 'include/uapi/linux') diff --git a/drivers/vdpa/vdpa_user/vduse_dev.c b/drivers/vdpa/vdpa_user/vduse_dev.c index 861a8093daa0..f3f24cc59eb0 100644 --- a/drivers/vdpa/vdpa_user/vduse_dev.c +++ b/drivers/vdpa/vdpa_user/vduse_dev.c @@ -51,6 +51,9 @@ #define IRQ_UNBOUND -1 +/* Supported VDUSE features */ +static const uint64_t vduse_features; + /* * VDUSE instance have not asked the vduse API version, so assume 0. * @@ -2342,6 +2345,10 @@ static long vduse_ioctl(struct file *file, unsigned int cmd, ret = vduse_destroy_dev(name); break; } + case VDUSE_GET_FEATURES: + ret = put_user(vduse_features, (u64 __user *)argp); + break; + default: ret = -EINVAL; break; diff --git a/include/uapi/linux/vduse.h b/include/uapi/linux/vduse.h index 361eea511c21..89aa3b448c0a 100644 --- a/include/uapi/linux/vduse.h +++ b/include/uapi/linux/vduse.h @@ -63,6 +63,9 @@ struct vduse_dev_config { */ #define VDUSE_DESTROY_DEV _IOW(VDUSE_BASE, 0x03, char[VDUSE_NAME_MAX]) +/* Get the VDUSE supported features */ +#define VDUSE_GET_FEATURES _IOR(VDUSE_BASE, 0x04, __u64) + /* The ioctls for VDUSE device (/dev/vduse/$NAME) */ /** -- cgit v1.2.3 From 3b441820cb61ac1137dd4b336adfb4892cda4233 Mon Sep 17 00:00:00 2001 From: Eugenio Pérez Date: Tue, 7 Jul 2026 14:25:01 +0200 Subject: vduse: add VDUSE_SET_FEATURES ioctl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add an ioctl to allow VDUSE instances to set the VDUSE features supported by the userland VDUSE instance. Signed-off-by: Eugenio Pérez Signed-off-by: Michael S. Tsirkin Message-ID: <20260707122502.239022-4-eperezma@redhat.com> --- drivers/vdpa/vdpa_user/vduse_dev.c | 24 ++++++++++++++++++++++++ include/uapi/linux/vduse.h | 3 +++ 2 files changed, 27 insertions(+) (limited to 'include/uapi/linux') diff --git a/drivers/vdpa/vdpa_user/vduse_dev.c b/drivers/vdpa/vdpa_user/vduse_dev.c index f3f24cc59eb0..2292cabe4270 100644 --- a/drivers/vdpa/vdpa_user/vduse_dev.c +++ b/drivers/vdpa/vdpa_user/vduse_dev.c @@ -159,6 +159,7 @@ struct vduse_dev_msg { struct vduse_control { u64 api_version; + u64 vduse_features; }; static DEFINE_MUTEX(vduse_lock); @@ -2348,7 +2349,29 @@ static long vduse_ioctl(struct file *file, unsigned int cmd, case VDUSE_GET_FEATURES: ret = put_user(vduse_features, (u64 __user *)argp); break; + case VDUSE_SET_FEATURES: { + u64 features; + ret = -EFAULT; + if (get_user(features, (u64 __user *)argp)) { + dev_dbg(vduse_ctrl_dev, "Could not get vduse features"); + break; + } + + ret = -EINVAL; + if (features & ~vduse_features) { + dev_dbg(vduse_ctrl_dev, + "Invalid features in %llx, expected %llx", + features, vduse_features); + break; + } + + ret = 0; + control->vduse_features = features; + dev_dbg(vduse_ctrl_dev, "Set features %llx", features); + + break; + } default: ret = -EINVAL; break; @@ -2375,6 +2398,7 @@ static int vduse_open(struct inode *inode, struct file *file) return -ENOMEM; control->api_version = VDUSE_API_VERSION_NOT_ASKED; + control->vduse_features = 0; file->private_data = control; return 0; diff --git a/include/uapi/linux/vduse.h b/include/uapi/linux/vduse.h index 89aa3b448c0a..f14c965bb7f6 100644 --- a/include/uapi/linux/vduse.h +++ b/include/uapi/linux/vduse.h @@ -66,6 +66,9 @@ struct vduse_dev_config { /* Get the VDUSE supported features */ #define VDUSE_GET_FEATURES _IOR(VDUSE_BASE, 0x04, __u64) +/* Set the VDUSE features */ +#define VDUSE_SET_FEATURES _IOW(VDUSE_BASE, 0x05, __u64) + /* The ioctls for VDUSE device (/dev/vduse/$NAME) */ /** -- cgit v1.2.3 From 4c318d91cc60a0c2c94bcb6db2630baed43e9387 Mon Sep 17 00:00:00 2001 From: Eugenio Pérez Date: Tue, 7 Jul 2026 14:25:02 +0200 Subject: vduse: add F_QUEUE_READY feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the VDUSE_F_QUEUE_READY feature flag. This allows the kernel module to explicitly signal userspace when a specific virtqueue has been enabled. In scenarios like Live Migration of VirtIO net devices, the dataplane starts after the control virtqueue allowing QEMU to apply configuration in the destination device. Signed-off-by: Eugenio Pérez Signed-off-by: Michael S. Tsirkin Message-ID: <20260707122502.239022-5-eperezma@redhat.com> --- drivers/vdpa/vdpa_user/vduse_dev.c | 73 +++++++++++++++++++++++++++++--------- include/uapi/linux/vduse.h | 18 ++++++++++ 2 files changed, 74 insertions(+), 17 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/vdpa/vdpa_user/vduse_dev.c b/drivers/vdpa/vdpa_user/vduse_dev.c index 2292cabe4270..87d6748b50cc 100644 --- a/drivers/vdpa/vdpa_user/vduse_dev.c +++ b/drivers/vdpa/vdpa_user/vduse_dev.c @@ -9,6 +9,7 @@ */ #include "linux/virtio_net.h" +#include #include #include #include @@ -52,7 +53,7 @@ #define IRQ_UNBOUND -1 /* Supported VDUSE features */ -static const uint64_t vduse_features; +static const uint64_t vduse_features = BIT_U64(VDUSE_F_QUEUE_READY); /* * VDUSE instance have not asked the vduse API version, so assume 0. @@ -76,6 +77,7 @@ struct vduse_virtqueue { u32 group; spinlock_t kick_lock; spinlock_t irq_lock; + spinlock_t ready_lock; struct eventfd_ctx *kickfd; struct vdpa_callback cb; struct work_struct inject; @@ -119,6 +121,7 @@ struct vduse_dev { char *name; struct mutex lock; spinlock_t msg_lock; + u64 vduse_features; u64 msg_unique; u32 msg_timeout; wait_queue_head_t waitq; @@ -513,7 +516,9 @@ static void vduse_dev_reset(struct vduse_dev *dev) for (i = 0; i < dev->vq_num; i++) { struct vduse_virtqueue *vq = dev->vqs[i]; - vq->ready = false; + scoped_guard(spinlock_bh, &vq->ready_lock) { + vq->ready = false; + } vq->desc_addr = 0; vq->driver_addr = 0; vq->device_addr = 0; @@ -555,16 +560,15 @@ static int vduse_vdpa_set_vq_address(struct vdpa_device *vdpa, u16 idx, static void vduse_vq_kick(struct vduse_virtqueue *vq) { - spin_lock(&vq->kick_lock); - if (!vq->ready) - goto unlock; + guard(spinlock)(&vq->kick_lock); + scoped_guard(spinlock_bh, &vq->ready_lock) + if (!vq->ready) + return; if (vq->kickfd) eventfd_signal(vq->kickfd); else vq->kicked = true; -unlock: - spin_unlock(&vq->kick_lock); } static void vduse_vq_kick_work(struct work_struct *work) @@ -624,7 +628,30 @@ static void vduse_vdpa_set_vq_ready(struct vdpa_device *vdpa, { struct vduse_dev *dev = vdpa_to_vduse(vdpa); struct vduse_virtqueue *vq = dev->vqs[idx]; + struct vduse_dev_msg msg = { 0 }; + int r; + + if (dev->vduse_features & BIT_U64(VDUSE_F_QUEUE_READY)) { + msg.req.type = VDUSE_SET_VQ_READY; + msg.req.vq_ready.num = idx; + msg.req.vq_ready.ready = !!ready; + + r = vduse_dev_msg_sync(dev, &msg); + + if (r < 0) { + dev_dbg(&vdpa->dev, "device refuses to set vq %u ready %u", + idx, ready); + /* We can't do better than break the device in this case */ + spin_lock(&dev->msg_lock); + vduse_dev_broken(dev); + spin_unlock(&dev->msg_lock); + + return; + } + } + + guard(spinlock_bh)(&vq->ready_lock); vq->ready = ready; } @@ -633,6 +660,7 @@ static bool vduse_vdpa_get_vq_ready(struct vdpa_device *vdpa, u16 idx) struct vduse_dev *dev = vdpa_to_vduse(vdpa); struct vduse_virtqueue *vq = dev->vqs[idx]; + guard(spinlock_bh)(&vq->ready_lock); return vq->ready; } @@ -1120,15 +1148,16 @@ static int vduse_kickfd_setup(struct vduse_dev *dev, } else if (eventfd->fd != VDUSE_EVENTFD_DEASSIGN) return 0; - spin_lock(&vq->kick_lock); + guard(spinlock)(&vq->kick_lock); if (vq->kickfd) eventfd_ctx_put(vq->kickfd); vq->kickfd = ctx; + + guard(spinlock_bh)(&vq->ready_lock); if (vq->ready && vq->kicked && vq->kickfd) { eventfd_signal(vq->kickfd); vq->kicked = false; } - spin_unlock(&vq->kick_lock); return 0; } @@ -1159,10 +1188,10 @@ static void vduse_vq_irq_inject(struct work_struct *work) struct vduse_virtqueue *vq = container_of(work, struct vduse_virtqueue, inject); - spin_lock_bh(&vq->irq_lock); + guard(spinlock_bh)(&vq->irq_lock); + guard(spinlock_bh)(&vq->ready_lock); if (vq->ready && vq->cb.callback) vq->cb.callback(vq->cb.private); - spin_unlock_bh(&vq->irq_lock); } static bool vduse_vq_signal_irqfd(struct vduse_virtqueue *vq) @@ -1172,12 +1201,12 @@ static bool vduse_vq_signal_irqfd(struct vduse_virtqueue *vq) if (!vq->cb.trigger) return false; - spin_lock_irq(&vq->irq_lock); + guard(spinlock_irq)(&vq->irq_lock); + guard(spinlock_irq)(&vq->ready_lock); if (vq->ready && vq->cb.trigger) { eventfd_signal(vq->cb.trigger); signal = true; } - spin_unlock_irq(&vq->irq_lock); return signal; } @@ -1515,7 +1544,9 @@ static long vduse_dev_ioctl(struct file *file, unsigned int cmd, vq_info.split.avail_index = vq->state.split.avail_index; - vq_info.ready = vq->ready; + scoped_guard(spinlock_bh, &vq->ready_lock) { + vq_info.ready = vq->ready; + } ret = -EFAULT; if (copy_to_user(argp, &vq_info, sizeof(vq_info))) @@ -1745,7 +1776,9 @@ static long vduse_dev_compat_ioctl(struct file *file, unsigned int cmd, vq_info.split.avail_index = vq->state.split.avail_index; - vq_info.ready = vq->ready; + scoped_guard(spinlock_bh, &vq->ready_lock) { + vq_info.ready = vq->ready; + } ret = -EFAULT; if (copy_to_user(argp, &vq_info, @@ -1958,6 +1991,7 @@ static int vduse_dev_init_vqs(struct vduse_dev *dev, u32 vq_align, u32 vq_num) INIT_WORK(&dev->vqs[i]->kick, vduse_vq_kick_work); spin_lock_init(&dev->vqs[i]->kick_lock); spin_lock_init(&dev->vqs[i]->irq_lock); + spin_lock_init(&dev->vqs[i]->ready_lock); cpumask_setall(&dev->vqs[i]->irq_affinity); kobject_init(&dev->vqs[i]->kobj, &vq_type); @@ -2193,7 +2227,8 @@ static struct attribute *vduse_dev_attrs[] = { ATTRIBUTE_GROUPS(vduse_dev); static int vduse_create_dev(struct vduse_dev_config *config, - void *config_buf, u64 api_version) + void *config_buf, u64 api_version, + uint64_t vduse_features) { int ret; struct vduse_dev *dev; @@ -2215,6 +2250,9 @@ static int vduse_create_dev(struct vduse_dev_config *config, dev->device_features = config->features; dev->device_id = config->device_id; dev->vendor_id = config->vendor_id; + dev->vduse_features = vduse_features; + dev_dbg(vduse_ctrl_dev, "Creating device %s with features 0x%llx", + config->name, vduse_features); dev->nas = (dev->api_version < VDUSE_API_VERSION_1) ? 1 : config->nas; dev->as = kzalloc_objs(dev->as[0], dev->nas); @@ -2330,7 +2368,8 @@ static long vduse_ioctl(struct file *file, unsigned int cmd, break; } config.name[VDUSE_NAME_MAX - 1] = '\0'; - ret = vduse_create_dev(&config, buf, control->api_version); + ret = vduse_create_dev(&config, buf, control->api_version, + control->vduse_features); if (ret) kvfree(buf); break; diff --git a/include/uapi/linux/vduse.h b/include/uapi/linux/vduse.h index f14c965bb7f6..7285f8570237 100644 --- a/include/uapi/linux/vduse.h +++ b/include/uapi/linux/vduse.h @@ -14,6 +14,9 @@ #define VDUSE_API_VERSION_1 1 +/* The VDUSE instance expects a request for vq ready */ +#define VDUSE_F_QUEUE_READY 0 + /* * Get the version of VDUSE API that kernel supported (VDUSE_API_VERSION). * This is used for future extension. @@ -331,6 +334,7 @@ enum vduse_req_type { VDUSE_SET_STATUS, VDUSE_UPDATE_IOTLB, VDUSE_SET_VQ_GROUP_ASID, + VDUSE_SET_VQ_READY, }; /** @@ -378,6 +382,15 @@ struct vduse_iova_range_v2 { __u32 padding; }; +/** + * struct vduse_vq_ready - Virtqueue ready request message + * @num: Virtqueue number + */ +struct vduse_vq_ready { + __u32 num; + __u32 ready; +}; + /** * struct vduse_dev_request - control request * @type: request type @@ -388,6 +401,7 @@ struct vduse_iova_range_v2 { * @iova: IOVA range for updating * @iova_v2: IOVA range for updating if API_VERSION >= 1 * @vq_group_asid: ASID of a virtqueue group + * @vq_ready: Virtqueue ready request * @padding: padding * * Structure used by read(2) on /dev/vduse/$NAME. @@ -405,6 +419,10 @@ struct vduse_dev_request { */ struct vduse_iova_range_v2 iova_v2; struct vduse_vq_group_asid vq_group_asid; + + /* Only if VDUSE_F_QUEUE_READY is negotiated */ + struct vduse_vq_ready vq_ready; + __u32 padding[32]; }; }; -- cgit v1.2.3 From b282418bc366194677eafd1dad180d92254586ac Mon Sep 17 00:00:00 2001 From: Eugenio Pérez Date: Tue, 7 Jul 2026 14:33:44 +0200 Subject: vduse: Add suspend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement suspend operation for vduse devices, so vhost-vdpa will offer that backend feature and userspace can effectively suspend the device. This is a must before get virtqueue indexes (base) for live migration, since the device could modify them after userland gets them. This patch does not implement resume, so VMM resets the whole device to recover from a live migration failure. Resume optimization can be implemented on top of these patches, as other vDPA devices have done in the past. Signed-off-by: Eugenio Pérez Signed-off-by: Michael S. Tsirkin Message-ID: <20260707123344.244575-3-eperezma@redhat.com> --- drivers/vdpa/vdpa_user/vduse_dev.c | 95 +++++++++++++++++++++++++++++++++++--- include/uapi/linux/vduse.h | 4 ++ 2 files changed, 92 insertions(+), 7 deletions(-) (limited to 'include/uapi/linux') diff --git a/drivers/vdpa/vdpa_user/vduse_dev.c b/drivers/vdpa/vdpa_user/vduse_dev.c index 9aff26fbb583..9891cd2cf712 100644 --- a/drivers/vdpa/vdpa_user/vduse_dev.c +++ b/drivers/vdpa/vdpa_user/vduse_dev.c @@ -53,7 +53,8 @@ #define IRQ_UNBOUND -1 /* Supported VDUSE features */ -static const uint64_t vduse_features = BIT_U64(VDUSE_F_QUEUE_READY); +static const uint64_t vduse_features = BIT_U64(VDUSE_F_QUEUE_READY) | + BIT_U64(VDUSE_F_SUSPEND); /* * VDUSE instance have not asked the vduse API version, so assume 0. @@ -85,6 +86,7 @@ struct vduse_virtqueue { int irq_effective_cpu; struct cpumask irq_affinity; struct kobject kobj; + struct vduse_dev *dev; }; struct vduse_dev; @@ -134,6 +136,7 @@ struct vduse_dev { int minor; bool broken; bool connected; + bool suspended; u64 api_version; u64 device_features; u64 driver_features; @@ -503,6 +506,7 @@ static void vduse_dev_reset(struct vduse_dev *dev) } scoped_guard(rwsem_write, &dev->rwsem) { + dev->suspended = false; dev->status = 0; dev->driver_features = 0; dev->generation++; @@ -563,6 +567,10 @@ static int vduse_vdpa_set_vq_address(struct vdpa_device *vdpa, u16 idx, static void vduse_vq_kick(struct vduse_virtqueue *vq) { + guard(rwsem_read)(&vq->dev->rwsem); + if (vq->dev->suspended) + return; + guard(spinlock)(&vq->kick_lock); scoped_guard(spinlock_bh, &vq->ready_lock) if (!vq->ready) @@ -927,6 +935,27 @@ static int vduse_vdpa_set_map(struct vdpa_device *vdpa, return 0; } +static int vduse_vdpa_suspend(struct vdpa_device *vdpa) +{ + struct vduse_dev *dev = vdpa_to_vduse(vdpa); + struct vduse_dev_msg msg = { 0 }; + int ret; + + msg.req.type = VDUSE_SUSPEND; + + ret = vduse_dev_msg_sync(dev, &msg); + if (ret == 0) { + scoped_guard(rwsem_write, &dev->rwsem) + dev->suspended = true; + + cancel_work_sync(&dev->inject); + for (u32 i = 0; i < dev->vq_num; i++) + cancel_work_sync(&dev->vqs[i]->inject); + } + + return ret; +} + static void vduse_vdpa_free(struct vdpa_device *vdpa) { struct vduse_dev *dev = vdpa_to_vduse(vdpa); @@ -968,6 +997,41 @@ static const struct vdpa_config_ops vduse_vdpa_config_ops = { .free = vduse_vdpa_free, }; +static const struct vdpa_config_ops vduse_vdpa_config_ops_with_suspend = { + .set_vq_address = vduse_vdpa_set_vq_address, + .kick_vq = vduse_vdpa_kick_vq, + .set_vq_cb = vduse_vdpa_set_vq_cb, + .set_vq_num = vduse_vdpa_set_vq_num, + .get_vq_size = vduse_vdpa_get_vq_size, + .get_vq_group = vduse_get_vq_group, + .set_vq_ready = vduse_vdpa_set_vq_ready, + .get_vq_ready = vduse_vdpa_get_vq_ready, + .set_vq_state = vduse_vdpa_set_vq_state, + .get_vq_state = vduse_vdpa_get_vq_state, + .get_vq_align = vduse_vdpa_get_vq_align, + .get_device_features = vduse_vdpa_get_device_features, + .set_driver_features = vduse_vdpa_set_driver_features, + .get_driver_features = vduse_vdpa_get_driver_features, + .set_config_cb = vduse_vdpa_set_config_cb, + .get_vq_num_max = vduse_vdpa_get_vq_num_max, + .get_device_id = vduse_vdpa_get_device_id, + .get_vendor_id = vduse_vdpa_get_vendor_id, + .get_status = vduse_vdpa_get_status, + .set_status = vduse_vdpa_set_status, + .get_config_size = vduse_vdpa_get_config_size, + .get_config = vduse_vdpa_get_config, + .set_config = vduse_vdpa_set_config, + .get_generation = vduse_vdpa_get_generation, + .set_vq_affinity = vduse_vdpa_set_vq_affinity, + .get_vq_affinity = vduse_vdpa_get_vq_affinity, + .reset = vduse_vdpa_reset, + .set_map = vduse_vdpa_set_map, + .set_group_asid = vduse_set_group_asid, + .get_vq_map = vduse_get_vq_map, + .suspend = vduse_vdpa_suspend, + .free = vduse_vdpa_free, +}; + static void vduse_dev_sync_single_for_device(union virtio_map token, dma_addr_t dma_addr, size_t size, enum dma_data_direction dir) @@ -1180,6 +1244,10 @@ static void vduse_dev_irq_inject(struct work_struct *work) { struct vduse_dev *dev = container_of(work, struct vduse_dev, inject); + guard(rwsem_read)(&dev->rwsem); + if (dev->suspended) + return; + spin_lock_bh(&dev->irq_lock); if (dev->config_cb.callback) dev->config_cb.callback(dev->config_cb.private); @@ -1191,6 +1259,10 @@ static void vduse_vq_irq_inject(struct work_struct *work) struct vduse_virtqueue *vq = container_of(work, struct vduse_virtqueue, inject); + guard(rwsem_read)(&vq->dev->rwsem); + if (vq->dev->suspended) + return; + guard(spinlock_bh)(&vq->irq_lock); guard(spinlock_bh)(&vq->ready_lock); if (vq->ready && vq->cb.callback) @@ -1201,6 +1273,10 @@ static bool vduse_vq_signal_irqfd(struct vduse_virtqueue *vq) { bool signal = false; + guard(rwsem_read)(&vq->dev->rwsem); + if (vq->dev->suspended) + return false; + if (!vq->cb.trigger) return false; @@ -1220,9 +1296,9 @@ static int vduse_dev_queue_irq_work(struct vduse_dev *dev, { int ret = -EINVAL; - down_read(&dev->rwsem); - if (!(dev->status & VIRTIO_CONFIG_S_DRIVER_OK)) - goto unlock; + guard(rwsem_read)(&dev->rwsem); + if (dev->suspended || !(dev->status & VIRTIO_CONFIG_S_DRIVER_OK)) + return ret; ret = 0; if (irq_effective_cpu == IRQ_UNBOUND) @@ -1230,8 +1306,6 @@ static int vduse_dev_queue_irq_work(struct vduse_dev *dev, else queue_work_on(irq_effective_cpu, vduse_irq_bound_wq, irq_work); -unlock: - up_read(&dev->rwsem); return ret; } @@ -1989,6 +2063,7 @@ static int vduse_dev_init_vqs(struct vduse_dev *dev, u32 vq_align, u32 vq_num) } dev->vqs[i]->index = i; + dev->vqs[i]->dev = dev; dev->vqs[i]->irq_effective_cpu = IRQ_UNBOUND; INIT_WORK(&dev->vqs[i]->inject, vduse_vq_irq_inject); INIT_WORK(&dev->vqs[i]->kick, vduse_vq_kick_work); @@ -2465,12 +2540,18 @@ static struct vduse_mgmt_dev *vduse_mgmt; static int vduse_dev_init_vdpa(struct vduse_dev *dev, const char *name) { struct vduse_vdpa *vdev; + const struct vdpa_config_ops *ops; if (dev->vdev) return -EEXIST; + if (dev->vduse_features & BIT_U64(VDUSE_F_SUSPEND)) + ops = &vduse_vdpa_config_ops_with_suspend; + else + ops = &vduse_vdpa_config_ops; + vdev = vdpa_alloc_device(struct vduse_vdpa, vdpa, dev->dev, - &vduse_vdpa_config_ops, &vduse_map_ops, + ops, &vduse_map_ops, dev->ngroups, dev->nas, name, true); if (IS_ERR(vdev)) return PTR_ERR(vdev); diff --git a/include/uapi/linux/vduse.h b/include/uapi/linux/vduse.h index 7285f8570237..b7f8c04a0a44 100644 --- a/include/uapi/linux/vduse.h +++ b/include/uapi/linux/vduse.h @@ -17,6 +17,9 @@ /* The VDUSE instance expects a request for vq ready */ #define VDUSE_F_QUEUE_READY 0 +/* The VDUSE instance expects a request for suspend */ +#define VDUSE_F_SUSPEND 1 + /* * Get the version of VDUSE API that kernel supported (VDUSE_API_VERSION). * This is used for future extension. @@ -335,6 +338,7 @@ enum vduse_req_type { VDUSE_UPDATE_IOTLB, VDUSE_SET_VQ_GROUP_ASID, VDUSE_SET_VQ_READY, + VDUSE_SUSPEND, }; /** -- cgit v1.2.3 From bd7d30bd1d3856d8354b5acef947d68ad1faf886 Mon Sep 17 00:00:00 2001 From: Stanislav Fomichev Date: Wed, 19 Aug 2026 09:05:33 -0700 Subject: xsk: align TX metadata layout across ABIs Add explicit padding before launch_time so xsk_tx_metadata has the same layout on 32-bit and 64-bit systems. On several architectures (csky, i386, nios2, m65k, openrisc, sh), the old native 32-bit layout put launch_time at offset 12 and had a natural size of 20 bytes. Using sizeof(struct xsk_tx_metadata) as tx_metadata_len was already rejected because the length must be a multiple of eight, so the straightforward use of the interface was broken on those ABIs. Userspace could still register a padded length of 24 bytes, though; mixing the old and new layouts then silently reads launch_time from the wrong offset and misprograms packet launch times. This intentionally replaces that incompatible layout because the affected architectures are unlikely to have any notable users. (x86_64 and arm64 have the most users and are _not_ affected) Fixes: ca4419f15abd ("xsk: Add launch time hardware offload support to XDP Tx metadata") Reviewed-by: Simon Horman Signed-off-by: Stanislav Fomichev Link: https://patch.msgid.link/20260819160535.1472459-2-sdf@fomichev.me Signed-off-by: Jakub Kicinski --- include/uapi/linux/if_xdp.h | 1 + tools/include/uapi/linux/if_xdp.h | 1 + 2 files changed, 2 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/if_xdp.h b/include/uapi/linux/if_xdp.h index 23a062781468..50d67df78911 100644 --- a/include/uapi/linux/if_xdp.h +++ b/include/uapi/linux/if_xdp.h @@ -149,6 +149,7 @@ struct xsk_tx_metadata { __u16 csum_start; /* Offset from csum_start where checksum should be stored. */ __u16 csum_offset; + __u32 reserved; /* XDP_TXMD_FLAGS_LAUNCH_TIME */ /* Launch time in nanosecond against the PTP HW Clock */ diff --git a/tools/include/uapi/linux/if_xdp.h b/tools/include/uapi/linux/if_xdp.h index 23a062781468..50d67df78911 100644 --- a/tools/include/uapi/linux/if_xdp.h +++ b/tools/include/uapi/linux/if_xdp.h @@ -149,6 +149,7 @@ struct xsk_tx_metadata { __u16 csum_start; /* Offset from csum_start where checksum should be stored. */ __u16 csum_offset; + __u32 reserved; /* XDP_TXMD_FLAGS_LAUNCH_TIME */ /* Launch time in nanosecond against the PTP HW Clock */ -- cgit v1.2.3 From 1d581ab2348cdbb6d4d0a467382926b68e374ec9 Mon Sep 17 00:00:00 2001 From: Suren Baghdasaryan Date: Wed, 8 Jul 2026 18:01:23 +0000 Subject: alloc_tag: add ioctl to /proc/allocinfo Patch series "alloc_tag: introduce IOCTL-based filtering for MAP", v8. Currently, memory allocation profiling data is primarily exposed through /proc/allocinfo. While useful for manual inspection, this text-based interface poses challenges for production monitoring and large-scale analysis: 1. Userspace must parse large amounts of text to extract specific fields. 2. To find specific tags, userspace must read the entire dataset, requiring many context switches and high data copying. 3. The kernel currently aggregates per-CPU counters for every allocation size, even those the user intends to filter out immediately. This series introduces a new IOCTL-based binary interface for allocinfo that supports kernel-side filtering. By allowing the user to specify a filter mask, we significantly reduce the work performed in-kernel and the amount of data transferred to userspace. The IOCTL mechanism was chosen for allocinfo to address the per-CPU counter aggregation bottleneck. A traditional read() operation must report the total allocation count and sizes for every code tag in the system. Doing so requires iterating across all CPUs to sum their per-CPU counters for thousands of tags, which introduces substantial runtime overhead. The IOCTL interface allows userspace to push selective filtering criteria directly into the kernel before the per-CPU counter aggregation. The kernel aggregates per-CPU counters only for a small subset of tags that match the filter. This results in significant performance improvement. Beyond fast filtered retrieval, the IOCTL foundation allows introducing a context capture mechanism in the future to capture the context for specific allocations. Performance measurements were conducted on an Intel Xeon Platinum 8481C (224 CPUs) with caches dropped before each run. The IOCTL mechanism shows a ~20x performance improvement for filtered queries. The kernel avoids the expensive per-CPU counter aggregation (alloc_tag_read) for any tags that fail the initial string or location filters. Scenario 1: Specific File Filtering (arch/x86/events/rapl.c) 1. Traditional (cat /proc/allocinfo | grep): 22ms (sys) 2. IOCTL Interface: 1ms (sys) Scenario 2: Compound Filtering (Filename + Size) 1. Traditional: (cat ... | grep | awk): 21ms (sys) 2. IOCTL Interface: 1ms (sys) Scenario 3: Size-Based Filtering (min_size = 1MB) 1. Traditional: (cat ... | awk): 21ms (sys) 2. IOCTL Interface: 14ms (sys) This patch (of 6): Add the following ioctl commands for /proc/allocinfo file: ALLOCINFO_IOC_CONTENT_ID - gets content identifier which can be used to check whether the file content has changed specifically due to module load/unload. Every time a module is loaded / unloaded, the returned value will be different. By comparing the identifier value at the beginning and at the end of the content retrieval operation, users can validate retrieved information for consistency. ALLOCINFO_IOC_GET_AT - gets the record at the specified position. This is the position of a record in /proc/allocinfo. ALLOCINFO_IOC_GET_NEXT - gets the record next to the last retrieved one. If no records were previously retrieved, returns the first record. Note, function file and module names often have the same prefixes, therefore when filtering for them, we compare the last 64 characters to minimize the chances of name collisions. [akpm@linux-foundation.org: include compat.h, per Suren] Closes: https://lore.kernel.org/oe-kbuild-all/202607091820.qbjlGhKK-lkp@intel.com/ Link: https://lore.kernel.org/cover.1783532853.git.abhishekbapat@google.com Link: https://lore.kernel.org/15596de2607ef13e7c77c6d74763f4ae992ec475.1783532853.git.abhishekbapat@google.com Signed-off-by: Suren Baghdasaryan Signed-off-by: Abhishek Bapat Acked-by: Hao Ge Cc: Jonathan Corbet Cc: Kent Overstreet Cc: Sourav Panda Signed-off-by: Andrew Morton --- Documentation/mm/allocation-profiling.rst | 5 + Documentation/userspace-api/ioctl/ioctl-number.rst | 2 + MAINTAINERS | 1 + include/linux/codetag.h | 2 + include/uapi/linux/alloc_tag.h | 65 ++++++ lib/codetag.c | 18 ++ mm/alloc_tag.c | 239 ++++++++++++++++++++- 7 files changed, 330 insertions(+), 2 deletions(-) create mode 100644 include/uapi/linux/alloc_tag.h (limited to 'include/uapi/linux') diff --git a/Documentation/mm/allocation-profiling.rst b/Documentation/mm/allocation-profiling.rst index e928aa3e4e1e..b2ebcef8af6f 100644 --- a/Documentation/mm/allocation-profiling.rst +++ b/Documentation/mm/allocation-profiling.rst @@ -57,6 +57,11 @@ sysctl: Runtime info: /proc/allocinfo + Profiling data can be retrieved either by reading `/proc/allocinfo` directly as + text or programmatically via `ioctl()` calls defined in ``. + The ioctl interface supports structured binary data extraction as well as filtering + by module name, function, file, line number, accuracy, or allocation size limits. + Example output:: root@moria-kvm:~# sort -g /proc/allocinfo|tail|numfmt --to=iec diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst index 3f0ef1e27eb0..2fc53093752d 100644 --- a/Documentation/userspace-api/ioctl/ioctl-number.rst +++ b/Documentation/userspace-api/ioctl/ioctl-number.rst @@ -346,6 +346,8 @@ Code Seq# Include File Comments 0xA5 20-2F linux/surface_aggregator/dtx.h Microsoft Surface DTX driver +0xA6 00-0F uapi/linux/alloc_tag.h Memory allocation profiling + 0xAA 00-3F linux/uapi/linux/userfaultfd.h 0xAB 00-1F linux/nbd.h 0xAC 00-1F linux/raw.h diff --git a/MAINTAINERS b/MAINTAINERS index 06271e742d32..557e5fd32073 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16940,6 +16940,7 @@ S: Maintained F: Documentation/mm/allocation-profiling.rst F: include/linux/alloc_tag.h F: include/linux/pgalloc_tag.h +F: include/uapi/linux/alloc_tag.h F: mm/alloc_tag.c MEMORY MANAGEMENT - BALLOON diff --git a/include/linux/codetag.h b/include/linux/codetag.h index ddae7484ca45..a25a085c2df1 100644 --- a/include/linux/codetag.h +++ b/include/linux/codetag.h @@ -77,6 +77,8 @@ struct codetag_iterator { void codetag_lock_module_list(struct codetag_type *cttype); bool codetag_trylock_module_list(struct codetag_type *cttype); void codetag_unlock_module_list(struct codetag_type *cttype); +unsigned long codetag_get_content_id(struct codetag_type *cttype); +unsigned int codetag_get_count(struct codetag_type *cttype); struct codetag_iterator codetag_get_ct_iter(struct codetag_type *cttype); struct codetag *codetag_next_ct(struct codetag_iterator *iter); diff --git a/include/uapi/linux/alloc_tag.h b/include/uapi/linux/alloc_tag.h new file mode 100644 index 000000000000..ee6a023cbaf4 --- /dev/null +++ b/include/uapi/linux/alloc_tag.h @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * alloc_tag IOCTL API definition + * + * Copyright (C) 2026 Google, LLC. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _UAPI_ALLOC_TAG_H +#define _UAPI_ALLOC_TAG_H + +#include + +/* + * Function, file and module names often have the same prefixes, therefore + * when filtering by these criteria, we compare the last 64 characters to + * minimize the chances of name collisions + */ +#define ALLOCINFO_STR_SIZE 64 + +struct allocinfo_content_id { + __u64 id; +}; + +struct allocinfo_tag { + /* Longer names are trimmed */ + char modname[ALLOCINFO_STR_SIZE]; + char function[ALLOCINFO_STR_SIZE]; + char filename[ALLOCINFO_STR_SIZE]; + __u64 lineno; +}; + +/* The alignment ensures 32-bit compatible interfaces are not broken */ +struct allocinfo_counter { + __u64 bytes; + __u64 calls; + __u8 accurate; +} __attribute__((aligned(8))); + +struct allocinfo_tag_data { + struct allocinfo_tag tag; + struct allocinfo_counter counter; +}; + +struct allocinfo_get_at { + __u64 pos; /* input */ + struct allocinfo_tag_data data; +}; + +#define _ALLOCINFO_IOC_CONTENT_ID 0 +#define _ALLOCINFO_IOC_GET_AT 1 +#define _ALLOCINFO_IOC_GET_NEXT 2 + +#define ALLOCINFO_IOC_BASE 0xA6 +#define ALLOCINFO_IOC_CONTENT_ID _IOR(ALLOCINFO_IOC_BASE, _ALLOCINFO_IOC_CONTENT_ID, \ + struct allocinfo_content_id) +#define ALLOCINFO_IOC_GET_AT _IOWR(ALLOCINFO_IOC_BASE, _ALLOCINFO_IOC_GET_AT, \ + struct allocinfo_get_at) +#define ALLOCINFO_IOC_GET_NEXT _IOR(ALLOCINFO_IOC_BASE, _ALLOCINFO_IOC_GET_NEXT, \ + struct allocinfo_tag_data) + +#endif /* _UAPI_ALLOC_TAG_H */ diff --git a/lib/codetag.c b/lib/codetag.c index 4001a7ea6675..a9cda4c962a3 100644 --- a/lib/codetag.c +++ b/lib/codetag.c @@ -19,6 +19,8 @@ struct codetag_type { struct codetag_type_desc desc; /* generates unique sequence number for module load */ unsigned long next_mod_seq; + /* bumped on every module load and unload */ + unsigned long content_id; }; struct codetag_range { @@ -50,6 +52,20 @@ void codetag_unlock_module_list(struct codetag_type *cttype) up_read(&cttype->mod_lock); } +unsigned long codetag_get_content_id(struct codetag_type *cttype) +{ + lockdep_assert_held(&cttype->mod_lock); + + return cttype->content_id; +} + +unsigned int codetag_get_count(struct codetag_type *cttype) +{ + lockdep_assert_held(&cttype->mod_lock); + + return cttype->count; +} + struct codetag_iterator codetag_get_ct_iter(struct codetag_type *cttype) { struct codetag_iterator iter = { @@ -204,6 +220,7 @@ static int codetag_module_init(struct codetag_type *cttype, struct module *mod) down_write(&cttype->mod_lock); cmod->mod_seq = ++cttype->next_mod_seq; + ++cttype->content_id; mod_id = idr_alloc(&cttype->mod_idr, cmod, 0, 0, GFP_KERNEL); if (mod_id >= 0) { if (cttype->desc.module_load) { @@ -368,6 +385,7 @@ void codetag_unload_module(struct module *mod) cttype->count -= range_size(cttype, &cmod->range); idr_remove(&cttype->mod_idr, mod_id); kfree(cmod); + ++cttype->content_id; } up_write(&cttype->mod_lock); if (found && cttype->desc.free_section_mem) diff --git a/mm/alloc_tag.c b/mm/alloc_tag.c index b60ee89704cc..b2ac166880ac 100644 --- a/mm/alloc_tag.c +++ b/mm/alloc_tag.c @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include #include #include @@ -14,6 +16,7 @@ #include #include #include +#include #include "internal.h" #include "page_alloc.h" @@ -59,6 +62,10 @@ struct allocinfo_private { struct codetag_iterator iter; struct codetag_iterator reported_iter; bool print_header; + /* ioctl uses a separate iterator not to interfere with reads */ + struct codetag_iterator ioctl_iter; + bool positioned; /* seq_open_private() sets to 0 */ + struct mutex ioctl_lock; }; static void *allocinfo_start(struct seq_file *m, loff_t *pos) @@ -142,6 +149,235 @@ static const struct seq_operations allocinfo_seq_op = { .show = allocinfo_show, }; +/* + * Initializes seq_file operations and allocates private state when opening + * the /proc/allocinfo procfs entry. + */ +static int allocinfo_open(struct inode *inode, struct file *file) +{ + int ret; + + ret = seq_open_private(file, &allocinfo_seq_op, + sizeof(struct allocinfo_private)); + if (!ret) { + struct seq_file *m = file->private_data; + struct allocinfo_private *priv = m->private; + + mutex_init(&priv->ioctl_lock); + } + return ret; +} + +/* + * Cleans up the seq_file state and frees up the private state allocated in + * allocinfo_open() when closing the /proc/allocinfo file descriptor. + */ +static int allocinfo_release(struct inode *inode, struct file *file) +{ + struct seq_file *m = file->private_data; + struct allocinfo_private *priv = m->private; + + mutex_destroy(&priv->ioctl_lock); + return seq_release_private(inode, file); +} + +/* + * Returns a pointer to the suffix of a string so that its length fits within + * ALLOCINFO_STR_SIZE, preserving the trailing characters. + * Function, file and module names often have the same prefixes, therefore + * when filtering by these criteria, we compare the last 64 characters to + * minimize the chances of name collisions + */ +static const char *allocinfo_str(const char *str) +{ + size_t len = strlen(str); + + /* Keep an extra space for the trailing NULL. */ + if (len >= ALLOCINFO_STR_SIZE) + str += (len - ALLOCINFO_STR_SIZE) + 1; + return str; +} + +/* Copy a string and trim from the beginning if it's too long */ +static void allocinfo_copy_str(char *dest, const char *src) +{ + strscpy_pad(dest, allocinfo_str(src), ALLOCINFO_STR_SIZE); +} + +/* + * Populates the UAPI allocinfo_tag_data structure with active runtime + * profiling counters extracted from the given kernel codetag. + */ +static void allocinfo_to_params(struct codetag *ct, + struct allocinfo_tag_data *data) +{ + struct alloc_tag *tag = ct_to_alloc_tag(ct); + struct alloc_tag_counters counter = alloc_tag_read(tag); + + if (ct->modname) + allocinfo_copy_str(data->tag.modname, ct->modname); + else + data->tag.modname[0] = '\0'; + allocinfo_copy_str(data->tag.function, ct->function); + allocinfo_copy_str(data->tag.filename, ct->filename); + data->tag.lineno = ct->lineno; + data->counter.bytes = counter.bytes; + data->counter.calls = counter.calls; + data->counter.accurate = !alloc_tag_is_inaccurate(tag); +} + +/* + * Retrieves the unique content ID representing the current allocation tag module + * layout, allowing userspace to detect if modules were loaded / unloaded. + */ +static int allocinfo_ioctl_get_content_id(struct seq_file *m, void __user *arg) +{ + struct allocinfo_content_id params; + + codetag_lock_module_list(alloc_tag_cttype); + params.id = codetag_get_content_id(alloc_tag_cttype); + codetag_unlock_module_list(alloc_tag_cttype); + if (copy_to_user(arg, ¶ms, sizeof(params))) + return -EFAULT; + + return 0; +} + +/* + * Seeks the ioctl iterator to the specified 0-indexed tag position, reads its + * profiling data and returns it to userspace. + */ +static int allocinfo_ioctl_get_at(struct seq_file *m, void __user *arg) +{ + struct allocinfo_private *priv; + struct codetag *ct; + __u64 pos; + struct allocinfo_get_at params = {0}; + + if (copy_from_user(¶ms, arg, sizeof(params))) + return -EFAULT; + + priv = m->private; + pos = params.pos; + + mutex_lock(&priv->ioctl_lock); + codetag_lock_module_list(alloc_tag_cttype); + + if (pos >= codetag_get_count(alloc_tag_cttype)) { + codetag_unlock_module_list(alloc_tag_cttype); + mutex_unlock(&priv->ioctl_lock); + return -ENOENT; + } + + /* Find the codetag */ + priv->ioctl_iter = codetag_get_ct_iter(alloc_tag_cttype); + ct = codetag_next_ct(&priv->ioctl_iter); + while (ct && pos--) + ct = codetag_next_ct(&priv->ioctl_iter); + if (ct) { + allocinfo_to_params(ct, ¶ms.data); + priv->positioned = true; + } + + codetag_unlock_module_list(alloc_tag_cttype); + mutex_unlock(&priv->ioctl_lock); + + if (!ct) + return -ENOENT; + + if (copy_to_user(arg, ¶ms, sizeof(params))) + return -EFAULT; + + return 0; +} + +/* + * Advances the ioctl iterator to the next allocation tag in the sequence and + * returns its profiling data to userspace. + */ +static int allocinfo_ioctl_get_next(struct seq_file *m, void __user *arg) +{ + struct allocinfo_private *priv; + struct codetag *ct; + struct allocinfo_tag_data params; + int ret = 0; + + memset(¶ms, 0, sizeof(params)); + priv = m->private; + + mutex_lock(&priv->ioctl_lock); + codetag_lock_module_list(alloc_tag_cttype); + + if (!priv->positioned) { + priv->ioctl_iter = codetag_get_ct_iter(alloc_tag_cttype); + priv->positioned = true; + } + + ct = codetag_next_ct(&priv->ioctl_iter); + if (ct) + allocinfo_to_params(ct, ¶ms); + + if (!ct) { + priv->positioned = false; + ret = -ENOENT; + } + codetag_unlock_module_list(alloc_tag_cttype); + mutex_unlock(&priv->ioctl_lock); + + if (ret == 0) { + if (copy_to_user(arg, ¶ms, sizeof(params))) + return -EFAULT; + } + return ret; +} + +/* + * Entry point ioctl function for /proc/allocinfo routing requests to fetch the + * layout content ID, seek to a specific tag, or read sequential tags. + */ +static long allocinfo_ioctl(struct file *file, unsigned int cmd, + unsigned long __arg) +{ + void __user *arg = (void __user *)__arg; + int ret; + + switch (cmd) { + case ALLOCINFO_IOC_CONTENT_ID: + ret = allocinfo_ioctl_get_content_id(file->private_data, arg); + break; + case ALLOCINFO_IOC_GET_AT: + ret = allocinfo_ioctl_get_at(file->private_data, arg); + break; + case ALLOCINFO_IOC_GET_NEXT: + ret = allocinfo_ioctl_get_next(file->private_data, arg); + break; + default: + ret = -ENOIOCTLCMD; + break; + } + + return ret; +} + +#ifdef CONFIG_COMPAT +static long allocinfo_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + return allocinfo_ioctl(file, cmd, (unsigned long)compat_ptr(arg)); +} +#endif + +static const struct proc_ops allocinfo_proc_ops = { + .proc_open = allocinfo_open, + .proc_read_iter = seq_read_iter, + .proc_lseek = seq_lseek, + .proc_release = allocinfo_release, + .proc_ioctl = allocinfo_ioctl, +#ifdef CONFIG_COMPAT + .proc_compat_ioctl = allocinfo_compat_ioctl, +#endif +}; + size_t alloc_tag_top_users(struct codetag_bytes *tags, size_t count, bool can_sleep) { struct codetag_iterator iter; @@ -999,8 +1235,7 @@ static int __init alloc_tag_init(void) return 0; } - if (!proc_create_seq_private(ALLOCINFO_FILE_NAME, 0400, NULL, &allocinfo_seq_op, - sizeof(struct allocinfo_private), NULL)) { + if (!proc_create(ALLOCINFO_FILE_NAME, 0400, NULL, &allocinfo_proc_ops)) { pr_err("Failed to create %s file\n", ALLOCINFO_FILE_NAME); shutdown_mem_profiling(false); return -ENOMEM; -- cgit v1.2.3 From 5732a4e4c18acac152768df9ce48e057232edbf4 Mon Sep 17 00:00:00 2001 From: Abhishek Bapat Date: Wed, 8 Jul 2026 18:01:24 +0000 Subject: alloc_tag: add ioctl filters to /proc/allocinfo Extend the capability of the IOCTL mechanism to filter allocations based on tag's module name, function name, file name and line number. Link: https://lore.kernel.org/6a6100c0c58cb2911f39126b9fe177a8c17db16f.1783532853.git.abhishekbapat@google.com Signed-off-by: Abhishek Bapat Acked-by: Hao Ge Acked-by: Suren Baghdasaryan Cc: Jonathan Corbet Cc: Kent Overstreet Cc: Sourav Panda Signed-off-by: Andrew Morton --- include/uapi/linux/alloc_tag.h | 26 +++++++++++++++- mm/alloc_tag.c | 68 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 89 insertions(+), 5 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/alloc_tag.h b/include/uapi/linux/alloc_tag.h index ee6a023cbaf4..13e9b5916bf5 100644 --- a/include/uapi/linux/alloc_tag.h +++ b/include/uapi/linux/alloc_tag.h @@ -45,8 +45,32 @@ struct allocinfo_tag_data { struct allocinfo_counter counter; }; +enum { + ALLOCINFO_FILTER_MODNAME, + ALLOCINFO_FILTER_FUNCTION, + ALLOCINFO_FILTER_FILENAME, + ALLOCINFO_FILTER_LINENO, + __ALLOCINFO_FILTER_LAST = ALLOCINFO_FILTER_LINENO +}; + +#define ALLOCINFO_FILTER_MASK_MODNAME (1 << ALLOCINFO_FILTER_MODNAME) +#define ALLOCINFO_FILTER_MASK_FUNCTION (1 << ALLOCINFO_FILTER_FUNCTION) +#define ALLOCINFO_FILTER_MASK_FILENAME (1 << ALLOCINFO_FILTER_FILENAME) +#define ALLOCINFO_FILTER_MASK_LINENO (1 << ALLOCINFO_FILTER_LINENO) + +#define ALLOCINFO_FILTER_MASKS \ + ((1 << (__ALLOCINFO_FILTER_LAST + 1)) - 1) + +struct allocinfo_filter { + __u64 mask; /* bitmask of the filter fields used */ + struct allocinfo_tag fields; +}; + struct allocinfo_get_at { - __u64 pos; /* input */ + /* inputs */ + __u64 pos; + struct allocinfo_filter filter; + /* output */ struct allocinfo_tag_data data; }; diff --git a/mm/alloc_tag.c b/mm/alloc_tag.c index b2ac166880ac..d7ed0034c49f 100644 --- a/mm/alloc_tag.c +++ b/mm/alloc_tag.c @@ -62,6 +62,7 @@ struct allocinfo_private { struct codetag_iterator iter; struct codetag_iterator reported_iter; bool print_header; + struct allocinfo_filter filter; /* ioctl uses a separate iterator not to interfere with reads */ struct codetag_iterator ioctl_iter; bool positioned; /* seq_open_private() sets to 0 */ @@ -204,6 +205,12 @@ static void allocinfo_copy_str(char *dest, const char *src) strscpy_pad(dest, allocinfo_str(src), ALLOCINFO_STR_SIZE); } +/* Compare two strings and only consider the trimmed suffix if s1 is too long */ +static int allocinfo_cmp_str(const char *str, const char *template) +{ + return strncmp(allocinfo_str(str), template, ALLOCINFO_STR_SIZE); +} + /* * Populates the UAPI allocinfo_tag_data structure with active runtime * profiling counters extracted from the given kernel codetag. @@ -243,6 +250,40 @@ static int allocinfo_ioctl_get_content_id(struct seq_file *m, void __user *arg) return 0; } +/* + * Verifies whether a given codetag satisfies the active filtering criteria by + * matching its characteristics against the specified filter. + */ +static bool matches_filter(struct codetag *ct, struct allocinfo_filter *filter) +{ + if (!filter || !filter->mask) + return true; + + if (filter->mask & ALLOCINFO_FILTER_MASK_MODNAME) { + /* user wants to filter by modname but ct->modname is NULL */ + if (!ct->modname) { + /* validate if user was attempting to filter for built-in allocations */ + if (filter->fields.modname[0] != '\0') + return false; + } else if (allocinfo_cmp_str(ct->modname, filter->fields.modname)) + return false; + } + + if ((filter->mask & ALLOCINFO_FILTER_MASK_FUNCTION) && + ct->function && allocinfo_cmp_str(ct->function, filter->fields.function)) + return false; + + if ((filter->mask & ALLOCINFO_FILTER_MASK_FILENAME) && + ct->filename && allocinfo_cmp_str(ct->filename, filter->fields.filename)) + return false; + + if ((filter->mask & ALLOCINFO_FILTER_MASK_LINENO) && + ct->lineno != filter->fields.lineno) + return false; + + return true; +} + /* * Seeks the ioctl iterator to the specified 0-indexed tag position, reads its * profiling data and returns it to userspace. @@ -251,29 +292,46 @@ static int allocinfo_ioctl_get_at(struct seq_file *m, void __user *arg) { struct allocinfo_private *priv; struct codetag *ct; - __u64 pos; struct allocinfo_get_at params = {0}; + __u64 skip_count; if (copy_from_user(¶ms, arg, sizeof(params))) return -EFAULT; + if (params.filter.mask & ~ALLOCINFO_FILTER_MASKS) + return -EINVAL; + priv = m->private; - pos = params.pos; mutex_lock(&priv->ioctl_lock); codetag_lock_module_list(alloc_tag_cttype); - if (pos >= codetag_get_count(alloc_tag_cttype)) { + if (params.pos >= codetag_get_count(alloc_tag_cttype)) { codetag_unlock_module_list(alloc_tag_cttype); mutex_unlock(&priv->ioctl_lock); return -ENOENT; } + skip_count = params.pos; + + if (params.filter.mask) + priv->filter = params.filter; + else + priv->filter.mask = 0; + /* Find the codetag */ priv->ioctl_iter = codetag_get_ct_iter(alloc_tag_cttype); ct = codetag_next_ct(&priv->ioctl_iter); - while (ct && pos--) + + while (ct) { + if (matches_filter(ct, &priv->filter)) { + if (skip_count == 0) + break; + skip_count--; + } ct = codetag_next_ct(&priv->ioctl_iter); + } + if (ct) { allocinfo_to_params(ct, ¶ms.data); priv->positioned = true; @@ -314,6 +372,8 @@ static int allocinfo_ioctl_get_next(struct seq_file *m, void __user *arg) } ct = codetag_next_ct(&priv->ioctl_iter); + while (ct && !matches_filter(ct, &priv->filter)) + ct = codetag_next_ct(&priv->ioctl_iter); if (ct) allocinfo_to_params(ct, ¶ms); -- cgit v1.2.3 From 6f6769ea88f89116e8d66a4ac77056e2e3b377c5 Mon Sep 17 00:00:00 2001 From: Abhishek Bapat Date: Wed, 8 Jul 2026 18:01:25 +0000 Subject: alloc_tag: add size-based filtering to ioctl Extend the allocinfo filtering mechanism to allow users to filter tags based on the total number of bytes allocated [min_size, max_size]. The size range is inclusive. Filtering by size involves retrieving allocinfo per-CPU counters, which is an expensive operation. Hence, the performance of size-based filtering will be worse than other filters. Link: https://lore.kernel.org/0a7653b70ae0d64e967fbea0e933bc35f8ac656e.1783532853.git.abhishekbapat@google.com Signed-off-by: Abhishek Bapat Acked-by: Hao Ge Acked-by: Suren Baghdasaryan Cc: Jonathan Corbet Cc: Kent Overstreet Cc: Sourav Panda Signed-off-by: Andrew Morton --- include/uapi/linux/alloc_tag.h | 8 +++++- mm/alloc_tag.c | 64 +++++++++++++++++++++++++++++++++--------- 2 files changed, 58 insertions(+), 14 deletions(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/alloc_tag.h b/include/uapi/linux/alloc_tag.h index 13e9b5916bf5..0de5fc180790 100644 --- a/include/uapi/linux/alloc_tag.h +++ b/include/uapi/linux/alloc_tag.h @@ -50,13 +50,17 @@ enum { ALLOCINFO_FILTER_FUNCTION, ALLOCINFO_FILTER_FILENAME, ALLOCINFO_FILTER_LINENO, - __ALLOCINFO_FILTER_LAST = ALLOCINFO_FILTER_LINENO + ALLOCINFO_FILTER_MIN_SIZE, + ALLOCINFO_FILTER_MAX_SIZE, + __ALLOCINFO_FILTER_LAST = ALLOCINFO_FILTER_MAX_SIZE }; #define ALLOCINFO_FILTER_MASK_MODNAME (1 << ALLOCINFO_FILTER_MODNAME) #define ALLOCINFO_FILTER_MASK_FUNCTION (1 << ALLOCINFO_FILTER_FUNCTION) #define ALLOCINFO_FILTER_MASK_FILENAME (1 << ALLOCINFO_FILTER_FILENAME) #define ALLOCINFO_FILTER_MASK_LINENO (1 << ALLOCINFO_FILTER_LINENO) +#define ALLOCINFO_FILTER_MASK_MIN_SIZE (1 << ALLOCINFO_FILTER_MIN_SIZE) +#define ALLOCINFO_FILTER_MASK_MAX_SIZE (1 << ALLOCINFO_FILTER_MAX_SIZE) #define ALLOCINFO_FILTER_MASKS \ ((1 << (__ALLOCINFO_FILTER_LAST + 1)) - 1) @@ -64,6 +68,8 @@ enum { struct allocinfo_filter { __u64 mask; /* bitmask of the filter fields used */ struct allocinfo_tag fields; + __u64 min_size; + __u64 max_size; }; struct allocinfo_get_at { diff --git a/mm/alloc_tag.c b/mm/alloc_tag.c index d7ed0034c49f..04b640d74bcc 100644 --- a/mm/alloc_tag.c +++ b/mm/alloc_tag.c @@ -211,16 +211,20 @@ static int allocinfo_cmp_str(const char *str, const char *template) return strncmp(allocinfo_str(str), template, ALLOCINFO_STR_SIZE); } +/* Fetch the per-CPU counters */ +static inline struct alloc_tag_counters allocinfo_prefetch_counters(struct codetag *ct) +{ + return alloc_tag_read(ct_to_alloc_tag(ct)); +} + /* * Populates the UAPI allocinfo_tag_data structure with active runtime * profiling counters extracted from the given kernel codetag. */ static void allocinfo_to_params(struct codetag *ct, - struct allocinfo_tag_data *data) + struct allocinfo_tag_data *data, + struct alloc_tag_counters *counters) { - struct alloc_tag *tag = ct_to_alloc_tag(ct); - struct alloc_tag_counters counter = alloc_tag_read(tag); - if (ct->modname) allocinfo_copy_str(data->tag.modname, ct->modname); else @@ -228,9 +232,9 @@ static void allocinfo_to_params(struct codetag *ct, allocinfo_copy_str(data->tag.function, ct->function); allocinfo_copy_str(data->tag.filename, ct->filename); data->tag.lineno = ct->lineno; - data->counter.bytes = counter.bytes; - data->counter.calls = counter.calls; - data->counter.accurate = !alloc_tag_is_inaccurate(tag); + data->counter.bytes = counters->bytes; + data->counter.calls = counters->calls; + data->counter.accurate = !alloc_tag_is_inaccurate(ct_to_alloc_tag(ct)); } /* @@ -254,7 +258,9 @@ static int allocinfo_ioctl_get_content_id(struct seq_file *m, void __user *arg) * Verifies whether a given codetag satisfies the active filtering criteria by * matching its characteristics against the specified filter. */ -static bool matches_filter(struct codetag *ct, struct allocinfo_filter *filter) +static bool matches_filter(struct codetag *ct, struct allocinfo_filter *filter, + struct alloc_tag_counters *counters, + bool *fetched_counters) { if (!filter || !filter->mask) return true; @@ -281,6 +287,19 @@ static bool matches_filter(struct codetag *ct, struct allocinfo_filter *filter) ct->lineno != filter->fields.lineno) return false; + if (filter->mask & (ALLOCINFO_FILTER_MASK_MIN_SIZE | ALLOCINFO_FILTER_MASK_MAX_SIZE)) { + if (!*fetched_counters) { + *counters = allocinfo_prefetch_counters(ct); + *fetched_counters = true; + } + if ((filter->mask & ALLOCINFO_FILTER_MASK_MIN_SIZE) && + counters->bytes < filter->min_size) + return false; + if ((filter->mask & ALLOCINFO_FILTER_MASK_MAX_SIZE) && + counters->bytes > filter->max_size) + return false; + } + return true; } @@ -294,6 +313,8 @@ static int allocinfo_ioctl_get_at(struct seq_file *m, void __user *arg) struct codetag *ct; struct allocinfo_get_at params = {0}; __u64 skip_count; + struct alloc_tag_counters counters; + bool fetched_counters; if (copy_from_user(¶ms, arg, sizeof(params))) return -EFAULT; @@ -301,6 +322,11 @@ static int allocinfo_ioctl_get_at(struct seq_file *m, void __user *arg) if (params.filter.mask & ~ALLOCINFO_FILTER_MASKS) return -EINVAL; + if ((params.filter.mask & ALLOCINFO_FILTER_MASK_MIN_SIZE) && + (params.filter.mask & ALLOCINFO_FILTER_MASK_MAX_SIZE) && + params.filter.min_size > params.filter.max_size) + return -EINVAL; + priv = m->private; mutex_lock(&priv->ioctl_lock); @@ -324,7 +350,8 @@ static int allocinfo_ioctl_get_at(struct seq_file *m, void __user *arg) ct = codetag_next_ct(&priv->ioctl_iter); while (ct) { - if (matches_filter(ct, &priv->filter)) { + fetched_counters = false; + if (matches_filter(ct, &priv->filter, &counters, &fetched_counters)) { if (skip_count == 0) break; skip_count--; @@ -333,7 +360,9 @@ static int allocinfo_ioctl_get_at(struct seq_file *m, void __user *arg) } if (ct) { - allocinfo_to_params(ct, ¶ms.data); + if (!fetched_counters) + counters = allocinfo_prefetch_counters(ct); + allocinfo_to_params(ct, ¶ms.data, &counters); priv->positioned = true; } @@ -359,6 +388,8 @@ static int allocinfo_ioctl_get_next(struct seq_file *m, void __user *arg) struct codetag *ct; struct allocinfo_tag_data params; int ret = 0; + struct alloc_tag_counters counters; + bool fetched_counters; memset(¶ms, 0, sizeof(params)); priv = m->private; @@ -372,11 +403,18 @@ static int allocinfo_ioctl_get_next(struct seq_file *m, void __user *arg) } ct = codetag_next_ct(&priv->ioctl_iter); - while (ct && !matches_filter(ct, &priv->filter)) + while (ct) { + fetched_counters = false; + if (matches_filter(ct, &priv->filter, &counters, &fetched_counters)) + break; ct = codetag_next_ct(&priv->ioctl_iter); - if (ct) - allocinfo_to_params(ct, ¶ms); + } + if (ct) { + if (!fetched_counters) + counters = allocinfo_prefetch_counters(ct); + allocinfo_to_params(ct, ¶ms, &counters); + } if (!ct) { priv->positioned = false; ret = -ENOENT; -- cgit v1.2.3 From 33588e0b81df2972922f2591767a4e3c16813ab9 Mon Sep 17 00:00:00 2001 From: Abhishek Bapat Date: Wed, 8 Jul 2026 18:01:26 +0000 Subject: alloc_tag: add accuracy based filtering to ioctl Extend the allocinfo filtering mechanism to allow users to filter tags based on their accuracy. [abhishekbapat@google.com: move `inaccurate` filtering criteria from `struct allocinfo_tag` to `struct allocinfo_filter`] Link: https://lore.kernel.org/e4e49ec4a5960292aeeb9e196526c18dc95228a2.1785867739.git.abhishekbapat@google.com Link: https://lore.kernel.org/396a5e4bc3b2990223ab355f2cd3ceb6aa15499e.1783532853.git.abhishekbapat@google.com Signed-off-by: Abhishek Bapat Acked-by: Hao Ge Acked-by: Suren Baghdasaryan Cc: Jonathan Corbet Cc: Kent Overstreet Cc: Sourav Panda Signed-off-by: Andrew Morton --- include/uapi/linux/alloc_tag.h | 4 ++++ mm/alloc_tag.c | 8 ++++++++ 2 files changed, 12 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/alloc_tag.h b/include/uapi/linux/alloc_tag.h index 0de5fc180790..e3ad94444864 100644 --- a/include/uapi/linux/alloc_tag.h +++ b/include/uapi/linux/alloc_tag.h @@ -50,6 +50,7 @@ enum { ALLOCINFO_FILTER_FUNCTION, ALLOCINFO_FILTER_FILENAME, ALLOCINFO_FILTER_LINENO, + ALLOCINFO_FILTER_INACCURATE, ALLOCINFO_FILTER_MIN_SIZE, ALLOCINFO_FILTER_MAX_SIZE, __ALLOCINFO_FILTER_LAST = ALLOCINFO_FILTER_MAX_SIZE @@ -59,6 +60,7 @@ enum { #define ALLOCINFO_FILTER_MASK_FUNCTION (1 << ALLOCINFO_FILTER_FUNCTION) #define ALLOCINFO_FILTER_MASK_FILENAME (1 << ALLOCINFO_FILTER_FILENAME) #define ALLOCINFO_FILTER_MASK_LINENO (1 << ALLOCINFO_FILTER_LINENO) +#define ALLOCINFO_FILTER_MASK_INACCURATE (1 << ALLOCINFO_FILTER_INACCURATE) #define ALLOCINFO_FILTER_MASK_MIN_SIZE (1 << ALLOCINFO_FILTER_MIN_SIZE) #define ALLOCINFO_FILTER_MASK_MAX_SIZE (1 << ALLOCINFO_FILTER_MAX_SIZE) @@ -70,6 +72,8 @@ struct allocinfo_filter { struct allocinfo_tag fields; __u64 min_size; __u64 max_size; + /* filter criteria only; see allocinfo_counter.accurate for actual accuracy */ + __u64 inaccurate; }; struct allocinfo_get_at { diff --git a/mm/alloc_tag.c b/mm/alloc_tag.c index 04b640d74bcc..b33410310477 100644 --- a/mm/alloc_tag.c +++ b/mm/alloc_tag.c @@ -262,6 +262,8 @@ static bool matches_filter(struct codetag *ct, struct allocinfo_filter *filter, struct alloc_tag_counters *counters, bool *fetched_counters) { + bool inaccurate; + if (!filter || !filter->mask) return true; @@ -287,6 +289,12 @@ static bool matches_filter(struct codetag *ct, struct allocinfo_filter *filter, ct->lineno != filter->fields.lineno) return false; + if (filter->mask & ALLOCINFO_FILTER_MASK_INACCURATE) { + inaccurate = !!(ct->flags & CODETAG_FLAG_INACCURATE); + if (inaccurate != !!(filter->inaccurate)) + return false; + } + if (filter->mask & (ALLOCINFO_FILTER_MASK_MIN_SIZE | ALLOCINFO_FILTER_MASK_MAX_SIZE)) { if (!*fetched_counters) { *counters = allocinfo_prefetch_counters(ct); -- cgit v1.2.3 From ab84c314417e4743f72f4d3d5e58cf96e07213cc Mon Sep 17 00:00:00 2001 From: Frederick Lawler Date: Wed, 19 Aug 2026 18:45:22 -0500 Subject: configfs: move CONFIGFS_MAGIC definition to magic.h IMA shouldn't measure or appraise configfs, but currently does because it's missing from the default exclusion policies. Move CONFIGFS_MAGIC to magic.h to expose the file system's magic to IMA, as well as other userland applications. Suggested-by: Mimi Zohar Signed-off-by: Frederick Lawler Acked-by: Breno Leitao Signed-off-by: Mimi Zohar --- fs/configfs/mount.c | 4 +--- include/uapi/linux/magic.h | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) (limited to 'include/uapi/linux') diff --git a/fs/configfs/mount.c b/fs/configfs/mount.c index 4929f3431189..d8cac1cbf3bd 100644 --- a/fs/configfs/mount.c +++ b/fs/configfs/mount.c @@ -9,6 +9,7 @@ */ #include +#include #include #include #include @@ -19,9 +20,6 @@ #include #include "configfs_internal.h" -/* Random magic number */ -#define CONFIGFS_MAGIC 0x62656570 - static struct vfsmount *configfs_mount = NULL; struct kmem_cache *configfs_dir_cachep; static int configfs_mnt_count = 0; diff --git a/include/uapi/linux/magic.h b/include/uapi/linux/magic.h index fd5f0e95648e..66a91c8b1cb9 100644 --- a/include/uapi/linux/magic.h +++ b/include/uapi/linux/magic.h @@ -8,6 +8,7 @@ #define AUTOFS_SUPER_MAGIC 0x0187 #define CEPH_SUPER_MAGIC 0x00c36400 #define CODA_SUPER_MAGIC 0x73757245 +#define CONFIGFS_MAGIC 0x62656570 /* some random number */ #define CRAMFS_MAGIC 0x28cd3d45 /* some random number */ #define CRAMFS_MAGIC_WEND 0x453dcd28 /* magic number with the wrong endianess */ #define DEBUGFS_MAGIC 0x64626720 -- cgit v1.2.3 From ea48250a0dc9708c198e8665391973bfc6c86fd2 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sun, 13 Sep 2026 17:19:48 -0700 Subject: Input: document that no new LED codes should be added Add a comment to input-event-codes.h clarifying that no new LED definitions should be added to the input subsystem. The existing LED_* definitions are legacy and grandfathered for backwards compatibility with userspace via evdev. Any new LED indicators should instead use the dedicated LED subsystem. Signed-off-by: Dmitry Torokhov --- include/uapi/linux/input-event-codes.h | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/input-event-codes.h b/include/uapi/linux/input-event-codes.h index 3528168f7c6d..4217950e5d16 100644 --- a/include/uapi/linux/input-event-codes.h +++ b/include/uapi/linux/input-event-codes.h @@ -970,6 +970,12 @@ /* * LEDs + * + * Do not add any new LED definitions to the input subsystem. The existing + * definitions are legacy and grandfathered for backwards compatibility with + * userspace (via evdev). Any new LEDs should be implemented using the + * LED subsystem (struct led_classdev). The input core provides a bridge to + * the LED subsystem in drivers/input/input-leds.c. */ #define LED_NUML 0x00 -- cgit v1.2.3 From 703749b069d53b55f92499e088b30b031e47b8f9 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Wed, 9 Sep 2026 08:37:44 +0200 Subject: random: fix vgetrandom_opaque_params kernel-doc struct vgetrandom_opaque_params contains size_of_opaque_state, but its kernel-doc describes size_per_opaque_state. This leaves the real member undescribed and adds a nonexistent member to the generated documentation. Use the member's actual name. Fixes: 4ad10a5f5f78 ("random: introduce generic vDSO getrandom() implementation") Signed-off-by: Karl Mehltretter Signed-off-by: Jason A. Donenfeld --- include/uapi/linux/random.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/random.h b/include/uapi/linux/random.h index 1dd047ec98a1..ef6614d9c704 100644 --- a/include/uapi/linux/random.h +++ b/include/uapi/linux/random.h @@ -58,7 +58,7 @@ struct rand_pool_info { /** * struct vgetrandom_opaque_params - arguments for allocating memory for vgetrandom * - * @size_per_opaque_state: Size of each state that is to be passed to vgetrandom(). + * @size_of_opaque_state: Size of each state passed to vgetrandom(). * @mmap_prot: Value of the prot argument in mmap(2). * @mmap_flags: Value of the flags argument in mmap(2). * @reserved: Reserved for future use. -- cgit v1.2.3 From 347c6ed8cf7768883252e106994de62c7947f0a0 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 15 Sep 2026 22:23:47 +0200 Subject: alloc_tag: avoid implicit padding in uapi The implied padding causes a harmless warning when testing the uapi headers with -Wpadded that could in theory indicate incompatibilities or data leaks: ./usr/include/linux/alloc_tag.h:41:1: error: padding struct size to alignment boundary with 7 bytes [-Werror=padded] The code here is fine, but it's better to make the padding explicit and avoid the warning here. Link: https://lore.kernel.org/20260916065830.1619425-1-arnd@kernel.org Link: https://lore.kernel.org/20260915202404.3568029-1-arnd@kernel.org Fixes: 1d581ab2348c ("alloc_tag: add ioctl to /proc/allocinfo") Signed-off-by: Arnd Bergmann Signed-off-by: Andrew Morton Acked-by: Suren Baghdasaryan Acked-by: SJ Park Acked-by: Hao Ge --- include/uapi/linux/alloc_tag.h | 1 + 1 file changed, 1 insertion(+) (limited to 'include/uapi/linux') diff --git a/include/uapi/linux/alloc_tag.h b/include/uapi/linux/alloc_tag.h index e3ad94444864..7d4618bea043 100644 --- a/include/uapi/linux/alloc_tag.h +++ b/include/uapi/linux/alloc_tag.h @@ -38,6 +38,7 @@ struct allocinfo_counter { __u64 bytes; __u64 calls; __u8 accurate; + __u8 pad[7]; } __attribute__((aligned(8))); struct allocinfo_tag_data { -- cgit v1.2.3