Recreate RHEL 6.12.0-211.20.1 from CS10/upstream backports

Add the RHEL 211.19.1..211.20.1 backports (1245-1287) from centos-stream-10 and
upstream, on top of 211.18.1, plus the dpll RHEL kABI adaptation (1287). RHEL now
ships the smb cifs.spnego fix too; the existing ahead-fix 1105 is byte-identical,
so RHEL's redundant copy is omitted. Bump to 211.20.1.
This commit is contained in:
Andrew Lukoshko 2026-06-07 00:04:20 +00:00
parent b9d46fff46
commit f6916a8ae1
44 changed files with 10075 additions and 1 deletions

View File

@ -0,0 +1,89 @@
From 72ad184f05e7a77a7043b9e71943ba3031cc683a Mon Sep 17 00:00:00 2001
From: CKI Backport Bot <cki-ci-bot+cki-gitlab-backport-bot@redhat.com>
Date: Tue, 31 Mar 2026 13:17:02 +0000
Subject: [PATCH] proc: use the same treatment to check proc_lseek as ones for
proc_read_iter et.al
JIRA: https://redhat.atlassian.net/browse/RHEL-163346
CVE: CVE-2025-38653
commit ff7ec8dc1b646296f8d94c39339e8d3833d16c05
Author: wangzijie <wangzijie1@honor.com>
Date: Sat Jun 7 10:13:53 2025 +0800
proc: use the same treatment to check proc_lseek as ones for proc_read_iter et.al
Check pde->proc_ops->proc_lseek directly may cause UAF in rmmod scenario.
It's a gap in proc_reg_open() after commit 654b33ada4ab("proc: fix UAF in
proc_get_inode()"). Followed by AI Viro's suggestion, fix it in same
manner.
Link: https://lkml.kernel.org/r/20250607021353.1127963-1-wangzijie1@honor.com
Fixes: 3f61631d47f1 ("take care to handle NULL ->proc_lseek()")
Signed-off-by: wangzijie <wangzijie1@honor.com>
Reviewed-by: Alexey Dobriyan <adobriyan@gmail.com>
Cc: Alexei Starovoitov <ast@kernel.org>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: "Edgecombe, Rick P" <rick.p.edgecombe@intel.com>
Cc: Kirill A. Shuemov <kirill.shutemov@linux.intel.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: CKI Backport Bot <cki-ci-bot+cki-gitlab-backport-bot@redhat.com>
diff --git a/fs/proc/generic.c b/fs/proc/generic.c
index 9b3b4efe2041..26e118f1dc9e 100644
--- a/fs/proc/generic.c
+++ b/fs/proc/generic.c
@@ -567,6 +567,8 @@ static void pde_set_flags(struct proc_dir_entry *pde)
if (pde->proc_ops->proc_compat_ioctl)
pde->flags |= PROC_ENTRY_proc_compat_ioctl;
#endif
+ if (pde->proc_ops->proc_lseek)
+ pde->flags |= PROC_ENTRY_proc_lseek;
}
struct proc_dir_entry *proc_create_data(const char *name, umode_t mode,
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index a3eb3b740f76..73074b9c715a 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -473,7 +473,7 @@ static int proc_reg_open(struct inode *inode, struct file *file)
typeof_member(struct proc_ops, proc_open) open;
struct pde_opener *pdeo;
- if (!pde->proc_ops->proc_lseek)
+ if (!pde_has_proc_lseek(pde))
file->f_mode &= ~FMODE_LSEEK;
if (pde_is_permanent(pde)) {
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index 77a517f91821..2c590e4a4022 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -99,6 +99,11 @@ static inline bool pde_has_proc_compat_ioctl(const struct proc_dir_entry *pde)
#endif
}
+static inline bool pde_has_proc_lseek(const struct proc_dir_entry *pde)
+{
+ return pde->flags & PROC_ENTRY_proc_lseek;
+}
+
extern struct kmem_cache *proc_dir_entry_cache;
void pde_free(struct proc_dir_entry *pde);
diff --git a/include/linux/proc_fs.h b/include/linux/proc_fs.h
index ea62201c74c4..703d0c76cc9a 100644
--- a/include/linux/proc_fs.h
+++ b/include/linux/proc_fs.h
@@ -27,6 +27,7 @@ enum {
PROC_ENTRY_proc_read_iter = 1U << 1,
PROC_ENTRY_proc_compat_ioctl = 1U << 2,
+ PROC_ENTRY_proc_lseek = 1U << 3,
};
struct proc_ops {
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,140 @@
From 5b2ab6d1f5e5488a9faa61f4d560b239dce0e08b Mon Sep 17 00:00:00 2001
From: Abhi Das <adas@redhat.com>
Date: Fri, 17 Apr 2026 13:31:25 -0500
Subject: [PATCH] proc: fix missing pde_set_flags() for net proc files
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
JIRA: https://redhat.atlassian.net/browse/RHEL-163346
CVE: CVE-2025-38653
commit 2ce3d282bd5050fca8577defeff08ada0d55d062
Author: wangzijie <wangzijie1@honor.com>
Date: Mon Aug 18 20:31:02 2025 +0800
proc: fix missing pde_set_flags() for net proc files
To avoid potential UAF issues during module removal races, we use
pde_set_flags() to save proc_ops flags in PDE itself before
proc_register(), and then use pde_has_proc_*() helpers instead of directly
dereferencing pde->proc_ops->*.
However, the pde_set_flags() call was missing when creating net related
proc files. This omission caused incorrect behavior which FMODE_LSEEK was
being cleared inappropriately in proc_reg_open() for net proc files. Lars
reported it in this link[1].
Fix this by ensuring pde_set_flags() is called when register proc entry,
and add NULL check for proc_ops in pde_set_flags().
[wangzijie1@honor.com: stash pde->proc_ops in a local const variable, per Christian]
Link: https://lkml.kernel.org/r/20250821105806.1453833-1-wangzijie1@honor.com
Link: https://lkml.kernel.org/r/20250818123102.959595-1-wangzijie1@honor.com
Link: https://lore.kernel.org/all/20250815195616.64497967@chagall.paradoxon.rec/ [1]
Fixes: ff7ec8dc1b64 ("proc: use the same treatment to check proc_lseek as ones for proc_read_iter et.al")
Signed-off-by: wangzijie <wangzijie1@honor.com>
Reported-by: Lars Wendler <polynomial-c@gmx.de>
Tested-by: Stefano Brivio <sbrivio@redhat.com>
Tested-by: Petr Vaněk <pv@excello.cz>
Tested by: Lars Wendler <polynomial-c@gmx.de>
Cc: Alexei Starovoitov <ast@kernel.org>
Cc: Alexey Dobriyan <adobriyan@gmail.com>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: "Edgecombe, Rick P" <rick.p.edgecombe@intel.com>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Jiri Slaby <jirislaby@kernel.org>
Cc: Kirill A. Shutemov <k.shutemov@gmail.com>
Cc: wangzijie <wangzijie1@honor.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Abhi Das <adas@redhat.com>
diff --git a/fs/proc/generic.c b/fs/proc/generic.c
index 26e118f1dc9e..88423b46e5ce 100644
--- a/fs/proc/generic.c
+++ b/fs/proc/generic.c
@@ -362,6 +362,25 @@ static const struct inode_operations proc_dir_inode_operations = {
.setattr = proc_notify_change,
};
+static void pde_set_flags(struct proc_dir_entry *pde)
+{
+ const struct proc_ops *proc_ops = pde->proc_ops;
+
+ if (!proc_ops)
+ return;
+
+ if (proc_ops->proc_flags & PROC_ENTRY_PERMANENT)
+ pde->flags |= PROC_ENTRY_PERMANENT;
+ if (proc_ops->proc_read_iter)
+ pde->flags |= PROC_ENTRY_proc_read_iter;
+#ifdef CONFIG_COMPAT
+ if (proc_ops->proc_compat_ioctl)
+ pde->flags |= PROC_ENTRY_proc_compat_ioctl;
+#endif
+ if (proc_ops->proc_lseek)
+ pde->flags |= PROC_ENTRY_proc_lseek;
+}
+
/* returns the registered entry, or frees dp and returns NULL on failure */
struct proc_dir_entry *proc_register(struct proc_dir_entry *dir,
struct proc_dir_entry *dp)
@@ -369,6 +388,8 @@ struct proc_dir_entry *proc_register(struct proc_dir_entry *dir,
if (proc_alloc_inum(&dp->low_ino))
goto out_free_entry;
+ pde_set_flags(dp);
+
write_lock(&proc_subdir_lock);
dp->parent = dir;
if (pde_subdir_insert(dir, dp) == false) {
@@ -557,20 +578,6 @@ struct proc_dir_entry *proc_create_reg(const char *name, umode_t mode,
return p;
}
-static void pde_set_flags(struct proc_dir_entry *pde)
-{
- if (pde->proc_ops->proc_flags & PROC_ENTRY_PERMANENT)
- pde->flags |= PROC_ENTRY_PERMANENT;
- if (pde->proc_ops->proc_read_iter)
- pde->flags |= PROC_ENTRY_proc_read_iter;
-#ifdef CONFIG_COMPAT
- if (pde->proc_ops->proc_compat_ioctl)
- pde->flags |= PROC_ENTRY_proc_compat_ioctl;
-#endif
- if (pde->proc_ops->proc_lseek)
- pde->flags |= PROC_ENTRY_proc_lseek;
-}
-
struct proc_dir_entry *proc_create_data(const char *name, umode_t mode,
struct proc_dir_entry *parent,
const struct proc_ops *proc_ops, void *data)
@@ -581,7 +588,6 @@ struct proc_dir_entry *proc_create_data(const char *name, umode_t mode,
if (!p)
return NULL;
p->proc_ops = proc_ops;
- pde_set_flags(p);
return proc_register(parent, p);
}
EXPORT_SYMBOL(proc_create_data);
@@ -632,7 +638,6 @@ struct proc_dir_entry *proc_create_seq_private(const char *name, umode_t mode,
p->proc_ops = &proc_seq_ops;
p->seq_ops = ops;
p->state_size = state_size;
- pde_set_flags(p);
return proc_register(parent, p);
}
EXPORT_SYMBOL(proc_create_seq_private);
@@ -663,7 +668,6 @@ struct proc_dir_entry *proc_create_single_data(const char *name, umode_t mode,
return NULL;
p->proc_ops = &proc_single_ops;
p->single_show = show;
- pde_set_flags(p);
return proc_register(parent, p);
}
EXPORT_SYMBOL(proc_create_single_data);
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,59 @@
From 51903c441e999d6c7afdf651f0724c26627950d8 Mon Sep 17 00:00:00 2001
From: Abhi Das <adas@redhat.com>
Date: Fri, 17 Apr 2026 13:33:50 -0500
Subject: [PATCH] proc: fix type confusion in pde_set_flags()
JIRA: https://redhat.atlassian.net/browse/RHEL-163346
CVE: CVE-2025-38653
commit 0ce9398aa0830f15f92bbed73853f9861c3e74ff
Author: wangzijie <wangzijie1@honor.com>
Date: Thu Sep 4 21:57:15 2025 +0800
proc: fix type confusion in pde_set_flags()
Commit 2ce3d282bd50 ("proc: fix missing pde_set_flags() for net proc
files") missed a key part in the definition of proc_dir_entry:
union {
const struct proc_ops *proc_ops;
const struct file_operations *proc_dir_ops;
};
So dereference of ->proc_ops assumes it is a proc_ops structure results in
type confusion and make NULL check for 'proc_ops' not work for proc dir.
Add !S_ISDIR(dp->mode) test before calling pde_set_flags() to fix it.
Link: https://lkml.kernel.org/r/20250904135715.3972782-1-wangzijie1@honor.com
Fixes: 2ce3d282bd50 ("proc: fix missing pde_set_flags() for net proc files")
Signed-off-by: wangzijie <wangzijie1@honor.com>
Reported-by: Brad Spengler <spender@grsecurity.net>
Closes: https://lore.kernel.org/all/20250903065758.3678537-1-wangzijie1@honor.com/
Cc: Alexey Dobriyan <adobriyan@gmail.com>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: Christian Brauner <brauner@kernel.org>
Cc: Jiri Slaby <jirislaby@kernel.org>
Cc: Stefano Brivio <sbrivio@redhat.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Abhi Das <adas@redhat.com>
diff --git a/fs/proc/generic.c b/fs/proc/generic.c
index 88423b46e5ce..69150974ad87 100644
--- a/fs/proc/generic.c
+++ b/fs/proc/generic.c
@@ -388,7 +388,8 @@ struct proc_dir_entry *proc_register(struct proc_dir_entry *dir,
if (proc_alloc_inum(&dp->low_ino))
goto out_free_entry;
- pde_set_flags(dp);
+ if (!S_ISDIR(dp->mode))
+ pde_set_flags(dp);
write_lock(&proc_subdir_lock);
dp->parent = dir;
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,74 @@
From f78e5b8575baca940843081e029427cfb2679d32 Mon Sep 17 00:00:00 2001
From: Ming Lei <ming.lei@redhat.com>
Date: Mon, 10 Nov 2025 20:49:20 +0800
Subject: [PATCH] nbd: defer config unlock in nbd_genl_connect
JIRA: https://issues.redhat.com/browse/RHEL-144763
commit 1649714b930f9ea6233ce0810ba885999da3b5d4
Author: Zheng Qixing <zhengqixing@huawei.com>
Date: Mon Nov 10 20:49:20 2025 +0800
nbd: defer config unlock in nbd_genl_connect
There is one use-after-free warning when running NBD_CMD_CONNECT and
NBD_CLEAR_SOCK:
nbd_genl_connect
nbd_alloc_and_init_config // config_refs=1
nbd_start_device // config_refs=2
set NBD_RT_HAS_CONFIG_REF open nbd // config_refs=3
recv_work done // config_refs=2
NBD_CLEAR_SOCK // config_refs=1
close nbd // config_refs=0
refcount_inc -> uaf
------------[ cut here ]------------
refcount_t: addition on 0; use-after-free.
WARNING: CPU: 24 PID: 1014 at lib/refcount.c:25 refcount_warn_saturate+0x12e/0x290
nbd_genl_connect+0x16d0/0x1ab0
genl_family_rcv_msg_doit+0x1f3/0x310
genl_rcv_msg+0x44a/0x790
The issue can be easily reproduced by adding a small delay before
refcount_inc(&nbd->config_refs) in nbd_genl_connect():
mutex_unlock(&nbd->config_lock);
if (!ret) {
set_bit(NBD_RT_HAS_CONFIG_REF, &config->runtime_flags);
+ printk("before sleep\n");
+ mdelay(5 * 1000);
+ printk("after sleep\n");
refcount_inc(&nbd->config_refs);
nbd_connect_reply(info, nbd->index);
}
Fixes: e46c7287b1c2 ("nbd: add a basic netlink interface")
Signed-off-by: Zheng Qixing <zhengqixing@huawei.com>
Reviewed-by: Yu Kuai <yukuai@fnnas.com>
Signed-off-by: Jens Axboe <axboe@kernel.dk>
Signed-off-by: Ming Lei <ming.lei@redhat.com>
diff --git a/drivers/block/nbd.c b/drivers/block/nbd.c
index 215fc18115b7..a05ff68e58d0 100644
--- a/drivers/block/nbd.c
+++ b/drivers/block/nbd.c
@@ -2241,12 +2241,13 @@ static int nbd_genl_connect(struct sk_buff *skb, struct genl_info *info)
ret = nbd_start_device(nbd);
out:
- mutex_unlock(&nbd->config_lock);
if (!ret) {
set_bit(NBD_RT_HAS_CONFIG_REF, &config->runtime_flags);
refcount_inc(&nbd->config_refs);
nbd_connect_reply(info, nbd->index);
}
+ mutex_unlock(&nbd->config_lock);
+
nbd_config_put(nbd);
if (put_dev)
nbd_put(nbd);
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,205 @@
From 70b8cf8679755c2013b28e9f61da1576eab4c707 Mon Sep 17 00:00:00 2001
From: CKI Backport Bot <cki-ci-bot+cki-gitlab-backport-bot@redhat.com>
Date: Mon, 27 Apr 2026 15:02:27 +0000
Subject: [PATCH] crypto: authenc - Correctly pass EINPROGRESS back up to the
caller
JIRA: https://redhat.atlassian.net/browse/RHEL-130557
commit 96feb73def02d175850daa0e7c2c90c876681b5c
Author: Herbert Xu <herbert@gondor.apana.org.au>
Date: Wed Sep 24 18:20:17 2025 +0800
crypto: authenc - Correctly pass EINPROGRESS back up to the caller
When authenc is invoked with MAY_BACKLOG, it needs to pass EINPROGRESS
notifications back up to the caller when the underlying algorithm
returns EBUSY synchronously.
However, if the EBUSY comes from the second part of an authenc call,
i.e., it is asynchronous, both the EBUSY and the subsequent EINPROGRESS
notification must not be passed to the caller.
Implement this by passing a mask to the function that starts the
second half of authenc and using it to determine whether EBUSY
and EINPROGRESS should be passed to the caller.
This was a deficiency in the original implementation of authenc
because it was not expected to be used with MAY_BACKLOG.
Reported-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reported-by: Mikulas Patocka <mpatocka@redhat.com>
Fixes: 180ce7e81030 ("crypto: authenc - Add EINPROGRESS check")
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: CKI Backport Bot <cki-ci-bot+cki-gitlab-backport-bot@redhat.com>
diff --git a/crypto/authenc.c b/crypto/authenc.c
index 3aaf3ab4e360..d04068af9833 100644
--- a/crypto/authenc.c
+++ b/crypto/authenc.c
@@ -39,7 +39,7 @@ struct authenc_request_ctx {
static void authenc_request_complete(struct aead_request *req, int err)
{
- if (err != -EINPROGRESS)
+ if (err != -EINPROGRESS && err != -EBUSY)
aead_request_complete(req, err);
}
@@ -109,27 +109,42 @@ static int crypto_authenc_setkey(struct crypto_aead *authenc, const u8 *key,
return err;
}
-static void authenc_geniv_ahash_done(void *data, int err)
+static void authenc_geniv_ahash_finish(struct aead_request *req)
{
- struct aead_request *req = data;
struct crypto_aead *authenc = crypto_aead_reqtfm(req);
struct aead_instance *inst = aead_alg_instance(authenc);
struct authenc_instance_ctx *ictx = aead_instance_ctx(inst);
struct authenc_request_ctx *areq_ctx = aead_request_ctx(req);
struct ahash_request *ahreq = (void *)(areq_ctx->tail + ictx->reqoff);
- if (err)
- goto out;
-
scatterwalk_map_and_copy(ahreq->result, req->dst,
req->assoclen + req->cryptlen,
crypto_aead_authsize(authenc), 1);
+}
-out:
+static void authenc_geniv_ahash_done(void *data, int err)
+{
+ struct aead_request *req = data;
+
+ if (!err)
+ authenc_geniv_ahash_finish(req);
aead_request_complete(req, err);
}
-static int crypto_authenc_genicv(struct aead_request *req, unsigned int flags)
+/*
+ * Used when the ahash request was invoked in the async callback context
+ * of the previous skcipher request. Eat any EINPROGRESS notifications.
+ */
+static void authenc_geniv_ahash_done2(void *data, int err)
+{
+ struct aead_request *req = data;
+
+ if (!err)
+ authenc_geniv_ahash_finish(req);
+ authenc_request_complete(req, err);
+}
+
+static int crypto_authenc_genicv(struct aead_request *req, unsigned int mask)
{
struct crypto_aead *authenc = crypto_aead_reqtfm(req);
struct aead_instance *inst = aead_alg_instance(authenc);
@@ -138,6 +153,7 @@ static int crypto_authenc_genicv(struct aead_request *req, unsigned int flags)
struct crypto_ahash *auth = ctx->auth;
struct authenc_request_ctx *areq_ctx = aead_request_ctx(req);
struct ahash_request *ahreq = (void *)(areq_ctx->tail + ictx->reqoff);
+ unsigned int flags = aead_request_flags(req) & ~mask;
u8 *hash = areq_ctx->tail;
int err;
@@ -145,7 +161,8 @@ static int crypto_authenc_genicv(struct aead_request *req, unsigned int flags)
ahash_request_set_crypt(ahreq, req->dst, hash,
req->assoclen + req->cryptlen);
ahash_request_set_callback(ahreq, flags,
- authenc_geniv_ahash_done, req);
+ mask ? authenc_geniv_ahash_done2 :
+ authenc_geniv_ahash_done, req);
err = crypto_ahash_digest(ahreq);
if (err)
@@ -161,12 +178,11 @@ static void crypto_authenc_encrypt_done(void *data, int err)
{
struct aead_request *areq = data;
- if (err)
- goto out;
-
- err = crypto_authenc_genicv(areq, 0);
-
-out:
+ if (err) {
+ aead_request_complete(areq, err);
+ return;
+ }
+ err = crypto_authenc_genicv(areq, CRYPTO_TFM_REQ_MAY_SLEEP);
authenc_request_complete(areq, err);
}
@@ -219,11 +235,18 @@ static int crypto_authenc_encrypt(struct aead_request *req)
if (err)
return err;
- return crypto_authenc_genicv(req, aead_request_flags(req));
+ return crypto_authenc_genicv(req, 0);
+}
+
+static void authenc_decrypt_tail_done(void *data, int err)
+{
+ struct aead_request *req = data;
+
+ authenc_request_complete(req, err);
}
static int crypto_authenc_decrypt_tail(struct aead_request *req,
- unsigned int flags)
+ unsigned int mask)
{
struct crypto_aead *authenc = crypto_aead_reqtfm(req);
struct aead_instance *inst = aead_alg_instance(authenc);
@@ -234,6 +257,7 @@ static int crypto_authenc_decrypt_tail(struct aead_request *req,
struct skcipher_request *skreq = (void *)(areq_ctx->tail +
ictx->reqoff);
unsigned int authsize = crypto_aead_authsize(authenc);
+ unsigned int flags = aead_request_flags(req) & ~mask;
u8 *ihash = ahreq->result + authsize;
struct scatterlist *src, *dst;
@@ -250,7 +274,9 @@ static int crypto_authenc_decrypt_tail(struct aead_request *req,
skcipher_request_set_tfm(skreq, ctx->enc);
skcipher_request_set_callback(skreq, flags,
- req->base.complete, req->base.data);
+ mask ? authenc_decrypt_tail_done :
+ req->base.complete,
+ mask ? req : req->base.data);
skcipher_request_set_crypt(skreq, src, dst,
req->cryptlen - authsize, req->iv);
@@ -261,12 +287,11 @@ static void authenc_verify_ahash_done(void *data, int err)
{
struct aead_request *req = data;
- if (err)
- goto out;
-
- err = crypto_authenc_decrypt_tail(req, 0);
-
-out:
+ if (err) {
+ aead_request_complete(req, err);
+ return;
+ }
+ err = crypto_authenc_decrypt_tail(req, CRYPTO_TFM_REQ_MAY_SLEEP);
authenc_request_complete(req, err);
}
@@ -293,7 +318,7 @@ static int crypto_authenc_decrypt(struct aead_request *req)
if (err)
return err;
- return crypto_authenc_decrypt_tail(req, aead_request_flags(req));
+ return crypto_authenc_decrypt_tail(req, 0);
}
static int crypto_authenc_init_tfm(struct crypto_aead *tfm)
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,426 @@
From bf31f0732fbaa881f28bf71e012d2aba97e59316 Mon Sep 17 00:00:00 2001
From: Ivan Vecera <ivecera@redhat.com>
Date: Wed, 29 Apr 2026 14:50:08 +0200
Subject: [PATCH] dpll: zl3073x: detect DPLL channel count from chip ID at
runtime
JIRA: https://issues.redhat.com/browse/RHEL-171979
commit 4845f2fff730f0cdf8f7fe6401c8b871891cf1cb
Author: Ivan Vecera <ivecera@redhat.com>
Date: Fri Feb 27 11:52:59 2026 +0100
dpll: zl3073x: detect DPLL channel count from chip ID at runtime
Replace the five per-variant zl3073x_chip_info structures and their
exported symbol definitions with a single consolidated chip ID lookup
table. The chip variant is now detected at runtime by reading the chip
ID register from hardware and looking it up in the table, rather than
being selected at compile time via the bus driver match data.
Repurpose struct zl3073x_chip_info to hold a single chip ID, its
channel count, and a flags field. Introduce enum zl3073x_flags with
ZL3073X_FLAG_REF_PHASE_COMP_32 to replace the chip_id switch statement
in zl3073x_dev_is_ref_phase_comp_32bit(). Store a pointer to the
detected chip_info entry in struct zl3073x_dev for runtime access.
This simplifies the bus drivers by removing per-variant .data and
.driver_data references from the I2C/SPI match tables, and makes
adding support for new chip variants a single-line table addition.
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
Link: https://patch.msgid.link/20260227105300.710272-2-ivecera@redhat.com
Reviewed-by: Simon Horman <horms@kernel.org>
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
(cherry picked from commit 4845f2fff730f0cdf8f7fe6401c8b871891cf1cb)
Assisted-by: Patchpal
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c
index 37f3c33570ee..c8af34301045 100644
--- a/drivers/dpll/zl3073x/core.c
+++ b/drivers/dpll/zl3073x/core.c
@@ -20,79 +20,30 @@
#include "dpll.h"
#include "regs.h"
-/* Chip IDs for zl30731 */
-static const u16 zl30731_ids[] = {
- 0x0E93,
- 0x1E93,
- 0x2E93,
+#define ZL_CHIP_INFO(_id, _nchannels, _flags) \
+ { .id = (_id), .num_channels = (_nchannels), .flags = (_flags) }
+
+static const struct zl3073x_chip_info zl3073x_chip_ids[] = {
+ ZL_CHIP_INFO(0x0E30, 2, ZL3073X_FLAG_REF_PHASE_COMP_32),
+ ZL_CHIP_INFO(0x0E93, 1, ZL3073X_FLAG_REF_PHASE_COMP_32),
+ ZL_CHIP_INFO(0x0E94, 2, ZL3073X_FLAG_REF_PHASE_COMP_32),
+ ZL_CHIP_INFO(0x0E95, 3, ZL3073X_FLAG_REF_PHASE_COMP_32),
+ ZL_CHIP_INFO(0x0E96, 4, ZL3073X_FLAG_REF_PHASE_COMP_32),
+ ZL_CHIP_INFO(0x0E97, 5, ZL3073X_FLAG_REF_PHASE_COMP_32),
+ ZL_CHIP_INFO(0x1E93, 1, 0),
+ ZL_CHIP_INFO(0x1E94, 2, 0),
+ ZL_CHIP_INFO(0x1E95, 3, 0),
+ ZL_CHIP_INFO(0x1E96, 4, 0),
+ ZL_CHIP_INFO(0x1E97, 5, 0),
+ ZL_CHIP_INFO(0x1F60, 2, ZL3073X_FLAG_REF_PHASE_COMP_32),
+ ZL_CHIP_INFO(0x2E93, 1, 0),
+ ZL_CHIP_INFO(0x2E94, 2, 0),
+ ZL_CHIP_INFO(0x2E95, 3, 0),
+ ZL_CHIP_INFO(0x2E96, 4, 0),
+ ZL_CHIP_INFO(0x2E97, 5, 0),
+ ZL_CHIP_INFO(0x3FC4, 2, 0),
};
-const struct zl3073x_chip_info zl30731_chip_info = {
- .ids = zl30731_ids,
- .num_ids = ARRAY_SIZE(zl30731_ids),
- .num_channels = 1,
-};
-EXPORT_SYMBOL_NS_GPL(zl30731_chip_info, "ZL3073X");
-
-/* Chip IDs for zl30732 */
-static const u16 zl30732_ids[] = {
- 0x0E30,
- 0x0E94,
- 0x1E94,
- 0x1F60,
- 0x2E94,
- 0x3FC4,
-};
-
-const struct zl3073x_chip_info zl30732_chip_info = {
- .ids = zl30732_ids,
- .num_ids = ARRAY_SIZE(zl30732_ids),
- .num_channels = 2,
-};
-EXPORT_SYMBOL_NS_GPL(zl30732_chip_info, "ZL3073X");
-
-/* Chip IDs for zl30733 */
-static const u16 zl30733_ids[] = {
- 0x0E95,
- 0x1E95,
- 0x2E95,
-};
-
-const struct zl3073x_chip_info zl30733_chip_info = {
- .ids = zl30733_ids,
- .num_ids = ARRAY_SIZE(zl30733_ids),
- .num_channels = 3,
-};
-EXPORT_SYMBOL_NS_GPL(zl30733_chip_info, "ZL3073X");
-
-/* Chip IDs for zl30734 */
-static const u16 zl30734_ids[] = {
- 0x0E96,
- 0x1E96,
- 0x2E96,
-};
-
-const struct zl3073x_chip_info zl30734_chip_info = {
- .ids = zl30734_ids,
- .num_ids = ARRAY_SIZE(zl30734_ids),
- .num_channels = 4,
-};
-EXPORT_SYMBOL_NS_GPL(zl30734_chip_info, "ZL3073X");
-
-/* Chip IDs for zl30735 */
-static const u16 zl30735_ids[] = {
- 0x0E97,
- 0x1E97,
- 0x2E97,
-};
-
-const struct zl3073x_chip_info zl30735_chip_info = {
- .ids = zl30735_ids,
- .num_ids = ARRAY_SIZE(zl30735_ids),
- .num_channels = 5,
-};
-EXPORT_SYMBOL_NS_GPL(zl30735_chip_info, "ZL3073X");
-
#define ZL_RANGE_OFFSET 0x80
#define ZL_PAGE_SIZE 0x80
#define ZL_NUM_PAGES 256
@@ -942,7 +893,7 @@ static void zl3073x_dev_dpll_fini(void *ptr)
}
static int
-zl3073x_devm_dpll_init(struct zl3073x_dev *zldev, u8 num_dplls)
+zl3073x_devm_dpll_init(struct zl3073x_dev *zldev)
{
struct kthread_worker *kworker;
struct zl3073x_dpll *zldpll;
@@ -952,7 +903,7 @@ zl3073x_devm_dpll_init(struct zl3073x_dev *zldev, u8 num_dplls)
INIT_LIST_HEAD(&zldev->dplls);
/* Allocate all DPLLs */
- for (i = 0; i < num_dplls; i++) {
+ for (i = 0; i < zldev->info->num_channels; i++) {
zldpll = zl3073x_dpll_alloc(zldev, i);
if (IS_ERR(zldpll)) {
dev_err_probe(zldev->dev, PTR_ERR(zldpll),
@@ -992,14 +943,12 @@ zl3073x_devm_dpll_init(struct zl3073x_dev *zldev, u8 num_dplls)
/**
* zl3073x_dev_probe - initialize zl3073x device
* @zldev: pointer to zl3073x device
- * @chip_info: chip info based on compatible
*
* Common initialization of zl3073x device structure.
*
* Returns: 0 on success, <0 on error
*/
-int zl3073x_dev_probe(struct zl3073x_dev *zldev,
- const struct zl3073x_chip_info *chip_info)
+int zl3073x_dev_probe(struct zl3073x_dev *zldev)
{
u16 id, revision, fw_ver;
unsigned int i;
@@ -1011,18 +960,17 @@ int zl3073x_dev_probe(struct zl3073x_dev *zldev,
if (rc)
return rc;
- /* Check it matches */
- for (i = 0; i < chip_info->num_ids; i++) {
- if (id == chip_info->ids[i])
+ /* Detect chip variant */
+ for (i = 0; i < ARRAY_SIZE(zl3073x_chip_ids); i++) {
+ if (zl3073x_chip_ids[i].id == id)
break;
}
- if (i == chip_info->num_ids) {
+ if (i == ARRAY_SIZE(zl3073x_chip_ids))
return dev_err_probe(zldev->dev, -ENODEV,
- "Unknown or non-match chip ID: 0x%0x\n",
- id);
- }
- zldev->chip_id = id;
+ "Unknown chip ID: 0x%04x\n", id);
+
+ zldev->info = &zl3073x_chip_ids[i];
/* Read revision, firmware version and custom config version */
rc = zl3073x_read_u16(zldev, ZL_REG_REVISION, &revision);
@@ -1061,7 +1009,7 @@ int zl3073x_dev_probe(struct zl3073x_dev *zldev,
"Failed to initialize mutex\n");
/* Register DPLL channels */
- rc = zl3073x_devm_dpll_init(zldev, chip_info->num_channels);
+ rc = zl3073x_devm_dpll_init(zldev);
if (rc)
return rc;
diff --git a/drivers/dpll/zl3073x/core.h b/drivers/dpll/zl3073x/core.h
index fd2af3c62a7d..fde5c8371fbd 100644
--- a/drivers/dpll/zl3073x/core.h
+++ b/drivers/dpll/zl3073x/core.h
@@ -30,12 +30,32 @@ struct zl3073x_dpll;
#define ZL3073X_NUM_PINS (ZL3073X_NUM_INPUT_PINS + \
ZL3073X_NUM_OUTPUT_PINS)
+enum zl3073x_flags {
+ ZL3073X_FLAG_REF_PHASE_COMP_32_BIT,
+ ZL3073X_FLAGS_NBITS /* must be last */
+};
+
+#define __ZL3073X_FLAG(name) BIT(ZL3073X_FLAG_ ## name ## _BIT)
+#define ZL3073X_FLAG_REF_PHASE_COMP_32 __ZL3073X_FLAG(REF_PHASE_COMP_32)
+
+/**
+ * struct zl3073x_chip_info - chip variant identification
+ * @id: chip ID
+ * @num_channels: number of DPLL channels supported by this variant
+ * @flags: chip variant flags
+ */
+struct zl3073x_chip_info {
+ u16 id;
+ u8 num_channels;
+ unsigned long flags;
+};
+
/**
* struct zl3073x_dev - zl3073x device
* @dev: pointer to device
* @regmap: regmap to access device registers
+ * @info: detected chip info
* @multiop_lock: to serialize multiple register operations
- * @chip_id: chip ID read from hardware
* @ref: array of input references' invariants
* @out: array of outs' invariants
* @synth: array of synths' invariants
@@ -46,10 +66,10 @@ struct zl3073x_dpll;
* @phase_avg_factor: phase offset measurement averaging factor
*/
struct zl3073x_dev {
- struct device *dev;
- struct regmap *regmap;
- struct mutex multiop_lock;
- u16 chip_id;
+ struct device *dev;
+ struct regmap *regmap;
+ const struct zl3073x_chip_info *info;
+ struct mutex multiop_lock;
/* Invariants */
struct zl3073x_ref ref[ZL3073X_NUM_REFS];
@@ -68,22 +88,10 @@ struct zl3073x_dev {
u8 phase_avg_factor;
};
-struct zl3073x_chip_info {
- const u16 *ids;
- size_t num_ids;
- int num_channels;
-};
-
-extern const struct zl3073x_chip_info zl30731_chip_info;
-extern const struct zl3073x_chip_info zl30732_chip_info;
-extern const struct zl3073x_chip_info zl30733_chip_info;
-extern const struct zl3073x_chip_info zl30734_chip_info;
-extern const struct zl3073x_chip_info zl30735_chip_info;
extern const struct regmap_config zl3073x_regmap_config;
struct zl3073x_dev *zl3073x_devm_alloc(struct device *dev);
-int zl3073x_dev_probe(struct zl3073x_dev *zldev,
- const struct zl3073x_chip_info *chip_info);
+int zl3073x_dev_probe(struct zl3073x_dev *zldev);
int zl3073x_dev_start(struct zl3073x_dev *zldev, bool full);
void zl3073x_dev_stop(struct zl3073x_dev *zldev);
@@ -158,18 +166,7 @@ int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel);
static inline bool
zl3073x_dev_is_ref_phase_comp_32bit(struct zl3073x_dev *zldev)
{
- switch (zldev->chip_id) {
- case 0x0E30:
- case 0x0E93:
- case 0x0E94:
- case 0x0E95:
- case 0x0E96:
- case 0x0E97:
- case 0x1F60:
- return true;
- default:
- return false;
- }
+ return zldev->info->flags & ZL3073X_FLAG_REF_PHASE_COMP_32;
}
static inline bool
diff --git a/drivers/dpll/zl3073x/i2c.c b/drivers/dpll/zl3073x/i2c.c
index 7bbfdd4ed867..979df85826ab 100644
--- a/drivers/dpll/zl3073x/i2c.c
+++ b/drivers/dpll/zl3073x/i2c.c
@@ -22,40 +22,25 @@ static int zl3073x_i2c_probe(struct i2c_client *client)
return dev_err_probe(dev, PTR_ERR(zldev->regmap),
"Failed to initialize regmap\n");
- return zl3073x_dev_probe(zldev, i2c_get_match_data(client));
+ return zl3073x_dev_probe(zldev);
}
static const struct i2c_device_id zl3073x_i2c_id[] = {
- {
- .name = "zl30731",
- .driver_data = (kernel_ulong_t)&zl30731_chip_info,
- },
- {
- .name = "zl30732",
- .driver_data = (kernel_ulong_t)&zl30732_chip_info,
- },
- {
- .name = "zl30733",
- .driver_data = (kernel_ulong_t)&zl30733_chip_info,
- },
- {
- .name = "zl30734",
- .driver_data = (kernel_ulong_t)&zl30734_chip_info,
- },
- {
- .name = "zl30735",
- .driver_data = (kernel_ulong_t)&zl30735_chip_info,
- },
+ { "zl30731" },
+ { "zl30732" },
+ { "zl30733" },
+ { "zl30734" },
+ { "zl30735" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(i2c, zl3073x_i2c_id);
static const struct of_device_id zl3073x_i2c_of_match[] = {
- { .compatible = "microchip,zl30731", .data = &zl30731_chip_info },
- { .compatible = "microchip,zl30732", .data = &zl30732_chip_info },
- { .compatible = "microchip,zl30733", .data = &zl30733_chip_info },
- { .compatible = "microchip,zl30734", .data = &zl30734_chip_info },
- { .compatible = "microchip,zl30735", .data = &zl30735_chip_info },
+ { .compatible = "microchip,zl30731" },
+ { .compatible = "microchip,zl30732" },
+ { .compatible = "microchip,zl30733" },
+ { .compatible = "microchip,zl30734" },
+ { .compatible = "microchip,zl30735" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, zl3073x_i2c_of_match);
diff --git a/drivers/dpll/zl3073x/spi.c b/drivers/dpll/zl3073x/spi.c
index af901b4d6dda..f024f42b78d0 100644
--- a/drivers/dpll/zl3073x/spi.c
+++ b/drivers/dpll/zl3073x/spi.c
@@ -22,40 +22,25 @@ static int zl3073x_spi_probe(struct spi_device *spi)
return dev_err_probe(dev, PTR_ERR(zldev->regmap),
"Failed to initialize regmap\n");
- return zl3073x_dev_probe(zldev, spi_get_device_match_data(spi));
+ return zl3073x_dev_probe(zldev);
}
static const struct spi_device_id zl3073x_spi_id[] = {
- {
- .name = "zl30731",
- .driver_data = (kernel_ulong_t)&zl30731_chip_info
- },
- {
- .name = "zl30732",
- .driver_data = (kernel_ulong_t)&zl30732_chip_info,
- },
- {
- .name = "zl30733",
- .driver_data = (kernel_ulong_t)&zl30733_chip_info,
- },
- {
- .name = "zl30734",
- .driver_data = (kernel_ulong_t)&zl30734_chip_info,
- },
- {
- .name = "zl30735",
- .driver_data = (kernel_ulong_t)&zl30735_chip_info,
- },
+ { "zl30731" },
+ { "zl30732" },
+ { "zl30733" },
+ { "zl30734" },
+ { "zl30735" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(spi, zl3073x_spi_id);
static const struct of_device_id zl3073x_spi_of_match[] = {
- { .compatible = "microchip,zl30731", .data = &zl30731_chip_info },
- { .compatible = "microchip,zl30732", .data = &zl30732_chip_info },
- { .compatible = "microchip,zl30733", .data = &zl30733_chip_info },
- { .compatible = "microchip,zl30734", .data = &zl30734_chip_info },
- { .compatible = "microchip,zl30735", .data = &zl30735_chip_info },
+ { .compatible = "microchip,zl30731" },
+ { .compatible = "microchip,zl30732" },
+ { .compatible = "microchip,zl30733" },
+ { .compatible = "microchip,zl30734" },
+ { .compatible = "microchip,zl30735" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, zl3073x_spi_of_match);
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,183 @@
From 0320baa880dd1e760bf8d15d5d8da898b265f4ed Mon Sep 17 00:00:00 2001
From: Ivan Vecera <ivecera@redhat.com>
Date: Wed, 29 Apr 2026 14:50:27 +0200
Subject: [PATCH] dpll: zl3073x: add die temperature reporting for supported
chips
JIRA: https://issues.redhat.com/browse/RHEL-171979
commit 3a97e02b3e91e4d40095ad9bb6e466d8d7c1a1bc
Author: Ivan Vecera <ivecera@redhat.com>
Date: Fri Feb 27 11:53:00 2026 +0100
dpll: zl3073x: add die temperature reporting for supported chips
Some zl3073x chip variants (0x1Exx, 0x2Exx and 0x3FC4) provide a die
temperature status register with 0.1 C resolution.
Add a ZL3073X_FLAG_DIE_TEMP chip flag to identify these variants and
implement zl3073x_dpll_temp_get() as the dpll_device_ops.temp_get
callback. The register value is converted from 0.1 C units to
millidegrees as expected by the DPLL subsystem.
To support per-instance ops selection, copy the base dpll_device_ops
into struct zl3073x_dpll and conditionally set .temp_get during device
registration based on the chip flag.
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
Link: https://patch.msgid.link/20260227105300.710272-3-ivecera@redhat.com
Reviewed-by: Simon Horman <horms@kernel.org>
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
(cherry picked from commit 3a97e02b3e91e4d40095ad9bb6e466d8d7c1a1bc)
Assisted-by: Patchpal
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c
index c8af34301045..10e036ccf08f 100644
--- a/drivers/dpll/zl3073x/core.c
+++ b/drivers/dpll/zl3073x/core.c
@@ -30,18 +30,18 @@ static const struct zl3073x_chip_info zl3073x_chip_ids[] = {
ZL_CHIP_INFO(0x0E95, 3, ZL3073X_FLAG_REF_PHASE_COMP_32),
ZL_CHIP_INFO(0x0E96, 4, ZL3073X_FLAG_REF_PHASE_COMP_32),
ZL_CHIP_INFO(0x0E97, 5, ZL3073X_FLAG_REF_PHASE_COMP_32),
- ZL_CHIP_INFO(0x1E93, 1, 0),
- ZL_CHIP_INFO(0x1E94, 2, 0),
- ZL_CHIP_INFO(0x1E95, 3, 0),
- ZL_CHIP_INFO(0x1E96, 4, 0),
- ZL_CHIP_INFO(0x1E97, 5, 0),
+ ZL_CHIP_INFO(0x1E93, 1, ZL3073X_FLAG_DIE_TEMP),
+ ZL_CHIP_INFO(0x1E94, 2, ZL3073X_FLAG_DIE_TEMP),
+ ZL_CHIP_INFO(0x1E95, 3, ZL3073X_FLAG_DIE_TEMP),
+ ZL_CHIP_INFO(0x1E96, 4, ZL3073X_FLAG_DIE_TEMP),
+ ZL_CHIP_INFO(0x1E97, 5, ZL3073X_FLAG_DIE_TEMP),
ZL_CHIP_INFO(0x1F60, 2, ZL3073X_FLAG_REF_PHASE_COMP_32),
- ZL_CHIP_INFO(0x2E93, 1, 0),
- ZL_CHIP_INFO(0x2E94, 2, 0),
- ZL_CHIP_INFO(0x2E95, 3, 0),
- ZL_CHIP_INFO(0x2E96, 4, 0),
- ZL_CHIP_INFO(0x2E97, 5, 0),
- ZL_CHIP_INFO(0x3FC4, 2, 0),
+ ZL_CHIP_INFO(0x2E93, 1, ZL3073X_FLAG_DIE_TEMP),
+ ZL_CHIP_INFO(0x2E94, 2, ZL3073X_FLAG_DIE_TEMP),
+ ZL_CHIP_INFO(0x2E95, 3, ZL3073X_FLAG_DIE_TEMP),
+ ZL_CHIP_INFO(0x2E96, 4, ZL3073X_FLAG_DIE_TEMP),
+ ZL_CHIP_INFO(0x2E97, 5, ZL3073X_FLAG_DIE_TEMP),
+ ZL_CHIP_INFO(0x3FC4, 2, ZL3073X_FLAG_DIE_TEMP),
};
#define ZL_RANGE_OFFSET 0x80
diff --git a/drivers/dpll/zl3073x/core.h b/drivers/dpll/zl3073x/core.h
index fde5c8371fbd..b6f22ee1c0bd 100644
--- a/drivers/dpll/zl3073x/core.h
+++ b/drivers/dpll/zl3073x/core.h
@@ -32,11 +32,13 @@ struct zl3073x_dpll;
enum zl3073x_flags {
ZL3073X_FLAG_REF_PHASE_COMP_32_BIT,
+ ZL3073X_FLAG_DIE_TEMP_BIT,
ZL3073X_FLAGS_NBITS /* must be last */
};
#define __ZL3073X_FLAG(name) BIT(ZL3073X_FLAG_ ## name ## _BIT)
#define ZL3073X_FLAG_REF_PHASE_COMP_32 __ZL3073X_FLAG(REF_PHASE_COMP_32)
+#define ZL3073X_FLAG_DIE_TEMP __ZL3073X_FLAG(DIE_TEMP)
/**
* struct zl3073x_chip_info - chip variant identification
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index 8ffbede117c6..31926a74c414 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -1065,6 +1065,25 @@ zl3073x_dpll_output_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin,
return 0;
}
+static int
+zl3073x_dpll_temp_get(const struct dpll_device *dpll, void *dpll_priv,
+ s32 *temp, struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+ struct zl3073x_dev *zldev = zldpll->dev;
+ u16 val;
+ int rc;
+
+ rc = zl3073x_read_u16(zldev, ZL_REG_DIE_TEMP_STATUS, &val);
+ if (rc)
+ return rc;
+
+ /* Register value is in units of 0.1 C, convert to millidegrees */
+ *temp = (s16)val * 100;
+
+ return 0;
+}
+
static int
zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv,
enum dpll_lock_status *status,
@@ -1671,6 +1690,10 @@ zl3073x_dpll_device_register(struct zl3073x_dpll *zldpll)
zldpll->forced_ref = FIELD_GET(ZL_DPLL_MODE_REFSEL_REF,
dpll_mode_refsel);
+ zldpll->ops = zl3073x_dpll_device_ops;
+ if (zldev->info->flags & ZL3073X_FLAG_DIE_TEMP)
+ zldpll->ops.temp_get = zl3073x_dpll_temp_get;
+
zldpll->dpll_dev = dpll_device_get(zldev->clock_id, zldpll->id,
THIS_MODULE, &zldpll->tracker);
if (IS_ERR(zldpll->dpll_dev)) {
@@ -1682,7 +1705,7 @@ zl3073x_dpll_device_register(struct zl3073x_dpll *zldpll)
rc = dpll_device_register(zldpll->dpll_dev,
zl3073x_prop_dpll_type_get(zldev, zldpll->id),
- &zl3073x_dpll_device_ops, zldpll);
+ &zldpll->ops, zldpll);
if (rc) {
dpll_device_put(zldpll->dpll_dev, &zldpll->tracker);
zldpll->dpll_dev = NULL;
@@ -1705,8 +1728,7 @@ zl3073x_dpll_device_unregister(struct zl3073x_dpll *zldpll)
cancel_work_sync(&zldpll->change_work);
- dpll_device_unregister(zldpll->dpll_dev, &zl3073x_dpll_device_ops,
- zldpll);
+ dpll_device_unregister(zldpll->dpll_dev, &zldpll->ops, zldpll);
dpll_device_put(zldpll->dpll_dev, &zldpll->tracker);
zldpll->dpll_dev = NULL;
}
diff --git a/drivers/dpll/zl3073x/dpll.h b/drivers/dpll/zl3073x/dpll.h
index c65c798c3792..278a24f357c9 100644
--- a/drivers/dpll/zl3073x/dpll.h
+++ b/drivers/dpll/zl3073x/dpll.h
@@ -17,6 +17,7 @@
* @forced_ref: selected reference in forced reference lock mode
* @check_count: periodic check counter
* @phase_monitor: is phase offset monitor enabled
+ * @ops: DPLL device operations for this instance
* @dpll_dev: pointer to registered DPLL device
* @tracker: tracking object for the acquired reference
* @lock_status: last saved DPLL lock status
@@ -31,6 +32,7 @@ struct zl3073x_dpll {
u8 forced_ref;
u8 check_count;
bool phase_monitor;
+ struct dpll_device_ops ops;
struct dpll_device *dpll_dev;
dpll_tracker tracker;
enum dpll_lock_status lock_status;
diff --git a/drivers/dpll/zl3073x/regs.h b/drivers/dpll/zl3073x/regs.h
index 5573d7188406..19c598daa784 100644
--- a/drivers/dpll/zl3073x/regs.h
+++ b/drivers/dpll/zl3073x/regs.h
@@ -78,6 +78,8 @@
#define ZL_REG_RESET_STATUS ZL_REG(0, 0x18, 1)
#define ZL_REG_RESET_STATUS_RESET BIT(0)
+#define ZL_REG_DIE_TEMP_STATUS ZL_REG(0, 0x44, 2)
+
/*************************
* Register Page 2, Status
*************************/
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,305 @@
From f222542cbe20b882e741d32029bd9d3a2136cb1c Mon Sep 17 00:00:00 2001
From: Ivan Vecera <ivecera@redhat.com>
Date: Wed, 29 Apr 2026 14:50:47 +0200
Subject: [PATCH] dpll: zl3073x: use struct_group to partition states
JIRA: https://issues.redhat.com/browse/RHEL-171979
commit f327f5a8115e80d954598cf2d5c461873042b7f6
Author: Ivan Vecera <ivecera@redhat.com>
Date: Sun Mar 15 18:42:19 2026 +0100
dpll: zl3073x: use struct_group to partition states
Organize the zl3073x_out, zl3073x_ref, and zl3073x_synth structures
using struct_group() to partition fields into semantic groups:
* cfg: mutable configuration written to HW via state_set
* inv: invariant fields set once during state_fetch
* stat: read-only status
This enables group-level operations in place of field-by-field copies:
* state_set validates invariants haven't changed (WARN_ON + -EINVAL)
* state_set short-circuits when cfg is unchanged
* state_set copy entire groups in a single assignment instead of
enumerating each field
Add kernel doc for zl3073x_out_state_set and zl3073x_ref_state_set
documenting the new invariant validation and short-circuit semantics.
Remove forward declaration of zl3073x_synth_state_set().
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
Link: https://patch.msgid.link/20260315174224.399074-2-ivecera@redhat.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
(cherry picked from commit f327f5a8115e80d954598cf2d5c461873042b7f6)
Assisted-by: Patchpal
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
diff --git a/drivers/dpll/zl3073x/out.c b/drivers/dpll/zl3073x/out.c
index 86829a0c1c02..eb5628aebcee 100644
--- a/drivers/dpll/zl3073x/out.c
+++ b/drivers/dpll/zl3073x/out.c
@@ -106,12 +106,32 @@ const struct zl3073x_out *zl3073x_out_state_get(struct zl3073x_dev *zldev,
return &zldev->out[index];
}
+/**
+ * zl3073x_out_state_set - commit output state changes to hardware
+ * @zldev: pointer to zl3073x_dev structure
+ * @index: output index to set state for
+ * @out: desired output state
+ *
+ * Validates that invariant fields have not been modified, skips the HW
+ * write if the mutable configuration is unchanged, and otherwise writes
+ * only the changed cfg fields to hardware via the mailbox interface.
+ *
+ * Return: 0 on success, -EINVAL if invariants changed, <0 on HW error
+ */
int zl3073x_out_state_set(struct zl3073x_dev *zldev, u8 index,
const struct zl3073x_out *out)
{
struct zl3073x_out *dout = &zldev->out[index];
int rc;
+ /* Reject attempts to change invariant fields (set at fetch only) */
+ if (WARN_ON(memcmp(&dout->inv, &out->inv, sizeof(out->inv))))
+ return -EINVAL;
+
+ /* Skip HW write if configuration hasn't changed */
+ if (!memcmp(&dout->cfg, &out->cfg, sizeof(out->cfg)))
+ return 0;
+
guard(mutex)(&zldev->multiop_lock);
/* Read output configuration into mailbox */
@@ -146,12 +166,7 @@ int zl3073x_out_state_set(struct zl3073x_dev *zldev, u8 index,
return rc;
/* After successful commit store new state */
- dout->div = out->div;
- dout->width = out->width;
- dout->esync_n_period = out->esync_n_period;
- dout->esync_n_width = out->esync_n_width;
- dout->mode = out->mode;
- dout->phase_comp = out->phase_comp;
+ dout->cfg = out->cfg;
return 0;
}
diff --git a/drivers/dpll/zl3073x/out.h b/drivers/dpll/zl3073x/out.h
index 318f9bb8da3a..edf40432bba5 100644
--- a/drivers/dpll/zl3073x/out.h
+++ b/drivers/dpll/zl3073x/out.h
@@ -4,6 +4,7 @@
#define _ZL3073X_OUT_H
#include <linux/bitfield.h>
+#include <linux/stddef.h>
#include <linux/types.h>
#include "regs.h"
@@ -17,17 +18,21 @@ struct zl3073x_dev;
* @esync_n_period: embedded sync or n-pin period (for n-div formats)
* @esync_n_width: embedded sync or n-pin pulse width
* @phase_comp: phase compensation
- * @ctrl: output control
* @mode: output mode
+ * @ctrl: output control
*/
struct zl3073x_out {
- u32 div;
- u32 width;
- u32 esync_n_period;
- u32 esync_n_width;
- s32 phase_comp;
- u8 ctrl;
- u8 mode;
+ struct_group(cfg, /* Config */
+ u32 div;
+ u32 width;
+ u32 esync_n_period;
+ u32 esync_n_width;
+ s32 phase_comp;
+ u8 mode;
+ );
+ struct_group(inv, /* Invariants */
+ u8 ctrl;
+ );
};
int zl3073x_out_state_fetch(struct zl3073x_dev *zldev, u8 index);
diff --git a/drivers/dpll/zl3073x/ref.c b/drivers/dpll/zl3073x/ref.c
index 6b65e6103999..8b4c4807bcc4 100644
--- a/drivers/dpll/zl3073x/ref.c
+++ b/drivers/dpll/zl3073x/ref.c
@@ -73,14 +73,8 @@ int zl3073x_ref_state_fetch(struct zl3073x_dev *zldev, u8 index)
struct zl3073x_ref *p_ref = ref - 1; /* P-pin counterpart*/
/* Copy the shared items from the P-pin */
- ref->config = p_ref->config;
- ref->esync_n_div = p_ref->esync_n_div;
- ref->freq_base = p_ref->freq_base;
- ref->freq_mult = p_ref->freq_mult;
- ref->freq_ratio_m = p_ref->freq_ratio_m;
- ref->freq_ratio_n = p_ref->freq_ratio_n;
- ref->phase_comp = p_ref->phase_comp;
- ref->sync_ctrl = p_ref->sync_ctrl;
+ ref->cfg = p_ref->cfg;
+ ref->inv = p_ref->inv;
return 0; /* Finish - no non-shared items for now */
}
@@ -154,12 +148,32 @@ zl3073x_ref_state_get(struct zl3073x_dev *zldev, u8 index)
return &zldev->ref[index];
}
+/**
+ * zl3073x_ref_state_set - commit input reference state changes to hardware
+ * @zldev: pointer to zl3073x_dev structure
+ * @index: input reference index to set state for
+ * @ref: desired reference state
+ *
+ * Validates that invariant fields have not been modified, skips the HW
+ * write if the mutable configuration is unchanged, and otherwise writes
+ * only the changed cfg fields to hardware via the mailbox interface.
+ *
+ * Return: 0 on success, -EINVAL if invariants changed, <0 on HW error
+ */
int zl3073x_ref_state_set(struct zl3073x_dev *zldev, u8 index,
const struct zl3073x_ref *ref)
{
struct zl3073x_ref *dref = &zldev->ref[index];
int rc;
+ /* Reject attempts to change invariant fields (set at init only) */
+ if (WARN_ON(memcmp(&dref->inv, &ref->inv, sizeof(ref->inv))))
+ return -EINVAL;
+
+ /* Skip HW write if configuration hasn't changed */
+ if (!memcmp(&dref->cfg, &ref->cfg, sizeof(ref->cfg)))
+ return 0;
+
guard(mutex)(&zldev->multiop_lock);
/* Read reference configuration into mailbox */
@@ -207,13 +221,7 @@ int zl3073x_ref_state_set(struct zl3073x_dev *zldev, u8 index,
return rc;
/* After successful commit store new state */
- dref->freq_base = ref->freq_base;
- dref->freq_mult = ref->freq_mult;
- dref->freq_ratio_m = ref->freq_ratio_m;
- dref->freq_ratio_n = ref->freq_ratio_n;
- dref->esync_n_div = ref->esync_n_div;
- dref->sync_ctrl = ref->sync_ctrl;
- dref->phase_comp = ref->phase_comp;
+ dref->cfg = ref->cfg;
return 0;
}
diff --git a/drivers/dpll/zl3073x/ref.h b/drivers/dpll/zl3073x/ref.h
index 0d8618f5ce8d..ab3a816c2910 100644
--- a/drivers/dpll/zl3073x/ref.h
+++ b/drivers/dpll/zl3073x/ref.h
@@ -5,6 +5,7 @@
#include <linux/bitfield.h>
#include <linux/math64.h>
+#include <linux/stddef.h>
#include <linux/types.h>
#include "regs.h"
@@ -13,28 +14,34 @@ struct zl3073x_dev;
/**
* struct zl3073x_ref - input reference state
- * @ffo: current fractional frequency offset
* @phase_comp: phase compensation
* @esync_n_div: divisor for embedded sync or n-divided signal formats
* @freq_base: frequency base
* @freq_mult: frequnecy multiplier
* @freq_ratio_m: FEC mode multiplier
* @freq_ratio_n: FEC mode divisor
- * @config: reference config
* @sync_ctrl: reference sync control
+ * @config: reference config
+ * @ffo: current fractional frequency offset
* @mon_status: reference monitor status
*/
struct zl3073x_ref {
- s64 ffo;
- u64 phase_comp;
- u32 esync_n_div;
- u16 freq_base;
- u16 freq_mult;
- u16 freq_ratio_m;
- u16 freq_ratio_n;
- u8 config;
- u8 sync_ctrl;
- u8 mon_status;
+ struct_group(cfg, /* Configuration */
+ u64 phase_comp;
+ u32 esync_n_div;
+ u16 freq_base;
+ u16 freq_mult;
+ u16 freq_ratio_m;
+ u16 freq_ratio_n;
+ u8 sync_ctrl;
+ );
+ struct_group(inv, /* Invariants */
+ u8 config;
+ );
+ struct_group(stat, /* Status */
+ s64 ffo;
+ u8 mon_status;
+ );
};
int zl3073x_ref_state_fetch(struct zl3073x_dev *zldev, u8 index);
diff --git a/drivers/dpll/zl3073x/synth.h b/drivers/dpll/zl3073x/synth.h
index 6c55eb8a888c..89e13ea2e6d9 100644
--- a/drivers/dpll/zl3073x/synth.h
+++ b/drivers/dpll/zl3073x/synth.h
@@ -5,6 +5,7 @@
#include <linux/bitfield.h>
#include <linux/math64.h>
+#include <linux/stddef.h>
#include <linux/types.h>
#include "regs.h"
@@ -20,11 +21,13 @@ struct zl3073x_dev;
* @ctrl: synth control
*/
struct zl3073x_synth {
- u32 freq_mult;
- u16 freq_base;
- u16 freq_m;
- u16 freq_n;
- u8 ctrl;
+ struct_group(inv, /* Invariants */
+ u32 freq_mult;
+ u16 freq_base;
+ u16 freq_m;
+ u16 freq_n;
+ u8 ctrl;
+ );
};
int zl3073x_synth_state_fetch(struct zl3073x_dev *zldev, u8 synth_id);
@@ -32,9 +35,6 @@ int zl3073x_synth_state_fetch(struct zl3073x_dev *zldev, u8 synth_id);
const struct zl3073x_synth *zl3073x_synth_state_get(struct zl3073x_dev *zldev,
u8 synth_id);
-int zl3073x_synth_state_set(struct zl3073x_dev *zldev, u8 synth_id,
- const struct zl3073x_synth *synth);
-
/**
* zl3073x_synth_dpll_get - get DPLL ID the synth is driven by
* @synth: pointer to synth state
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,115 @@
From 98b442e23ddaf8448ee6e7ed30dcbeb137a4f7ad Mon Sep 17 00:00:00 2001
From: Ivan Vecera <ivecera@redhat.com>
Date: Wed, 29 Apr 2026 14:51:01 +0200
Subject: [PATCH] dpll: zl3073x: add zl3073x_ref_state_update helper
JIRA: https://issues.redhat.com/browse/RHEL-171979
commit 05ea2ab3b10075e8fcfcd5e283e486df7055de5e
Author: Ivan Vecera <ivecera@redhat.com>
Date: Sun Mar 15 18:42:20 2026 +0100
dpll: zl3073x: add zl3073x_ref_state_update helper
Extract the per-reference monitor status HW read into a dedicated
zl3073x_ref_state_update() helper in the ref module. Rename
zl3073x_dev_ref_status_update() to zl3073x_dev_ref_states_update()
and use the new helper in it. Call it from zl3073x_ref_state_fetch()
as well so that mon_status is initialized at device startup. This
keeps direct register access and struct field writes behind the ref
module's interface, consistent with the state management pattern
used for other ref operations.
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
Link: https://patch.msgid.link/20260315174224.399074-3-ivecera@redhat.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
(cherry picked from commit 05ea2ab3b10075e8fcfcd5e283e486df7055de5e)
Assisted-by: Patchpal
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c
index 10e036ccf08f..07626082aae3 100644
--- a/drivers/dpll/zl3073x/core.c
+++ b/drivers/dpll/zl3073x/core.c
@@ -543,13 +543,12 @@ zl3073x_dev_state_fetch(struct zl3073x_dev *zldev)
}
static void
-zl3073x_dev_ref_status_update(struct zl3073x_dev *zldev)
+zl3073x_dev_ref_states_update(struct zl3073x_dev *zldev)
{
int i, rc;
for (i = 0; i < ZL3073X_NUM_REFS; i++) {
- rc = zl3073x_read_u8(zldev, ZL_REG_REF_MON_STATUS(i),
- &zldev->ref[i].mon_status);
+ rc = zl3073x_ref_state_update(zldev, i);
if (rc)
dev_warn(zldev->dev,
"Failed to get REF%u status: %pe\n", i,
@@ -679,8 +678,8 @@ zl3073x_dev_periodic_work(struct kthread_work *work)
struct zl3073x_dpll *zldpll;
int rc;
- /* Update input references status */
- zl3073x_dev_ref_status_update(zldev);
+ /* Update input references' states */
+ zl3073x_dev_ref_states_update(zldev);
/* Update DPLL-to-connected-ref phase offsets registers */
rc = zl3073x_ref_phase_offsets_update(zldev, -1);
diff --git a/drivers/dpll/zl3073x/ref.c b/drivers/dpll/zl3073x/ref.c
index 8b4c4807bcc4..825ac30bcd6f 100644
--- a/drivers/dpll/zl3073x/ref.c
+++ b/drivers/dpll/zl3073x/ref.c
@@ -51,6 +51,21 @@ zl3073x_ref_freq_factorize(u32 freq, u16 *base, u16 *mult)
return -EINVAL;
}
+/**
+ * zl3073x_ref_state_update - update input reference status from HW
+ * @zldev: pointer to zl3073x_dev structure
+ * @index: input reference index
+ *
+ * Return: 0 on success, <0 on error
+ */
+int zl3073x_ref_state_update(struct zl3073x_dev *zldev, u8 index)
+{
+ struct zl3073x_ref *ref = &zldev->ref[index];
+
+ return zl3073x_read_u8(zldev, ZL_REG_REF_MON_STATUS(index),
+ &ref->mon_status);
+}
+
/**
* zl3073x_ref_state_fetch - fetch input reference state from hardware
* @zldev: pointer to zl3073x_dev structure
@@ -79,6 +94,11 @@ int zl3073x_ref_state_fetch(struct zl3073x_dev *zldev, u8 index)
return 0; /* Finish - no non-shared items for now */
}
+ /* Read reference status */
+ rc = zl3073x_ref_state_update(zldev, index);
+ if (rc)
+ return rc;
+
guard(mutex)(&zldev->multiop_lock);
/* Read reference configuration */
diff --git a/drivers/dpll/zl3073x/ref.h b/drivers/dpll/zl3073x/ref.h
index ab3a816c2910..06d8d4d97ea2 100644
--- a/drivers/dpll/zl3073x/ref.h
+++ b/drivers/dpll/zl3073x/ref.h
@@ -52,6 +52,8 @@ const struct zl3073x_ref *zl3073x_ref_state_get(struct zl3073x_dev *zldev,
int zl3073x_ref_state_set(struct zl3073x_dev *zldev, u8 index,
const struct zl3073x_ref *ref);
+int zl3073x_ref_state_update(struct zl3073x_dev *zldev, u8 index);
+
int zl3073x_ref_freq_factorize(u32 freq, u16 *base, u16 *mult);
/**
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,563 @@
From 99589ed2ea279188527603207e5beeeeb232c7ed Mon Sep 17 00:00:00 2001
From: Ivan Vecera <ivecera@redhat.com>
Date: Wed, 29 Apr 2026 14:51:15 +0200
Subject: [PATCH] dpll: zl3073x: introduce zl3073x_chan for DPLL channel state
JIRA: https://issues.redhat.com/browse/RHEL-171979
commit 3032e95987fa0da656ce3a5eb454674e7cc60a12
Author: Ivan Vecera <ivecera@redhat.com>
Date: Sun Mar 15 18:42:21 2026 +0100
dpll: zl3073x: introduce zl3073x_chan for DPLL channel state
Extract DPLL channel state management into a dedicated zl3073x_chan
module, following the pattern already established by zl3073x_ref,
zl3073x_out and zl3073x_synth.
The new struct zl3073x_chan caches the raw mode_refsel register value
in a cfg group with inline getters and setters to extract and update
the bitfields. Three standard state management functions are provided:
- zl3073x_chan_state_fetch: read the mode_refsel register from HW
- zl3073x_chan_state_get: return cached channel state
- zl3073x_chan_state_set: write changed state to HW, skip if unchanged
The channel state array chan[ZL3073X_MAX_CHANNELS] is added to struct
zl3073x_dev. Channel state is fetched as part of
zl3073x_dev_state_fetch, using the chip-specific channel count.
The refsel_mode and forced_ref fields are removed from struct
zl3073x_dpll and all direct register accesses in dpll.c are replaced
with the new chan state operations.
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
Link: https://patch.msgid.link/20260315174224.399074-4-ivecera@redhat.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
(cherry picked from commit 3032e95987fa0da656ce3a5eb454674e7cc60a12)
Assisted-by: Patchpal
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
diff --git a/drivers/dpll/zl3073x/Makefile b/drivers/dpll/zl3073x/Makefile
index bd324c7fe710..906ec3fbcc20 100644
--- a/drivers/dpll/zl3073x/Makefile
+++ b/drivers/dpll/zl3073x/Makefile
@@ -1,8 +1,8 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_ZL3073X) += zl3073x.o
-zl3073x-objs := core.o devlink.o dpll.o flash.o fw.o \
- out.o prop.o ref.o synth.o
+zl3073x-objs := chan.o core.o devlink.o dpll.o \
+ flash.o fw.o out.o prop.o ref.o synth.o
obj-$(CONFIG_ZL3073X_I2C) += zl3073x_i2c.o
zl3073x_i2c-objs := i2c.o
diff --git a/drivers/dpll/zl3073x/chan.c b/drivers/dpll/zl3073x/chan.c
new file mode 100644
index 000000000000..8019b8ce7351
--- /dev/null
+++ b/drivers/dpll/zl3073x/chan.c
@@ -0,0 +1,79 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/dev_printk.h>
+#include <linux/string.h>
+#include <linux/types.h>
+
+#include "chan.h"
+#include "core.h"
+
+/**
+ * zl3073x_chan_state_fetch - fetch DPLL channel state from hardware
+ * @zldev: pointer to zl3073x_dev structure
+ * @index: DPLL channel index to fetch state for
+ *
+ * Reads the mode_refsel register for the given DPLL channel and stores
+ * the raw value for later use.
+ *
+ * Return: 0 on success, <0 on error
+ */
+int zl3073x_chan_state_fetch(struct zl3073x_dev *zldev, u8 index)
+{
+ struct zl3073x_chan *chan = &zldev->chan[index];
+ int rc;
+
+ rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index),
+ &chan->mode_refsel);
+ if (rc)
+ return rc;
+
+ dev_dbg(zldev->dev, "DPLL%u mode: %u, ref: %u\n", index,
+ zl3073x_chan_mode_get(chan), zl3073x_chan_ref_get(chan));
+
+ return 0;
+}
+
+/**
+ * zl3073x_chan_state_get - get current DPLL channel state
+ * @zldev: pointer to zl3073x_dev structure
+ * @index: DPLL channel index to get state for
+ *
+ * Return: pointer to given DPLL channel state
+ */
+const struct zl3073x_chan *zl3073x_chan_state_get(struct zl3073x_dev *zldev,
+ u8 index)
+{
+ return &zldev->chan[index];
+}
+
+/**
+ * zl3073x_chan_state_set - commit DPLL channel state changes to hardware
+ * @zldev: pointer to zl3073x_dev structure
+ * @index: DPLL channel index to set state for
+ * @chan: desired channel state
+ *
+ * Skips the HW write if the configuration is unchanged, and otherwise
+ * writes the mode_refsel register to hardware.
+ *
+ * Return: 0 on success, <0 on HW error
+ */
+int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index,
+ const struct zl3073x_chan *chan)
+{
+ struct zl3073x_chan *dchan = &zldev->chan[index];
+ int rc;
+
+ /* Skip HW write if configuration hasn't changed */
+ if (!memcmp(&dchan->cfg, &chan->cfg, sizeof(chan->cfg)))
+ return 0;
+
+ rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index),
+ chan->mode_refsel);
+ if (rc)
+ return rc;
+
+ /* After successful write store new state */
+ dchan->cfg = chan->cfg;
+
+ return 0;
+}
diff --git a/drivers/dpll/zl3073x/chan.h b/drivers/dpll/zl3073x/chan.h
new file mode 100644
index 000000000000..3e6ffaef0c74
--- /dev/null
+++ b/drivers/dpll/zl3073x/chan.h
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef _ZL3073X_CHAN_H
+#define _ZL3073X_CHAN_H
+
+#include <linux/bitfield.h>
+#include <linux/stddef.h>
+#include <linux/types.h>
+
+#include "regs.h"
+
+struct zl3073x_dev;
+
+/**
+ * struct zl3073x_chan - DPLL channel state
+ * @mode_refsel: mode and reference selection register value
+ */
+struct zl3073x_chan {
+ struct_group(cfg,
+ u8 mode_refsel;
+ );
+};
+
+int zl3073x_chan_state_fetch(struct zl3073x_dev *zldev, u8 index);
+const struct zl3073x_chan *zl3073x_chan_state_get(struct zl3073x_dev *zldev,
+ u8 index);
+int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index,
+ const struct zl3073x_chan *chan);
+
+/**
+ * zl3073x_chan_mode_get - get DPLL channel operating mode
+ * @chan: pointer to channel state
+ *
+ * Return: reference selection mode of the given DPLL channel
+ */
+static inline u8 zl3073x_chan_mode_get(const struct zl3073x_chan *chan)
+{
+ return FIELD_GET(ZL_DPLL_MODE_REFSEL_MODE, chan->mode_refsel);
+}
+
+/**
+ * zl3073x_chan_ref_get - get manually selected reference
+ * @chan: pointer to channel state
+ *
+ * Return: reference selected in forced reference lock mode
+ */
+static inline u8 zl3073x_chan_ref_get(const struct zl3073x_chan *chan)
+{
+ return FIELD_GET(ZL_DPLL_MODE_REFSEL_REF, chan->mode_refsel);
+}
+
+/**
+ * zl3073x_chan_mode_set - set DPLL channel operating mode
+ * @chan: pointer to channel state
+ * @mode: mode to set
+ */
+static inline void zl3073x_chan_mode_set(struct zl3073x_chan *chan, u8 mode)
+{
+ chan->mode_refsel &= ~ZL_DPLL_MODE_REFSEL_MODE;
+ chan->mode_refsel |= FIELD_PREP(ZL_DPLL_MODE_REFSEL_MODE, mode);
+}
+
+/**
+ * zl3073x_chan_ref_set - set manually selected reference
+ * @chan: pointer to channel state
+ * @ref: reference to set
+ */
+static inline void zl3073x_chan_ref_set(struct zl3073x_chan *chan, u8 ref)
+{
+ chan->mode_refsel &= ~ZL_DPLL_MODE_REFSEL_REF;
+ chan->mode_refsel |= FIELD_PREP(ZL_DPLL_MODE_REFSEL_REF, ref);
+}
+
+#endif /* _ZL3073X_CHAN_H */
diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c
index 07626082aae3..b03e59fa0834 100644
--- a/drivers/dpll/zl3073x/core.c
+++ b/drivers/dpll/zl3073x/core.c
@@ -539,6 +539,16 @@ zl3073x_dev_state_fetch(struct zl3073x_dev *zldev)
}
}
+ for (i = 0; i < zldev->info->num_channels; i++) {
+ rc = zl3073x_chan_state_fetch(zldev, i);
+ if (rc) {
+ dev_err(zldev->dev,
+ "Failed to fetch channel state: %pe\n",
+ ERR_PTR(rc));
+ return rc;
+ }
+ }
+
return rc;
}
diff --git a/drivers/dpll/zl3073x/core.h b/drivers/dpll/zl3073x/core.h
index b6f22ee1c0bd..2cfb9dd74aa5 100644
--- a/drivers/dpll/zl3073x/core.h
+++ b/drivers/dpll/zl3073x/core.h
@@ -9,6 +9,7 @@
#include <linux/mutex.h>
#include <linux/types.h>
+#include "chan.h"
#include "out.h"
#include "ref.h"
#include "regs.h"
@@ -61,6 +62,7 @@ struct zl3073x_chip_info {
* @ref: array of input references' invariants
* @out: array of outs' invariants
* @synth: array of synths' invariants
+ * @chan: array of DPLL channels' state
* @dplls: list of DPLLs
* @kworker: thread for periodic work
* @work: periodic work
@@ -77,6 +79,7 @@ struct zl3073x_dev {
struct zl3073x_ref ref[ZL3073X_NUM_REFS];
struct zl3073x_out out[ZL3073X_NUM_OUTS];
struct zl3073x_synth synth[ZL3073X_NUM_SYNTHS];
+ struct zl3073x_chan chan[ZL3073X_MAX_CHANNELS];
/* DPLL channels */
struct list_head dplls;
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index 31926a74c414..767af6dd724d 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -259,10 +259,13 @@ static int
zl3073x_dpll_selected_ref_get(struct zl3073x_dpll *zldpll, u8 *ref)
{
struct zl3073x_dev *zldev = zldpll->dev;
+ const struct zl3073x_chan *chan;
u8 state, value;
int rc;
- switch (zldpll->refsel_mode) {
+ chan = zl3073x_chan_state_get(zldev, zldpll->id);
+
+ switch (zl3073x_chan_mode_get(chan)) {
case ZL_DPLL_MODE_REFSEL_MODE_AUTO:
/* For automatic mode read refsel_status register */
rc = zl3073x_read_u8(zldev,
@@ -282,7 +285,7 @@ zl3073x_dpll_selected_ref_get(struct zl3073x_dpll *zldpll, u8 *ref)
break;
case ZL_DPLL_MODE_REFSEL_MODE_REFLOCK:
/* For manual mode return stored value */
- *ref = zldpll->forced_ref;
+ *ref = zl3073x_chan_ref_get(chan);
break;
default:
/* For other modes like NCO, freerun... there is no input ref */
@@ -307,10 +310,11 @@ static int
zl3073x_dpll_selected_ref_set(struct zl3073x_dpll *zldpll, u8 ref)
{
struct zl3073x_dev *zldev = zldpll->dev;
- u8 mode, mode_refsel;
- int rc;
+ struct zl3073x_chan chan;
+ u8 mode;
- mode = zldpll->refsel_mode;
+ chan = *zl3073x_chan_state_get(zldev, zldpll->id);
+ mode = zl3073x_chan_mode_get(&chan);
switch (mode) {
case ZL_DPLL_MODE_REFSEL_MODE_REFLOCK:
@@ -328,8 +332,8 @@ zl3073x_dpll_selected_ref_set(struct zl3073x_dpll *zldpll, u8 ref)
break;
}
/* Keep selected reference */
- ref = zldpll->forced_ref;
- } else if (ref == zldpll->forced_ref) {
+ ref = zl3073x_chan_ref_get(&chan);
+ } else if (ref == zl3073x_chan_ref_get(&chan)) {
/* No register update - same mode and same ref */
return 0;
}
@@ -351,21 +355,10 @@ zl3073x_dpll_selected_ref_set(struct zl3073x_dpll *zldpll, u8 ref)
return -EOPNOTSUPP;
}
- /* Build mode_refsel value */
- mode_refsel = FIELD_PREP(ZL_DPLL_MODE_REFSEL_MODE, mode) |
- FIELD_PREP(ZL_DPLL_MODE_REFSEL_REF, ref);
-
- /* Update dpll_mode_refsel register */
- rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(zldpll->id),
- mode_refsel);
- if (rc)
- return rc;
-
- /* Store new mode and forced reference */
- zldpll->refsel_mode = mode;
- zldpll->forced_ref = ref;
+ zl3073x_chan_mode_set(&chan, mode);
+ zl3073x_chan_ref_set(&chan, ref);
- return rc;
+ return zl3073x_chan_state_set(zldev, zldpll->id, &chan);
}
/**
@@ -624,9 +617,11 @@ zl3073x_dpll_ref_state_get(struct zl3073x_dpll_pin *pin,
{
struct zl3073x_dpll *zldpll = pin->dpll;
struct zl3073x_dev *zldev = zldpll->dev;
+ const struct zl3073x_chan *chan;
u8 ref, ref_conn;
int rc;
+ chan = zl3073x_chan_state_get(zldev, zldpll->id);
ref = zl3073x_input_pin_ref_get(pin->id);
/* Get currently connected reference */
@@ -643,7 +638,7 @@ zl3073x_dpll_ref_state_get(struct zl3073x_dpll_pin *pin,
* selectable and its monitor does not report any error then report
* pin as selectable.
*/
- if (zldpll->refsel_mode == ZL_DPLL_MODE_REFSEL_MODE_AUTO &&
+ if (zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_AUTO &&
zl3073x_dev_ref_is_status_ok(zldev, ref) && pin->selectable) {
*state = DPLL_PIN_STATE_SELECTABLE;
return 0;
@@ -678,10 +673,13 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin,
{
struct zl3073x_dpll *zldpll = dpll_priv;
struct zl3073x_dpll_pin *pin = pin_priv;
+ const struct zl3073x_chan *chan;
u8 new_ref;
int rc;
- switch (zldpll->refsel_mode) {
+ chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+
+ switch (zl3073x_chan_mode_get(chan)) {
case ZL_DPLL_MODE_REFSEL_MODE_REFLOCK:
case ZL_DPLL_MODE_REFSEL_MODE_FREERUN:
case ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER:
@@ -1092,10 +1090,13 @@ zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv,
{
struct zl3073x_dpll *zldpll = dpll_priv;
struct zl3073x_dev *zldev = zldpll->dev;
+ const struct zl3073x_chan *chan;
u8 mon_status, state;
int rc;
- switch (zldpll->refsel_mode) {
+ chan = zl3073x_chan_state_get(zldev, zldpll->id);
+
+ switch (zl3073x_chan_mode_get(chan)) {
case ZL_DPLL_MODE_REFSEL_MODE_FREERUN:
case ZL_DPLL_MODE_REFSEL_MODE_NCO:
/* In FREERUN and NCO modes the DPLL is always unlocked */
@@ -1140,13 +1141,16 @@ zl3073x_dpll_supported_modes_get(const struct dpll_device *dpll,
struct netlink_ext_ack *extack)
{
struct zl3073x_dpll *zldpll = dpll_priv;
+ const struct zl3073x_chan *chan;
+
+ chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
/* We support switching between automatic and manual mode, except in
* a case where the DPLL channel is configured to run in NCO mode.
* In this case, report only the manual mode to which the NCO is mapped
* as the only supported one.
*/
- if (zldpll->refsel_mode != ZL_DPLL_MODE_REFSEL_MODE_NCO)
+ if (zl3073x_chan_mode_get(chan) != ZL_DPLL_MODE_REFSEL_MODE_NCO)
__set_bit(DPLL_MODE_AUTOMATIC, modes);
__set_bit(DPLL_MODE_MANUAL, modes);
@@ -1159,8 +1163,11 @@ zl3073x_dpll_mode_get(const struct dpll_device *dpll, void *dpll_priv,
enum dpll_mode *mode, struct netlink_ext_ack *extack)
{
struct zl3073x_dpll *zldpll = dpll_priv;
+ const struct zl3073x_chan *chan;
- switch (zldpll->refsel_mode) {
+ chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+
+ switch (zl3073x_chan_mode_get(chan)) {
case ZL_DPLL_MODE_REFSEL_MODE_FREERUN:
case ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER:
case ZL_DPLL_MODE_REFSEL_MODE_NCO:
@@ -1239,7 +1246,8 @@ zl3073x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv,
enum dpll_mode mode, struct netlink_ext_ack *extack)
{
struct zl3073x_dpll *zldpll = dpll_priv;
- u8 hw_mode, mode_refsel, ref;
+ struct zl3073x_chan chan;
+ u8 hw_mode, ref;
int rc;
rc = zl3073x_dpll_selected_ref_get(zldpll, &ref);
@@ -1287,26 +1295,18 @@ zl3073x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv,
hw_mode = ZL_DPLL_MODE_REFSEL_MODE_AUTO;
}
- /* Build mode_refsel value */
- mode_refsel = FIELD_PREP(ZL_DPLL_MODE_REFSEL_MODE, hw_mode);
-
+ chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+ zl3073x_chan_mode_set(&chan, hw_mode);
if (ZL3073X_DPLL_REF_IS_VALID(ref))
- mode_refsel |= FIELD_PREP(ZL_DPLL_MODE_REFSEL_REF, ref);
+ zl3073x_chan_ref_set(&chan, ref);
- /* Update dpll_mode_refsel register */
- rc = zl3073x_write_u8(zldpll->dev, ZL_REG_DPLL_MODE_REFSEL(zldpll->id),
- mode_refsel);
+ rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan);
if (rc) {
NL_SET_ERR_MSG_MOD(extack,
"failed to set reference selection mode");
return rc;
}
- zldpll->refsel_mode = hw_mode;
-
- if (ZL3073X_DPLL_REF_IS_VALID(ref))
- zldpll->forced_ref = ref;
-
return 0;
}
@@ -1559,15 +1559,18 @@ zl3073x_dpll_pin_is_registrable(struct zl3073x_dpll *zldpll,
enum dpll_pin_direction dir, u8 index)
{
struct zl3073x_dev *zldev = zldpll->dev;
+ const struct zl3073x_chan *chan;
bool is_diff, is_enabled;
const char *name;
+ chan = zl3073x_chan_state_get(zldev, zldpll->id);
+
if (dir == DPLL_PIN_DIRECTION_INPUT) {
u8 ref_id = zl3073x_input_pin_ref_get(index);
const struct zl3073x_ref *ref;
/* Skip the pin if the DPLL is running in NCO mode */
- if (zldpll->refsel_mode == ZL_DPLL_MODE_REFSEL_MODE_NCO)
+ if (zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_NCO)
return false;
name = "REF";
@@ -1675,21 +1678,8 @@ static int
zl3073x_dpll_device_register(struct zl3073x_dpll *zldpll)
{
struct zl3073x_dev *zldev = zldpll->dev;
- u8 dpll_mode_refsel;
int rc;
- /* Read DPLL mode and forcibly selected reference */
- rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(zldpll->id),
- &dpll_mode_refsel);
- if (rc)
- return rc;
-
- /* Extract mode and selected input reference */
- zldpll->refsel_mode = FIELD_GET(ZL_DPLL_MODE_REFSEL_MODE,
- dpll_mode_refsel);
- zldpll->forced_ref = FIELD_GET(ZL_DPLL_MODE_REFSEL_REF,
- dpll_mode_refsel);
-
zldpll->ops = zl3073x_dpll_device_ops;
if (zldev->info->flags & ZL3073X_FLAG_DIE_TEMP)
zldpll->ops.temp_get = zl3073x_dpll_temp_get;
@@ -1844,8 +1834,10 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll)
struct zl3073x_dev *zldev = zldpll->dev;
enum dpll_lock_status lock_status;
struct device *dev = zldev->dev;
+ const struct zl3073x_chan *chan;
struct zl3073x_dpll_pin *pin;
int rc;
+ u8 mode;
zldpll->check_count++;
@@ -1867,8 +1859,10 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll)
/* Input pin monitoring does make sense only in automatic
* or forced reference modes.
*/
- if (zldpll->refsel_mode != ZL_DPLL_MODE_REFSEL_MODE_AUTO &&
- zldpll->refsel_mode != ZL_DPLL_MODE_REFSEL_MODE_REFLOCK)
+ chan = zl3073x_chan_state_get(zldev, zldpll->id);
+ mode = zl3073x_chan_mode_get(chan);
+ if (mode != ZL_DPLL_MODE_REFSEL_MODE_AUTO &&
+ mode != ZL_DPLL_MODE_REFSEL_MODE_REFLOCK)
return;
/* Update phase offset latch registers for this DPLL if the phase
diff --git a/drivers/dpll/zl3073x/dpll.h b/drivers/dpll/zl3073x/dpll.h
index 278a24f357c9..115ee4f67e7a 100644
--- a/drivers/dpll/zl3073x/dpll.h
+++ b/drivers/dpll/zl3073x/dpll.h
@@ -13,8 +13,6 @@
* @list: this DPLL list entry
* @dev: pointer to multi-function parent device
* @id: DPLL index
- * @refsel_mode: reference selection mode
- * @forced_ref: selected reference in forced reference lock mode
* @check_count: periodic check counter
* @phase_monitor: is phase offset monitor enabled
* @ops: DPLL device operations for this instance
@@ -28,8 +26,6 @@ struct zl3073x_dpll {
struct list_head list;
struct zl3073x_dev *dev;
u8 id;
- u8 refsel_mode;
- u8 forced_ref;
u8 check_count;
bool phase_monitor;
struct dpll_device_ops ops;
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,288 @@
From 1325513643b0d024227fc1ef13470e2a84989622 Mon Sep 17 00:00:00 2001
From: Ivan Vecera <ivecera@redhat.com>
Date: Wed, 29 Apr 2026 14:51:26 +0200
Subject: [PATCH] dpll: zl3073x: add DPLL channel status fields to zl3073x_chan
JIRA: https://issues.redhat.com/browse/RHEL-171979
commit 41bab554d7e9840e17e4c8c7e93647161c6596bf
Author: Ivan Vecera <ivecera@redhat.com>
Date: Sun Mar 15 18:42:22 2026 +0100
dpll: zl3073x: add DPLL channel status fields to zl3073x_chan
Add mon_status and refsel_status fields to struct zl3073x_chan in a
stat group to cache the 'dpll_mon_status' and 'dpll_refsel_status'
registers.
Add zl3073x_chan_lock_state_get(), zl3073x_chan_is_ho_ready(),
zl3073x_chan_refsel_state_get() and zl3073x_chan_refsel_ref_get()
inline helpers for reading cached state, and zl3073x_chan_state_update()
for refreshing both registers from hardware. Call it from
zl3073x_chan_state_fetch() as well so that channel status is
initialized at device startup.
Call zl3073x_dev_chan_states_update() from the periodic work to
keep the cached state up to date and convert
zl3073x_dpll_lock_status_get() and zl3073x_dpll_selected_ref_get()
to use the cached state via the new helpers instead of direct register
reads.
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
Link: https://patch.msgid.link/20260315174224.399074-5-ivecera@redhat.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
(cherry picked from commit 41bab554d7e9840e17e4c8c7e93647161c6596bf)
Assisted-by: Patchpal
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
diff --git a/drivers/dpll/zl3073x/chan.c b/drivers/dpll/zl3073x/chan.c
index 8019b8ce7351..71fb60a9859b 100644
--- a/drivers/dpll/zl3073x/chan.c
+++ b/drivers/dpll/zl3073x/chan.c
@@ -7,6 +7,27 @@
#include "chan.h"
#include "core.h"
+/**
+ * zl3073x_chan_state_update - update DPLL channel status from HW
+ * @zldev: pointer to zl3073x_dev structure
+ * @index: DPLL channel index
+ *
+ * Return: 0 on success, <0 on error
+ */
+int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index)
+{
+ struct zl3073x_chan *chan = &zldev->chan[index];
+ int rc;
+
+ rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MON_STATUS(index),
+ &chan->mon_status);
+ if (rc)
+ return rc;
+
+ return zl3073x_read_u8(zldev, ZL_REG_DPLL_REFSEL_STATUS(index),
+ &chan->refsel_status);
+}
+
/**
* zl3073x_chan_state_fetch - fetch DPLL channel state from hardware
* @zldev: pointer to zl3073x_dev structure
@@ -30,6 +51,17 @@ int zl3073x_chan_state_fetch(struct zl3073x_dev *zldev, u8 index)
dev_dbg(zldev->dev, "DPLL%u mode: %u, ref: %u\n", index,
zl3073x_chan_mode_get(chan), zl3073x_chan_ref_get(chan));
+ rc = zl3073x_chan_state_update(zldev, index);
+ if (rc)
+ return rc;
+
+ dev_dbg(zldev->dev,
+ "DPLL%u lock_state: %u, ho: %u, sel_state: %u, sel_ref: %u\n",
+ index, zl3073x_chan_lock_state_get(chan),
+ zl3073x_chan_is_ho_ready(chan) ? 1 : 0,
+ zl3073x_chan_refsel_state_get(chan),
+ zl3073x_chan_refsel_ref_get(chan));
+
return 0;
}
diff --git a/drivers/dpll/zl3073x/chan.h b/drivers/dpll/zl3073x/chan.h
index 3e6ffaef0c74..f73a07610855 100644
--- a/drivers/dpll/zl3073x/chan.h
+++ b/drivers/dpll/zl3073x/chan.h
@@ -14,11 +14,17 @@ struct zl3073x_dev;
/**
* struct zl3073x_chan - DPLL channel state
* @mode_refsel: mode and reference selection register value
+ * @mon_status: monitor status register value
+ * @refsel_status: reference selection status register value
*/
struct zl3073x_chan {
struct_group(cfg,
u8 mode_refsel;
);
+ struct_group(stat,
+ u8 mon_status;
+ u8 refsel_status;
+ );
};
int zl3073x_chan_state_fetch(struct zl3073x_dev *zldev, u8 index);
@@ -27,6 +33,8 @@ const struct zl3073x_chan *zl3073x_chan_state_get(struct zl3073x_dev *zldev,
int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index,
const struct zl3073x_chan *chan);
+int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index);
+
/**
* zl3073x_chan_mode_get - get DPLL channel operating mode
* @chan: pointer to channel state
@@ -71,4 +79,48 @@ static inline void zl3073x_chan_ref_set(struct zl3073x_chan *chan, u8 ref)
chan->mode_refsel |= FIELD_PREP(ZL_DPLL_MODE_REFSEL_REF, ref);
}
+/**
+ * zl3073x_chan_lock_state_get - get DPLL channel lock state
+ * @chan: pointer to channel state
+ *
+ * Return: lock state of the given DPLL channel
+ */
+static inline u8 zl3073x_chan_lock_state_get(const struct zl3073x_chan *chan)
+{
+ return FIELD_GET(ZL_DPLL_MON_STATUS_STATE, chan->mon_status);
+}
+
+/**
+ * zl3073x_chan_is_ho_ready - check if holdover is ready
+ * @chan: pointer to channel state
+ *
+ * Return: true if holdover is ready, false otherwise
+ */
+static inline bool zl3073x_chan_is_ho_ready(const struct zl3073x_chan *chan)
+{
+ return !!FIELD_GET(ZL_DPLL_MON_STATUS_HO_READY, chan->mon_status);
+}
+
+/**
+ * zl3073x_chan_refsel_state_get - get reference selection state
+ * @chan: pointer to channel state
+ *
+ * Return: reference selection state of the given DPLL channel
+ */
+static inline u8 zl3073x_chan_refsel_state_get(const struct zl3073x_chan *chan)
+{
+ return FIELD_GET(ZL_DPLL_REFSEL_STATUS_STATE, chan->refsel_status);
+}
+
+/**
+ * zl3073x_chan_refsel_ref_get - get currently selected reference in auto mode
+ * @chan: pointer to channel state
+ *
+ * Return: reference selected by the DPLL in automatic mode
+ */
+static inline u8 zl3073x_chan_refsel_ref_get(const struct zl3073x_chan *chan)
+{
+ return FIELD_GET(ZL_DPLL_REFSEL_STATUS_REFSEL, chan->refsel_status);
+}
+
#endif /* _ZL3073X_CHAN_H */
diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c
index b03e59fa0834..6363002d48d4 100644
--- a/drivers/dpll/zl3073x/core.c
+++ b/drivers/dpll/zl3073x/core.c
@@ -566,6 +566,20 @@ zl3073x_dev_ref_states_update(struct zl3073x_dev *zldev)
}
}
+static void
+zl3073x_dev_chan_states_update(struct zl3073x_dev *zldev)
+{
+ int i, rc;
+
+ for (i = 0; i < zldev->info->num_channels; i++) {
+ rc = zl3073x_chan_state_update(zldev, i);
+ if (rc)
+ dev_warn(zldev->dev,
+ "Failed to get DPLL%u state: %pe\n", i,
+ ERR_PTR(rc));
+ }
+}
+
/**
* zl3073x_ref_phase_offsets_update - update reference phase offsets
* @zldev: pointer to zl3073x_dev structure
@@ -691,6 +705,9 @@ zl3073x_dev_periodic_work(struct kthread_work *work)
/* Update input references' states */
zl3073x_dev_ref_states_update(zldev);
+ /* Update DPLL channels' states */
+ zl3073x_dev_chan_states_update(zldev);
+
/* Update DPLL-to-connected-ref phase offsets registers */
rc = zl3073x_ref_phase_offsets_update(zldev, -1);
if (rc)
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index 767af6dd724d..33c4f05ec9d3 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -258,28 +258,16 @@ zl3073x_dpll_input_pin_frequency_set(const struct dpll_pin *dpll_pin,
static int
zl3073x_dpll_selected_ref_get(struct zl3073x_dpll *zldpll, u8 *ref)
{
- struct zl3073x_dev *zldev = zldpll->dev;
const struct zl3073x_chan *chan;
- u8 state, value;
- int rc;
- chan = zl3073x_chan_state_get(zldev, zldpll->id);
+ chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
switch (zl3073x_chan_mode_get(chan)) {
case ZL_DPLL_MODE_REFSEL_MODE_AUTO:
- /* For automatic mode read refsel_status register */
- rc = zl3073x_read_u8(zldev,
- ZL_REG_DPLL_REFSEL_STATUS(zldpll->id),
- &value);
- if (rc)
- return rc;
-
- /* Extract reference state */
- state = FIELD_GET(ZL_DPLL_REFSEL_STATUS_STATE, value);
-
/* Return the reference only if the DPLL is locked to it */
- if (state == ZL_DPLL_REFSEL_STATUS_STATE_LOCK)
- *ref = FIELD_GET(ZL_DPLL_REFSEL_STATUS_REFSEL, value);
+ if (zl3073x_chan_refsel_state_get(chan) ==
+ ZL_DPLL_REFSEL_STATUS_STATE_LOCK)
+ *ref = zl3073x_chan_refsel_ref_get(chan);
else
*ref = ZL3073X_DPLL_REF_NONE;
break;
@@ -1089,12 +1077,9 @@ zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv,
struct netlink_ext_ack *extack)
{
struct zl3073x_dpll *zldpll = dpll_priv;
- struct zl3073x_dev *zldev = zldpll->dev;
const struct zl3073x_chan *chan;
- u8 mon_status, state;
- int rc;
- chan = zl3073x_chan_state_get(zldev, zldpll->id);
+ chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
switch (zl3073x_chan_mode_get(chan)) {
case ZL_DPLL_MODE_REFSEL_MODE_FREERUN:
@@ -1107,16 +1092,9 @@ zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv,
break;
}
- /* Read DPLL monitor status */
- rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MON_STATUS(zldpll->id),
- &mon_status);
- if (rc)
- return rc;
- state = FIELD_GET(ZL_DPLL_MON_STATUS_STATE, mon_status);
-
- switch (state) {
+ switch (zl3073x_chan_lock_state_get(chan)) {
case ZL_DPLL_MON_STATUS_STATE_LOCK:
- if (FIELD_GET(ZL_DPLL_MON_STATUS_HO_READY, mon_status))
+ if (zl3073x_chan_is_ho_ready(chan))
*status = DPLL_LOCK_STATUS_LOCKED_HO_ACQ;
else
*status = DPLL_LOCK_STATUS_LOCKED;
@@ -1126,8 +1104,9 @@ zl3073x_dpll_lock_status_get(const struct dpll_device *dpll, void *dpll_priv,
*status = DPLL_LOCK_STATUS_HOLDOVER;
break;
default:
- dev_warn(zldev->dev, "Unknown DPLL monitor status: 0x%02x\n",
- mon_status);
+ dev_warn(zldpll->dev->dev,
+ "Unknown DPLL monitor status: 0x%02x\n",
+ chan->mon_status);
*status = DPLL_LOCK_STATUS_UNLOCKED;
break;
}
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,713 @@
From 4b33e0ad68ce2edfb7cdce8dc91d166eebf7f54b Mon Sep 17 00:00:00 2001
From: Ivan Vecera <ivecera@redhat.com>
Date: Wed, 29 Apr 2026 14:51:32 +0200
Subject: [PATCH] dpll: zl3073x: add reference priority to zl3073x_chan
JIRA: https://issues.redhat.com/browse/RHEL-171979
commit f6b075bc3ad545d1c91e181c3f8ea6193d01602f
Author: Ivan Vecera <ivecera@redhat.com>
Date: Sun Mar 15 18:42:23 2026 +0100
dpll: zl3073x: add reference priority to zl3073x_chan
Cache the ZL_REG_DPLL_REF_PRIO registers in the zl3073x_chan cfg group.
These mailbox-based registers store per-reference priority values
(4 bits each, P/N packed) used for automatic reference selection.
Add ref_prio[] array to struct zl3073x_chan and provide inline helpers
zl3073x_chan_ref_prio_get(), zl3073x_chan_ref_prio_set(), and
zl3073x_chan_ref_is_selectable() for nibble-level access and priority
queries. Extend state_fetch and state_set with DPLL mailbox operations
to read and write the priority registers.
Replace the ad-hoc zl3073x_dpll_ref_prio_get/set functions in dpll.c
with the cached state pattern, removing direct mailbox access from
the DPLL layer. This also simplifies pin registration since reading
priority from cached state cannot fail.
Remove the pin->selectable flag from struct zl3073x_dpll_pin and
derive the selectable state from the cached ref priority via
zl3073x_chan_ref_is_selectable(), eliminating a redundant cache.
Inline zl3073x_dpll_selected_ref_set() into
zl3073x_dpll_input_pin_state_on_dpll_set(), unifying all manual and
automatic mode paths to commit changes through a single
zl3073x_chan_state_set() call at the end of the function.
Move hardware limit constants from core.h to regs.h so that chan.h
can reference ZL3073X_NUM_REFS for the ref_prio array size.
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
Link: https://patch.msgid.link/20260315174224.399074-6-ivecera@redhat.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
(cherry picked from commit f6b075bc3ad545d1c91e181c3f8ea6193d01602f)
Assisted-by: Patchpal
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
diff --git a/drivers/dpll/zl3073x/chan.c b/drivers/dpll/zl3073x/chan.c
index 71fb60a9859b..2f48ca239149 100644
--- a/drivers/dpll/zl3073x/chan.c
+++ b/drivers/dpll/zl3073x/chan.c
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-only
+#include <linux/cleanup.h>
#include <linux/dev_printk.h>
#include <linux/string.h>
#include <linux/types.h>
@@ -33,15 +34,15 @@ int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index)
* @zldev: pointer to zl3073x_dev structure
* @index: DPLL channel index to fetch state for
*
- * Reads the mode_refsel register for the given DPLL channel and stores
- * the raw value for later use.
+ * Reads the mode_refsel register and reference priority registers for
+ * the given DPLL channel and stores the raw values for later use.
*
* Return: 0 on success, <0 on error
*/
int zl3073x_chan_state_fetch(struct zl3073x_dev *zldev, u8 index)
{
struct zl3073x_chan *chan = &zldev->chan[index];
- int rc;
+ int rc, i;
rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index),
&chan->mode_refsel);
@@ -62,6 +63,22 @@ int zl3073x_chan_state_fetch(struct zl3073x_dev *zldev, u8 index)
zl3073x_chan_refsel_state_get(chan),
zl3073x_chan_refsel_ref_get(chan));
+ guard(mutex)(&zldev->multiop_lock);
+
+ /* Read DPLL configuration from mailbox */
+ rc = zl3073x_mb_op(zldev, ZL_REG_DPLL_MB_SEM, ZL_DPLL_MB_SEM_RD,
+ ZL_REG_DPLL_MB_MASK, BIT(index));
+ if (rc)
+ return rc;
+
+ /* Read reference priority registers */
+ for (i = 0; i < ARRAY_SIZE(chan->ref_prio); i++) {
+ rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_REF_PRIO(i),
+ &chan->ref_prio[i]);
+ if (rc)
+ return rc;
+ }
+
return 0;
}
@@ -85,7 +102,9 @@ const struct zl3073x_chan *zl3073x_chan_state_get(struct zl3073x_dev *zldev,
* @chan: desired channel state
*
* Skips the HW write if the configuration is unchanged, and otherwise
- * writes the mode_refsel register to hardware.
+ * writes only the changed registers to hardware. The mode_refsel register
+ * is written directly, while the reference priority registers are written
+ * via the DPLL mailbox interface.
*
* Return: 0 on success, <0 on HW error
*/
@@ -93,14 +112,49 @@ int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index,
const struct zl3073x_chan *chan)
{
struct zl3073x_chan *dchan = &zldev->chan[index];
- int rc;
+ int rc, i;
/* Skip HW write if configuration hasn't changed */
if (!memcmp(&dchan->cfg, &chan->cfg, sizeof(chan->cfg)))
return 0;
- rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index),
- chan->mode_refsel);
+ /* Direct register write for mode_refsel */
+ if (dchan->mode_refsel != chan->mode_refsel) {
+ rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MODE_REFSEL(index),
+ chan->mode_refsel);
+ if (rc)
+ return rc;
+ dchan->mode_refsel = chan->mode_refsel;
+ }
+
+ /* Mailbox write for ref_prio if changed */
+ if (!memcmp(dchan->ref_prio, chan->ref_prio, sizeof(chan->ref_prio))) {
+ dchan->cfg = chan->cfg;
+ return 0;
+ }
+
+ guard(mutex)(&zldev->multiop_lock);
+
+ /* Read DPLL configuration into mailbox */
+ rc = zl3073x_mb_op(zldev, ZL_REG_DPLL_MB_SEM, ZL_DPLL_MB_SEM_RD,
+ ZL_REG_DPLL_MB_MASK, BIT(index));
+ if (rc)
+ return rc;
+
+ /* Update changed ref_prio registers */
+ for (i = 0; i < ARRAY_SIZE(chan->ref_prio); i++) {
+ if (dchan->ref_prio[i] != chan->ref_prio[i]) {
+ rc = zl3073x_write_u8(zldev,
+ ZL_REG_DPLL_REF_PRIO(i),
+ chan->ref_prio[i]);
+ if (rc)
+ return rc;
+ }
+ }
+
+ /* Commit DPLL configuration */
+ rc = zl3073x_mb_op(zldev, ZL_REG_DPLL_MB_SEM, ZL_DPLL_MB_SEM_WR,
+ ZL_REG_DPLL_MB_MASK, BIT(index));
if (rc)
return rc;
diff --git a/drivers/dpll/zl3073x/chan.h b/drivers/dpll/zl3073x/chan.h
index f73a07610855..e0f02d343208 100644
--- a/drivers/dpll/zl3073x/chan.h
+++ b/drivers/dpll/zl3073x/chan.h
@@ -14,12 +14,14 @@ struct zl3073x_dev;
/**
* struct zl3073x_chan - DPLL channel state
* @mode_refsel: mode and reference selection register value
+ * @ref_prio: reference priority registers (4 bits per ref, P/N packed)
* @mon_status: monitor status register value
* @refsel_status: reference selection status register value
*/
struct zl3073x_chan {
struct_group(cfg,
u8 mode_refsel;
+ u8 ref_prio[ZL3073X_NUM_REFS / 2];
);
struct_group(stat,
u8 mon_status;
@@ -79,6 +81,57 @@ static inline void zl3073x_chan_ref_set(struct zl3073x_chan *chan, u8 ref)
chan->mode_refsel |= FIELD_PREP(ZL_DPLL_MODE_REFSEL_REF, ref);
}
+/**
+ * zl3073x_chan_ref_prio_get - get reference priority
+ * @chan: pointer to channel state
+ * @ref: input reference index
+ *
+ * Return: priority of the given reference <0, 15>
+ */
+static inline u8
+zl3073x_chan_ref_prio_get(const struct zl3073x_chan *chan, u8 ref)
+{
+ u8 val = chan->ref_prio[ref / 2];
+
+ if (!(ref & 1))
+ return FIELD_GET(ZL_DPLL_REF_PRIO_REF_P, val);
+ else
+ return FIELD_GET(ZL_DPLL_REF_PRIO_REF_N, val);
+}
+
+/**
+ * zl3073x_chan_ref_prio_set - set reference priority
+ * @chan: pointer to channel state
+ * @ref: input reference index
+ * @prio: priority to set
+ */
+static inline void
+zl3073x_chan_ref_prio_set(struct zl3073x_chan *chan, u8 ref, u8 prio)
+{
+ u8 *val = &chan->ref_prio[ref / 2];
+
+ if (!(ref & 1)) {
+ *val &= ~ZL_DPLL_REF_PRIO_REF_P;
+ *val |= FIELD_PREP(ZL_DPLL_REF_PRIO_REF_P, prio);
+ } else {
+ *val &= ~ZL_DPLL_REF_PRIO_REF_N;
+ *val |= FIELD_PREP(ZL_DPLL_REF_PRIO_REF_N, prio);
+ }
+}
+
+/**
+ * zl3073x_chan_ref_is_selectable - check if reference is selectable
+ * @chan: pointer to channel state
+ * @ref: input reference index
+ *
+ * Return: true if the reference priority is not NONE, false otherwise
+ */
+static inline bool
+zl3073x_chan_ref_is_selectable(const struct zl3073x_chan *chan, u8 ref)
+{
+ return zl3073x_chan_ref_prio_get(chan, ref) != ZL_DPLL_REF_PRIO_NONE;
+}
+
/**
* zl3073x_chan_lock_state_get - get DPLL channel lock state
* @chan: pointer to channel state
diff --git a/drivers/dpll/zl3073x/core.h b/drivers/dpll/zl3073x/core.h
index 2cfb9dd74aa5..99440620407d 100644
--- a/drivers/dpll/zl3073x/core.h
+++ b/drivers/dpll/zl3073x/core.h
@@ -19,17 +19,6 @@ struct device;
struct regmap;
struct zl3073x_dpll;
-/*
- * Hardware limits for ZL3073x chip family
- */
-#define ZL3073X_MAX_CHANNELS 5
-#define ZL3073X_NUM_REFS 10
-#define ZL3073X_NUM_OUTS 10
-#define ZL3073X_NUM_SYNTHS 5
-#define ZL3073X_NUM_INPUT_PINS ZL3073X_NUM_REFS
-#define ZL3073X_NUM_OUTPUT_PINS (ZL3073X_NUM_OUTS * 2)
-#define ZL3073X_NUM_PINS (ZL3073X_NUM_INPUT_PINS + \
- ZL3073X_NUM_OUTPUT_PINS)
enum zl3073x_flags {
ZL3073X_FLAG_REF_PHASE_COMP_32_BIT,
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index 33c4f05ec9d3..9ea66a7e26b8 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -34,7 +34,6 @@
* @dir: pin direction
* @id: pin id
* @prio: pin priority <0, 14>
- * @selectable: pin is selectable in automatic mode
* @esync_control: embedded sync is controllable
* @phase_gran: phase adjustment granularity
* @pin_state: last saved pin state
@@ -50,7 +49,6 @@ struct zl3073x_dpll_pin {
enum dpll_pin_direction dir;
u8 id;
u8 prio;
- bool selectable;
bool esync_control;
s32 phase_gran;
enum dpll_pin_state pin_state;
@@ -284,71 +282,6 @@ zl3073x_dpll_selected_ref_get(struct zl3073x_dpll *zldpll, u8 *ref)
return 0;
}
-/**
- * zl3073x_dpll_selected_ref_set - select reference in manual mode
- * @zldpll: pointer to zl3073x_dpll
- * @ref: input reference to be selected
- *
- * Selects the given reference for the DPLL channel it should be
- * locked to.
- *
- * Return: 0 on success, <0 on error
- */
-static int
-zl3073x_dpll_selected_ref_set(struct zl3073x_dpll *zldpll, u8 ref)
-{
- struct zl3073x_dev *zldev = zldpll->dev;
- struct zl3073x_chan chan;
- u8 mode;
-
- chan = *zl3073x_chan_state_get(zldev, zldpll->id);
- mode = zl3073x_chan_mode_get(&chan);
-
- switch (mode) {
- case ZL_DPLL_MODE_REFSEL_MODE_REFLOCK:
- /* Manual mode with ref selected */
- if (ref == ZL3073X_DPLL_REF_NONE) {
- switch (zldpll->lock_status) {
- case DPLL_LOCK_STATUS_LOCKED_HO_ACQ:
- case DPLL_LOCK_STATUS_HOLDOVER:
- /* Switch to forced holdover */
- mode = ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER;
- break;
- default:
- /* Switch to freerun */
- mode = ZL_DPLL_MODE_REFSEL_MODE_FREERUN;
- break;
- }
- /* Keep selected reference */
- ref = zl3073x_chan_ref_get(&chan);
- } else if (ref == zl3073x_chan_ref_get(&chan)) {
- /* No register update - same mode and same ref */
- return 0;
- }
- break;
- case ZL_DPLL_MODE_REFSEL_MODE_FREERUN:
- case ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER:
- /* Manual mode without no ref */
- if (ref == ZL3073X_DPLL_REF_NONE)
- /* No register update - keep current mode */
- return 0;
-
- /* Switch to reflock mode and update ref selection */
- mode = ZL_DPLL_MODE_REFSEL_MODE_REFLOCK;
- break;
- default:
- /* For other modes like automatic or NCO ref cannot be selected
- * manually
- */
- return -EOPNOTSUPP;
- }
-
- zl3073x_chan_mode_set(&chan, mode);
- zl3073x_chan_ref_set(&chan, ref);
-
- return zl3073x_chan_state_set(zldev, zldpll->id, &chan);
-}
-
/**
* zl3073x_dpll_connected_ref_get - get currently connected reference
* @zldpll: pointer to zl3073x_dpll
@@ -497,98 +430,6 @@ zl3073x_dpll_input_pin_phase_adjust_set(const struct dpll_pin *dpll_pin,
return zl3073x_ref_state_set(zldev, ref_id, &ref);
}
-/**
- * zl3073x_dpll_ref_prio_get - get priority for given input pin
- * @pin: pointer to pin
- * @prio: place to store priority
- *
- * Reads current priority for the given input pin and stores the value
- * to @prio.
- *
- * Return: 0 on success, <0 on error
- */
-static int
-zl3073x_dpll_ref_prio_get(struct zl3073x_dpll_pin *pin, u8 *prio)
-{
- struct zl3073x_dpll *zldpll = pin->dpll;
- struct zl3073x_dev *zldev = zldpll->dev;
- u8 ref, ref_prio;
- int rc;
-
- guard(mutex)(&zldev->multiop_lock);
-
- /* Read DPLL configuration */
- rc = zl3073x_mb_op(zldev, ZL_REG_DPLL_MB_SEM, ZL_DPLL_MB_SEM_RD,
- ZL_REG_DPLL_MB_MASK, BIT(zldpll->id));
- if (rc)
- return rc;
-
- /* Read reference priority - one value for P&N pins (4 bits/pin) */
- ref = zl3073x_input_pin_ref_get(pin->id);
- rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_REF_PRIO(ref / 2),
- &ref_prio);
- if (rc)
- return rc;
-
- /* Select nibble according pin type */
- if (zl3073x_dpll_is_p_pin(pin))
- *prio = FIELD_GET(ZL_DPLL_REF_PRIO_REF_P, ref_prio);
- else
- *prio = FIELD_GET(ZL_DPLL_REF_PRIO_REF_N, ref_prio);
-
- return rc;
-}
-
-/**
- * zl3073x_dpll_ref_prio_set - set priority for given input pin
- * @pin: pointer to pin
- * @prio: place to store priority
- *
- * Sets priority for the given input pin.
- *
- * Return: 0 on success, <0 on error
- */
-static int
-zl3073x_dpll_ref_prio_set(struct zl3073x_dpll_pin *pin, u8 prio)
-{
- struct zl3073x_dpll *zldpll = pin->dpll;
- struct zl3073x_dev *zldev = zldpll->dev;
- u8 ref, ref_prio;
- int rc;
-
- guard(mutex)(&zldev->multiop_lock);
-
- /* Read DPLL configuration into mailbox */
- rc = zl3073x_mb_op(zldev, ZL_REG_DPLL_MB_SEM, ZL_DPLL_MB_SEM_RD,
- ZL_REG_DPLL_MB_MASK, BIT(zldpll->id));
- if (rc)
- return rc;
-
- /* Read reference priority - one value shared between P&N pins */
- ref = zl3073x_input_pin_ref_get(pin->id);
- rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_REF_PRIO(ref / 2), &ref_prio);
- if (rc)
- return rc;
-
- /* Update nibble according pin type */
- if (zl3073x_dpll_is_p_pin(pin)) {
- ref_prio &= ~ZL_DPLL_REF_PRIO_REF_P;
- ref_prio |= FIELD_PREP(ZL_DPLL_REF_PRIO_REF_P, prio);
- } else {
- ref_prio &= ~ZL_DPLL_REF_PRIO_REF_N;
- ref_prio |= FIELD_PREP(ZL_DPLL_REF_PRIO_REF_N, prio);
- }
-
- /* Update reference priority */
- rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_REF_PRIO(ref / 2), ref_prio);
- if (rc)
- return rc;
-
- /* Commit configuration */
- return zl3073x_mb_op(zldev, ZL_REG_DPLL_MB_SEM, ZL_DPLL_MB_SEM_WR,
- ZL_REG_DPLL_MB_MASK, BIT(zldpll->id));
-}
-
/**
* zl3073x_dpll_ref_state_get - get status for given input pin
* @pin: pointer to pin
@@ -627,7 +468,8 @@ zl3073x_dpll_ref_state_get(struct zl3073x_dpll_pin *pin,
* pin as selectable.
*/
if (zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_AUTO &&
- zl3073x_dev_ref_is_status_ok(zldev, ref) && pin->selectable) {
+ zl3073x_dev_ref_is_status_ok(zldev, ref) &&
+ zl3073x_chan_ref_is_selectable(chan, ref)) {
*state = DPLL_PIN_STATE_SELECTABLE;
return 0;
}
@@ -661,71 +503,81 @@ zl3073x_dpll_input_pin_state_on_dpll_set(const struct dpll_pin *dpll_pin,
{
struct zl3073x_dpll *zldpll = dpll_priv;
struct zl3073x_dpll_pin *pin = pin_priv;
- const struct zl3073x_chan *chan;
- u8 new_ref;
+ struct zl3073x_chan chan;
+ u8 mode, ref;
int rc;
- chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+ chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+ ref = zl3073x_input_pin_ref_get(pin->id);
+ mode = zl3073x_chan_mode_get(&chan);
- switch (zl3073x_chan_mode_get(chan)) {
+ switch (mode) {
case ZL_DPLL_MODE_REFSEL_MODE_REFLOCK:
- case ZL_DPLL_MODE_REFSEL_MODE_FREERUN:
- case ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER:
if (state == DPLL_PIN_STATE_CONNECTED) {
/* Choose the pin as new selected reference */
- new_ref = zl3073x_input_pin_ref_get(pin->id);
+ zl3073x_chan_ref_set(&chan, ref);
} else if (state == DPLL_PIN_STATE_DISCONNECTED) {
- /* No reference */
- new_ref = ZL3073X_DPLL_REF_NONE;
+ /* Choose new mode based on lock status */
+ switch (zldpll->lock_status) {
+ case DPLL_LOCK_STATUS_LOCKED_HO_ACQ:
+ case DPLL_LOCK_STATUS_HOLDOVER:
+ mode = ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER;
+ break;
+ default:
+ mode = ZL_DPLL_MODE_REFSEL_MODE_FREERUN;
+ break;
+ }
+ zl3073x_chan_mode_set(&chan, mode);
} else {
- NL_SET_ERR_MSG_MOD(extack,
- "Invalid pin state for manual mode");
- return -EINVAL;
+ goto invalid_state;
+ }
+ break;
+ case ZL_DPLL_MODE_REFSEL_MODE_FREERUN:
+ case ZL_DPLL_MODE_REFSEL_MODE_HOLDOVER:
+ if (state == DPLL_PIN_STATE_CONNECTED) {
+ /* Choose the pin as new selected reference */
+ zl3073x_chan_ref_set(&chan, ref);
+ /* Switch to reflock mode */
+ zl3073x_chan_mode_set(&chan,
+ ZL_DPLL_MODE_REFSEL_MODE_REFLOCK);
+ } else if (state != DPLL_PIN_STATE_DISCONNECTED) {
+ goto invalid_state;
}
-
- rc = zl3073x_dpll_selected_ref_set(zldpll, new_ref);
break;
-
case ZL_DPLL_MODE_REFSEL_MODE_AUTO:
if (state == DPLL_PIN_STATE_SELECTABLE) {
- if (pin->selectable)
+ if (zl3073x_chan_ref_is_selectable(&chan, ref))
return 0; /* Pin is already selectable */
/* Restore pin priority in HW */
- rc = zl3073x_dpll_ref_prio_set(pin, pin->prio);
- if (rc)
- return rc;
-
- /* Mark pin as selectable */
- pin->selectable = true;
+ zl3073x_chan_ref_prio_set(&chan, ref, pin->prio);
} else if (state == DPLL_PIN_STATE_DISCONNECTED) {
- if (!pin->selectable)
+ if (!zl3073x_chan_ref_is_selectable(&chan, ref))
return 0; /* Pin is already disconnected */
/* Set pin priority to none in HW */
- rc = zl3073x_dpll_ref_prio_set(pin,
- ZL_DPLL_REF_PRIO_NONE);
- if (rc)
- return rc;
-
- /* Mark pin as non-selectable */
- pin->selectable = false;
+ zl3073x_chan_ref_prio_set(&chan, ref,
+ ZL_DPLL_REF_PRIO_NONE);
} else {
- NL_SET_ERR_MSG(extack,
- "Invalid pin state for automatic mode");
- return -EINVAL;
+ goto invalid_state;
}
break;
-
default:
/* In other modes we cannot change input reference */
NL_SET_ERR_MSG(extack,
"Pin state cannot be changed in current mode");
- rc = -EOPNOTSUPP;
- break;
+ return -EOPNOTSUPP;
}
- return rc;
+ /* Commit DPLL channel changes */
+ rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan);
+ if (rc)
+ return rc;
+
+ return 0;
+invalid_state:
+ NL_SET_ERR_MSG_MOD(extack, "Invalid pin state for this device mode");
+ return -EINVAL;
}
static int
@@ -745,15 +597,21 @@ zl3073x_dpll_input_pin_prio_set(const struct dpll_pin *dpll_pin, void *pin_priv,
const struct dpll_device *dpll, void *dpll_priv,
u32 prio, struct netlink_ext_ack *extack)
{
+ struct zl3073x_dpll *zldpll = dpll_priv;
struct zl3073x_dpll_pin *pin = pin_priv;
+ struct zl3073x_chan chan;
+ u8 ref;
int rc;
if (prio > ZL_DPLL_REF_PRIO_MAX)
return -EINVAL;
/* If the pin is selectable then update HW registers */
- if (pin->selectable) {
- rc = zl3073x_dpll_ref_prio_set(pin, prio);
+ chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+ ref = zl3073x_input_pin_ref_get(pin->id);
+ if (zl3073x_chan_ref_is_selectable(&chan, ref)) {
+ zl3073x_chan_ref_prio_set(&chan, ref, prio);
+ rc = zl3073x_chan_state_set(zldpll->dev, zldpll->id, &chan);
if (rc)
return rc;
}
@@ -1235,6 +1093,8 @@ zl3073x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv,
return rc;
}
+ chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+
if (mode == DPLL_MODE_MANUAL) {
/* We are switching from automatic to manual mode:
* - if we have a valid reference selected during auto mode then
@@ -1256,25 +1116,21 @@ zl3073x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv,
* it is selectable after switch to automatic mode
* - switch to automatic mode
*/
- struct zl3073x_dpll_pin *pin;
-
- pin = zl3073x_dpll_pin_get_by_ref(zldpll, ref);
- if (pin && !pin->selectable) {
- /* Restore pin priority in HW */
- rc = zl3073x_dpll_ref_prio_set(pin, pin->prio);
- if (rc) {
- NL_SET_ERR_MSG_MOD(extack,
- "failed to restore pin priority");
- return rc;
+ if (ZL3073X_DPLL_REF_IS_VALID(ref) &&
+ !zl3073x_chan_ref_is_selectable(&chan, ref)) {
+ struct zl3073x_dpll_pin *pin;
+
+ pin = zl3073x_dpll_pin_get_by_ref(zldpll, ref);
+ if (pin) {
+ /* Restore pin priority in HW */
+ zl3073x_chan_ref_prio_set(&chan, ref,
+ pin->prio);
}
-
- pin->selectable = true;
}
hw_mode = ZL_DPLL_MODE_REFSEL_MODE_AUTO;
}
- chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id);
zl3073x_chan_mode_set(&chan, hw_mode);
if (ZL3073X_DPLL_REF_IS_VALID(ref))
zl3073x_chan_ref_set(&chan, ref);
@@ -1426,18 +1282,16 @@ zl3073x_dpll_pin_register(struct zl3073x_dpll_pin *pin, u32 index)
pin->phase_gran = props->dpll_props.phase_gran;
if (zl3073x_dpll_is_input_pin(pin)) {
- rc = zl3073x_dpll_ref_prio_get(pin, &pin->prio);
- if (rc)
- goto err_prio_get;
+ const struct zl3073x_chan *chan;
+ u8 ref;
+
+ chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+ ref = zl3073x_input_pin_ref_get(pin->id);
+ pin->prio = zl3073x_chan_ref_prio_get(chan, ref);
- if (pin->prio == ZL_DPLL_REF_PRIO_NONE) {
- /* Clamp prio to max value & mark pin non-selectable */
+ if (pin->prio == ZL_DPLL_REF_PRIO_NONE)
+ /* Clamp prio to max value */
pin->prio = ZL_DPLL_REF_PRIO_MAX;
- pin->selectable = false;
- } else {
- /* Mark pin as selectable */
- pin->selectable = true;
- }
}
/* Create or get existing DPLL pin */
@@ -1466,7 +1320,6 @@ zl3073x_dpll_pin_register(struct zl3073x_dpll_pin *pin, u32 index)
err_register:
dpll_pin_put(pin->dpll_pin, &pin->tracker);
-err_prio_get:
pin->dpll_pin = NULL;
err_pin_get:
zl3073x_pin_props_put(props);
diff --git a/drivers/dpll/zl3073x/regs.h b/drivers/dpll/zl3073x/regs.h
index 19c598daa784..5ae50cb761a9 100644
--- a/drivers/dpll/zl3073x/regs.h
+++ b/drivers/dpll/zl3073x/regs.h
@@ -6,6 +6,18 @@
#include <linux/bitfield.h>
#include <linux/bits.h>
+/*
+ * Hardware limits for ZL3073x chip family
+ */
+#define ZL3073X_MAX_CHANNELS 5
+#define ZL3073X_NUM_REFS 10
+#define ZL3073X_NUM_OUTS 10
+#define ZL3073X_NUM_SYNTHS 5
+#define ZL3073X_NUM_INPUT_PINS ZL3073X_NUM_REFS
+#define ZL3073X_NUM_OUTPUT_PINS (ZL3073X_NUM_OUTS * 2)
+#define ZL3073X_NUM_PINS (ZL3073X_NUM_INPUT_PINS + \
+ ZL3073X_NUM_OUTPUT_PINS)
+
/*
* Register address structure:
* ===========================
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,188 @@
From 6d8e25eab47b0e42b2df4fee21761986644d4f7b Mon Sep 17 00:00:00 2001
From: Ivan Vecera <ivecera@redhat.com>
Date: Wed, 29 Apr 2026 14:51:39 +0200
Subject: [PATCH] dpll: zl3073x: drop selected and simplify connected ref
getter
JIRA: https://issues.redhat.com/browse/RHEL-171979
commit acee049a6af2a7b4baabd28f16fb53628ea6e7a5
Author: Ivan Vecera <ivecera@redhat.com>
Date: Sun Mar 15 18:42:24 2026 +0100
dpll: zl3073x: drop selected and simplify connected ref getter
The HW reports the currently selected reference in the
dpll_refsel_status register regardless of the DPLL mode. Use this to
delete zl3073x_dpll_selected_ref_get() and have callers read the
register directly via the cached channel state.
Simplify zl3073x_dpll_connected_ref_get() to check refsel_state for
LOCK directly and return the reference index, changing the return
type from int to u8. The redundant ref_is_status_ok check is removed
since the DPLL cannot be in LOCK state with a failed reference.
In zl3073x_dpll_mode_set(), replace the selected_ref_get() call with
zl3073x_chan_refsel_ref_get() to read the currently selected
reference directly from the cached channel state.
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
Link: https://patch.msgid.link/20260315174224.399074-7-ivecera@redhat.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
(cherry picked from commit acee049a6af2a7b4baabd28f16fb53628ea6e7a5)
Assisted-by: Patchpal
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index 9ea66a7e26b8..cf7a335baed5 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -243,72 +243,27 @@ zl3073x_dpll_input_pin_frequency_set(const struct dpll_pin *dpll_pin,
return zl3073x_ref_state_set(zldev, ref_id, &ref);
}
-/**
- * zl3073x_dpll_selected_ref_get - get currently selected reference
- * @zldpll: pointer to zl3073x_dpll
- * @ref: place to store selected reference
- *
- * Check for currently selected reference the DPLL should be locked to
- * and stores its index to given @ref.
- *
- * Return: 0 on success, <0 on error
- */
-static int
-zl3073x_dpll_selected_ref_get(struct zl3073x_dpll *zldpll, u8 *ref)
-{
- const struct zl3073x_chan *chan;
-
- chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
-
- switch (zl3073x_chan_mode_get(chan)) {
- case ZL_DPLL_MODE_REFSEL_MODE_AUTO:
- /* Return the reference only if the DPLL is locked to it */
- if (zl3073x_chan_refsel_state_get(chan) ==
- ZL_DPLL_REFSEL_STATUS_STATE_LOCK)
- *ref = zl3073x_chan_refsel_ref_get(chan);
- else
- *ref = ZL3073X_DPLL_REF_NONE;
- break;
- case ZL_DPLL_MODE_REFSEL_MODE_REFLOCK:
- /* For manual mode return stored value */
- *ref = zl3073x_chan_ref_get(chan);
- break;
- default:
- /* For other modes like NCO, freerun... there is no input ref */
- *ref = ZL3073X_DPLL_REF_NONE;
- break;
- }
-
- return 0;
-}
-
/**
* zl3073x_dpll_connected_ref_get - get currently connected reference
* @zldpll: pointer to zl3073x_dpll
- * @ref: place to store selected reference
*
- * Looks for currently connected the DPLL is locked to and stores its index
- * to given @ref.
+ * Looks for currently connected reference the DPLL is locked to.
*
- * Return: 0 on success, <0 on error
+ * Return: reference index if locked, ZL3073X_DPLL_REF_NONE otherwise
*/
-static int
-zl3073x_dpll_connected_ref_get(struct zl3073x_dpll *zldpll, u8 *ref)
+static u8
+zl3073x_dpll_connected_ref_get(struct zl3073x_dpll *zldpll)
{
- struct zl3073x_dev *zldev = zldpll->dev;
- int rc;
-
- /* Get currently selected input reference */
- rc = zl3073x_dpll_selected_ref_get(zldpll, ref);
- if (rc)
- return rc;
+ const struct zl3073x_chan *chan = zl3073x_chan_state_get(zldpll->dev,
+ zldpll->id);
+ u8 state;
- /* If the monitor indicates an error nothing is connected */
- if (ZL3073X_DPLL_REF_IS_VALID(*ref) &&
- !zl3073x_dev_ref_is_status_ok(zldev, *ref))
- *ref = ZL3073X_DPLL_REF_NONE;
+ /* A reference is connected only when the DPLL is locked to it */
+ state = zl3073x_chan_refsel_state_get(chan);
+ if (state == ZL_DPLL_REFSEL_STATUS_STATE_LOCK)
+ return zl3073x_chan_refsel_ref_get(chan);
- return 0;
+ return ZL3073X_DPLL_REF_NONE;
}
static int
@@ -324,12 +279,9 @@ zl3073x_dpll_input_pin_phase_offset_get(const struct dpll_pin *dpll_pin,
const struct zl3073x_ref *ref;
u8 conn_id, ref_id;
s64 ref_phase;
- int rc;
/* Get currently connected reference */
- rc = zl3073x_dpll_connected_ref_get(zldpll, &conn_id);
- if (rc)
- return rc;
+ conn_id = zl3073x_dpll_connected_ref_get(zldpll);
/* Report phase offset only for currently connected pin if the phase
* monitor feature is disabled and only if the input pin signal is
@@ -367,7 +319,7 @@ zl3073x_dpll_input_pin_phase_offset_get(const struct dpll_pin *dpll_pin,
*phase_offset = ref_phase * DPLL_PHASE_OFFSET_DIVIDER;
- return rc;
+ return 0;
}
static int
@@ -447,18 +399,13 @@ zl3073x_dpll_ref_state_get(struct zl3073x_dpll_pin *pin,
struct zl3073x_dpll *zldpll = pin->dpll;
struct zl3073x_dev *zldev = zldpll->dev;
const struct zl3073x_chan *chan;
- u8 ref, ref_conn;
- int rc;
+ u8 ref;
chan = zl3073x_chan_state_get(zldev, zldpll->id);
ref = zl3073x_input_pin_ref_get(pin->id);
- /* Get currently connected reference */
- rc = zl3073x_dpll_connected_ref_get(zldpll, &ref_conn);
- if (rc)
- return rc;
-
- if (ref == ref_conn) {
+ /* Check if the pin reference is connected */
+ if (ref == zl3073x_dpll_connected_ref_get(zldpll)) {
*state = DPLL_PIN_STATE_CONNECTED;
return 0;
}
@@ -1087,13 +1034,8 @@ zl3073x_dpll_mode_set(const struct dpll_device *dpll, void *dpll_priv,
u8 hw_mode, ref;
int rc;
- rc = zl3073x_dpll_selected_ref_get(zldpll, &ref);
- if (rc) {
- NL_SET_ERR_MSG_MOD(extack, "failed to get selected reference");
- return rc;
- }
-
chan = *zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+ ref = zl3073x_chan_refsel_ref_get(&chan);
if (mode == DPLL_MODE_MANUAL) {
/* We are switching from automatic to manual mode:
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,211 @@
From cf443396fbf1d5230af6f3e6ff9429f897ac1e2e Mon Sep 17 00:00:00 2001
From: Ivan Vecera <ivecera@redhat.com>
Date: Mon, 13 Apr 2026 17:47:07 +0200
Subject: [PATCH] dpll: add frequency monitoring to netlink spec
JIRA: https://issues.redhat.com/browse/RHEL-165161
commit 3fdea79c09d169b6ea172b8d36232c3773f39973
Author: Ivan Vecera <ivecera@redhat.com>
Date: Thu Apr 2 20:40:55 2026 +0200
dpll: add frequency monitoring to netlink spec
Add DPLL_A_FREQUENCY_MONITOR device attribute to allow control over
the frequency monitor feature. The attribute uses the existing
dpll_feature_state enum (enable/disable) and is present in both
device-get reply and device-set request.
Add DPLL_A_PIN_MEASURED_FREQUENCY pin attribute to expose the measured
input frequency in millihertz (mHz). The attribute is present in the
pin-get reply. Add DPLL_PIN_MEASURED_FREQUENCY_DIVIDER constant to
allow userspace to extract integer and fractional parts.
Reviewed-by: Vadim Fedorenko <vadim.fedorenko@linux.dev>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
Link: https://patch.msgid.link/20260402184057.1890514-2-ivecera@redhat.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
(cherry picked from commit 3fdea79c09d169b6ea172b8d36232c3773f39973)
Assisted-by: Patchpal
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
diff --git a/Documentation/driver-api/dpll.rst b/Documentation/driver-api/dpll.rst
index 83118c728ed9..93c191b2d089 100644
--- a/Documentation/driver-api/dpll.rst
+++ b/Documentation/driver-api/dpll.rst
@@ -250,6 +250,24 @@ in the ``DPLL_A_PIN_PHASE_OFFSET`` attribute.
``DPLL_A_PHASE_OFFSET_MONITOR`` attr state of a feature
=============================== ========================
+Frequency monitor
+=================
+
+Some DPLL devices may offer the capability to measure the actual
+frequency of all available input pins. The attribute and current feature state
+shall be included in the response message of the ``DPLL_CMD_DEVICE_GET``
+command for supported DPLL devices. In such cases, users can also control
+the feature using the ``DPLL_CMD_DEVICE_SET`` command by setting the
+``enum dpll_feature_state`` values for the attribute.
+Once enabled the measured input frequency for each input pin shall be
+returned in the ``DPLL_A_PIN_MEASURED_FREQUENCY`` attribute. The value
+is in millihertz (mHz), using ``DPLL_PIN_MEASURED_FREQUENCY_DIVIDER``
+as the divider.
+
+ =============================== ========================
+ ``DPLL_A_FREQUENCY_MONITOR`` attr state of a feature
+ =============================== ========================
+
Embedded SYNC
=============
@@ -411,6 +429,8 @@ according to attribute purpose.
``DPLL_A_PIN_STATE`` attr state of pin on the parent
pin
``DPLL_A_PIN_CAPABILITIES`` attr bitmask of pin capabilities
+ ``DPLL_A_PIN_MEASURED_FREQUENCY`` attr measured frequency of
+ an input pin in mHz
==================================== ==================================
==================================== =================================
diff --git a/Documentation/netlink/specs/dpll.yaml b/Documentation/netlink/specs/dpll.yaml
index ed5866238682..cb5da356bb64 100644
--- a/Documentation/netlink/specs/dpll.yaml
+++ b/Documentation/netlink/specs/dpll.yaml
@@ -240,6 +240,20 @@ definitions:
integer part of a measured phase offset value.
Value of (DPLL_A_PHASE_OFFSET % DPLL_PHASE_OFFSET_DIVIDER) is a
fractional part of a measured phase offset value.
+ -
+ type: const
+ name: pin-measured-frequency-divider
+ value: 1000
+ doc: |
+ pin measured frequency divider allows userspace to calculate
+ a value of measured input frequency as a fractional value with
+ three digit decimal precision (millihertz).
+ Value of (DPLL_A_PIN_MEASURED_FREQUENCY /
+ DPLL_PIN_MEASURED_FREQUENCY_DIVIDER) is an integer part of
+ a measured frequency value.
+ Value of (DPLL_A_PIN_MEASURED_FREQUENCY %
+ DPLL_PIN_MEASURED_FREQUENCY_DIVIDER) is a fractional part of
+ a measured frequency value.
-
type: enum
name: feature-state
@@ -319,6 +333,13 @@ attribute-sets:
name: phase-offset-avg-factor
type: u32
doc: Averaging factor applied to calculation of reported phase offset.
+ -
+ name: frequency-monitor
+ type: u32
+ enum: feature-state
+ doc: Current or desired state of the frequency monitor feature.
+ If enabled, dpll device shall measure all currently available
+ inputs for their actual input frequency.
-
name: pin
enum-name: dpll_a_pin
@@ -456,6 +477,17 @@ attribute-sets:
Value is in PPT (parts per trillion, 10^-12).
Note: This attribute provides higher resolution than the standard
fractional-frequency-offset (which is in PPM).
+ -
+ name: measured-frequency
+ type: u64
+ doc: |
+ The measured frequency of the input pin in millihertz (mHz).
+ Value of (DPLL_A_PIN_MEASURED_FREQUENCY /
+ DPLL_PIN_MEASURED_FREQUENCY_DIVIDER) is an integer part (Hz)
+ of a measured frequency value.
+ Value of (DPLL_A_PIN_MEASURED_FREQUENCY %
+ DPLL_PIN_MEASURED_FREQUENCY_DIVIDER) is a fractional part
+ of a measured frequency value.
-
name: pin-parent-device
@@ -544,6 +576,7 @@ operations:
- type
- phase-offset-monitor
- phase-offset-avg-factor
+ - frequency-monitor
dump:
reply: *dev-attrs
@@ -563,6 +596,7 @@ operations:
- mode
- phase-offset-monitor
- phase-offset-avg-factor
+ - frequency-monitor
-
name: device-create-ntf
doc: Notification about device appearing
@@ -643,6 +677,7 @@ operations:
- esync-frequency-supported
- esync-pulse
- reference-sync
+ - measured-frequency
dump:
request:
diff --git a/drivers/dpll/dpll_nl.c b/drivers/dpll/dpll_nl.c
index 3fb64aab18fa..da92aa8f9bc7 100644
--- a/drivers/dpll/dpll_nl.c
+++ b/drivers/dpll/dpll_nl.c
@@ -42,11 +42,12 @@ static const struct nla_policy dpll_device_get_nl_policy[DPLL_A_ID + 1] = {
};
/* DPLL_CMD_DEVICE_SET - do */
-static const struct nla_policy dpll_device_set_nl_policy[DPLL_A_PHASE_OFFSET_AVG_FACTOR + 1] = {
+static const struct nla_policy dpll_device_set_nl_policy[DPLL_A_FREQUENCY_MONITOR + 1] = {
[DPLL_A_ID] = { .type = NLA_U32, },
[DPLL_A_MODE] = NLA_POLICY_RANGE(NLA_U32, 1, 2),
[DPLL_A_PHASE_OFFSET_MONITOR] = NLA_POLICY_MAX(NLA_U32, 1),
[DPLL_A_PHASE_OFFSET_AVG_FACTOR] = { .type = NLA_U32, },
+ [DPLL_A_FREQUENCY_MONITOR] = NLA_POLICY_MAX(NLA_U32, 1),
};
/* DPLL_CMD_PIN_ID_GET - do */
@@ -114,7 +115,7 @@ static const struct genl_split_ops dpll_nl_ops[] = {
.doit = dpll_nl_device_set_doit,
.post_doit = dpll_post_doit,
.policy = dpll_device_set_nl_policy,
- .maxattr = DPLL_A_PHASE_OFFSET_AVG_FACTOR,
+ .maxattr = DPLL_A_FREQUENCY_MONITOR,
.flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
},
{
diff --git a/include/uapi/linux/dpll.h b/include/uapi/linux/dpll.h
index 603a88cb665f..93614ccd8a84 100644
--- a/include/uapi/linux/dpll.h
+++ b/include/uapi/linux/dpll.h
@@ -190,7 +190,8 @@ enum dpll_pin_capabilities {
DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE = 4,
};
-#define DPLL_PHASE_OFFSET_DIVIDER 1000
+#define DPLL_PHASE_OFFSET_DIVIDER 1000
+#define DPLL_PIN_MEASURED_FREQUENCY_DIVIDER 1000
/**
* enum dpll_feature_state - Allow control (enable/disable) and status checking
@@ -217,6 +218,7 @@ enum dpll_a {
DPLL_A_CLOCK_QUALITY_LEVEL,
DPLL_A_PHASE_OFFSET_MONITOR,
DPLL_A_PHASE_OFFSET_AVG_FACTOR,
+ DPLL_A_FREQUENCY_MONITOR,
__DPLL_A_MAX,
DPLL_A_MAX = (__DPLL_A_MAX - 1)
@@ -253,6 +255,7 @@ enum dpll_a_pin {
DPLL_A_PIN_REFERENCE_SYNC,
DPLL_A_PIN_PHASE_ADJUST_GRAN,
DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET_PPT,
+ DPLL_A_PIN_MEASURED_FREQUENCY,
__DPLL_A_PIN_MAX,
DPLL_A_PIN_MAX = (__DPLL_A_PIN_MAX - 1)
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,224 @@
From c549611ebab6cedb1f8d0981320051d4dfb86ccc Mon Sep 17 00:00:00 2001
From: Ivan Vecera <ivecera@redhat.com>
Date: Mon, 13 Apr 2026 17:49:27 +0200
Subject: [PATCH] dpll: add frequency monitoring callback ops
JIRA: https://issues.redhat.com/browse/RHEL-165161
commit 15ed91aa84ea7bacef3c24286d5136055b4335a8
Author: Ivan Vecera <ivecera@redhat.com>
Date: Thu Apr 2 20:40:56 2026 +0200
dpll: add frequency monitoring callback ops
Add new callback operations for a dpll device:
- freq_monitor_get(..) - to obtain current state of frequency monitor
feature from dpll device,
- freq_monitor_set(..) - to allow feature configuration.
Add new callback operation for a dpll pin:
- measured_freq_get(..) - to obtain the measured frequency in mHz.
Obtain the feature state value using the get callback and provide it to
the user if the device driver implements callbacks. The measured_freq_get
pin callback is only invoked when the frequency monitor is enabled.
The freq_monitor_get device callback is required when measured_freq_get
is provided by the driver.
Execute the set callback upon user requests.
Reviewed-by: Vadim Fedorenko <vadim.fedorenko@linux.dev>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
Link: https://patch.msgid.link/20260402184057.1890514-3-ivecera@redhat.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Conflicts:
- include/linux/dpll.h: context conflict due usage of RH_KABI_* macros
(cherry picked from commit 15ed91aa84ea7bacef3c24286d5136055b4335a8)
Assisted-by: Patchpal AI
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
diff --git a/drivers/dpll/dpll_core.c b/drivers/dpll/dpll_core.c
index 842f7334db4d..84e1b637b4e5 100644
--- a/drivers/dpll/dpll_core.c
+++ b/drivers/dpll/dpll_core.c
@@ -880,7 +880,10 @@ dpll_pin_register(struct dpll_device *dpll, struct dpll_pin *pin,
if (WARN_ON(!ops) ||
WARN_ON(!ops->state_on_dpll_get) ||
- WARN_ON(!ops->direction_get))
+ WARN_ON(!ops->direction_get) ||
+ WARN_ON(ops->measured_freq_get &&
+ (!dpll_device_ops(dpll)->freq_monitor_get ||
+ !dpll_device_ops(dpll)->freq_monitor_set)))
return -EINVAL;
mutex_lock(&dpll_lock);
diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c
index 83cbd64abf5a..af7ce62ec55c 100644
--- a/drivers/dpll/dpll_netlink.c
+++ b/drivers/dpll/dpll_netlink.c
@@ -175,6 +175,26 @@ dpll_msg_add_phase_offset_monitor(struct sk_buff *msg, struct dpll_device *dpll,
return 0;
}
+static int
+dpll_msg_add_freq_monitor(struct sk_buff *msg, struct dpll_device *dpll,
+ struct netlink_ext_ack *extack)
+{
+ const struct dpll_device_ops *ops = dpll_device_ops(dpll);
+ enum dpll_feature_state state;
+ int ret;
+
+ if (ops->freq_monitor_set && ops->freq_monitor_get) {
+ ret = ops->freq_monitor_get(dpll, dpll_priv(dpll),
+ &state, extack);
+ if (ret)
+ return ret;
+ if (nla_put_u32(msg, DPLL_A_FREQUENCY_MONITOR, state))
+ return -EMSGSIZE;
+ }
+
+ return 0;
+}
+
static int
dpll_msg_add_phase_offset_avg_factor(struct sk_buff *msg,
struct dpll_device *dpll,
@@ -400,6 +420,38 @@ static int dpll_msg_add_ffo(struct sk_buff *msg, struct dpll_pin *pin,
ffo);
}
+static int dpll_msg_add_measured_freq(struct sk_buff *msg, struct dpll_pin *pin,
+ struct dpll_pin_ref *ref,
+ struct netlink_ext_ack *extack)
+{
+ const struct dpll_device_ops *dev_ops = dpll_device_ops(ref->dpll);
+ const struct dpll_pin_ops *ops = dpll_pin_ops(ref);
+ struct dpll_device *dpll = ref->dpll;
+ enum dpll_feature_state state;
+ u64 measured_freq;
+ int ret;
+
+ if (!ops->measured_freq_get)
+ return 0;
+ ret = dev_ops->freq_monitor_get(dpll, dpll_priv(dpll),
+ &state, extack);
+ if (ret)
+ return ret;
+ if (state == DPLL_FEATURE_STATE_DISABLE)
+ return 0;
+ ret = ops->measured_freq_get(pin, dpll_pin_on_dpll_priv(dpll, pin),
+ dpll, dpll_priv(dpll), &measured_freq,
+ extack);
+ if (ret)
+ return ret;
+ if (nla_put_64bit(msg, DPLL_A_PIN_MEASURED_FREQUENCY,
+ sizeof(measured_freq), &measured_freq,
+ DPLL_A_PIN_PAD))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
static int
dpll_msg_add_pin_freq(struct sk_buff *msg, struct dpll_pin *pin,
struct dpll_pin_ref *ref, struct netlink_ext_ack *extack)
@@ -670,6 +722,9 @@ dpll_cmd_pin_get_one(struct sk_buff *msg, struct dpll_pin *pin,
if (ret)
return ret;
ret = dpll_msg_add_ffo(msg, pin, ref, extack);
+ if (ret)
+ return ret;
+ ret = dpll_msg_add_measured_freq(msg, pin, ref, extack);
if (ret)
return ret;
ret = dpll_msg_add_pin_esync(msg, pin, ref, extack);
@@ -722,6 +777,9 @@ dpll_device_get_one(struct dpll_device *dpll, struct sk_buff *msg,
if (ret)
return ret;
ret = dpll_msg_add_phase_offset_avg_factor(msg, dpll, extack);
+ if (ret)
+ return ret;
+ ret = dpll_msg_add_freq_monitor(msg, dpll, extack);
if (ret)
return ret;
@@ -948,6 +1006,32 @@ dpll_phase_offset_avg_factor_set(struct dpll_device *dpll, struct nlattr *a,
extack);
}
+static int
+dpll_freq_monitor_set(struct dpll_device *dpll, struct nlattr *a,
+ struct netlink_ext_ack *extack)
+{
+ const struct dpll_device_ops *ops = dpll_device_ops(dpll);
+ enum dpll_feature_state state = nla_get_u32(a), old_state;
+ int ret;
+
+ if (!(ops->freq_monitor_set && ops->freq_monitor_get)) {
+ NL_SET_ERR_MSG_ATTR(extack, a,
+ "dpll device not capable of frequency monitor");
+ return -EOPNOTSUPP;
+ }
+ ret = ops->freq_monitor_get(dpll, dpll_priv(dpll), &old_state,
+ extack);
+ if (ret) {
+ NL_SET_ERR_MSG(extack,
+ "unable to get current state of frequency monitor");
+ return ret;
+ }
+ if (state == old_state)
+ return 0;
+
+ return ops->freq_monitor_set(dpll, dpll_priv(dpll), state, extack);
+}
+
static int
dpll_pin_freq_set(struct dpll_pin *pin, struct nlattr *a,
struct netlink_ext_ack *extack)
@@ -1878,6 +1962,12 @@ dpll_set_from_nlattr(struct dpll_device *dpll, struct genl_info *info)
if (ret)
return ret;
break;
+ case DPLL_A_FREQUENCY_MONITOR:
+ ret = dpll_freq_monitor_set(dpll, a,
+ info->extack);
+ if (ret)
+ return ret;
+ break;
}
}
diff --git a/include/linux/dpll.h b/include/linux/dpll.h
index 8862389ed50a..441fe25ba4f4 100644
--- a/include/linux/dpll.h
+++ b/include/linux/dpll.h
@@ -54,6 +54,12 @@ struct dpll_device_ops {
int (*phase_offset_avg_factor_get)(const struct dpll_device *dpll,
void *dpll_priv, u32 *factor,
struct netlink_ext_ack *extack);
+ int (*freq_monitor_set)(const struct dpll_device *dpll, void *dpll_priv,
+ enum dpll_feature_state state,
+ struct netlink_ext_ack *extack);
+ int (*freq_monitor_get)(const struct dpll_device *dpll, void *dpll_priv,
+ enum dpll_feature_state *state,
+ struct netlink_ext_ack *extack);
RH_KABI_RESERVE(1)
RH_KABI_RESERVE(2)
@@ -123,6 +129,10 @@ struct dpll_pin_ops {
int (*ffo_get)(const struct dpll_pin *pin, void *pin_priv,
const struct dpll_device *dpll, void *dpll_priv,
s64 *ffo, struct netlink_ext_ack *extack);
+ int (*measured_freq_get)(const struct dpll_pin *pin, void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv, u64 *measured_freq,
+ struct netlink_ext_ack *extack);
int (*esync_set)(const struct dpll_pin *pin, void *pin_priv,
const struct dpll_device *dpll, void *dpll_priv,
u64 freq, struct netlink_ext_ack *extack);
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,399 @@
From 7f9d57090a5680a3a47f6695f886ca8feb0e5bac Mon Sep 17 00:00:00 2001
From: Ivan Vecera <ivecera@redhat.com>
Date: Wed, 29 Apr 2026 14:51:52 +0200
Subject: [PATCH] dpll: zl3073x: implement frequency monitoring
JIRA: https://issues.redhat.com/browse/RHEL-165161
commit bfc923b642874ea6f94763d6060782072944ebd5
Author: Ivan Vecera <ivecera@redhat.com>
Date: Thu Apr 2 20:40:57 2026 +0200
dpll: zl3073x: implement frequency monitoring
Extract common measurement latch logic from zl3073x_ref_ffo_update()
into a new zl3073x_ref_freq_meas_latch() helper and add
zl3073x_ref_freq_meas_update() that uses it to latch and read absolute
input reference frequencies in Hz.
Add meas_freq field to struct zl3073x_ref and the corresponding
zl3073x_ref_meas_freq_get() accessor. The measured frequencies are
updated periodically alongside the existing FFO measurements.
Add freq_monitor boolean to struct zl3073x_dpll and implement the
freq_monitor_set/get device callbacks to enable/disable frequency
monitoring via the DPLL netlink interface.
Implement measured_freq_get pin callback for input pins that returns the
measured input frequency in mHz.
Reviewed-by: Petr Oros <poros@redhat.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
Link: https://patch.msgid.link/20260402184057.1890514-4-ivecera@redhat.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
(cherry picked from commit bfc923b642874ea6f94763d6060782072944ebd5)
Assisted-by: Patchpal
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c
index 6363002d48d4..cb47a5db061a 100644
--- a/drivers/dpll/zl3073x/core.c
+++ b/drivers/dpll/zl3073x/core.c
@@ -632,22 +632,21 @@ int zl3073x_ref_phase_offsets_update(struct zl3073x_dev *zldev, int channel)
}
/**
- * zl3073x_ref_ffo_update - update reference fractional frequency offsets
+ * zl3073x_ref_freq_meas_latch - latch reference frequency measurements
* @zldev: pointer to zl3073x_dev structure
+ * @type: measurement type (ZL_REF_FREQ_MEAS_CTRL_*)
*
- * The function asks device to update fractional frequency offsets latch
- * registers the latest measured values, reads and stores them into
+ * The function waits for the previous measurement to finish, selects all
+ * references and requests a new measurement of the given type.
*
* Return: 0 on success, <0 on error
*/
static int
-zl3073x_ref_ffo_update(struct zl3073x_dev *zldev)
+zl3073x_ref_freq_meas_latch(struct zl3073x_dev *zldev, u8 type)
{
- int i, rc;
+ int rc;
- /* Per datasheet we have to wait for 'ref_freq_meas_ctrl' to be zero
- * to ensure that the measured data are coherent.
- */
+ /* Wait for previous measurement to finish */
rc = zl3073x_poll_zero_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL,
ZL_REF_FREQ_MEAS_CTRL);
if (rc)
@@ -663,15 +662,64 @@ zl3073x_ref_ffo_update(struct zl3073x_dev *zldev)
if (rc)
return rc;
- /* Request frequency offset measurement */
- rc = zl3073x_write_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL,
- ZL_REF_FREQ_MEAS_CTRL_REF_FREQ_OFF);
+ /* Request measurement */
+ rc = zl3073x_write_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL, type);
if (rc)
return rc;
/* Wait for finish */
- rc = zl3073x_poll_zero_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL,
- ZL_REF_FREQ_MEAS_CTRL);
+ return zl3073x_poll_zero_u8(zldev, ZL_REG_REF_FREQ_MEAS_CTRL,
+ ZL_REF_FREQ_MEAS_CTRL);
+}
+
+/**
+ * zl3073x_ref_freq_meas_update - update measured input reference frequencies
+ * @zldev: pointer to zl3073x_dev structure
+ *
+ * The function asks device to latch measured input reference frequencies
+ * and stores the results in the ref state.
+ *
+ * Return: 0 on success, <0 on error
+ */
+static int
+zl3073x_ref_freq_meas_update(struct zl3073x_dev *zldev)
+{
+ int i, rc;
+
+ rc = zl3073x_ref_freq_meas_latch(zldev, ZL_REF_FREQ_MEAS_CTRL_REF_FREQ);
+ if (rc)
+ return rc;
+
+ /* Read measured frequencies in Hz (unsigned 32-bit, LSB = 1 Hz) */
+ for (i = 0; i < ZL3073X_NUM_REFS; i++) {
+ u32 value;
+
+ rc = zl3073x_read_u32(zldev, ZL_REG_REF_FREQ(i), &value);
+ if (rc)
+ return rc;
+
+ zldev->ref[i].meas_freq = value;
+ }
+
+ return 0;
+}
+
+/**
+ * zl3073x_ref_ffo_update - update reference fractional frequency offsets
+ * @zldev: pointer to zl3073x_dev structure
+ *
+ * The function asks device to latch the latest measured fractional
+ * frequency offset values, reads and stores them into the ref state.
+ *
+ * Return: 0 on success, <0 on error
+ */
+static int
+zl3073x_ref_ffo_update(struct zl3073x_dev *zldev)
+{
+ int i, rc;
+
+ rc = zl3073x_ref_freq_meas_latch(zldev,
+ ZL_REF_FREQ_MEAS_CTRL_REF_FREQ_OFF);
if (rc)
return rc;
@@ -714,6 +762,20 @@ zl3073x_dev_periodic_work(struct kthread_work *work)
dev_warn(zldev->dev, "Failed to update phase offsets: %pe\n",
ERR_PTR(rc));
+ /* Update measured input reference frequencies if any DPLL has
+ * frequency monitoring enabled.
+ */
+ list_for_each_entry(zldpll, &zldev->dplls, list) {
+ if (zldpll->freq_monitor) {
+ rc = zl3073x_ref_freq_meas_update(zldev);
+ if (rc)
+ dev_warn(zldev->dev,
+ "Failed to update measured frequencies: %pe\n",
+ ERR_PTR(rc));
+ break;
+ }
+ }
+
/* Update references' fractional frequency offsets */
rc = zl3073x_ref_ffo_update(zldev);
if (rc)
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index cf7a335baed5..2b307a467a7f 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -39,6 +39,7 @@
* @pin_state: last saved pin state
* @phase_offset: last saved pin phase offset
* @freq_offset: last saved fractional frequency offset
+ * @measured_freq: last saved measured frequency
*/
struct zl3073x_dpll_pin {
struct list_head list;
@@ -54,6 +55,7 @@ struct zl3073x_dpll_pin {
enum dpll_pin_state pin_state;
s64 phase_offset;
s64 freq_offset;
+ u32 measured_freq;
};
/*
@@ -202,6 +204,21 @@ zl3073x_dpll_input_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_priv,
return 0;
}
+static int
+zl3073x_dpll_input_pin_measured_freq_get(const struct dpll_pin *dpll_pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv, u64 *measured_freq,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll_pin *pin = pin_priv;
+
+ *measured_freq = pin->measured_freq;
+ *measured_freq *= DPLL_PIN_MEASURED_FREQUENCY_DIVIDER;
+
+ return 0;
+}
+
static int
zl3073x_dpll_input_pin_frequency_get(const struct dpll_pin *dpll_pin,
void *pin_priv,
@@ -1116,6 +1133,35 @@ zl3073x_dpll_phase_offset_monitor_set(const struct dpll_device *dpll,
return 0;
}
+static int
+zl3073x_dpll_freq_monitor_get(const struct dpll_device *dpll,
+ void *dpll_priv,
+ enum dpll_feature_state *state,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+
+ if (zldpll->freq_monitor)
+ *state = DPLL_FEATURE_STATE_ENABLE;
+ else
+ *state = DPLL_FEATURE_STATE_DISABLE;
+
+ return 0;
+}
+
+static int
+zl3073x_dpll_freq_monitor_set(const struct dpll_device *dpll,
+ void *dpll_priv,
+ enum dpll_feature_state state,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+
+ zldpll->freq_monitor = (state == DPLL_FEATURE_STATE_ENABLE);
+
+ return 0;
+}
+
static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = {
.direction_get = zl3073x_dpll_pin_direction_get,
.esync_get = zl3073x_dpll_input_pin_esync_get,
@@ -1123,6 +1169,7 @@ static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = {
.ffo_get = zl3073x_dpll_input_pin_ffo_get,
.frequency_get = zl3073x_dpll_input_pin_frequency_get,
.frequency_set = zl3073x_dpll_input_pin_frequency_set,
+ .measured_freq_get = zl3073x_dpll_input_pin_measured_freq_get,
.phase_offset_get = zl3073x_dpll_input_pin_phase_offset_get,
.phase_adjust_get = zl3073x_dpll_input_pin_phase_adjust_get,
.phase_adjust_set = zl3073x_dpll_input_pin_phase_adjust_set,
@@ -1151,6 +1198,8 @@ static const struct dpll_device_ops zl3073x_dpll_device_ops = {
.phase_offset_avg_factor_set = zl3073x_dpll_phase_offset_avg_factor_set,
.phase_offset_monitor_get = zl3073x_dpll_phase_offset_monitor_get,
.phase_offset_monitor_set = zl3073x_dpll_phase_offset_monitor_set,
+ .freq_monitor_get = zl3073x_dpll_freq_monitor_get,
+ .freq_monitor_set = zl3073x_dpll_freq_monitor_set,
.supported_modes_get = zl3073x_dpll_supported_modes_get,
};
@@ -1572,6 +1621,7 @@ zl3073x_dpll_pin_ffo_check(struct zl3073x_dpll_pin *pin)
struct zl3073x_dev *zldev = zldpll->dev;
const struct zl3073x_ref *ref;
u8 ref_id;
+ s64 ffo;
/* Get reference monitor status */
ref_id = zl3073x_input_pin_ref_get(pin->id);
@@ -1582,10 +1632,47 @@ zl3073x_dpll_pin_ffo_check(struct zl3073x_dpll_pin *pin)
return false;
/* Compare with previous value */
- if (pin->freq_offset != ref->ffo) {
+ ffo = zl3073x_ref_ffo_get(ref);
+ if (pin->freq_offset != ffo) {
dev_dbg(zldev->dev, "%s freq offset changed: %lld -> %lld\n",
- pin->label, pin->freq_offset, ref->ffo);
- pin->freq_offset = ref->ffo;
+ pin->label, pin->freq_offset, ffo);
+ pin->freq_offset = ffo;
+
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * zl3073x_dpll_pin_measured_freq_check - check for pin measured frequency
+ * change
+ * @pin: pin to check
+ *
+ * Check for the given pin's measured frequency change.
+ *
+ * Return: true on measured frequency change, false otherwise
+ */
+static bool
+zl3073x_dpll_pin_measured_freq_check(struct zl3073x_dpll_pin *pin)
+{
+ struct zl3073x_dpll *zldpll = pin->dpll;
+ struct zl3073x_dev *zldev = zldpll->dev;
+ const struct zl3073x_ref *ref;
+ u8 ref_id;
+ u32 freq;
+
+ if (!zldpll->freq_monitor)
+ return false;
+
+ ref_id = zl3073x_input_pin_ref_get(pin->id);
+ ref = zl3073x_ref_state_get(zldev, ref_id);
+
+ freq = zl3073x_ref_meas_freq_get(ref);
+ if (pin->measured_freq != freq) {
+ dev_dbg(zldev->dev, "%s measured freq changed: %u -> %u\n",
+ pin->label, pin->measured_freq, freq);
+ pin->measured_freq = freq;
return true;
}
@@ -1677,13 +1764,18 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll)
pin_changed = true;
}
- /* Check for phase offset and ffo change once per second */
+ /* Check for phase offset, ffo, and measured freq change
+ * once per second.
+ */
if (zldpll->check_count % 2 == 0) {
if (zl3073x_dpll_pin_phase_offset_check(pin))
pin_changed = true;
if (zl3073x_dpll_pin_ffo_check(pin))
pin_changed = true;
+
+ if (zl3073x_dpll_pin_measured_freq_check(pin))
+ pin_changed = true;
}
if (pin_changed)
diff --git a/drivers/dpll/zl3073x/dpll.h b/drivers/dpll/zl3073x/dpll.h
index 115ee4f67e7a..434c32a7db12 100644
--- a/drivers/dpll/zl3073x/dpll.h
+++ b/drivers/dpll/zl3073x/dpll.h
@@ -15,6 +15,7 @@
* @id: DPLL index
* @check_count: periodic check counter
* @phase_monitor: is phase offset monitor enabled
+ * @freq_monitor: is frequency monitor enabled
* @ops: DPLL device operations for this instance
* @dpll_dev: pointer to registered DPLL device
* @tracker: tracking object for the acquired reference
@@ -28,6 +29,7 @@ struct zl3073x_dpll {
u8 id;
u8 check_count;
bool phase_monitor;
+ bool freq_monitor;
struct dpll_device_ops ops;
struct dpll_device *dpll_dev;
dpll_tracker tracker;
diff --git a/drivers/dpll/zl3073x/ref.h b/drivers/dpll/zl3073x/ref.h
index 06d8d4d97ea2..be16be20dbc7 100644
--- a/drivers/dpll/zl3073x/ref.h
+++ b/drivers/dpll/zl3073x/ref.h
@@ -23,6 +23,7 @@ struct zl3073x_dev;
* @sync_ctrl: reference sync control
* @config: reference config
* @ffo: current fractional frequency offset
+ * @meas_freq: measured input frequency in Hz
* @mon_status: reference monitor status
*/
struct zl3073x_ref {
@@ -40,6 +41,7 @@ struct zl3073x_ref {
);
struct_group(stat, /* Status */
s64 ffo;
+ u32 meas_freq;
u8 mon_status;
);
};
@@ -68,6 +70,18 @@ zl3073x_ref_ffo_get(const struct zl3073x_ref *ref)
return ref->ffo;
}
+/**
+ * zl3073x_ref_meas_freq_get - get measured input frequency
+ * @ref: pointer to ref state
+ *
+ * Return: measured input frequency in Hz
+ */
+static inline u32
+zl3073x_ref_meas_freq_get(const struct zl3073x_ref *ref)
+{
+ return ref->meas_freq;
+}
+
/**
* zl3073x_ref_freq_get - get given input reference frequency
* @ref: pointer to ref state
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,181 @@
From 83c5fcf1f1b2adc253e0eb10ae85ef11ce0ded24 Mon Sep 17 00:00:00 2001
From: Ivan Vecera <ivecera@redhat.com>
Date: Wed, 29 Apr 2026 14:55:25 +0200
Subject: [PATCH] dpll: zl3073x: clean up esync get/set and use
zl3073x_out_is_ndiv()
JIRA: https://issues.redhat.com/browse/RHEL-157828
commit 3c8c39768b10867e4f630080785b602245f01760
Author: Ivan Vecera <ivecera@redhat.com>
Date: Wed Apr 8 12:27:12 2026 +0200
dpll: zl3073x: clean up esync get/set and use zl3073x_out_is_ndiv()
Return -EOPNOTSUPP early in esync_get callbacks when esync is not
supported instead of conditionally populating the range at the end.
This simplifies the control flow by removing the finish label/goto
in the output variant and the conditional range assignment in both
input and output variants.
Replace open-coded N-div signal format switch statements with
zl3073x_out_is_ndiv() helper in esync_get, esync_set and
frequency_set callbacks.
Reviewed-by: Petr Oros <poros@redhat.com>
Reviewed-by: Prathosh Satish <Prathosh.Satish@microchip.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
Link: https://patch.msgid.link/20260408102716.443099-2-ivecera@redhat.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
(cherry picked from commit 3c8c39768b10867e4f630080785b602245f01760)
Assisted-by: Patchpal
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index 2b307a467a7f..86df28415318 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -133,6 +133,12 @@ zl3073x_dpll_input_pin_esync_get(const struct dpll_pin *dpll_pin,
ref_id = zl3073x_input_pin_ref_get(pin->id);
ref = zl3073x_ref_state_get(zldev, ref_id);
+ if (!pin->esync_control || zl3073x_ref_freq_get(ref) <= 1)
+ return -EOPNOTSUPP;
+
+ esync->range = esync_freq_ranges;
+ esync->range_num = ARRAY_SIZE(esync_freq_ranges);
+
switch (FIELD_GET(ZL_REF_SYNC_CTRL_MODE, ref->sync_ctrl)) {
case ZL_REF_SYNC_CTRL_MODE_50_50_ESYNC_25_75:
esync->freq = ref->esync_n_div == ZL_REF_ESYNC_DIV_1HZ ? 1 : 0;
@@ -144,17 +150,6 @@ zl3073x_dpll_input_pin_esync_get(const struct dpll_pin *dpll_pin,
break;
}
- /* If the pin supports esync control expose its range but only
- * if the current reference frequency is > 1 Hz.
- */
- if (pin->esync_control && zl3073x_ref_freq_get(ref) > 1) {
- esync->range = esync_freq_ranges;
- esync->range_num = ARRAY_SIZE(esync_freq_ranges);
- } else {
- esync->range = NULL;
- esync->range_num = 0;
- }
-
return 0;
}
@@ -599,8 +594,8 @@ zl3073x_dpll_output_pin_esync_get(const struct dpll_pin *dpll_pin,
struct zl3073x_dpll_pin *pin = pin_priv;
const struct zl3073x_synth *synth;
const struct zl3073x_out *out;
+ u32 synth_freq, out_freq;
u8 clock_type, out_id;
- u32 synth_freq;
out_id = zl3073x_output_pin_out_get(pin->id);
out = zl3073x_out_state_get(zldev, out_id);
@@ -609,17 +604,19 @@ zl3073x_dpll_output_pin_esync_get(const struct dpll_pin *dpll_pin,
* for N-division is also used for the esync divider so both cannot
* be used.
*/
- switch (zl3073x_out_signal_format_get(out)) {
- case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV:
- case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV_INV:
+ if (zl3073x_out_is_ndiv(out))
return -EOPNOTSUPP;
- default:
- break;
- }
/* Get attached synth frequency */
synth = zl3073x_synth_state_get(zldev, zl3073x_out_synth_get(out));
synth_freq = zl3073x_synth_freq_get(synth);
+ out_freq = synth_freq / out->div;
+
+ if (!pin->esync_control || out_freq <= 1)
+ return -EOPNOTSUPP;
+
+ esync->range = esync_freq_ranges;
+ esync->range_num = ARRAY_SIZE(esync_freq_ranges);
clock_type = FIELD_GET(ZL_OUTPUT_MODE_CLOCK_TYPE, out->mode);
if (clock_type != ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC) {
@@ -627,11 +624,11 @@ zl3073x_dpll_output_pin_esync_get(const struct dpll_pin *dpll_pin,
esync->freq = 0;
esync->pulse = 0;
- goto finish;
+ return 0;
}
/* Compute esync frequency */
- esync->freq = synth_freq / out->div / out->esync_n_period;
+ esync->freq = out_freq / out->esync_n_period;
/* By comparing the esync_pulse_width to the half of the pulse width
* the esync pulse percentage can be determined.
@@ -640,18 +637,6 @@ zl3073x_dpll_output_pin_esync_get(const struct dpll_pin *dpll_pin,
*/
esync->pulse = (50 * out->esync_n_width) / out->div;
-finish:
- /* Set supported esync ranges if the pin supports esync control and
- * if the output frequency is > 1 Hz.
- */
- if (pin->esync_control && (synth_freq / out->div) > 1) {
- esync->range = esync_freq_ranges;
- esync->range_num = ARRAY_SIZE(esync_freq_ranges);
- } else {
- esync->range = NULL;
- esync->range_num = 0;
- }
-
return 0;
}
@@ -677,13 +662,8 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin,
* for N-division is also used for the esync divider so both cannot
* be used.
*/
- switch (zl3073x_out_signal_format_get(&out)) {
- case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV:
- case ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV_INV:
+ if (zl3073x_out_is_ndiv(&out))
return -EOPNOTSUPP;
- default:
- break;
- }
/* Select clock type */
if (freq)
@@ -745,9 +725,9 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
struct zl3073x_dev *zldev = zldpll->dev;
struct zl3073x_dpll_pin *pin = pin_priv;
const struct zl3073x_synth *synth;
- u8 out_id, signal_format;
u32 new_div, synth_freq;
struct zl3073x_out out;
+ u8 out_id;
out_id = zl3073x_output_pin_out_get(pin->id);
out = *zl3073x_out_state_get(zldev, out_id);
@@ -757,12 +737,8 @@ zl3073x_dpll_output_pin_frequency_set(const struct dpll_pin *dpll_pin,
synth_freq = zl3073x_synth_freq_get(synth);
new_div = synth_freq / (u32)frequency;
- /* Get used signal format for the given output */
- signal_format = zl3073x_out_signal_format_get(&out);
-
/* Check signal format */
- if (signal_format != ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV &&
- signal_format != ZL_OUTPUT_MODE_SIGNAL_FORMAT_2_NDIV_INV) {
+ if (!zl3073x_out_is_ndiv(&out)) {
/* For non N-divided signal formats the frequency is computed
* as division of synth frequency and output divisor.
*/
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,99 @@
From f4680f5c34a8af844573d88340c8b625a0628d25 Mon Sep 17 00:00:00 2001
From: Ivan Vecera <ivecera@redhat.com>
Date: Wed, 29 Apr 2026 14:55:32 +0200
Subject: [PATCH] dpll: zl3073x: use FIELD_MODIFY() for clear-and-set patterns
JIRA: https://issues.redhat.com/browse/RHEL-157828
commit 737cb6195c40acf67c876f509e209158436cf287
Author: Ivan Vecera <ivecera@redhat.com>
Date: Wed Apr 8 12:27:13 2026 +0200
dpll: zl3073x: use FIELD_MODIFY() for clear-and-set patterns
Replace open-coded clear-and-set bitfield operations with
FIELD_MODIFY().
Reviewed-by: Petr Oros <poros@redhat.com>
Reviewed-by: Prathosh Satish <Prathosh.Satish@microchip.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
Link: https://patch.msgid.link/20260408102716.443099-3-ivecera@redhat.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
(cherry picked from commit 737cb6195c40acf67c876f509e209158436cf287)
Assisted-by: Patchpal
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
diff --git a/drivers/dpll/zl3073x/chan.h b/drivers/dpll/zl3073x/chan.h
index e0f02d343208..481da2133202 100644
--- a/drivers/dpll/zl3073x/chan.h
+++ b/drivers/dpll/zl3073x/chan.h
@@ -66,8 +66,7 @@ static inline u8 zl3073x_chan_ref_get(const struct zl3073x_chan *chan)
*/
static inline void zl3073x_chan_mode_set(struct zl3073x_chan *chan, u8 mode)
{
- chan->mode_refsel &= ~ZL_DPLL_MODE_REFSEL_MODE;
- chan->mode_refsel |= FIELD_PREP(ZL_DPLL_MODE_REFSEL_MODE, mode);
+ FIELD_MODIFY(ZL_DPLL_MODE_REFSEL_MODE, &chan->mode_refsel, mode);
}
/**
@@ -77,8 +76,7 @@ static inline void zl3073x_chan_mode_set(struct zl3073x_chan *chan, u8 mode)
*/
static inline void zl3073x_chan_ref_set(struct zl3073x_chan *chan, u8 ref)
{
- chan->mode_refsel &= ~ZL_DPLL_MODE_REFSEL_REF;
- chan->mode_refsel |= FIELD_PREP(ZL_DPLL_MODE_REFSEL_REF, ref);
+ FIELD_MODIFY(ZL_DPLL_MODE_REFSEL_REF, &chan->mode_refsel, ref);
}
/**
@@ -110,13 +108,10 @@ zl3073x_chan_ref_prio_set(struct zl3073x_chan *chan, u8 ref, u8 prio)
{
u8 *val = &chan->ref_prio[ref / 2];
- if (!(ref & 1)) {
- *val &= ~ZL_DPLL_REF_PRIO_REF_P;
- *val |= FIELD_PREP(ZL_DPLL_REF_PRIO_REF_P, prio);
- } else {
- *val &= ~ZL_DPLL_REF_PRIO_REF_N;
- *val |= FIELD_PREP(ZL_DPLL_REF_PRIO_REF_N, prio);
- }
+ if (!(ref & 1))
+ FIELD_MODIFY(ZL_DPLL_REF_PRIO_REF_P, val, prio);
+ else
+ FIELD_MODIFY(ZL_DPLL_REF_PRIO_REF_N, val, prio);
}
/**
diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c
index cb47a5db061a..5f1e70f3e40a 100644
--- a/drivers/dpll/zl3073x/core.c
+++ b/drivers/dpll/zl3073x/core.c
@@ -805,8 +805,7 @@ int zl3073x_dev_phase_avg_factor_set(struct zl3073x_dev *zldev, u8 factor)
value = (factor + 1) & 0x0f;
/* Update phase measurement control register */
- dpll_meas_ctrl &= ~ZL_DPLL_MEAS_CTRL_AVG_FACTOR;
- dpll_meas_ctrl |= FIELD_PREP(ZL_DPLL_MEAS_CTRL_AVG_FACTOR, value);
+ FIELD_MODIFY(ZL_DPLL_MEAS_CTRL_AVG_FACTOR, &dpll_meas_ctrl, value);
rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_MEAS_CTRL, dpll_meas_ctrl);
if (rc)
return rc;
diff --git a/drivers/dpll/zl3073x/flash.c b/drivers/dpll/zl3073x/flash.c
index 83452a77e3e9..f85535c8ad24 100644
--- a/drivers/dpll/zl3073x/flash.c
+++ b/drivers/dpll/zl3073x/flash.c
@@ -194,8 +194,7 @@ zl3073x_flash_cmd_wait(struct zl3073x_dev *zldev, u32 operation,
if (rc)
return rc;
- value &= ~ZL_WRITE_FLASH_OP;
- value |= FIELD_PREP(ZL_WRITE_FLASH_OP, operation);
+ FIELD_MODIFY(ZL_WRITE_FLASH_OP, &value, operation);
rc = zl3073x_write_u8(zldev, ZL_REG_WRITE_FLASH, value);
if (rc)
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,219 @@
From ea098b764d85e9c992398b51e59f4fc69a7351ba Mon Sep 17 00:00:00 2001
From: Ivan Vecera <ivecera@redhat.com>
Date: Wed, 29 Apr 2026 14:55:39 +0200
Subject: [PATCH] dpll: zl3073x: add ref sync and output clock type helpers
JIRA: https://issues.redhat.com/browse/RHEL-157828
commit 63009eb92b0f379afddbba8dfdf8df087f6d5b62
Author: Ivan Vecera <ivecera@redhat.com>
Date: Wed Apr 8 12:27:14 2026 +0200
dpll: zl3073x: add ref sync and output clock type helpers
Add ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR and ZL_REF_SYNC_CTRL_PAIR
register definitions.
Add inline helpers to get and set the sync control mode and sync pair
fields of the reference sync control register:
zl3073x_ref_sync_mode_get/set() - ZL_REF_SYNC_CTRL_MODE field
zl3073x_ref_sync_pair_get/set() - ZL_REF_SYNC_CTRL_PAIR field
Add inline helpers to get and set the clock type field of the output
mode register:
zl3073x_out_clock_type_get/set() - ZL_OUTPUT_MODE_CLOCK_TYPE field
Convert existing esync callbacks to use the new helpers.
Reviewed-by: Petr Oros <poros@redhat.com>
Reviewed-by: Prathosh Satish <Prathosh.Satish@microchip.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
Link: https://patch.msgid.link/20260408102716.443099-4-ivecera@redhat.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
(cherry picked from commit 63009eb92b0f379afddbba8dfdf8df087f6d5b62)
Assisted-by: Patchpal
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index 86df28415318..a151be700e6e 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -139,7 +139,7 @@ zl3073x_dpll_input_pin_esync_get(const struct dpll_pin *dpll_pin,
esync->range = esync_freq_ranges;
esync->range_num = ARRAY_SIZE(esync_freq_ranges);
- switch (FIELD_GET(ZL_REF_SYNC_CTRL_MODE, ref->sync_ctrl)) {
+ switch (zl3073x_ref_sync_mode_get(ref)) {
case ZL_REF_SYNC_CTRL_MODE_50_50_ESYNC_25_75:
esync->freq = ref->esync_n_div == ZL_REF_ESYNC_DIV_1HZ ? 1 : 0;
esync->pulse = 25;
@@ -175,8 +175,7 @@ zl3073x_dpll_input_pin_esync_set(const struct dpll_pin *dpll_pin,
else
sync_mode = ZL_REF_SYNC_CTRL_MODE_50_50_ESYNC_25_75;
- ref.sync_ctrl &= ~ZL_REF_SYNC_CTRL_MODE;
- ref.sync_ctrl |= FIELD_PREP(ZL_REF_SYNC_CTRL_MODE, sync_mode);
+ zl3073x_ref_sync_mode_set(&ref, sync_mode);
if (freq) {
/* 1 Hz is only supported frequency now */
@@ -595,7 +594,7 @@ zl3073x_dpll_output_pin_esync_get(const struct dpll_pin *dpll_pin,
const struct zl3073x_synth *synth;
const struct zl3073x_out *out;
u32 synth_freq, out_freq;
- u8 clock_type, out_id;
+ u8 out_id;
out_id = zl3073x_output_pin_out_get(pin->id);
out = zl3073x_out_state_get(zldev, out_id);
@@ -618,8 +617,7 @@ zl3073x_dpll_output_pin_esync_get(const struct dpll_pin *dpll_pin,
esync->range = esync_freq_ranges;
esync->range_num = ARRAY_SIZE(esync_freq_ranges);
- clock_type = FIELD_GET(ZL_OUTPUT_MODE_CLOCK_TYPE, out->mode);
- if (clock_type != ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC) {
+ if (zl3073x_out_clock_type_get(out) != ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC) {
/* No need to read esync data if it is not enabled */
esync->freq = 0;
esync->pulse = 0;
@@ -652,8 +650,8 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin,
struct zl3073x_dpll_pin *pin = pin_priv;
const struct zl3073x_synth *synth;
struct zl3073x_out out;
- u8 clock_type, out_id;
u32 synth_freq;
+ u8 out_id;
out_id = zl3073x_output_pin_out_get(pin->id);
out = *zl3073x_out_state_get(zldev, out_id);
@@ -665,15 +663,13 @@ zl3073x_dpll_output_pin_esync_set(const struct dpll_pin *dpll_pin,
if (zl3073x_out_is_ndiv(&out))
return -EOPNOTSUPP;
- /* Select clock type */
+ /* Update clock type in output mode */
if (freq)
- clock_type = ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC;
+ zl3073x_out_clock_type_set(&out,
+ ZL_OUTPUT_MODE_CLOCK_TYPE_ESYNC);
else
- clock_type = ZL_OUTPUT_MODE_CLOCK_TYPE_NORMAL;
-
- /* Update clock type in output mode */
- out.mode &= ~ZL_OUTPUT_MODE_CLOCK_TYPE;
- out.mode |= FIELD_PREP(ZL_OUTPUT_MODE_CLOCK_TYPE, clock_type);
+ zl3073x_out_clock_type_set(&out,
+ ZL_OUTPUT_MODE_CLOCK_TYPE_NORMAL);
/* If esync is being disabled just write mailbox and finish */
if (!freq)
diff --git a/drivers/dpll/zl3073x/out.h b/drivers/dpll/zl3073x/out.h
index edf40432bba5..660889c57bff 100644
--- a/drivers/dpll/zl3073x/out.h
+++ b/drivers/dpll/zl3073x/out.h
@@ -42,6 +42,28 @@ const struct zl3073x_out *zl3073x_out_state_get(struct zl3073x_dev *zldev,
int zl3073x_out_state_set(struct zl3073x_dev *zldev, u8 index,
const struct zl3073x_out *out);
+/**
+ * zl3073x_out_clock_type_get - get output clock type
+ * @out: pointer to out state
+ *
+ * Return: clock type of given output (ZL_OUTPUT_MODE_CLOCK_TYPE_*)
+ */
+static inline u8 zl3073x_out_clock_type_get(const struct zl3073x_out *out)
+{
+ return FIELD_GET(ZL_OUTPUT_MODE_CLOCK_TYPE, out->mode);
+}
+
+/**
+ * zl3073x_out_clock_type_set - set output clock type
+ * @out: pointer to out state
+ * @type: clock type (ZL_OUTPUT_MODE_CLOCK_TYPE_*)
+ */
+static inline void
+zl3073x_out_clock_type_set(struct zl3073x_out *out, u8 type)
+{
+ FIELD_MODIFY(ZL_OUTPUT_MODE_CLOCK_TYPE, &out->mode, type);
+}
+
/**
* zl3073x_out_signal_format_get - get output signal format
* @out: pointer to out state
diff --git a/drivers/dpll/zl3073x/ref.h b/drivers/dpll/zl3073x/ref.h
index be16be20dbc7..55e80e4f0873 100644
--- a/drivers/dpll/zl3073x/ref.h
+++ b/drivers/dpll/zl3073x/ref.h
@@ -120,6 +120,52 @@ zl3073x_ref_freq_set(struct zl3073x_ref *ref, u32 freq)
return 0;
}
+/**
+ * zl3073x_ref_sync_mode_get - get sync control mode
+ * @ref: pointer to ref state
+ *
+ * Return: sync control mode (ZL_REF_SYNC_CTRL_MODE_*)
+ */
+static inline u8
+zl3073x_ref_sync_mode_get(const struct zl3073x_ref *ref)
+{
+ return FIELD_GET(ZL_REF_SYNC_CTRL_MODE, ref->sync_ctrl);
+}
+
+/**
+ * zl3073x_ref_sync_mode_set - set sync control mode
+ * @ref: pointer to ref state
+ * @mode: sync control mode (ZL_REF_SYNC_CTRL_MODE_*)
+ */
+static inline void
+zl3073x_ref_sync_mode_set(struct zl3073x_ref *ref, u8 mode)
+{
+ FIELD_MODIFY(ZL_REF_SYNC_CTRL_MODE, &ref->sync_ctrl, mode);
+}
+
+/**
+ * zl3073x_ref_sync_pair_get - get sync pair reference index
+ * @ref: pointer to ref state
+ *
+ * Return: paired reference index
+ */
+static inline u8
+zl3073x_ref_sync_pair_get(const struct zl3073x_ref *ref)
+{
+ return FIELD_GET(ZL_REF_SYNC_CTRL_PAIR, ref->sync_ctrl);
+}
+
+/**
+ * zl3073x_ref_sync_pair_set - set sync pair reference index
+ * @ref: pointer to ref state
+ * @pair: paired reference index
+ */
+static inline void
+zl3073x_ref_sync_pair_set(struct zl3073x_ref *ref, u8 pair)
+{
+ FIELD_MODIFY(ZL_REF_SYNC_CTRL_PAIR, &ref->sync_ctrl, pair);
+}
+
/**
* zl3073x_ref_is_diff - check if the given input reference is differential
* @ref: pointer to ref state
diff --git a/drivers/dpll/zl3073x/regs.h b/drivers/dpll/zl3073x/regs.h
index 5ae50cb761a9..d425dc67250f 100644
--- a/drivers/dpll/zl3073x/regs.h
+++ b/drivers/dpll/zl3073x/regs.h
@@ -213,7 +213,9 @@
#define ZL_REG_REF_SYNC_CTRL ZL_REG(10, 0x2e, 1)
#define ZL_REF_SYNC_CTRL_MODE GENMASK(2, 0)
#define ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR_OFF 0
+#define ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR 1
#define ZL_REF_SYNC_CTRL_MODE_50_50_ESYNC_25_75 2
+#define ZL_REF_SYNC_CTRL_PAIR GENMASK(7, 4)
#define ZL_REG_REF_ESYNC_DIV ZL_REG(10, 0x30, 4)
#define ZL_REF_ESYNC_DIV_1HZ 0
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,325 @@
From ef38c6a6c3f4e6f931e21f54c88741a1a0c84f27 Mon Sep 17 00:00:00 2001
From: Ivan Vecera <ivecera@redhat.com>
Date: Wed, 29 Apr 2026 14:55:45 +0200
Subject: [PATCH] dpll: zl3073x: add ref-sync pair support
JIRA: https://issues.redhat.com/browse/RHEL-157828
commit 14f269ae699869ddaca7c29c9c6c52288e3bfb73
Author: Ivan Vecera <ivecera@redhat.com>
Date: Wed Apr 8 12:27:16 2026 +0200
dpll: zl3073x: add ref-sync pair support
Add support for ref-sync pair registration using the 'ref-sync-sources'
phandle property from device tree. A ref-sync pair consists of a clock
reference and a low-frequency sync signal where the DPLL locks to the
clock reference but phase-aligns to the sync reference.
The implementation:
- Stores fwnode handle in zl3073x_dpll_pin during pin registration
- Adds ref_sync_get/set callbacks to read and write the sync control
mode and pair registers
- Validates ref-sync frequency constraints: sync signal must be 8 kHz
or less, clock reference must be 1 kHz or more and higher than sync
- Excludes sync source from automatic reference selection by setting
its priority to NONE on connect; on disconnect the priority is left
as NONE and the user must explicitly make the pin selectable again
- Iterates ref-sync-sources phandles to register declared pairings
via dpll_pin_ref_sync_pair_add()
Reviewed-by: Petr Oros <poros@redhat.com>
Reviewed-by: Prathosh Satish <Prathosh.Satish@microchip.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
Link: https://patch.msgid.link/20260408102716.443099-6-ivecera@redhat.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
(cherry picked from commit 14f269ae699869ddaca7c29c9c6c52288e3bfb73)
Assisted-by: Patchpal
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index a151be700e6e..55195dcd21d6 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -13,6 +13,7 @@
#include <linux/module.h>
#include <linux/netlink.h>
#include <linux/platform_device.h>
+#include <linux/property.h>
#include <linux/slab.h>
#include <linux/sprintf.h>
@@ -30,6 +31,7 @@
* @dpll: DPLL the pin is registered to
* @dpll_pin: pointer to registered dpll_pin
* @tracker: tracking object for the acquired reference
+ * @fwnode: firmware node handle
* @label: package label
* @dir: pin direction
* @id: pin id
@@ -46,6 +48,7 @@ struct zl3073x_dpll_pin {
struct zl3073x_dpll *dpll;
struct dpll_pin *dpll_pin;
dpll_tracker tracker;
+ struct fwnode_handle *fwnode;
char label[8];
enum dpll_pin_direction dir;
u8 id;
@@ -186,6 +189,109 @@ zl3073x_dpll_input_pin_esync_set(const struct dpll_pin *dpll_pin,
return zl3073x_ref_state_set(zldev, ref_id, &ref);
}
+static int
+zl3073x_dpll_input_pin_ref_sync_get(const struct dpll_pin *dpll_pin,
+ void *pin_priv,
+ const struct dpll_pin *ref_sync_pin,
+ void *ref_sync_pin_priv,
+ enum dpll_pin_state *state,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll_pin *sync_pin = ref_sync_pin_priv;
+ struct zl3073x_dpll_pin *pin = pin_priv;
+ struct zl3073x_dpll *zldpll = pin->dpll;
+ struct zl3073x_dev *zldev = zldpll->dev;
+ const struct zl3073x_ref *ref;
+ u8 ref_id, mode, pair;
+
+ ref_id = zl3073x_input_pin_ref_get(pin->id);
+ ref = zl3073x_ref_state_get(zldev, ref_id);
+ mode = zl3073x_ref_sync_mode_get(ref);
+ pair = zl3073x_ref_sync_pair_get(ref);
+
+ if (mode == ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR &&
+ pair == zl3073x_input_pin_ref_get(sync_pin->id))
+ *state = DPLL_PIN_STATE_CONNECTED;
+ else
+ *state = DPLL_PIN_STATE_DISCONNECTED;
+
+ return 0;
+}
+
+static int
+zl3073x_dpll_input_pin_ref_sync_set(const struct dpll_pin *dpll_pin,
+ void *pin_priv,
+ const struct dpll_pin *ref_sync_pin,
+ void *ref_sync_pin_priv,
+ const enum dpll_pin_state state,
+ struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll_pin *sync_pin = ref_sync_pin_priv;
+ struct zl3073x_dpll_pin *pin = pin_priv;
+ struct zl3073x_dpll *zldpll = pin->dpll;
+ struct zl3073x_dev *zldev = zldpll->dev;
+ u8 mode, ref_id, sync_ref_id;
+ struct zl3073x_chan chan;
+ struct zl3073x_ref ref;
+ int rc;
+
+ ref_id = zl3073x_input_pin_ref_get(pin->id);
+ sync_ref_id = zl3073x_input_pin_ref_get(sync_pin->id);
+ ref = *zl3073x_ref_state_get(zldev, ref_id);
+
+ if (state == DPLL_PIN_STATE_CONNECTED) {
+ const struct zl3073x_ref *sync_ref;
+ u32 ref_freq, sync_freq;
+
+ sync_ref = zl3073x_ref_state_get(zldev, sync_ref_id);
+ ref_freq = zl3073x_ref_freq_get(&ref);
+ sync_freq = zl3073x_ref_freq_get(sync_ref);
+
+ /* Sync signal must be 8 kHz or less and clock reference
+ * must be 1 kHz or more and higher than the sync signal.
+ */
+ if (sync_freq > 8000) {
+ NL_SET_ERR_MSG(extack,
+ "sync frequency must be 8 kHz or less");
+ return -EINVAL;
+ }
+ if (ref_freq < 1000) {
+ NL_SET_ERR_MSG(extack,
+ "clock frequency must be 1 kHz or more");
+ return -EINVAL;
+ }
+ if (ref_freq <= sync_freq) {
+ NL_SET_ERR_MSG(extack,
+ "clock frequency must be higher than sync frequency");
+ return -EINVAL;
+ }
+
+ zl3073x_ref_sync_pair_set(&ref, sync_ref_id);
+ mode = ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR;
+ } else {
+ mode = ZL_REF_SYNC_CTRL_MODE_REFSYNC_PAIR_OFF;
+ }
+
+ zl3073x_ref_sync_mode_set(&ref, mode);
+
+ rc = zl3073x_ref_state_set(zldev, ref_id, &ref);
+ if (rc)
+ return rc;
+
+ /* Exclude sync source from automatic reference selection by setting
+ * its priority to NONE. On disconnect the priority is left as NONE
+ * and the user must explicitly make the pin selectable again.
+ */
+ if (state == DPLL_PIN_STATE_CONNECTED) {
+ chan = *zl3073x_chan_state_get(zldev, zldpll->id);
+ zl3073x_chan_ref_prio_set(&chan, sync_ref_id,
+ ZL_DPLL_REF_PRIO_NONE);
+ return zl3073x_chan_state_set(zldev, zldpll->id, &chan);
+ }
+
+ return 0;
+}
+
static int
zl3073x_dpll_input_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_priv,
const struct dpll_device *dpll, void *dpll_priv,
@@ -1147,6 +1253,8 @@ static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = {
.phase_adjust_set = zl3073x_dpll_input_pin_phase_adjust_set,
.prio_get = zl3073x_dpll_input_pin_prio_get,
.prio_set = zl3073x_dpll_input_pin_prio_set,
+ .ref_sync_get = zl3073x_dpll_input_pin_ref_sync_get,
+ .ref_sync_set = zl3073x_dpll_input_pin_ref_sync_set,
.state_on_dpll_get = zl3073x_dpll_input_pin_state_on_dpll_get,
.state_on_dpll_set = zl3073x_dpll_input_pin_state_on_dpll_set,
};
@@ -1239,8 +1347,11 @@ zl3073x_dpll_pin_register(struct zl3073x_dpll_pin *pin, u32 index)
if (IS_ERR(props))
return PTR_ERR(props);
- /* Save package label, esync capability and phase adjust granularity */
+ /* Save package label, fwnode, esync capability and phase adjust
+ * granularity.
+ */
strscpy(pin->label, props->package_label);
+ pin->fwnode = fwnode_handle_get(props->fwnode);
pin->esync_control = props->esync_control;
pin->phase_gran = props->dpll_props.phase_gran;
@@ -1285,6 +1396,8 @@ zl3073x_dpll_pin_register(struct zl3073x_dpll_pin *pin, u32 index)
dpll_pin_put(pin->dpll_pin, &pin->tracker);
pin->dpll_pin = NULL;
err_pin_get:
+ fwnode_handle_put(pin->fwnode);
+ pin->fwnode = NULL;
zl3073x_pin_props_put(props);
return rc;
@@ -1314,6 +1427,9 @@ zl3073x_dpll_pin_unregister(struct zl3073x_dpll_pin *pin)
dpll_pin_put(pin->dpll_pin, &pin->tracker);
pin->dpll_pin = NULL;
+
+ fwnode_handle_put(pin->fwnode);
+ pin->fwnode = NULL;
}
/**
@@ -1827,6 +1943,88 @@ zl3073x_dpll_free(struct zl3073x_dpll *zldpll)
kfree(zldpll);
}
+/**
+ * zl3073x_dpll_ref_sync_pair_register - register ref_sync pairs for a pin
+ * @pin: pointer to zl3073x_dpll_pin structure
+ *
+ * Iterates 'ref-sync-sources' phandles in the pin's firmware node and
+ * registers each declared pairing.
+ *
+ * Return: 0 on success, <0 on error
+ */
+static int
+zl3073x_dpll_ref_sync_pair_register(struct zl3073x_dpll_pin *pin)
+{
+ struct zl3073x_dev *zldev = pin->dpll->dev;
+ struct fwnode_handle *fwnode;
+ struct dpll_pin *sync_pin;
+ dpll_tracker tracker;
+ int n, rc;
+
+ for (n = 0; ; n++) {
+ /* Get n'th ref-sync source */
+ fwnode = fwnode_find_reference(pin->fwnode, "ref-sync-sources",
+ n);
+ if (IS_ERR(fwnode)) {
+ rc = PTR_ERR(fwnode);
+ break;
+ }
+
+ /* Find associated dpll pin */
+ sync_pin = fwnode_dpll_pin_find(fwnode, &tracker);
+ fwnode_handle_put(fwnode);
+ if (!sync_pin) {
+ dev_warn(zldev->dev, "%s: ref-sync source %d not found",
+ pin->label, n);
+ continue;
+ }
+
+ /* Register new ref-sync pair */
+ rc = dpll_pin_ref_sync_pair_add(pin->dpll_pin, sync_pin);
+ dpll_pin_put(sync_pin, &tracker);
+
+ /* -EBUSY means pairing already exists from another DPLL's
+ * registration.
+ */
+ if (rc && rc != -EBUSY) {
+ dev_err(zldev->dev,
+ "%s: failed to add ref-sync source %d: %pe",
+ pin->label, n, ERR_PTR(rc));
+ break;
+ }
+ }
+
+ return rc != -ENOENT ? rc : 0;
+}
+
+/**
+ * zl3073x_dpll_ref_sync_pairs_register - register ref_sync pairs for a DPLL
+ * @zldpll: pointer to zl3073x_dpll structure
+ *
+ * Iterates all registered input pins of the given DPLL and establishes
+ * ref_sync pairings declared by 'ref-sync-sources' phandles in the
+ * device tree.
+ *
+ * Return: 0 on success, <0 on error
+ */
+static int
+zl3073x_dpll_ref_sync_pairs_register(struct zl3073x_dpll *zldpll)
+{
+ struct zl3073x_dpll_pin *pin;
+ int rc;
+
+ list_for_each_entry(pin, &zldpll->pins, list) {
+ if (!zl3073x_dpll_is_input_pin(pin) || !pin->fwnode)
+ continue;
+
+ rc = zl3073x_dpll_ref_sync_pair_register(pin);
+ if (rc)
+ return rc;
+ }
+
+ return 0;
+}
+
/**
* zl3073x_dpll_register - register DPLL device and all its pins
* @zldpll: pointer to zl3073x_dpll structure
@@ -1850,6 +2048,13 @@ zl3073x_dpll_register(struct zl3073x_dpll *zldpll)
return rc;
}
+ rc = zl3073x_dpll_ref_sync_pairs_register(zldpll);
+ if (rc) {
+ zl3073x_dpll_pins_unregister(zldpll);
+ zl3073x_dpll_device_unregister(zldpll);
+ return rc;
+ }
+
return 0;
}
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,233 @@
From 55e372bffed1f937fe3cbac3a2afe058eb4657da Mon Sep 17 00:00:00 2001
From: Paulo Alcantara <paalcant@redhat.com>
Date: Sun, 10 May 2026 18:08:11 -0300
Subject: [PATCH] smb: client: validate the whole DACL before rewriting it in
cifsacl
JIRA: https://issues.redhat.com/browse/RHEL-172829
CVE: CVE-2026-31709
commit 0a8cf165566ba55a39fd0f4de172119dd646d39a
Author: Michael Bommarito <michael.bommarito@gmail.com>
Date: Sun Apr 19 20:11:31 2026 -0400
smb: client: validate the whole DACL before rewriting it in cifsacl
build_sec_desc() and id_mode_to_cifs_acl() derive a DACL pointer from a
server-supplied dacloffset and then use the incoming ACL to rebuild the
chmod/chown security descriptor.
The original fix only checked that the struct smb_acl header fits before
reading dacl_ptr->size or dacl_ptr->num_aces. That avoids the immediate
header-field OOB read, but the rewrite helpers still walk ACEs based on
pdacl->num_aces with no structural validation of the incoming DACL body.
A malicious server can return a truncated DACL that still contains a
header, claims one or more ACEs, and then drive
replace_sids_and_copy_aces() or set_chmod_dacl() past the validated
extent while they compare or copy attacker-controlled ACEs.
Factor the DACL structural checks into validate_dacl(), extend them to
validate each ACE against the DACL bounds, and use the shared validator
before the chmod/chown rebuild paths. parse_dacl() reuses the same
validator so the read-side parser and write-side rewrite paths agree on
what constitutes a well-formed incoming DACL.
Fixes: bc3e9dd9d104 ("cifs: Change SIDs in ACEs while transferring file ownership.")
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-opus-4-6
Assisted-by: Codex:gpt-5-4
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
Signed-off-by: Paulo Alcantara <paalcant@redhat.com>
diff --git a/fs/smb/client/cifsacl.c b/fs/smb/client/cifsacl.c
index 7e6e473bd4a0..74c320ad5c3a 100644
--- a/fs/smb/client/cifsacl.c
+++ b/fs/smb/client/cifsacl.c
@@ -759,6 +759,77 @@ static void dump_ace(struct smb_ace *pace, char *end_of_acl)
}
#endif
+static int validate_dacl(struct smb_acl *pdacl, char *end_of_acl)
+{
+ int i, ace_hdr_size, ace_size, min_ace_size;
+ u16 dacl_size, num_aces;
+ char *acl_base, *end_of_dacl;
+ struct smb_ace *pace;
+
+ if (!pdacl)
+ return 0;
+
+ if (end_of_acl < (char *)pdacl + sizeof(struct smb_acl)) {
+ cifs_dbg(VFS, "ACL too small to parse DACL\n");
+ return -EINVAL;
+ }
+
+ dacl_size = le16_to_cpu(pdacl->size);
+ if (dacl_size < sizeof(struct smb_acl) ||
+ end_of_acl < (char *)pdacl + dacl_size) {
+ cifs_dbg(VFS, "ACL too small to parse DACL\n");
+ return -EINVAL;
+ }
+
+ num_aces = le16_to_cpu(pdacl->num_aces);
+ if (!num_aces)
+ return 0;
+
+ ace_hdr_size = offsetof(struct smb_ace, sid) +
+ offsetof(struct smb_sid, sub_auth);
+ min_ace_size = ace_hdr_size + sizeof(__le32);
+ if (num_aces > (dacl_size - sizeof(struct smb_acl)) / min_ace_size) {
+ cifs_dbg(VFS, "ACL too small to parse DACL\n");
+ return -EINVAL;
+ }
+
+ end_of_dacl = (char *)pdacl + dacl_size;
+ acl_base = (char *)pdacl;
+ ace_size = sizeof(struct smb_acl);
+
+ for (i = 0; i < num_aces; ++i) {
+ if (end_of_dacl - acl_base < ace_size) {
+ cifs_dbg(VFS, "ACL too small to parse ACE\n");
+ return -EINVAL;
+ }
+
+ pace = (struct smb_ace *)(acl_base + ace_size);
+ acl_base = (char *)pace;
+
+ if (end_of_dacl - acl_base < ace_hdr_size ||
+ pace->sid.num_subauth == 0 ||
+ pace->sid.num_subauth > SID_MAX_SUB_AUTHORITIES) {
+ cifs_dbg(VFS, "ACL too small to parse ACE\n");
+ return -EINVAL;
+ }
+
+ ace_size = ace_hdr_size + sizeof(__le32) * pace->sid.num_subauth;
+ if (end_of_dacl - acl_base < ace_size ||
+ le16_to_cpu(pace->size) < ace_size) {
+ cifs_dbg(VFS, "ACL too small to parse ACE\n");
+ return -EINVAL;
+ }
+
+ ace_size = le16_to_cpu(pace->size);
+ if (end_of_dacl - acl_base < ace_size) {
+ cifs_dbg(VFS, "ACL too small to parse ACE\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
static void parse_dacl(struct smb_acl *pdacl, char *end_of_acl,
struct smb_sid *pownersid, struct smb_sid *pgrpsid,
struct cifs_fattr *fattr, bool mode_from_special_sid)
@@ -766,7 +837,7 @@ static void parse_dacl(struct smb_acl *pdacl, char *end_of_acl,
int i;
u16 num_aces = 0;
int acl_size;
- char *acl_base;
+ char *acl_base, *end_of_dacl;
struct smb_ace **ppace;
/* BB need to add parm so we can store the SID BB */
@@ -778,12 +849,8 @@ static void parse_dacl(struct smb_acl *pdacl, char *end_of_acl,
return;
}
- /* validate that we do not go past end of acl */
- if (end_of_acl < (char *)pdacl + sizeof(struct smb_acl) ||
- end_of_acl < (char *)pdacl + le16_to_cpu(pdacl->size)) {
- cifs_dbg(VFS, "ACL too small to parse DACL\n");
+ if (validate_dacl(pdacl, end_of_acl))
return;
- }
cifs_dbg(NOISY, "DACL revision %d size %d num aces %d\n",
le16_to_cpu(pdacl->revision), le16_to_cpu(pdacl->size),
@@ -794,6 +861,7 @@ static void parse_dacl(struct smb_acl *pdacl, char *end_of_acl,
user/group/other have no permissions */
fattr->cf_mode &= ~(0777);
+ end_of_dacl = (char *)pdacl + le16_to_cpu(pdacl->size);
acl_base = (char *)pdacl;
acl_size = sizeof(struct smb_acl);
@@ -801,36 +869,16 @@ static void parse_dacl(struct smb_acl *pdacl, char *end_of_acl,
if (num_aces > 0) {
umode_t denied_mode = 0;
- if (num_aces > (le16_to_cpu(pdacl->size) - sizeof(struct smb_acl)) /
- (offsetof(struct smb_ace, sid) +
- offsetof(struct smb_sid, sub_auth) + sizeof(__le16)))
- return;
-
ppace = kmalloc_array(num_aces, sizeof(struct smb_ace *),
GFP_KERNEL);
if (!ppace)
return;
for (i = 0; i < num_aces; ++i) {
- if (end_of_acl - acl_base < acl_size)
- break;
-
ppace[i] = (struct smb_ace *) (acl_base + acl_size);
- acl_base = (char *)ppace[i];
- acl_size = offsetof(struct smb_ace, sid) +
- offsetof(struct smb_sid, sub_auth);
-
- if (end_of_acl - acl_base < acl_size ||
- ppace[i]->sid.num_subauth == 0 ||
- ppace[i]->sid.num_subauth > SID_MAX_SUB_AUTHORITIES ||
- (end_of_acl - acl_base <
- acl_size + sizeof(__le32) * ppace[i]->sid.num_subauth) ||
- (le16_to_cpu(ppace[i]->size) <
- acl_size + sizeof(__le32) * ppace[i]->sid.num_subauth))
- break;
#ifdef CONFIG_CIFS_DEBUG2
- dump_ace(ppace[i], end_of_acl);
+ dump_ace(ppace[i], end_of_dacl);
#endif
if (mode_from_special_sid &&
(compare_sids(&(ppace[i]->sid),
@@ -872,6 +920,7 @@ static void parse_dacl(struct smb_acl *pdacl, char *end_of_acl,
(void *)ppace[i],
sizeof(struct smb_ace)); */
+ acl_base = (char *)ppace[i];
acl_size = le16_to_cpu(ppace[i]->size);
}
@@ -1295,10 +1344,9 @@ static int build_sec_desc(struct smb_ntsd *pntsd, struct smb_ntsd *pnntsd,
dacloffset = le32_to_cpu(pntsd->dacloffset);
if (dacloffset) {
dacl_ptr = (struct smb_acl *)((char *)pntsd + dacloffset);
- if (end_of_acl < (char *)dacl_ptr + le16_to_cpu(dacl_ptr->size)) {
- cifs_dbg(VFS, "Server returned illegal ACL size\n");
- return -EINVAL;
- }
+ rc = validate_dacl(dacl_ptr, end_of_acl);
+ if (rc)
+ return rc;
}
owner_sid_ptr = (struct smb_sid *)((char *)pntsd +
@@ -1671,6 +1719,12 @@ id_mode_to_cifs_acl(struct inode *inode, const char *path, __u64 *pnmode,
dacloffset = le32_to_cpu(pntsd->dacloffset);
if (dacloffset) {
dacl_ptr = (struct smb_acl *)((char *)pntsd + dacloffset);
+ rc = validate_dacl(dacl_ptr, (char *)pntsd + secdesclen);
+ if (rc) {
+ kfree(pntsd);
+ cifs_put_tlink(tlink);
+ return rc;
+ }
if (mode_from_sid)
nsecdesclen +=
le16_to_cpu(dacl_ptr->num_aces) * sizeof(struct smb_ace);
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,50 @@
From d8a6ff81310035796f62a3786a33b7ba906091b0 Mon Sep 17 00:00:00 2001
From: Paulo Alcantara <paalcant@redhat.com>
Date: Sun, 10 May 2026 18:08:12 -0300
Subject: [PATCH] smb: client: require a full NFS mode SID before reading mode
bits
JIRA: https://issues.redhat.com/browse/RHEL-172829
commit 2757ad3e4b6f9e0fed4c7739594e702abc5cab21
Author: Michael Bommarito <michael.bommarito@gmail.com>
Date: Mon Apr 20 09:50:58 2026 -0400
smb: client: require a full NFS mode SID before reading mode bits
parse_dacl() treats an ACE SID matching sid_unix_NFS_mode as an NFS
mode SID and reads sid.sub_auth[2] to recover the mode bits.
That assumes the ACE carries three subauthorities, but compare_sids()
only compares min(a, b) subauthorities. A malicious server can return
an ACE with num_subauth = 2 and sub_auth[] = {88, 3}, which still
matches sid_unix_NFS_mode and then drives the sub_auth[2] read four
bytes past the end of the ACE.
Require num_subauth >= 3 before treating the ACE as an NFS mode SID.
This keeps the fix local to the special-SID mode path without changing
compare_sids() semantics for the rest of cifsacl.
Fixes: e2f8fbfb8d09 ("cifs: get mode bits from special sid on stat")
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
Signed-off-by: Paulo Alcantara <paalcant@redhat.com>
diff --git a/fs/smb/client/cifsacl.c b/fs/smb/client/cifsacl.c
index 74c320ad5c3a..e6690f8e46a8 100644
--- a/fs/smb/client/cifsacl.c
+++ b/fs/smb/client/cifsacl.c
@@ -881,6 +881,7 @@ static void parse_dacl(struct smb_acl *pdacl, char *end_of_acl,
dump_ace(ppace[i], end_of_dacl);
#endif
if (mode_from_special_sid &&
+ ppace[i]->sid.num_subauth >= 3 &&
(compare_sids(&(ppace[i]->sid),
&sid_unix_NFS_mode) == 0)) {
/*
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,69 @@
From 59a296878f29e33031dad8c186168e8e5b50e95a Mon Sep 17 00:00:00 2001
From: Paulo Alcantara <paalcant@redhat.com>
Date: Sun, 10 May 2026 18:08:12 -0300
Subject: [PATCH] smb: client: scope end_of_dacl to CIFS_DEBUG2 use in
parse_dacl
JIRA: https://issues.redhat.com/browse/RHEL-172829
commit a55a60886e612bedb0e9a402ba0dca544c4c6a51
Author: Michael Bommarito <michael.bommarito@gmail.com>
Date: Tue Apr 21 19:40:22 2026 -0400
smb: client: scope end_of_dacl to CIFS_DEBUG2 use in parse_dacl
After validate_dacl() was factored out in commit 149822e5541c, the
local end_of_dacl in parse_dacl() is only read by the dump_ace()
call under #ifdef CONFIG_CIFS_DEBUG2. With CIFS_DEBUG2 off the
variable is assigned but never used, which gcc -W=1 flags as
-Wunused-but-set-variable.
Remove the local and compute the end-of-dacl pointer inline at the
single call site inside the existing CIFS_DEBUG2 guard. No
functional change: when CIFS_DEBUG2 is enabled the argument value
is identical to what the removed local carried; when CIFS_DEBUG2
is disabled the code was already dead.
Reported-by: kernel test robot <lkp@intel.com>
Closes: https://lore.kernel.org/oe-kbuild-all/202604220046.tGkRxVtS-lkp@intel.com/
Fixes: 149822e5541c ("smb: client: validate the whole DACL before rewriting it in cifsacl")
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Steve French <stfrench@microsoft.com>
Signed-off-by: Paulo Alcantara <paalcant@redhat.com>
diff --git a/fs/smb/client/cifsacl.c b/fs/smb/client/cifsacl.c
index e6690f8e46a8..717a40fa84aa 100644
--- a/fs/smb/client/cifsacl.c
+++ b/fs/smb/client/cifsacl.c
@@ -837,7 +837,7 @@ static void parse_dacl(struct smb_acl *pdacl, char *end_of_acl,
int i;
u16 num_aces = 0;
int acl_size;
- char *acl_base, *end_of_dacl;
+ char *acl_base;
struct smb_ace **ppace;
/* BB need to add parm so we can store the SID BB */
@@ -861,7 +861,6 @@ static void parse_dacl(struct smb_acl *pdacl, char *end_of_acl,
user/group/other have no permissions */
fattr->cf_mode &= ~(0777);
- end_of_dacl = (char *)pdacl + le16_to_cpu(pdacl->size);
acl_base = (char *)pdacl;
acl_size = sizeof(struct smb_acl);
@@ -878,7 +877,8 @@ static void parse_dacl(struct smb_acl *pdacl, char *end_of_acl,
ppace[i] = (struct smb_ace *) (acl_base + acl_size);
#ifdef CONFIG_CIFS_DEBUG2
- dump_ace(ppace[i], end_of_dacl);
+ dump_ace(ppace[i],
+ (char *)pdacl + le16_to_cpu(pdacl->size));
#endif
if (mode_from_special_sid &&
ppace[i]->sid.num_subauth >= 3 &&
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,57 @@
From f1579c843ecda9218102033597e2bafe1ce8ec74 Mon Sep 17 00:00:00 2001
From: Paulo Alcantara <paalcant@redhat.com>
Date: Sun, 10 May 2026 18:08:12 -0300
Subject: [PATCH] smb: client: use kzalloc to zero-initialize security
descriptor buffer
JIRA: https://issues.redhat.com/browse/RHEL-172829
commit 5e489c6c47a2ac15edbaca153b9348e42c1eacab
Author: Bjoern Doebel <doebel@amazon.de>
Date: Thu Apr 30 08:57:17 2026 +0000
smb: client: use kzalloc to zero-initialize security descriptor buffer
Commit 62e7dd0a39c2d ("smb: common: change the data type of num_aces
to le16") split struct smb_acl's __le32 num_aces field into __le16
num_aces and __le16 reserved. The reserved field corresponds to Sbz2
in the MS-DTYP ACL wire format, which must be zero [1].
When building an ACL descriptor in build_sec_desc(), we are using a
kmalloc()'ed descriptor buffer and writing the fields explicitly using
le16() writes now. This never writes to the 2 byte reserved field,
leaving it as uninitialized heap data.
When the reserved field happens to contain non-zero slab garbage,
Samba rejects the security descriptor with "ndr_pull_security_descriptor
failed: Range Error", causing chmod to fail with EINVAL.
Change kmalloc() to kzalloc() to ensure the entire buffer is
zero-initialized.
Fixes: 62e7dd0a39c2d ("smb: common: change the data type of num_aces to le16")
Cc: stable@vger.kernel.org
Signed-off-by: Bjoern Doebel <doebel@amazon.de>
Assisted-by: Kiro:claude-opus-4.6
[1] https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/20233ed8-a6c6-4097-aafa-dd545ed24428
Signed-off-by: Steve French <stfrench@microsoft.com>
Signed-off-by: Paulo Alcantara <paalcant@redhat.com>
diff --git a/fs/smb/client/cifsacl.c b/fs/smb/client/cifsacl.c
index 717a40fa84aa..03bad2885e96 100644
--- a/fs/smb/client/cifsacl.c
+++ b/fs/smb/client/cifsacl.c
@@ -1741,7 +1741,7 @@ id_mode_to_cifs_acl(struct inode *inode, const char *path, __u64 *pnmode,
* descriptor parameters, and security descriptor itself
*/
nsecdesclen = max_t(u32, nsecdesclen, DEFAULT_SEC_DESC_LEN);
- pnntsd = kmalloc(nsecdesclen, GFP_KERNEL);
+ pnntsd = kzalloc(nsecdesclen, GFP_KERNEL);
if (!pnntsd) {
kfree(pntsd);
cifs_put_tlink(tlink);
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,123 @@
From eff4c8b51d7c82746c2efa0e4ba30576d1082169 Mon Sep 17 00:00:00 2001
From: Paulo Alcantara <paalcant@redhat.com>
Date: Sun, 10 May 2026 18:08:12 -0300
Subject: [PATCH] smb: client: validate dacloffset before building DACL
pointers
JIRA: https://issues.redhat.com/browse/RHEL-172829
commit f98b48151cc502ada59d9778f0112d21f2586ca3
Author: Michael Bommarito <michael.bommarito@gmail.com>
Date: Mon Apr 20 10:47:47 2026 -0400
smb: client: validate dacloffset before building DACL pointers
parse_sec_desc(), build_sec_desc(), and the chown path in
id_mode_to_cifs_acl() all add the server-supplied dacloffset to pntsd
before proving a DACL header fits inside the returned security
descriptor.
On 32-bit builds a malicious server can return dacloffset near
U32_MAX, wrap the derived DACL pointer below end_of_acl, and then slip
past the later pointer-based bounds checks. build_sec_desc() and
id_mode_to_cifs_acl() can then dereference DACL fields from the wrapped
pointer in the chmod/chown rewrite paths.
Validate dacloffset numerically before building any DACL pointer and
reuse the same helper at the three DACL entry points.
Fixes: bc3e9dd9d104 ("cifs: Change SIDs in ACEs while transferring file ownership.")
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
Signed-off-by: Paulo Alcantara <paalcant@redhat.com>
diff --git a/fs/smb/client/cifsacl.c b/fs/smb/client/cifsacl.c
index 03bad2885e96..7ed526d23d14 100644
--- a/fs/smb/client/cifsacl.c
+++ b/fs/smb/client/cifsacl.c
@@ -1266,6 +1266,17 @@ static int parse_sid(struct smb_sid *psid, char *end_of_acl)
return 0;
}
+static bool dacl_offset_valid(unsigned int acl_len, __u32 dacloffset)
+{
+ if (acl_len < sizeof(struct smb_acl))
+ return false;
+
+ if (dacloffset < sizeof(struct smb_ntsd))
+ return false;
+
+ return dacloffset <= acl_len - sizeof(struct smb_acl);
+}
+
/* Convert CIFS ACL to POSIX form */
static int parse_sec_desc(struct cifs_sb_info *cifs_sb,
@@ -1286,7 +1297,6 @@ static int parse_sec_desc(struct cifs_sb_info *cifs_sb,
group_sid_ptr = (struct smb_sid *)((char *)pntsd +
le32_to_cpu(pntsd->gsidoffset));
dacloffset = le32_to_cpu(pntsd->dacloffset);
- dacl_ptr = (struct smb_acl *)((char *)pntsd + dacloffset);
cifs_dbg(NOISY, "revision %d type 0x%x ooffset 0x%x goffset 0x%x sacloffset 0x%x dacloffset 0x%x\n",
pntsd->revision, pntsd->type, le32_to_cpu(pntsd->osidoffset),
le32_to_cpu(pntsd->gsidoffset),
@@ -1317,11 +1327,18 @@ static int parse_sec_desc(struct cifs_sb_info *cifs_sb,
return rc;
}
- if (dacloffset)
+ if (dacloffset) {
+ if (!dacl_offset_valid(acl_len, dacloffset)) {
+ cifs_dbg(VFS, "Server returned illegal DACL offset\n");
+ return -EINVAL;
+ }
+
+ dacl_ptr = (struct smb_acl *)((char *)pntsd + dacloffset);
parse_dacl(dacl_ptr, end_of_acl, owner_sid_ptr,
group_sid_ptr, fattr, get_mode_from_special_sid);
- else
+ } else {
cifs_dbg(FYI, "no ACL\n"); /* BB grant all or default perms? */
+ }
return rc;
}
@@ -1344,6 +1361,11 @@ static int build_sec_desc(struct smb_ntsd *pntsd, struct smb_ntsd *pnntsd,
dacloffset = le32_to_cpu(pntsd->dacloffset);
if (dacloffset) {
+ if (!dacl_offset_valid(secdesclen, dacloffset)) {
+ cifs_dbg(VFS, "Server returned illegal DACL offset\n");
+ return -EINVAL;
+ }
+
dacl_ptr = (struct smb_acl *)((char *)pntsd + dacloffset);
rc = validate_dacl(dacl_ptr, end_of_acl);
if (rc)
@@ -1719,6 +1741,12 @@ id_mode_to_cifs_acl(struct inode *inode, const char *path, __u64 *pnmode,
nsecdesclen = sizeof(struct smb_ntsd) + (sizeof(struct smb_sid) * 2);
dacloffset = le32_to_cpu(pntsd->dacloffset);
if (dacloffset) {
+ if (!dacl_offset_valid(secdesclen, dacloffset)) {
+ cifs_dbg(VFS, "Server returned illegal DACL offset\n");
+ rc = -EINVAL;
+ goto id_mode_to_cifs_acl_exit;
+ }
+
dacl_ptr = (struct smb_acl *)((char *)pntsd + dacloffset);
rc = validate_dacl(dacl_ptr, (char *)pntsd + secdesclen);
if (rc) {
@@ -1761,6 +1789,7 @@ id_mode_to_cifs_acl(struct inode *inode, const char *path, __u64 *pnmode,
rc = ops->set_acl(pnntsd, nsecdesclen, inode, path, aclflag);
cifs_dbg(NOISY, "set_cifs_acl rc: %d\n", rc);
}
+id_mode_to_cifs_acl_exit:
cifs_put_tlink(tlink);
kfree(pnntsd);
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,294 @@
From 05ce58c7fda9c607fb135c297cbef74db41489c2 Mon Sep 17 00:00:00 2001
From: Ivan Vecera <ivecera@redhat.com>
Date: Wed, 13 May 2026 11:47:07 +0200
Subject: [PATCH] dpll: add pin operational state
JIRA: https://issues.redhat.com/browse/RHEL-173188
commit 781c8893a5da8522ae4ded93e5ef3adbe9559300
Author: Ivan Vecera <ivecera@redhat.com>
Date: Tue Apr 28 17:49:06 2026 +0200
dpll: add pin operational state
Add pin-operstate enum and operstate_on_dpll_get callback to report
the actual hardware status of a pin with respect to its parent DPLL
device. Unlike pin-state (which reflects administrative intent set
by the user), operstate reflects what the hardware is actually doing.
Defined operational states:
- active: pin is qualified and actively used by the DPLL
- standby: pin is qualified but not actively used by the DPLL
- no-signal: pin does not have a valid signal
- qual-failed: pin signal failed qualification
The operstate is reported inside the pin-parent-device nested
attribute alongside the existing state and phase-offset attributes.
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
Reviewed-by: Jiri Pirko <jiri@nvidia.com>
Reviewed-by: Vadim Fedorenko <vadim.fedorenko@linux.dev>
Reviewed-by: Petr Oros <poros@redhat.com>
Link: https://patch.msgid.link/20260428154907.2820654-2-ivecera@redhat.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
(cherry picked from commit 781c8893a5da8522ae4ded93e5ef3adbe9559300)
Assisted-by: Patchpal
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
diff --git a/Documentation/driver-api/dpll.rst b/Documentation/driver-api/dpll.rst
index 93c191b2d089..37eaef785e30 100644
--- a/Documentation/driver-api/dpll.rst
+++ b/Documentation/driver-api/dpll.rst
@@ -65,35 +65,43 @@ request, where user provides attributes that result in single pin match.
Pin selection
=============
-In general, selected pin (the one which signal is driving the dpll
-device) can be obtained from ``DPLL_A_PIN_STATE`` attribute, and only
-one pin shall be in ``DPLL_PIN_STATE_CONNECTED`` state for any dpll
-device.
+Pin state (``DPLL_A_PIN_STATE``) reflects the administrative intent set
+by the user. Pin operational state (``DPLL_A_PIN_OPERSTATE``) reflects
+what the hardware is actually doing with the pin.
Pin selection can be done either manually or automatically, depending
on hardware capabilities and active dpll device work mode
(``DPLL_A_MODE`` attribute). The consequence is that there are
-differences for each mode in terms of available pin states, as well as
-for the states the user can request for a dpll device.
+differences for each mode in terms of available pin states the user can
+request for a dpll device.
-In manual mode (``DPLL_MODE_MANUAL``) the user can request or receive
-one of following pin states:
+In manual mode (``DPLL_MODE_MANUAL``) the user can request one of
+following pin states:
-- ``DPLL_PIN_STATE_CONNECTED`` - the pin is used to drive dpll device
-- ``DPLL_PIN_STATE_DISCONNECTED`` - the pin is not used to drive dpll
+- ``DPLL_PIN_STATE_CONNECTED`` - the pin is selected to drive dpll
device
+- ``DPLL_PIN_STATE_DISCONNECTED`` - the pin is not selected to drive
+ dpll device
-In automatic mode (``DPLL_MODE_AUTOMATIC``) the user can request or
-receive one of following pin states:
+In automatic mode (``DPLL_MODE_AUTOMATIC``) the user can request one of
+following pin states:
- ``DPLL_PIN_STATE_SELECTABLE`` - the pin shall be considered as valid
input for automatic selection algorithm
- ``DPLL_PIN_STATE_DISCONNECTED`` - the pin shall be not considered as
a valid input for automatic selection algorithm
-In automatic mode (``DPLL_MODE_AUTOMATIC``) the user can only receive
-pin state ``DPLL_PIN_STATE_CONNECTED`` once automatic selection
-algorithm locks a dpll device with one of the inputs.
+The actual hardware status of a pin is reported via the operational
+state (``DPLL_A_PIN_OPERSTATE``) attribute nested under the parent
+device:
+
+- ``DPLL_PIN_OPERSTATE_ACTIVE`` - pin is qualified and actively used
+ by the DPLL
+- ``DPLL_PIN_OPERSTATE_STANDBY`` - pin is qualified but not actively
+ used by the DPLL
+- ``DPLL_PIN_OPERSTATE_NO_SIGNAL`` - pin does not have a valid signal
+- ``DPLL_PIN_OPERSTATE_QUAL_FAILED`` - pin signal failed qualification
+ checks
Shared pins
===========
diff --git a/Documentation/netlink/specs/dpll.yaml b/Documentation/netlink/specs/dpll.yaml
index cb5da356bb64..a47d7da54da5 100644
--- a/Documentation/netlink/specs/dpll.yaml
+++ b/Documentation/netlink/specs/dpll.yaml
@@ -212,6 +212,27 @@ definitions:
name: selectable
doc: pin enabled for automatic input selection
render-max: true
+ -
+ type: enum
+ name: pin-operstate
+ doc: |
+ defines possible operational states of a pin with respect to its
+ parent DPLL device, valid values for DPLL_A_PIN_OPERSTATE attribute
+ entries:
+ -
+ name: active
+ doc: pin is qualified and actively used by the DPLL
+ value: 1
+ -
+ name: standby
+ doc: pin is qualified but not actively used by the DPLL
+ -
+ name: no-signal
+ doc: pin does not have a valid signal
+ -
+ name: qual-failed
+ doc: pin signal failed qualification (e.g. frequency or phase monitor)
+ render-max: true
-
type: flags
name: pin-capabilities
@@ -488,6 +509,14 @@ attribute-sets:
Value of (DPLL_A_PIN_MEASURED_FREQUENCY %
DPLL_PIN_MEASURED_FREQUENCY_DIVIDER) is a fractional part
of a measured frequency value.
+ -
+ name: operstate
+ type: u32
+ enum: pin-operstate
+ doc: |
+ Operational state of the pin with respect to its parent DPLL
+ device. Unlike state (which reflects the administrative intent),
+ operstate reflects the actual hardware status.
-
name: pin-parent-device
@@ -501,6 +530,8 @@ attribute-sets:
name: prio
-
name: state
+ -
+ name: operstate
-
name: phase-offset
-
diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c
index af7ce62ec55c..05cf946b4be5 100644
--- a/drivers/dpll/dpll_netlink.c
+++ b/drivers/dpll/dpll_netlink.c
@@ -324,6 +324,30 @@ dpll_msg_add_pin_on_dpll_state(struct sk_buff *msg, struct dpll_pin *pin,
return 0;
}
+static int
+dpll_msg_add_pin_operstate(struct sk_buff *msg, struct dpll_pin *pin,
+ struct dpll_pin_ref *ref,
+ struct netlink_ext_ack *extack)
+{
+ const struct dpll_pin_ops *ops = dpll_pin_ops(ref);
+ struct dpll_device *dpll = ref->dpll;
+ enum dpll_pin_operstate operstate;
+ int ret;
+
+ if (!ops->operstate_on_dpll_get)
+ return 0;
+ ret = ops->operstate_on_dpll_get(pin,
+ dpll_pin_on_dpll_priv(dpll, pin),
+ dpll, dpll_priv(dpll),
+ &operstate, extack);
+ if (ret)
+ return ret;
+ if (nla_put_u32(msg, DPLL_A_PIN_OPERSTATE, operstate))
+ return -EMSGSIZE;
+
+ return 0;
+}
+
static int
dpll_msg_add_pin_direction(struct sk_buff *msg, struct dpll_pin *pin,
struct dpll_pin_ref *ref,
@@ -650,6 +674,9 @@ dpll_msg_add_pin_dplls(struct sk_buff *msg, struct dpll_pin *pin,
if (ret)
goto nest_cancel;
ret = dpll_msg_add_pin_on_dpll_state(msg, pin, ref, extack);
+ if (ret)
+ goto nest_cancel;
+ ret = dpll_msg_add_pin_operstate(msg, pin, ref, extack);
if (ret)
goto nest_cancel;
ret = dpll_msg_add_pin_prio(msg, pin, ref, extack);
diff --git a/drivers/dpll/dpll_nl.c b/drivers/dpll/dpll_nl.c
index da92aa8f9bc7..64398841cdcb 100644
--- a/drivers/dpll/dpll_nl.c
+++ b/drivers/dpll/dpll_nl.c
@@ -11,11 +11,12 @@
#include <uapi/linux/dpll.h>
/* Common nested types */
-const struct nla_policy dpll_pin_parent_device_nl_policy[DPLL_A_PIN_PHASE_OFFSET + 1] = {
+const struct nla_policy dpll_pin_parent_device_nl_policy[DPLL_A_PIN_OPERSTATE + 1] = {
[DPLL_A_PIN_PARENT_ID] = { .type = NLA_U32, },
[DPLL_A_PIN_DIRECTION] = NLA_POLICY_RANGE(NLA_U32, 1, 2),
[DPLL_A_PIN_PRIO] = { .type = NLA_U32, },
[DPLL_A_PIN_STATE] = NLA_POLICY_RANGE(NLA_U32, 1, 3),
+ [DPLL_A_PIN_OPERSTATE] = NLA_POLICY_RANGE(NLA_U32, 1, 4),
[DPLL_A_PIN_PHASE_OFFSET] = { .type = NLA_S64, },
};
diff --git a/drivers/dpll/dpll_nl.h b/drivers/dpll/dpll_nl.h
index 3da10cfe9a6e..8cf3b73c2013 100644
--- a/drivers/dpll/dpll_nl.h
+++ b/drivers/dpll/dpll_nl.h
@@ -12,7 +12,7 @@
#include <uapi/linux/dpll.h>
/* Common nested types */
-extern const struct nla_policy dpll_pin_parent_device_nl_policy[DPLL_A_PIN_PHASE_OFFSET + 1];
+extern const struct nla_policy dpll_pin_parent_device_nl_policy[DPLL_A_PIN_OPERSTATE + 1];
extern const struct nla_policy dpll_pin_parent_pin_nl_policy[DPLL_A_PIN_STATE + 1];
extern const struct nla_policy dpll_reference_sync_nl_policy[DPLL_A_PIN_STATE + 1];
diff --git a/include/linux/dpll.h b/include/linux/dpll.h
index 441fe25ba4f4..90249937008c 100644
--- a/include/linux/dpll.h
+++ b/include/linux/dpll.h
@@ -98,6 +98,12 @@ struct dpll_pin_ops {
const struct dpll_device *dpll,
void *dpll_priv, enum dpll_pin_state *state,
struct netlink_ext_ack *extack);
+ int (*operstate_on_dpll_get)(const struct dpll_pin *pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ enum dpll_pin_operstate *operstate,
+ struct netlink_ext_ack *extack);
int (*state_on_pin_set)(const struct dpll_pin *pin, void *pin_priv,
const struct dpll_pin *parent_pin,
void *parent_pin_priv,
diff --git a/include/uapi/linux/dpll.h b/include/uapi/linux/dpll.h
index 93614ccd8a84..aa45b79473de 100644
--- a/include/uapi/linux/dpll.h
+++ b/include/uapi/linux/dpll.h
@@ -177,6 +177,28 @@ enum dpll_pin_state {
DPLL_PIN_STATE_MAX = (__DPLL_PIN_STATE_MAX - 1)
};
+/**
+ * enum dpll_pin_operstate - defines possible operational states of a pin with
+ * respect to its parent DPLL device, valid values for DPLL_A_PIN_OPERSTATE
+ * attribute
+ * @DPLL_PIN_OPERSTATE_ACTIVE: pin is qualified and actively used by the DPLL
+ * @DPLL_PIN_OPERSTATE_STANDBY: pin is qualified but not actively used by the
+ * DPLL
+ * @DPLL_PIN_OPERSTATE_NO_SIGNAL: pin does not have a valid signal
+ * @DPLL_PIN_OPERSTATE_QUAL_FAILED: pin signal failed qualification (e.g.
+ * frequency or phase monitor)
+ */
+enum dpll_pin_operstate {
+ DPLL_PIN_OPERSTATE_ACTIVE = 1,
+ DPLL_PIN_OPERSTATE_STANDBY,
+ DPLL_PIN_OPERSTATE_NO_SIGNAL,
+ DPLL_PIN_OPERSTATE_QUAL_FAILED,
+
+ /* private: */
+ __DPLL_PIN_OPERSTATE_MAX,
+ DPLL_PIN_OPERSTATE_MAX = (__DPLL_PIN_OPERSTATE_MAX - 1)
+};
+
/**
* enum dpll_pin_capabilities - defines possible capabilities of a pin, valid
* flags on DPLL_A_PIN_CAPABILITIES attribute
@@ -256,6 +278,7 @@ enum dpll_a_pin {
DPLL_A_PIN_PHASE_ADJUST_GRAN,
DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET_PPT,
DPLL_A_PIN_MEASURED_FREQUENCY,
+ DPLL_A_PIN_OPERSTATE,
__DPLL_A_PIN_MAX,
DPLL_A_PIN_MAX = (__DPLL_A_PIN_MAX - 1)
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,254 @@
From 80d47d653c6ca35a7dbfc6c8abc9486445c1e068 Mon Sep 17 00:00:00 2001
From: Ivan Vecera <ivecera@redhat.com>
Date: Wed, 13 May 2026 11:48:51 +0200
Subject: [PATCH] dpll: zl3073x: implement pin operational state reporting
JIRA: https://issues.redhat.com/browse/RHEL-173188
commit c53f8f8dce776e032b1a11fb4d9b6e12bb63d958
Author: Ivan Vecera <ivecera@redhat.com>
Date: Tue Apr 28 17:49:07 2026 +0200
dpll: zl3073x: implement pin operational state reporting
Implement operstate_on_dpll_get callback for input pins to report
the actual hardware status:
- active: pin is the currently locked reference
- standby: signal is valid but pin is not actively used
- no-signal: reference monitor reports Loss of Signal (LOS)
- qual-failed: reference monitor reports a qualification failure
(SCM, CFM, GST, PFM, eSync or Split-XO)
Separate administrative state (state_on_dpll_get) from operational
state: admin state now reports purely the user-requested intent
(connected in reflock mode, selectable in auto mode).
Switch periodic monitoring to track operstate changes instead of
the mixed admin/oper state that was previously reported.
Add ref_mon_status bit definitions to regs.h.
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
Reviewed-by: Petr Oros <poros@redhat.com>
Link: https://patch.msgid.link/20260428154907.2820654-3-ivecera@redhat.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
(cherry picked from commit c53f8f8dce776e032b1a11fb4d9b6e12bb63d958)
Assisted-by: Patchpal
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index 55195dcd21d6..2c8bd211e31e 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -38,7 +38,7 @@
* @prio: pin priority <0, 14>
* @esync_control: embedded sync is controllable
* @phase_gran: phase adjustment granularity
- * @pin_state: last saved pin state
+ * @operstate: last saved operational state
* @phase_offset: last saved pin phase offset
* @freq_offset: last saved fractional frequency offset
* @measured_freq: last saved measured frequency
@@ -55,7 +55,7 @@ struct zl3073x_dpll_pin {
u8 prio;
bool esync_control;
s32 phase_gran;
- enum dpll_pin_state pin_state;
+ enum dpll_pin_operstate operstate;
s64 phase_offset;
s64 freq_offset;
u32 measured_freq;
@@ -500,46 +500,41 @@ zl3073x_dpll_input_pin_phase_adjust_set(const struct dpll_pin *dpll_pin,
}
/**
- * zl3073x_dpll_ref_state_get - get status for given input pin
+ * zl3073x_dpll_ref_operstate_get - get operational state for input pin
* @pin: pointer to pin
- * @state: place to store status
+ * @operstate: place to store operational state
*
- * Checks current status for the given input pin and stores the value
- * to @state.
+ * Returns the actual hardware state of the pin: whether it is actively
+ * used by the DPLL, has no signal, failed qualification, or is simply
+ * not in use.
*
* Return: 0 on success, <0 on error
*/
static int
-zl3073x_dpll_ref_state_get(struct zl3073x_dpll_pin *pin,
- enum dpll_pin_state *state)
+zl3073x_dpll_ref_operstate_get(struct zl3073x_dpll_pin *pin,
+ enum dpll_pin_operstate *operstate)
{
struct zl3073x_dpll *zldpll = pin->dpll;
struct zl3073x_dev *zldev = zldpll->dev;
- const struct zl3073x_chan *chan;
- u8 ref;
-
- chan = zl3073x_chan_state_get(zldev, zldpll->id);
- ref = zl3073x_input_pin_ref_get(pin->id);
+ const struct zl3073x_ref *ref;
+ u8 ref_id;
- /* Check if the pin reference is connected */
- if (ref == zl3073x_dpll_connected_ref_get(zldpll)) {
- *state = DPLL_PIN_STATE_CONNECTED;
- return 0;
- }
+ ref_id = zl3073x_input_pin_ref_get(pin->id);
- /* If the DPLL is running in automatic mode and the reference is
- * selectable and its monitor does not report any error then report
- * pin as selectable.
- */
- if (zl3073x_chan_mode_get(chan) == ZL_DPLL_MODE_REFSEL_MODE_AUTO &&
- zl3073x_dev_ref_is_status_ok(zldev, ref) &&
- zl3073x_chan_ref_is_selectable(chan, ref)) {
- *state = DPLL_PIN_STATE_SELECTABLE;
+ /* Check if this pin is the currently locked reference */
+ if (ref_id == zl3073x_dpll_connected_ref_get(zldpll)) {
+ *operstate = DPLL_PIN_OPERSTATE_ACTIVE;
return 0;
}
- /* Otherwise report the pin as disconnected */
- *state = DPLL_PIN_STATE_DISCONNECTED;
+ /* Check reference monitor status */
+ ref = zl3073x_ref_state_get(zldev, ref_id);
+ if (ref->mon_status & ZL_REF_MON_STATUS_LOS)
+ *operstate = DPLL_PIN_OPERSTATE_NO_SIGNAL;
+ else if (!zl3073x_ref_is_status_ok(ref))
+ *operstate = DPLL_PIN_OPERSTATE_QUAL_FAILED;
+ else
+ *operstate = DPLL_PIN_OPERSTATE_STANDBY;
return 0;
}
@@ -551,10 +546,48 @@ zl3073x_dpll_input_pin_state_on_dpll_get(const struct dpll_pin *dpll_pin,
void *dpll_priv,
enum dpll_pin_state *state,
struct netlink_ext_ack *extack)
+{
+ struct zl3073x_dpll *zldpll = dpll_priv;
+ struct zl3073x_dpll_pin *pin = pin_priv;
+ const struct zl3073x_chan *chan;
+ u8 mode, ref;
+
+ chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+ ref = zl3073x_input_pin_ref_get(pin->id);
+ mode = zl3073x_chan_mode_get(chan);
+
+ switch (mode) {
+ case ZL_DPLL_MODE_REFSEL_MODE_REFLOCK:
+ if (ref == zl3073x_chan_ref_get(chan))
+ *state = DPLL_PIN_STATE_CONNECTED;
+ else
+ *state = DPLL_PIN_STATE_DISCONNECTED;
+ break;
+ case ZL_DPLL_MODE_REFSEL_MODE_AUTO:
+ if (zl3073x_chan_ref_is_selectable(chan, ref))
+ *state = DPLL_PIN_STATE_SELECTABLE;
+ else
+ *state = DPLL_PIN_STATE_DISCONNECTED;
+ break;
+ default:
+ *state = DPLL_PIN_STATE_DISCONNECTED;
+ break;
+ }
+
+ return 0;
+}
+
+static int
+zl3073x_dpll_input_pin_operstate_on_dpll_get(const struct dpll_pin *dpll_pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ enum dpll_pin_operstate *operstate,
+ struct netlink_ext_ack *extack)
{
struct zl3073x_dpll_pin *pin = pin_priv;
- return zl3073x_dpll_ref_state_get(pin, state);
+ return zl3073x_dpll_ref_operstate_get(pin, operstate);
}
static int
@@ -1248,6 +1281,7 @@ static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = {
.frequency_get = zl3073x_dpll_input_pin_frequency_get,
.frequency_set = zl3073x_dpll_input_pin_frequency_set,
.measured_freq_get = zl3073x_dpll_input_pin_measured_freq_get,
+ .operstate_on_dpll_get = zl3073x_dpll_input_pin_operstate_on_dpll_get,
.phase_offset_get = zl3073x_dpll_input_pin_phase_offset_get,
.phase_adjust_get = zl3073x_dpll_input_pin_phase_adjust_get,
.phase_adjust_set = zl3073x_dpll_input_pin_phase_adjust_set,
@@ -1663,7 +1697,7 @@ zl3073x_dpll_pin_phase_offset_check(struct zl3073x_dpll_pin *pin)
* 2) For other pins use appropriate ref_phase register if the phase
* monitor feature is enabled.
*/
- if (pin->pin_state == DPLL_PIN_STATE_CONNECTED)
+ if (pin->operstate == DPLL_PIN_OPERSTATE_ACTIVE)
reg = ZL_REG_DPLL_PHASE_ERR_DATA(zldpll->id);
else if (zldpll->phase_monitor)
reg = ZL_REG_REF_PHASE(ref_id);
@@ -1828,7 +1862,7 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll)
}
list_for_each_entry(pin, &zldpll->pins, list) {
- enum dpll_pin_state state;
+ enum dpll_pin_operstate operstate;
bool pin_changed = false;
/* Output pins change checks are not necessary because output
@@ -1837,18 +1871,18 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll)
if (!zl3073x_dpll_is_input_pin(pin))
continue;
- rc = zl3073x_dpll_ref_state_get(pin, &state);
+ rc = zl3073x_dpll_ref_operstate_get(pin, &operstate);
if (rc) {
dev_err(dev,
- "Failed to get %s on DPLL%u state: %pe\n",
+ "Failed to get %s on DPLL%u oper state: %pe\n",
pin->label, zldpll->id, ERR_PTR(rc));
return;
}
- if (state != pin->pin_state) {
- dev_dbg(dev, "%s state changed: %u->%u\n", pin->label,
- pin->pin_state, state);
- pin->pin_state = state;
+ if (operstate != pin->operstate) {
+ dev_dbg(dev, "%s oper state changed: %u->%u\n",
+ pin->label, pin->operstate, operstate);
+ pin->operstate = operstate;
pin_changed = true;
}
diff --git a/drivers/dpll/zl3073x/regs.h b/drivers/dpll/zl3073x/regs.h
index d425dc67250f..8015808bdf54 100644
--- a/drivers/dpll/zl3073x/regs.h
+++ b/drivers/dpll/zl3073x/regs.h
@@ -98,7 +98,14 @@
#define ZL_REG_REF_MON_STATUS(_idx) \
ZL_REG_IDX(_idx, 2, 0x02, 1, ZL3073X_NUM_REFS, 1)
-#define ZL_REF_MON_STATUS_OK 0 /* all bits zeroed */
+#define ZL_REF_MON_STATUS_OK 0
+#define ZL_REF_MON_STATUS_LOS BIT(0)
+#define ZL_REF_MON_STATUS_SCM BIT(1)
+#define ZL_REF_MON_STATUS_CFM BIT(2)
+#define ZL_REF_MON_STATUS_GST BIT(3)
+#define ZL_REF_MON_STATUS_PFM BIT(4)
+#define ZL_REF_MON_STATUS_ESYNC BIT(6)
+#define ZL_REF_MON_STATUS_SPLIT_XO BIT(7)
#define ZL_REG_DPLL_MON_STATUS(_idx) \
ZL_REG_IDX(_idx, 2, 0x10, 1, ZL3073X_MAX_CHANNELS, 1)
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,325 @@
From 071d49d03d957fe6ab483b8447328dc278cbf6f1 Mon Sep 17 00:00:00 2001
From: Ivan Vecera <ivecera@redhat.com>
Date: Wed, 13 May 2026 11:50:25 +0200
Subject: [PATCH] dpll: add fractional frequency offset to pin-parent-device
JIRA: https://issues.redhat.com/browse/RHEL-173193
commit 9c11fcb2e9a54d0f1467380831e2e4bb68f7498d
Author: Ivan Vecera <ivecera@redhat.com>
Date: Mon May 11 17:58:15 2026 +0200
dpll: add fractional frequency offset to pin-parent-device
Add both fractional-frequency-offset (PPM) and
fractional-frequency-offset-ppt (PPT) attributes to the
pin-parent-device nested attribute set, alongside the existing
top-level pin attributes. Both carry the same measurement at
different precisions.
Introduce enum dpll_ffo_type and struct dpll_ffo_param to
distinguish FFO contexts: DPLL_FFO_PORT_RXTX_RATE for the RX vs
TX symbol rate offset reported at the top level, and
DPLL_FFO_PIN_DEVICE for the pin vs parent DPLL offset reported
in the pin-parent-device nest.
Add a supported_ffo bitmask to struct dpll_pin_ops so drivers
declare which FFO types they support. The core only calls ffo_get
for types the driver has opted into, eliminating the need for
per-driver NULL pointer guards. Validate at pin registration time
that supported_ffo is not set without an ffo_get callback.
Update mlx5 (DPLL_FFO_PORT_RXTX_RATE) and zl3073x
(DPLL_FFO_PORT_RXTX_RATE) drivers to use the new API.
Add documentation for both FFO types to dpll.rst.
Changes v3 -> v4:
- Replace dpll=NULL overloading with enum dpll_ffo_type and
struct dpll_ffo_param (Jakub Kicinski)
- Add supported_ffo opt-in bitmask in dpll_pin_ops for fail-close
driver validation (Jakub Kicinski)
- Add WARN_ON in dpll_pin_register for supported_ffo without
ffo_get callback
Reviewed-by: Jiri Pirko <jiri@nvidia.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
Link: https://patch.msgid.link/20260511155816.99936-2-ivecera@redhat.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
(cherry picked from commit 9c11fcb2e9a54d0f1467380831e2e4bb68f7498d)
Assisted-by: Patchpal
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
diff --git a/Documentation/driver-api/dpll.rst b/Documentation/driver-api/dpll.rst
index 37eaef785e30..bae14766d4f7 100644
--- a/Documentation/driver-api/dpll.rst
+++ b/Documentation/driver-api/dpll.rst
@@ -258,6 +258,26 @@ in the ``DPLL_A_PIN_PHASE_OFFSET`` attribute.
``DPLL_A_PHASE_OFFSET_MONITOR`` attr state of a feature
=============================== ========================
+Fractional frequency offset
+===========================
+
+The fractional frequency offset (FFO) is reported through two attributes
+that carry the same measurement at different precisions:
+
+- ``DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET`` in PPM (parts per million)
+- ``DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET_PPT`` in PPT (parts per trillion)
+
+Both attributes appear at the top level of a pin and inside each
+``pin-parent-device`` nest. Two FFO types are defined:
+
+- ``DPLL_FFO_PORT_RXTX_RATE`` - RX vs TX symbol rate offset (top-level)
+- ``DPLL_FFO_PIN_DEVICE`` - pin vs parent DPLL offset (per-parent)
+
+The driver declares which types it supports via the ``supported_ffo``
+bitmask in ``struct dpll_pin_ops``. The core only calls the ``ffo_get``
+callback for types the driver has opted into. The requested type is
+passed to the driver in the ``struct dpll_ffo_param``.
+
Frequency monitor
=================
diff --git a/Documentation/netlink/specs/dpll.yaml b/Documentation/netlink/specs/dpll.yaml
index a47d7da54da5..4ba8adcf823c 100644
--- a/Documentation/netlink/specs/dpll.yaml
+++ b/Documentation/netlink/specs/dpll.yaml
@@ -448,12 +448,14 @@ attribute-sets:
name: fractional-frequency-offset
type: sint
doc: |
- The FFO (Fractional Frequency Offset) between the RX and TX
- symbol rate on the media associated with the pin:
- (rx_frequency-tx_frequency)/rx_frequency
+ The FFO (Fractional Frequency Offset) of the pin.
+ At top level this represents the RX vs TX symbol rate
+ 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.
Value is in PPM (parts per million).
- This may be implemented for example for pin of type
- PIN_TYPE_SYNCE_ETH_PORT.
+ This is a lower-precision version of
+ fractional-frequency-offset-ppt.
-
name: esync-frequency
type: u64
@@ -492,12 +494,14 @@ attribute-sets:
name: fractional-frequency-offset-ppt
type: sint
doc: |
- The FFO (Fractional Frequency Offset) of the pin with respect to
- the nominal frequency.
- Value = (frequency_measured - frequency_nominal) / frequency_nominal
+ The FFO (Fractional Frequency Offset) of the pin.
+ At top level this represents the RX vs TX symbol rate
+ 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.
Value is in PPT (parts per trillion, 10^-12).
- Note: This attribute provides higher resolution than the standard
- fractional-frequency-offset (which is in PPM).
+ This is a higher-precision version of
+ fractional-frequency-offset.
-
name: measured-frequency
type: u64
@@ -534,6 +538,10 @@ attribute-sets:
name: operstate
-
name: phase-offset
+ -
+ name: fractional-frequency-offset
+ -
+ name: fractional-frequency-offset-ppt
-
name: pin-parent-pin
subset-of: pin
diff --git a/drivers/dpll/dpll_core.c b/drivers/dpll/dpll_core.c
index 84e1b637b4e5..8b260a31aa58 100644
--- a/drivers/dpll/dpll_core.c
+++ b/drivers/dpll/dpll_core.c
@@ -883,7 +883,8 @@ dpll_pin_register(struct dpll_device *dpll, struct dpll_pin *pin,
WARN_ON(!ops->direction_get) ||
WARN_ON(ops->measured_freq_get &&
(!dpll_device_ops(dpll)->freq_monitor_get ||
- !dpll_device_ops(dpll)->freq_monitor_set)))
+ !dpll_device_ops(dpll)->freq_monitor_set)) ||
+ WARN_ON(ops->supported_ffo && !ops->ffo_get))
return -EINVAL;
mutex_lock(&dpll_lock);
diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c
index 05cf946b4be5..00e8696cb267 100644
--- a/drivers/dpll/dpll_netlink.c
+++ b/drivers/dpll/dpll_netlink.c
@@ -417,31 +417,28 @@ dpll_msg_add_phase_offset(struct sk_buff *msg, struct dpll_pin *pin,
static int dpll_msg_add_ffo(struct sk_buff *msg, struct dpll_pin *pin,
struct dpll_pin_ref *ref,
+ enum dpll_ffo_type type,
struct netlink_ext_ack *extack)
{
const struct dpll_pin_ops *ops = dpll_pin_ops(ref);
- struct dpll_device *dpll = ref->dpll;
- s64 ffo;
+ struct dpll_ffo_param ffo = { .type = type };
int ret;
- if (!ops->ffo_get)
+ if (!ops->ffo_get || !(ops->supported_ffo & BIT(type)))
return 0;
- ret = ops->ffo_get(pin, dpll_pin_on_dpll_priv(dpll, pin),
- dpll, dpll_priv(dpll), &ffo, extack);
+ ret = ops->ffo_get(pin, dpll_pin_on_dpll_priv(ref->dpll, pin),
+ ref->dpll, dpll_priv(ref->dpll), &ffo, extack);
if (ret) {
if (ret == -ENODATA)
return 0;
return ret;
}
- /* Put the FFO value in PPM to preserve compatibility with older
- * programs.
- */
- ret = nla_put_sint(msg, DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET,
- div_s64(ffo, 1000000));
- if (ret)
+ if (nla_put_sint(msg, DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET,
+ div_s64(ffo.ffo, 1000000)))
return -EMSGSIZE;
- return nla_put_sint(msg, DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET_PPT,
- ffo);
+ return nla_put_sint(msg,
+ DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET_PPT,
+ ffo.ffo);
}
static int dpll_msg_add_measured_freq(struct sk_buff *msg, struct dpll_pin *pin,
@@ -686,6 +683,10 @@ dpll_msg_add_pin_dplls(struct sk_buff *msg, struct dpll_pin *pin,
if (ret)
goto nest_cancel;
ret = dpll_msg_add_phase_offset(msg, pin, ref, extack);
+ if (ret)
+ goto nest_cancel;
+ ret = dpll_msg_add_ffo(msg, pin, ref,
+ DPLL_FFO_PIN_DEVICE, extack);
if (ret)
goto nest_cancel;
nla_nest_end(msg, attr);
@@ -748,7 +749,8 @@ dpll_cmd_pin_get_one(struct sk_buff *msg, struct dpll_pin *pin,
ret = dpll_msg_add_pin_phase_adjust(msg, pin, ref, extack);
if (ret)
return ret;
- ret = dpll_msg_add_ffo(msg, pin, ref, extack);
+ ret = dpll_msg_add_ffo(msg, pin, ref,
+ DPLL_FFO_PORT_RXTX_RATE, extack);
if (ret)
return ret;
ret = dpll_msg_add_measured_freq(msg, pin, ref, extack);
diff --git a/drivers/dpll/dpll_nl.c b/drivers/dpll/dpll_nl.c
index 64398841cdcb..268999ddbcb6 100644
--- a/drivers/dpll/dpll_nl.c
+++ b/drivers/dpll/dpll_nl.c
@@ -18,6 +18,8 @@ const struct nla_policy dpll_pin_parent_device_nl_policy[DPLL_A_PIN_OPERSTATE +
[DPLL_A_PIN_STATE] = NLA_POLICY_RANGE(NLA_U32, 1, 3),
[DPLL_A_PIN_OPERSTATE] = NLA_POLICY_RANGE(NLA_U32, 1, 4),
[DPLL_A_PIN_PHASE_OFFSET] = { .type = NLA_S64, },
+ [DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET] = { .type = NLA_SINT, },
+ [DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET_PPT] = { .type = NLA_SINT, },
};
const struct nla_policy dpll_pin_parent_pin_nl_policy[DPLL_A_PIN_STATE + 1] = {
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index 2c8bd211e31e..b159dff2985e 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -295,11 +295,12 @@ zl3073x_dpll_input_pin_ref_sync_set(const struct dpll_pin *dpll_pin,
static int
zl3073x_dpll_input_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_priv,
const struct dpll_device *dpll, void *dpll_priv,
- s64 *ffo, struct netlink_ext_ack *extack)
+ struct dpll_ffo_param *ffo,
+ struct netlink_ext_ack *extack)
{
struct zl3073x_dpll_pin *pin = pin_priv;
- *ffo = pin->freq_offset;
+ ffo->ffo = pin->freq_offset;
return 0;
}
@@ -1274,6 +1275,7 @@ zl3073x_dpll_freq_monitor_set(const struct dpll_device *dpll,
}
static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = {
+ .supported_ffo = BIT(DPLL_FFO_PORT_RXTX_RATE),
.direction_get = zl3073x_dpll_pin_direction_get,
.esync_get = zl3073x_dpll_input_pin_esync_get,
.esync_set = zl3073x_dpll_input_pin_esync_set,
diff --git a/drivers/net/ethernet/mellanox/mlx5/core/dpll.c b/drivers/net/ethernet/mellanox/mlx5/core/dpll.c
index 3981dd81d4c1..3756c0278122 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/dpll.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/dpll.c
@@ -300,7 +300,8 @@ static int mlx5_dpll_state_on_dpll_set(const struct dpll_pin *pin,
static int mlx5_dpll_ffo_get(const struct dpll_pin *pin, void *pin_priv,
const struct dpll_device *dpll, void *dpll_priv,
- s64 *ffo, struct netlink_ext_ack *extack)
+ struct dpll_ffo_param *ffo,
+ struct netlink_ext_ack *extack)
{
struct mlx5_dpll_synce_status synce_status;
struct mlx5_dpll *mdpll = pin_priv;
@@ -309,10 +310,11 @@ static int mlx5_dpll_ffo_get(const struct dpll_pin *pin, void *pin_priv,
err = mlx5_dpll_synce_status_get(mdpll->mdev, &synce_status);
if (err)
return err;
- return mlx5_dpll_pin_ffo_get(&synce_status, ffo);
+ return mlx5_dpll_pin_ffo_get(&synce_status, &ffo->ffo);
}
static const struct dpll_pin_ops mlx5_dpll_pins_ops = {
+ .supported_ffo = BIT(DPLL_FFO_PORT_RXTX_RATE),
.direction_get = mlx5_dpll_pin_direction_get,
.state_on_dpll_get = mlx5_dpll_state_on_dpll_get,
.state_on_dpll_set = mlx5_dpll_state_on_dpll_set,
diff --git a/include/linux/dpll.h b/include/linux/dpll.h
index 90249937008c..cd21b1f3dff1 100644
--- a/include/linux/dpll.h
+++ b/include/linux/dpll.h
@@ -73,7 +73,20 @@ struct dpll_device_ops {
RH_KABI_RESERVE(10)
};
+enum dpll_ffo_type {
+ DPLL_FFO_PORT_RXTX_RATE,
+ DPLL_FFO_PIN_DEVICE,
+
+ __DPLL_FFO_TYPE_MAX,
+};
+
+struct dpll_ffo_param {
+ enum dpll_ffo_type type;
+ s64 ffo;
+};
+
struct dpll_pin_ops {
+ unsigned long supported_ffo;
int (*frequency_set)(const struct dpll_pin *pin, void *pin_priv,
const struct dpll_device *dpll, void *dpll_priv,
const u64 frequency,
@@ -134,7 +147,8 @@ struct dpll_pin_ops {
struct netlink_ext_ack *extack);
int (*ffo_get)(const struct dpll_pin *pin, void *pin_priv,
const struct dpll_device *dpll, void *dpll_priv,
- s64 *ffo, struct netlink_ext_ack *extack);
+ struct dpll_ffo_param *ffo,
+ struct netlink_ext_ack *extack);
int (*measured_freq_get)(const struct dpll_pin *pin, void *pin_priv,
const struct dpll_device *dpll,
void *dpll_priv, u64 *measured_freq,
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,362 @@
From ba5cbd1742d290baabec9f9d37a9a8f3a10ab860 Mon Sep 17 00:00:00 2001
From: Ivan Vecera <ivecera@redhat.com>
Date: Wed, 13 May 2026 11:52:05 +0200
Subject: [PATCH] dpll: zl3073x: report FFO as DPLL vs input reference offset
JIRA: https://issues.redhat.com/browse/RHEL-173193
commit 54e65df8cf18a741745645aed7ae91514d437b43
Author: Ivan Vecera <ivecera@redhat.com>
Date: Mon May 11 17:58:16 2026 +0200
dpll: zl3073x: report FFO as DPLL vs input reference offset
Replace the per-reference frequency offset measurement (which was
redundant with measured-frequency) with a direct read of the DPLL's
delta frequency offset vs its tracked input reference.
The new implementation uses the dpll_df_offset_x register with
ref_ofst=1 via the dpll_df_read_x semaphore mechanism. This
provides 2^-48 resolution (~3.5 fE) and reports the actual
frequency difference between the DPLL and its active input.
Switch supported_ffo from DPLL_FFO_PORT_RXTX_RATE to
DPLL_FFO_PIN_DEVICE so FFO is reported only in the per-parent
context for the active input pin.
Use atomic64_t for freq_offset to prevent torn reads on 32-bit
architectures between the periodic worker and netlink callbacks.
Rewrite ffo_check to compare the cached df_offset converted to PPT
instead of using the old per-reference measurement. Remove the
ref_ffo_update periodic measurement and the ref ffo field since
they are no longer needed.
Changes v3 -> v4:
- Switch to DPLL_FFO_PIN_DEVICE, remove dpll=NULL guard
- Use atomic64_t for freq_offset (torn read on 32-bit)
Reviewed-by: Petr Oros <poros@redhat.com>
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
Link: https://patch.msgid.link/20260511155816.99936-3-ivecera@redhat.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
(cherry picked from commit 54e65df8cf18a741745645aed7ae91514d437b43)
Assisted-by: Patchpal
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
diff --git a/drivers/dpll/zl3073x/chan.c b/drivers/dpll/zl3073x/chan.c
index 2f48ca239149..2fe3c3da84bb 100644
--- a/drivers/dpll/zl3073x/chan.c
+++ b/drivers/dpll/zl3073x/chan.c
@@ -18,6 +18,7 @@
int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index)
{
struct zl3073x_chan *chan = &zldev->chan[index];
+ u64 val;
int rc;
rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_MON_STATUS(index),
@@ -25,8 +26,34 @@ int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index)
if (rc)
return rc;
- return zl3073x_read_u8(zldev, ZL_REG_DPLL_REFSEL_STATUS(index),
- &chan->refsel_status);
+ rc = zl3073x_read_u8(zldev, ZL_REG_DPLL_REFSEL_STATUS(index),
+ &chan->refsel_status);
+ if (rc)
+ return rc;
+
+ /* Read df_offset vs tracked reference */
+ rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index),
+ ZL_DPLL_DF_READ_SEM);
+ if (rc)
+ return rc;
+
+ rc = zl3073x_write_u8(zldev, ZL_REG_DPLL_DF_READ(index),
+ ZL_DPLL_DF_READ_SEM | ZL_DPLL_DF_READ_REF_OFST);
+ if (rc)
+ return rc;
+
+ rc = zl3073x_poll_zero_u8(zldev, ZL_REG_DPLL_DF_READ(index),
+ ZL_DPLL_DF_READ_SEM);
+ if (rc)
+ return rc;
+
+ rc = zl3073x_read_u48(zldev, ZL_REG_DPLL_DF_OFFSET(index), &val);
+ if (rc)
+ return rc;
+
+ chan->df_offset = sign_extend64(val, 47);
+
+ return 0;
}
/**
diff --git a/drivers/dpll/zl3073x/chan.h b/drivers/dpll/zl3073x/chan.h
index 481da2133202..4353809c6912 100644
--- a/drivers/dpll/zl3073x/chan.h
+++ b/drivers/dpll/zl3073x/chan.h
@@ -17,6 +17,7 @@ struct zl3073x_dev;
* @ref_prio: reference priority registers (4 bits per ref, P/N packed)
* @mon_status: monitor status register value
* @refsel_status: reference selection status register value
+ * @df_offset: frequency offset vs tracked reference in 2^-48 steps
*/
struct zl3073x_chan {
struct_group(cfg,
@@ -26,6 +27,7 @@ struct zl3073x_chan {
struct_group(stat,
u8 mon_status;
u8 refsel_status;
+ s64 df_offset;
);
};
@@ -37,6 +39,18 @@ int zl3073x_chan_state_set(struct zl3073x_dev *zldev, u8 index,
int zl3073x_chan_state_update(struct zl3073x_dev *zldev, u8 index);
+/**
+ * zl3073x_chan_df_offset_get - get cached df_offset vs tracked reference
+ * @chan: pointer to channel state
+ *
+ * Return: frequency offset in 2^-48 steps
+ */
+static inline s64
+zl3073x_chan_df_offset_get(const struct zl3073x_chan *chan)
+{
+ return chan->df_offset;
+}
+
/**
* zl3073x_chan_mode_get - get DPLL channel operating mode
* @chan: pointer to channel state
diff --git a/drivers/dpll/zl3073x/core.c b/drivers/dpll/zl3073x/core.c
index 5f1e70f3e40a..b3345060490d 100644
--- a/drivers/dpll/zl3073x/core.c
+++ b/drivers/dpll/zl3073x/core.c
@@ -704,44 +704,6 @@ zl3073x_ref_freq_meas_update(struct zl3073x_dev *zldev)
return 0;
}
-/**
- * zl3073x_ref_ffo_update - update reference fractional frequency offsets
- * @zldev: pointer to zl3073x_dev structure
- *
- * The function asks device to latch the latest measured fractional
- * frequency offset values, reads and stores them into the ref state.
- *
- * Return: 0 on success, <0 on error
- */
-static int
-zl3073x_ref_ffo_update(struct zl3073x_dev *zldev)
-{
- int i, rc;
-
- rc = zl3073x_ref_freq_meas_latch(zldev,
- ZL_REF_FREQ_MEAS_CTRL_REF_FREQ_OFF);
- if (rc)
- return rc;
-
- /* Read DPLL-to-REFx frequency offset measurements */
- for (i = 0; i < ZL3073X_NUM_REFS; i++) {
- s32 value;
-
- /* Read value stored in units of 2^-32 signed */
- rc = zl3073x_read_u32(zldev, ZL_REG_REF_FREQ(i), &value);
- if (rc)
- return rc;
-
- /* Convert to ppt
- * ffo = (10^12 * value) / 2^32
- * ffo = ( 5^12 * value) / 2^20
- */
- zldev->ref[i].ffo = mul_s64_u64_shr(value, 244140625, 20);
- }
-
- return 0;
-}
-
static void
zl3073x_dev_periodic_work(struct kthread_work *work)
{
@@ -776,13 +738,6 @@ zl3073x_dev_periodic_work(struct kthread_work *work)
}
}
- /* Update references' fractional frequency offsets */
- rc = zl3073x_ref_ffo_update(zldev);
- if (rc)
- dev_warn(zldev->dev,
- "Failed to update fractional frequency offsets: %pe\n",
- ERR_PTR(rc));
-
list_for_each_entry(zldpll, &zldev->dplls, list)
zl3073x_dpll_changes_check(zldpll);
diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c
index b159dff2985e..6b714ec3b3c9 100644
--- a/drivers/dpll/zl3073x/dpll.c
+++ b/drivers/dpll/zl3073x/dpll.c
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-only
+#include <linux/atomic.h>
#include <linux/bits.h>
#include <linux/bitfield.h>
#include <linux/bug.h>
@@ -57,7 +58,7 @@ struct zl3073x_dpll_pin {
s32 phase_gran;
enum dpll_pin_operstate operstate;
s64 phase_offset;
- s64 freq_offset;
+ atomic64_t freq_offset;
u32 measured_freq;
};
@@ -300,7 +301,10 @@ zl3073x_dpll_input_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_priv,
{
struct zl3073x_dpll_pin *pin = pin_priv;
- ffo->ffo = pin->freq_offset;
+ if (pin->operstate != DPLL_PIN_OPERSTATE_ACTIVE)
+ return -ENODATA;
+
+ ffo->ffo = atomic64_read(&pin->freq_offset);
return 0;
}
@@ -1275,7 +1279,7 @@ zl3073x_dpll_freq_monitor_set(const struct dpll_device *dpll,
}
static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = {
- .supported_ffo = BIT(DPLL_FFO_PORT_RXTX_RATE),
+ .supported_ffo = BIT(DPLL_FFO_PIN_DEVICE),
.direction_get = zl3073x_dpll_pin_direction_get,
.esync_get = zl3073x_dpll_input_pin_esync_get,
.esync_set = zl3073x_dpll_input_pin_esync_set,
@@ -1731,37 +1735,29 @@ zl3073x_dpll_pin_phase_offset_check(struct zl3073x_dpll_pin *pin)
}
/**
- * zl3073x_dpll_pin_ffo_check - check for pin fractional frequency offset change
+ * zl3073x_dpll_pin_ffo_check - check for FFO change on active pin
* @pin: pin to check
*
- * Check for the given pin's fractional frequency change.
- *
- * Return: true on fractional frequency offset change, false otherwise
+ * Return: true on change, false otherwise
*/
static bool
zl3073x_dpll_pin_ffo_check(struct zl3073x_dpll_pin *pin)
{
struct zl3073x_dpll *zldpll = pin->dpll;
struct zl3073x_dev *zldev = zldpll->dev;
- const struct zl3073x_ref *ref;
- u8 ref_id;
+ const struct zl3073x_chan *chan;
s64 ffo;
- /* Get reference monitor status */
- ref_id = zl3073x_input_pin_ref_get(pin->id);
- ref = zl3073x_ref_state_get(zldev, ref_id);
-
- /* Do not report ffo changes if the reference monitor report errors */
- if (!zl3073x_ref_is_status_ok(ref))
+ if (pin->operstate != DPLL_PIN_OPERSTATE_ACTIVE)
return false;
- /* Compare with previous value */
- ffo = zl3073x_ref_ffo_get(ref);
- if (pin->freq_offset != ffo) {
- dev_dbg(zldev->dev, "%s freq offset changed: %lld -> %lld\n",
- pin->label, pin->freq_offset, ffo);
- pin->freq_offset = ffo;
+ chan = zl3073x_chan_state_get(zldpll->dev, zldpll->id);
+ ffo = mul_s64_u64_shr(zl3073x_chan_df_offset_get(chan),
+ 244140625, 36);
+ if (atomic64_xchg(&pin->freq_offset, ffo) != ffo) {
+ dev_dbg(zldev->dev, "%s freq offset changed to: %lld\n",
+ pin->label, ffo);
return true;
}
diff --git a/drivers/dpll/zl3073x/ref.h b/drivers/dpll/zl3073x/ref.h
index 55e80e4f0873..e140ca3ea17d 100644
--- a/drivers/dpll/zl3073x/ref.h
+++ b/drivers/dpll/zl3073x/ref.h
@@ -22,7 +22,6 @@ struct zl3073x_dev;
* @freq_ratio_n: FEC mode divisor
* @sync_ctrl: reference sync control
* @config: reference config
- * @ffo: current fractional frequency offset
* @meas_freq: measured input frequency in Hz
* @mon_status: reference monitor status
*/
@@ -40,7 +39,6 @@ struct zl3073x_ref {
u8 config;
);
struct_group(stat, /* Status */
- s64 ffo;
u32 meas_freq;
u8 mon_status;
);
@@ -58,18 +56,6 @@ int zl3073x_ref_state_update(struct zl3073x_dev *zldev, u8 index);
int zl3073x_ref_freq_factorize(u32 freq, u16 *base, u16 *mult);
-/**
- * zl3073x_ref_ffo_get - get current fractional frequency offset
- * @ref: pointer to ref state
- *
- * Return: the latest measured fractional frequency offset
- */
-static inline s64
-zl3073x_ref_ffo_get(const struct zl3073x_ref *ref)
-{
- return ref->ffo;
-}
-
/**
* zl3073x_ref_meas_freq_get - get measured input frequency
* @ref: pointer to ref state
diff --git a/drivers/dpll/zl3073x/regs.h b/drivers/dpll/zl3073x/regs.h
index 8015808bdf54..9578f0009528 100644
--- a/drivers/dpll/zl3073x/regs.h
+++ b/drivers/dpll/zl3073x/regs.h
@@ -164,6 +164,11 @@
#define ZL_DPLL_MODE_REFSEL_MODE_NCO 4
#define ZL_DPLL_MODE_REFSEL_REF GENMASK(7, 4)
+#define ZL_REG_DPLL_DF_READ(_idx) \
+ ZL_REG_IDX(_idx, 5, 0x28, 1, ZL3073X_MAX_CHANNELS, 1)
+#define ZL_DPLL_DF_READ_SEM BIT(4)
+#define ZL_DPLL_DF_READ_REF_OFST BIT(3)
+
#define ZL_REG_DPLL_MEAS_CTRL ZL_REG(5, 0x50, 1)
#define ZL_DPLL_MEAS_CTRL_EN BIT(0)
#define ZL_DPLL_MEAS_CTRL_AVG_FACTOR GENMASK(7, 4)
@@ -176,6 +181,16 @@
#define ZL_REG_DPLL_PHASE_ERR_DATA(_idx) \
ZL_REG_IDX(_idx, 5, 0x55, 6, ZL3073X_MAX_CHANNELS, 6)
+/*******************************
+ * Register Pages 6-7, DPLL Data
+ *******************************/
+
+#define ZL_REG_DPLL_DF_OFFSET_03(_idx) \
+ ZL_REG_IDX(_idx, 6, 0x00, 6, 4, 0x20)
+#define ZL_REG_DPLL_DF_OFFSET_4 ZL_REG(7, 0x00, 6)
+#define ZL_REG_DPLL_DF_OFFSET(_idx) \
+ ((_idx) < 4 ? ZL_REG_DPLL_DF_OFFSET_03(_idx) : ZL_REG_DPLL_DF_OFFSET_4)
+
/***********************************
* Register Page 9, Synth and Output
***********************************/
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,500 @@
From 57c78bd2e2dd08897acd35b2bf8bcef322e36f5e Mon Sep 17 00:00:00 2001
From: Pablo Neira Ayuso <pablo@netfilter.org>
Date: Thu, 26 Mar 2026 00:17:09 +0100
Subject: [PATCH] netfilter: flowtable: strictly check for maximum number of
actions
[ Upstream commit 76522fcdbc3a02b568f5d957f7e66fc194abb893 ]
The maximum number of flowtable hardware offload actions in IPv6 is:
* ethernet mangling (4 payload actions, 2 for each ethernet address)
* SNAT (4 payload actions)
* DNAT (4 payload actions)
* Double VLAN (4 vlan actions, 2 for popping vlan, and 2 for pushing)
for QinQ.
* Redirect (1 action)
Which makes 17, while the maximum is 16. But act_ct supports for tunnels
actions too. Note that payload action operates at 32-bit word level, so
mangling an IPv6 address takes 4 payload actions.
Update flow_action_entry_next() calls to check for the maximum number of
supported actions.
While at it, rise the maximum number of actions per flow from 16 to 24
so this works fine with IPv6 setups.
Fixes: c29f74e0df7a ("netfilter: nf_flow_table: hardware offload support")
Reported-by: Hyunwoo Kim <imv4bel@gmail.com>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
diff --git a/net/netfilter/nf_flow_table_offload.c b/net/netfilter/nf_flow_table_offload.c
index e06bc36f49fe..4f346f51d7d7 100644
--- a/net/netfilter/nf_flow_table_offload.c
+++ b/net/netfilter/nf_flow_table_offload.c
@@ -13,6 +13,8 @@
#include <net/netfilter/nf_conntrack_core.h>
#include <net/netfilter/nf_conntrack_tuple.h>
+#define NF_FLOW_RULE_ACTION_MAX 24
+
static struct workqueue_struct *nf_flow_offload_add_wq;
static struct workqueue_struct *nf_flow_offload_del_wq;
static struct workqueue_struct *nf_flow_offload_stats_wq;
@@ -215,7 +217,12 @@ static void flow_offload_mangle(struct flow_action_entry *entry,
static inline struct flow_action_entry *
flow_action_entry_next(struct nf_flow_rule *flow_rule)
{
- int i = flow_rule->rule->action.num_entries++;
+ int i;
+
+ if (unlikely(flow_rule->rule->action.num_entries >= NF_FLOW_RULE_ACTION_MAX))
+ return NULL;
+
+ i = flow_rule->rule->action.num_entries++;
return &flow_rule->rule->action.entries[i];
}
@@ -233,6 +240,9 @@ static int flow_offload_eth_src(struct net *net,
u32 mask, val;
u16 val16;
+ if (!entry0 || !entry1)
+ return -E2BIG;
+
this_tuple = &flow->tuplehash[dir].tuple;
switch (this_tuple->xmit_type) {
@@ -283,6 +293,9 @@ static int flow_offload_eth_dst(struct net *net,
u8 nud_state;
u16 val16;
+ if (!entry0 || !entry1)
+ return -E2BIG;
+
this_tuple = &flow->tuplehash[dir].tuple;
switch (this_tuple->xmit_type) {
@@ -324,16 +337,19 @@ static int flow_offload_eth_dst(struct net *net,
return 0;
}
-static void flow_offload_ipv4_snat(struct net *net,
- const struct flow_offload *flow,
- enum flow_offload_tuple_dir dir,
- struct nf_flow_rule *flow_rule)
+static int flow_offload_ipv4_snat(struct net *net,
+ const struct flow_offload *flow,
+ enum flow_offload_tuple_dir dir,
+ struct nf_flow_rule *flow_rule)
{
struct flow_action_entry *entry = flow_action_entry_next(flow_rule);
u32 mask = ~htonl(0xffffffff);
__be32 addr;
u32 offset;
+ if (!entry)
+ return -E2BIG;
+
switch (dir) {
case FLOW_OFFLOAD_DIR_ORIGINAL:
addr = flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.dst_v4.s_addr;
@@ -344,23 +360,27 @@ static void flow_offload_ipv4_snat(struct net *net,
offset = offsetof(struct iphdr, daddr);
break;
default:
- return;
+ return -EOPNOTSUPP;
}
flow_offload_mangle(entry, FLOW_ACT_MANGLE_HDR_TYPE_IP4, offset,
&addr, &mask);
+ return 0;
}
-static void flow_offload_ipv4_dnat(struct net *net,
- const struct flow_offload *flow,
- enum flow_offload_tuple_dir dir,
- struct nf_flow_rule *flow_rule)
+static int flow_offload_ipv4_dnat(struct net *net,
+ const struct flow_offload *flow,
+ enum flow_offload_tuple_dir dir,
+ struct nf_flow_rule *flow_rule)
{
struct flow_action_entry *entry = flow_action_entry_next(flow_rule);
u32 mask = ~htonl(0xffffffff);
__be32 addr;
u32 offset;
+ if (!entry)
+ return -E2BIG;
+
switch (dir) {
case FLOW_OFFLOAD_DIR_ORIGINAL:
addr = flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.src_v4.s_addr;
@@ -371,14 +391,15 @@ static void flow_offload_ipv4_dnat(struct net *net,
offset = offsetof(struct iphdr, saddr);
break;
default:
- return;
+ return -EOPNOTSUPP;
}
flow_offload_mangle(entry, FLOW_ACT_MANGLE_HDR_TYPE_IP4, offset,
&addr, &mask);
+ return 0;
}
-static void flow_offload_ipv6_mangle(struct nf_flow_rule *flow_rule,
+static int flow_offload_ipv6_mangle(struct nf_flow_rule *flow_rule,
unsigned int offset,
const __be32 *addr, const __be32 *mask)
{
@@ -387,15 +408,20 @@ static void flow_offload_ipv6_mangle(struct nf_flow_rule *flow_rule,
for (i = 0; i < sizeof(struct in6_addr) / sizeof(u32); i++) {
entry = flow_action_entry_next(flow_rule);
+ if (!entry)
+ return -E2BIG;
+
flow_offload_mangle(entry, FLOW_ACT_MANGLE_HDR_TYPE_IP6,
offset + i * sizeof(u32), &addr[i], mask);
}
+
+ return 0;
}
-static void flow_offload_ipv6_snat(struct net *net,
- const struct flow_offload *flow,
- enum flow_offload_tuple_dir dir,
- struct nf_flow_rule *flow_rule)
+static int flow_offload_ipv6_snat(struct net *net,
+ const struct flow_offload *flow,
+ enum flow_offload_tuple_dir dir,
+ struct nf_flow_rule *flow_rule)
{
u32 mask = ~htonl(0xffffffff);
const __be32 *addr;
@@ -411,16 +437,16 @@ static void flow_offload_ipv6_snat(struct net *net,
offset = offsetof(struct ipv6hdr, daddr);
break;
default:
- return;
+ return -EOPNOTSUPP;
}
- flow_offload_ipv6_mangle(flow_rule, offset, addr, &mask);
+ return flow_offload_ipv6_mangle(flow_rule, offset, addr, &mask);
}
-static void flow_offload_ipv6_dnat(struct net *net,
- const struct flow_offload *flow,
- enum flow_offload_tuple_dir dir,
- struct nf_flow_rule *flow_rule)
+static int flow_offload_ipv6_dnat(struct net *net,
+ const struct flow_offload *flow,
+ enum flow_offload_tuple_dir dir,
+ struct nf_flow_rule *flow_rule)
{
u32 mask = ~htonl(0xffffffff);
const __be32 *addr;
@@ -436,10 +462,10 @@ static void flow_offload_ipv6_dnat(struct net *net,
offset = offsetof(struct ipv6hdr, saddr);
break;
default:
- return;
+ return -EOPNOTSUPP;
}
- flow_offload_ipv6_mangle(flow_rule, offset, addr, &mask);
+ return flow_offload_ipv6_mangle(flow_rule, offset, addr, &mask);
}
static int flow_offload_l4proto(const struct flow_offload *flow)
@@ -461,15 +487,18 @@ static int flow_offload_l4proto(const struct flow_offload *flow)
return type;
}
-static void flow_offload_port_snat(struct net *net,
- const struct flow_offload *flow,
- enum flow_offload_tuple_dir dir,
- struct nf_flow_rule *flow_rule)
+static int flow_offload_port_snat(struct net *net,
+ const struct flow_offload *flow,
+ enum flow_offload_tuple_dir dir,
+ struct nf_flow_rule *flow_rule)
{
struct flow_action_entry *entry = flow_action_entry_next(flow_rule);
u32 mask, port;
u32 offset;
+ if (!entry)
+ return -E2BIG;
+
switch (dir) {
case FLOW_OFFLOAD_DIR_ORIGINAL:
port = ntohs(flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.dst_port);
@@ -484,22 +513,26 @@ static void flow_offload_port_snat(struct net *net,
mask = ~htonl(0xffff);
break;
default:
- return;
+ return -EOPNOTSUPP;
}
flow_offload_mangle(entry, flow_offload_l4proto(flow), offset,
&port, &mask);
+ return 0;
}
-static void flow_offload_port_dnat(struct net *net,
- const struct flow_offload *flow,
- enum flow_offload_tuple_dir dir,
- struct nf_flow_rule *flow_rule)
+static int flow_offload_port_dnat(struct net *net,
+ const struct flow_offload *flow,
+ enum flow_offload_tuple_dir dir,
+ struct nf_flow_rule *flow_rule)
{
struct flow_action_entry *entry = flow_action_entry_next(flow_rule);
u32 mask, port;
u32 offset;
+ if (!entry)
+ return -E2BIG;
+
switch (dir) {
case FLOW_OFFLOAD_DIR_ORIGINAL:
port = ntohs(flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.src_port);
@@ -514,20 +547,24 @@ static void flow_offload_port_dnat(struct net *net,
mask = ~htonl(0xffff0000);
break;
default:
- return;
+ return -EOPNOTSUPP;
}
flow_offload_mangle(entry, flow_offload_l4proto(flow), offset,
&port, &mask);
+ return 0;
}
-static void flow_offload_ipv4_checksum(struct net *net,
- const struct flow_offload *flow,
- struct nf_flow_rule *flow_rule)
+static int flow_offload_ipv4_checksum(struct net *net,
+ const struct flow_offload *flow,
+ struct nf_flow_rule *flow_rule)
{
u8 protonum = flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.l4proto;
struct flow_action_entry *entry = flow_action_entry_next(flow_rule);
+ if (!entry)
+ return -E2BIG;
+
entry->id = FLOW_ACTION_CSUM;
entry->csum_flags = TCA_CSUM_UPDATE_FLAG_IPV4HDR;
@@ -539,12 +576,14 @@ static void flow_offload_ipv4_checksum(struct net *net,
entry->csum_flags |= TCA_CSUM_UPDATE_FLAG_UDP;
break;
}
+
+ return 0;
}
-static void flow_offload_redirect(struct net *net,
- const struct flow_offload *flow,
- enum flow_offload_tuple_dir dir,
- struct nf_flow_rule *flow_rule)
+static int flow_offload_redirect(struct net *net,
+ const struct flow_offload *flow,
+ enum flow_offload_tuple_dir dir,
+ struct nf_flow_rule *flow_rule)
{
const struct flow_offload_tuple *this_tuple, *other_tuple;
struct flow_action_entry *entry;
@@ -562,21 +601,28 @@ static void flow_offload_redirect(struct net *net,
ifindex = other_tuple->iifidx;
break;
default:
- return;
+ return -EOPNOTSUPP;
}
dev = dev_get_by_index(net, ifindex);
if (!dev)
- return;
+ return -ENODEV;
entry = flow_action_entry_next(flow_rule);
+ if (!entry) {
+ dev_put(dev);
+ return -E2BIG;
+ }
+
entry->id = FLOW_ACTION_REDIRECT;
entry->dev = dev;
+
+ return 0;
}
-static void flow_offload_encap_tunnel(const struct flow_offload *flow,
- enum flow_offload_tuple_dir dir,
- struct nf_flow_rule *flow_rule)
+static int flow_offload_encap_tunnel(const struct flow_offload *flow,
+ enum flow_offload_tuple_dir dir,
+ struct nf_flow_rule *flow_rule)
{
const struct flow_offload_tuple *this_tuple;
struct flow_action_entry *entry;
@@ -584,7 +630,7 @@ static void flow_offload_encap_tunnel(const struct flow_offload *flow,
this_tuple = &flow->tuplehash[dir].tuple;
if (this_tuple->xmit_type == FLOW_OFFLOAD_XMIT_DIRECT)
- return;
+ return 0;
dst = this_tuple->dst_cache;
if (dst && dst->lwtstate) {
@@ -593,15 +639,19 @@ static void flow_offload_encap_tunnel(const struct flow_offload *flow,
tun_info = lwt_tun_info(dst->lwtstate);
if (tun_info && (tun_info->mode & IP_TUNNEL_INFO_TX)) {
entry = flow_action_entry_next(flow_rule);
+ if (!entry)
+ return -E2BIG;
entry->id = FLOW_ACTION_TUNNEL_ENCAP;
entry->tunnel = tun_info;
}
}
+
+ return 0;
}
-static void flow_offload_decap_tunnel(const struct flow_offload *flow,
- enum flow_offload_tuple_dir dir,
- struct nf_flow_rule *flow_rule)
+static int flow_offload_decap_tunnel(const struct flow_offload *flow,
+ enum flow_offload_tuple_dir dir,
+ struct nf_flow_rule *flow_rule)
{
const struct flow_offload_tuple *other_tuple;
struct flow_action_entry *entry;
@@ -609,7 +659,7 @@ static void flow_offload_decap_tunnel(const struct flow_offload *flow,
other_tuple = &flow->tuplehash[!dir].tuple;
if (other_tuple->xmit_type == FLOW_OFFLOAD_XMIT_DIRECT)
- return;
+ return 0;
dst = other_tuple->dst_cache;
if (dst && dst->lwtstate) {
@@ -618,9 +668,13 @@ static void flow_offload_decap_tunnel(const struct flow_offload *flow,
tun_info = lwt_tun_info(dst->lwtstate);
if (tun_info && (tun_info->mode & IP_TUNNEL_INFO_TX)) {
entry = flow_action_entry_next(flow_rule);
+ if (!entry)
+ return -E2BIG;
entry->id = FLOW_ACTION_TUNNEL_DECAP;
}
}
+
+ return 0;
}
static int
@@ -632,8 +686,9 @@ nf_flow_rule_route_common(struct net *net, const struct flow_offload *flow,
const struct flow_offload_tuple *tuple;
int i;
- flow_offload_decap_tunnel(flow, dir, flow_rule);
- flow_offload_encap_tunnel(flow, dir, flow_rule);
+ if (flow_offload_decap_tunnel(flow, dir, flow_rule) < 0 ||
+ flow_offload_encap_tunnel(flow, dir, flow_rule) < 0)
+ return -1;
if (flow_offload_eth_src(net, flow, dir, flow_rule) < 0 ||
flow_offload_eth_dst(net, flow, dir, flow_rule) < 0)
@@ -649,6 +704,8 @@ nf_flow_rule_route_common(struct net *net, const struct flow_offload *flow,
if (tuple->encap[i].proto == htons(ETH_P_8021Q)) {
entry = flow_action_entry_next(flow_rule);
+ if (!entry)
+ return -1;
entry->id = FLOW_ACTION_VLAN_POP;
}
}
@@ -662,6 +719,8 @@ nf_flow_rule_route_common(struct net *net, const struct flow_offload *flow,
continue;
entry = flow_action_entry_next(flow_rule);
+ if (!entry)
+ return -1;
switch (other_tuple->encap[i].proto) {
case htons(ETH_P_PPP_SES):
@@ -687,18 +746,22 @@ int nf_flow_rule_route_ipv4(struct net *net, struct flow_offload *flow,
return -1;
if (test_bit(NF_FLOW_SNAT, &flow->flags)) {
- flow_offload_ipv4_snat(net, flow, dir, flow_rule);
- flow_offload_port_snat(net, flow, dir, flow_rule);
+ if (flow_offload_ipv4_snat(net, flow, dir, flow_rule) < 0 ||
+ flow_offload_port_snat(net, flow, dir, flow_rule) < 0)
+ return -1;
}
if (test_bit(NF_FLOW_DNAT, &flow->flags)) {
- flow_offload_ipv4_dnat(net, flow, dir, flow_rule);
- flow_offload_port_dnat(net, flow, dir, flow_rule);
+ if (flow_offload_ipv4_dnat(net, flow, dir, flow_rule) < 0 ||
+ flow_offload_port_dnat(net, flow, dir, flow_rule) < 0)
+ return -1;
}
if (test_bit(NF_FLOW_SNAT, &flow->flags) ||
test_bit(NF_FLOW_DNAT, &flow->flags))
- flow_offload_ipv4_checksum(net, flow, flow_rule);
+ if (flow_offload_ipv4_checksum(net, flow, flow_rule) < 0)
+ return -1;
- flow_offload_redirect(net, flow, dir, flow_rule);
+ if (flow_offload_redirect(net, flow, dir, flow_rule) < 0)
+ return -1;
return 0;
}
@@ -712,22 +775,23 @@ int nf_flow_rule_route_ipv6(struct net *net, struct flow_offload *flow,
return -1;
if (test_bit(NF_FLOW_SNAT, &flow->flags)) {
- flow_offload_ipv6_snat(net, flow, dir, flow_rule);
- flow_offload_port_snat(net, flow, dir, flow_rule);
+ if (flow_offload_ipv6_snat(net, flow, dir, flow_rule) < 0 ||
+ flow_offload_port_snat(net, flow, dir, flow_rule) < 0)
+ return -1;
}
if (test_bit(NF_FLOW_DNAT, &flow->flags)) {
- flow_offload_ipv6_dnat(net, flow, dir, flow_rule);
- flow_offload_port_dnat(net, flow, dir, flow_rule);
+ if (flow_offload_ipv6_dnat(net, flow, dir, flow_rule) < 0 ||
+ flow_offload_port_dnat(net, flow, dir, flow_rule) < 0)
+ return -1;
}
- flow_offload_redirect(net, flow, dir, flow_rule);
+ if (flow_offload_redirect(net, flow, dir, flow_rule) < 0)
+ return -1;
return 0;
}
EXPORT_SYMBOL_GPL(nf_flow_rule_route_ipv6);
-#define NF_FLOW_RULE_ACTION_MAX 16
-
static struct nf_flow_rule *
nf_flow_offload_rule_alloc(struct net *net,
const struct flow_offload_work *offload,
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,355 @@
From c33ae38cff3d15c40384b4ecbaccc4c88d3cb741 Mon Sep 17 00:00:00 2001
From: David Marlin <dmarlin@redhat.com>
Date: Wed, 15 Apr 2026 16:59:13 -0500
Subject: [PATCH] Bluetooth: HCI: Add initial support for PAST
JIRA: https://issues.redhat.com/browse/RHEL-157827
commit 33b2835f0b7e2a458473b0e3a23b54b92108b6b0
Author: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Date: Tue Sep 2 11:11:40 2025 -0400
Bluetooth: HCI: Add initial support for PAST
This adds PAST related commands (HCI_OP_LE_PAST,
HCI_OP_LE_PAST_SET_INFO and HCI_OP_LE_PAST_PARAMS) and events
(HCI_EV_LE_PAST_RECEIVED) along with handling of PAST sender and
receiver features bits including new MGMG settings (
HCI_EV_LE_PAST_RECEIVED and MGMT_SETTING_PAST_RECEIVER) which
userspace can use to determine if PAST is supported by the
controller.
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Signed-off-by: David Marlin <dmarlin@redhat.com>
diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h
index cb4c02d00759..d883ad233ebc 100644
--- a/include/net/bluetooth/hci.h
+++ b/include/net/bluetooth/hci.h
@@ -647,6 +647,8 @@ enum {
#define HCI_LE_EXT_ADV 0x10
#define HCI_LE_PERIODIC_ADV 0x20
#define HCI_LE_CHAN_SEL_ALG2 0x40
+#define HCI_LE_PAST_SENDER 0x01
+#define HCI_LE_PAST_RECEIVER 0x02
#define HCI_LE_CIS_CENTRAL 0x10
#define HCI_LE_CIS_PERIPHERAL 0x20
#define HCI_LE_ISO_BROADCASTER 0x40
@@ -2068,6 +2070,44 @@ struct hci_cp_le_set_privacy_mode {
__u8 mode;
} __packed;
+#define HCI_OP_LE_PAST 0x205a
+struct hci_cp_le_past {
+ __le16 handle;
+ __le16 service_data;
+ __le16 sync_handle;
+} __packed;
+
+struct hci_rp_le_past {
+ __u8 status;
+ __le16 handle;
+} __packed;
+
+#define HCI_OP_LE_PAST_SET_INFO 0x205b
+struct hci_cp_le_past_set_info {
+ __le16 handle;
+ __le16 service_data;
+ __u8 adv_handle;
+} __packed;
+
+struct hci_rp_le_past_set_info {
+ __u8 status;
+ __le16 handle;
+} __packed;
+
+#define HCI_OP_LE_PAST_PARAMS 0x205c
+struct hci_cp_le_past_params {
+ __le16 handle;
+ __u8 mode;
+ __le16 skip;
+ __le16 sync_timeout;
+ __u8 cte_type;
+} __packed;
+
+struct hci_rp_le_past_params {
+ __u8 status;
+ __le16 handle;
+} __packed;
+
#define HCI_OP_LE_READ_BUFFER_SIZE_V2 0x2060
struct hci_rp_le_read_buffer_size_v2 {
__u8 status;
@@ -2800,6 +2840,20 @@ struct hci_evt_le_ext_adv_set_term {
__u8 num_evts;
} __packed;
+#define HCI_EV_LE_PAST_RECEIVED 0x18
+struct hci_ev_le_past_received {
+ __u8 status;
+ __le16 handle;
+ __le16 service_data;
+ __le16 sync_handle;
+ __u8 sid;
+ __u8 bdaddr_type;
+ bdaddr_t bdaddr;
+ __u8 phy;
+ __le16 interval;
+ __u8 clock_accuracy;
+} __packed;
+
#define HCI_EVT_LE_CIS_ESTABLISHED 0x19
struct hci_evt_le_cis_established {
__u8 status;
diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
index 0cb87687837f..1bd12c303e25 100644
--- a/include/net/bluetooth/hci_core.h
+++ b/include/net/bluetooth/hci_core.h
@@ -2053,6 +2053,18 @@ void hci_conn_del_sysfs(struct hci_conn *conn);
#define sync_recv_capable(dev) \
((dev)->le_features[3] & HCI_LE_ISO_SYNC_RECEIVER)
#define sync_recv_enabled(dev) (le_enabled(dev) && sync_recv_capable(dev))
+#define past_sender_capable(dev) \
+ ((dev)->le_features[3] & HCI_LE_PAST_SENDER)
+#define past_receiver_capable(dev) \
+ ((dev)->le_features[3] & HCI_LE_PAST_RECEIVER)
+#define past_capable(dev) \
+ (past_sender_capable(dev) || past_receiver_capable(dev))
+#define past_sender_enabled(dev) \
+ (le_enabled(dev) && past_sender_capable(dev))
+#define past_receiver_enabled(dev) \
+ (le_enabled(dev) && past_receiver_capable(dev))
+#define past_enabled(dev) \
+ (past_sender_enabled(dev) || past_receiver_enabled(dev))
#define mws_transport_config_capable(dev) (((dev)->commands[30] & 0x08) && \
(!hci_test_quirk((dev), HCI_QUIRK_BROKEN_MWS_TRANSPORT_CONFIG)))
diff --git a/include/net/bluetooth/mgmt.h b/include/net/bluetooth/mgmt.h
index f5be96f08b9d..8234915854b6 100644
--- a/include/net/bluetooth/mgmt.h
+++ b/include/net/bluetooth/mgmt.h
@@ -119,6 +119,8 @@ struct mgmt_rp_read_index_list {
#define MGMT_SETTING_ISO_BROADCASTER BIT(20)
#define MGMT_SETTING_ISO_SYNC_RECEIVER BIT(21)
#define MGMT_SETTING_LL_PRIVACY BIT(22)
+#define MGMT_SETTING_PAST_SENDER BIT(23)
+#define MGMT_SETTING_PAST_RECEIVER BIT(24)
#define MGMT_OP_READ_INFO 0x0004
#define MGMT_READ_INFO_SIZE 0
diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c
index 3838b90343d9..af34c9938509 100644
--- a/net/bluetooth/hci_event.c
+++ b/net/bluetooth/hci_event.c
@@ -5936,6 +5936,71 @@ static void hci_le_ext_adv_term_evt(struct hci_dev *hdev, void *data,
hci_dev_unlock(hdev);
}
+static int hci_le_pa_term_sync(struct hci_dev *hdev, __le16 handle)
+{
+ struct hci_cp_le_pa_term_sync cp;
+
+ memset(&cp, 0, sizeof(cp));
+ cp.handle = handle;
+
+ return hci_send_cmd(hdev, HCI_OP_LE_PA_TERM_SYNC, sizeof(cp), &cp);
+}
+
+static void hci_le_past_received_evt(struct hci_dev *hdev, void *data,
+ struct sk_buff *skb)
+{
+ struct hci_ev_le_past_received *ev = data;
+ int mask = hdev->link_mode;
+ __u8 flags = 0;
+ struct hci_conn *pa_sync, *conn;
+
+ bt_dev_dbg(hdev, "status 0x%2.2x", ev->status);
+
+ hci_dev_lock(hdev);
+
+ hci_dev_clear_flag(hdev, HCI_PA_SYNC);
+
+ conn = hci_conn_hash_lookup_create_pa_sync(hdev);
+ if (!conn) {
+ bt_dev_err(hdev,
+ "Unable to find connection for dst %pMR sid 0x%2.2x",
+ &ev->bdaddr, ev->sid);
+ goto unlock;
+ }
+
+ conn->sync_handle = le16_to_cpu(ev->sync_handle);
+ conn->sid = HCI_SID_INVALID;
+
+ mask |= hci_proto_connect_ind(hdev, &ev->bdaddr, PA_LINK,
+ &flags);
+ if (!(mask & HCI_LM_ACCEPT)) {
+ hci_le_pa_term_sync(hdev, ev->sync_handle);
+ goto unlock;
+ }
+
+ if (!(flags & HCI_PROTO_DEFER))
+ goto unlock;
+
+ /* Add connection to indicate PA sync event */
+ pa_sync = hci_conn_add_unset(hdev, PA_LINK, BDADDR_ANY,
+ HCI_ROLE_SLAVE);
+
+ if (IS_ERR(pa_sync))
+ goto unlock;
+
+ pa_sync->sync_handle = le16_to_cpu(ev->sync_handle);
+
+ if (ev->status) {
+ set_bit(HCI_CONN_PA_SYNC_FAILED, &pa_sync->flags);
+
+ /* Notify iso layer */
+ hci_connect_cfm(pa_sync, ev->status);
+ }
+
+unlock:
+ hci_dev_unlock(hdev);
+}
+
static void hci_le_conn_update_complete_evt(struct hci_dev *hdev, void *data,
struct sk_buff *skb)
{
@@ -6412,16 +6477,6 @@ static void hci_le_ext_adv_report_evt(struct hci_dev *hdev, void *data,
hci_dev_unlock(hdev);
}
-static int hci_le_pa_term_sync(struct hci_dev *hdev, __le16 handle)
-{
- struct hci_cp_le_pa_term_sync cp;
-
- memset(&cp, 0, sizeof(cp));
- cp.handle = handle;
-
- return hci_send_cmd(hdev, HCI_OP_LE_PA_TERM_SYNC, sizeof(cp), &cp);
-}
-
static void hci_le_pa_sync_established_evt(struct hci_dev *hdev, void *data,
struct sk_buff *skb)
{
@@ -7206,6 +7261,10 @@ static const struct hci_le_ev {
/* [0x12 = HCI_EV_LE_EXT_ADV_SET_TERM] */
HCI_LE_EV(HCI_EV_LE_EXT_ADV_SET_TERM, hci_le_ext_adv_term_evt,
sizeof(struct hci_evt_le_ext_adv_set_term)),
+ /* [0x18 = HCI_EVT_LE_PAST_RECEIVED] */
+ HCI_LE_EV(HCI_EV_LE_PAST_RECEIVED,
+ hci_le_past_received_evt,
+ sizeof(struct hci_ev_le_past_received)),
/* [0x19 = HCI_EVT_LE_CIS_ESTABLISHED] */
HCI_LE_EV(HCI_EVT_LE_CIS_ESTABLISHED, hci_le_cis_established_evt,
sizeof(struct hci_evt_le_cis_established)),
diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c
index e302afc781a5..d181c89a7b0e 100644
--- a/net/bluetooth/hci_sync.c
+++ b/net/bluetooth/hci_sync.c
@@ -4393,6 +4393,9 @@ static int hci_le_set_event_mask_sync(struct hci_dev *hdev)
if (ext_adv_capable(hdev))
events[2] |= 0x02; /* LE Advertising Set Terminated */
+ if (past_receiver_capable(hdev))
+ events[2] |= 0x80; /* LE PAST Received */
+
if (cis_capable(hdev)) {
events[3] |= 0x01; /* LE CIS Established */
if (cis_peripheral_capable(hdev))
diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c
index c0ae6c6fbe94..3c568d3d8ac9 100644
--- a/net/bluetooth/iso.c
+++ b/net/bluetooth/iso.c
@@ -80,6 +80,7 @@ static struct bt_iso_qos default_qos;
static bool check_ucast_qos(struct bt_iso_qos *qos);
static bool check_bcast_qos(struct bt_iso_qos *qos);
static bool iso_match_sid(struct sock *sk, void *data);
+static bool iso_match_sid_past(struct sock *sk, void *data);
static bool iso_match_sync_handle(struct sock *sk, void *data);
static bool iso_match_sync_handle_pa_report(struct sock *sk, void *data);
static void iso_sock_disconn(struct sock *sk);
@@ -2090,6 +2091,16 @@ static bool iso_match_sid(struct sock *sk, void *data)
return ev->sid == iso_pi(sk)->bc_sid;
}
+static bool iso_match_sid_past(struct sock *sk, void *data)
+{
+ struct hci_ev_le_past_received *ev = data;
+
+ if (iso_pi(sk)->bc_sid == HCI_SID_INVALID)
+ return true;
+
+ return ev->sid == iso_pi(sk)->bc_sid;
+}
+
static bool iso_match_sync_handle(struct sock *sk, void *data)
{
struct hci_evt_le_big_info_adv_report *ev = data;
@@ -2109,6 +2120,7 @@ static bool iso_match_sync_handle_pa_report(struct sock *sk, void *data)
int iso_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 *flags)
{
struct hci_ev_le_pa_sync_established *ev1;
+ struct hci_ev_le_past_received *ev1a;
struct hci_evt_le_big_info_adv_report *ev2;
struct hci_ev_le_per_adv_report *ev3;
struct sock *sk;
@@ -2122,6 +2134,7 @@ int iso_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 *flags)
* SID to listen to and once sync is established its handle needs to
* be stored in iso_pi(sk)->sync_handle so it can be matched once
* receiving the BIG Info.
+ * 1a. HCI_EV_LE_PAST_RECEIVED: alternative to 1.
* 2. HCI_EVT_LE_BIG_INFO_ADV_REPORT: When connect_ind is triggered by a
* a BIG Info it attempts to check if there any listening socket with
* the same sync_handle and if it does then attempt to create a sync.
@@ -2141,6 +2154,18 @@ int iso_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 *flags)
goto done;
}
+ ev1a = hci_recv_event_data(hdev, HCI_EV_LE_PAST_RECEIVED);
+ if (ev1a) {
+ sk = iso_get_sock(&hdev->bdaddr, bdaddr, BT_LISTEN,
+ iso_match_sid_past, ev1a);
+ if (sk && !ev1a->status) {
+ iso_pi(sk)->sync_handle = le16_to_cpu(ev1a->sync_handle);
+ iso_pi(sk)->bc_sid = ev1a->sid;
+ }
+
+ goto done;
+ }
+
ev2 = hci_recv_event_data(hdev, HCI_EVT_LE_BIG_INFO_ADV_REPORT);
if (ev2) {
/* Check if BIGInfo report has already been handled */
diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c
index 6fd13a39f48a..7cca24f6ef49 100644
--- a/net/bluetooth/mgmt.c
+++ b/net/bluetooth/mgmt.c
@@ -852,6 +852,12 @@ static u32 get_supported_settings(struct hci_dev *hdev)
if (ll_privacy_capable(hdev))
settings |= MGMT_SETTING_LL_PRIVACY;
+ if (past_sender_capable(hdev))
+ settings |= MGMT_SETTING_PAST_SENDER;
+
+ if (past_receiver_capable(hdev))
+ settings |= MGMT_SETTING_PAST_RECEIVER;
+
settings |= MGMT_SETTING_PHY_CONFIGURATION;
return settings;
@@ -937,6 +943,12 @@ static u32 get_current_settings(struct hci_dev *hdev)
if (ll_privacy_enabled(hdev))
settings |= MGMT_SETTING_LL_PRIVACY;
+ if (past_sender_enabled(hdev))
+ settings |= MGMT_SETTING_PAST_SENDER;
+
+ if (past_receiver_enabled(hdev))
+ settings |= MGMT_SETTING_PAST_RECEIVER;
+
return settings;
}
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,300 @@
From 414511072bcdf8a60a29bf43cca47260006df84b Mon Sep 17 00:00:00 2001
From: David Marlin <dmarlin@redhat.com>
Date: Wed, 15 Apr 2026 17:00:08 -0500
Subject: [PATCH] Bluetooth: ISO: Add support to bind to trigger PAST
JIRA: https://issues.redhat.com/browse/RHEL-157827
commit d3413703d5f8b7d1e6f514f9440ed5da1bc30796
Author: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Date: Fri Sep 5 11:34:44 2025 -0400
Bluetooth: ISO: Add support to bind to trigger PAST
This makes it possible to bind to a different destination address
after being connected (BT_CONNECTED, BT_CONNECT2) which then triggers
PAST Sender proceedure to transfer the PA Sync to the destination
address.
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Conflicts:
Context difference due to missing network commit:
commit 0e50474fa514 net: Convert proto_ops bind() callbacks to use sockaddr_unsized
Signed-off-by: David Marlin <dmarlin@redhat.com>
diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
index 8c2235444808..1f74722f3f4d 100644
--- a/include/net/bluetooth/hci_core.h
+++ b/include/net/bluetooth/hci_core.h
@@ -1602,6 +1602,7 @@ struct hci_conn *hci_bind_cis(struct hci_dev *hdev, bdaddr_t *dst,
struct hci_conn *hci_bind_bis(struct hci_dev *hdev, bdaddr_t *dst, __u8 sid,
struct bt_iso_qos *qos,
__u8 base_len, __u8 *base, u16 timeout);
+int hci_past_bis(struct hci_conn *conn, bdaddr_t *dst, __u8 dst_type);
struct hci_conn *hci_connect_cis(struct hci_dev *hdev, bdaddr_t *dst,
__u8 dst_type, struct bt_iso_qos *qos,
u16 timeout);
diff --git a/include/net/bluetooth/hci_sync.h b/include/net/bluetooth/hci_sync.h
index e352a4e0ef8d..3133f40fa9f9 100644
--- a/include/net/bluetooth/hci_sync.h
+++ b/include/net/bluetooth/hci_sync.h
@@ -188,3 +188,4 @@ int hci_le_conn_update_sync(struct hci_dev *hdev, struct hci_conn *conn,
int hci_connect_pa_sync(struct hci_dev *hdev, struct hci_conn *conn);
int hci_connect_big_sync(struct hci_dev *hdev, struct hci_conn *conn);
+int hci_past_sync(struct hci_conn *conn, struct hci_conn *le);
diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c
index 6fc0692abf05..4f9dc1435ccc 100644
--- a/net/bluetooth/hci_conn.c
+++ b/net/bluetooth/hci_conn.c
@@ -2245,6 +2245,18 @@ struct hci_conn *hci_bind_bis(struct hci_dev *hdev, bdaddr_t *dst, __u8 sid,
return conn;
}
+int hci_past_bis(struct hci_conn *conn, bdaddr_t *dst, __u8 dst_type)
+{
+ struct hci_conn *le;
+
+ /* Lookup existing LE connection to rebind to */
+ le = hci_conn_hash_lookup_le(conn->hdev, dst, dst_type);
+ if (!le)
+ return -EINVAL;
+
+ return hci_past_sync(conn, le);
+}
+
static void bis_mark_per_adv(struct hci_conn *conn, void *data)
{
struct iso_list_data *d = data;
diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c
index cb573b88505f..13d34c97b4d3 100644
--- a/net/bluetooth/hci_sync.c
+++ b/net/bluetooth/hci_sync.c
@@ -7229,3 +7229,95 @@ int hci_connect_big_sync(struct hci_dev *hdev, struct hci_conn *conn)
return hci_cmd_sync_queue_once(hdev, hci_le_big_create_sync, conn,
create_big_complete);
}
+
+struct past_data {
+ struct hci_conn *conn;
+ struct hci_conn *le;
+};
+
+static void past_complete(struct hci_dev *hdev, void *data, int err)
+{
+ struct past_data *past = data;
+
+ bt_dev_dbg(hdev, "err %d", err);
+
+ kfree(past);
+}
+
+static int hci_le_past_set_info_sync(struct hci_dev *hdev, void *data)
+{
+ struct past_data *past = data;
+ struct hci_cp_le_past_set_info cp;
+
+ hci_dev_lock(hdev);
+
+ if (!hci_conn_valid(hdev, past->conn) ||
+ !hci_conn_valid(hdev, past->le)) {
+ hci_dev_unlock(hdev);
+ return -ECANCELED;
+ }
+
+ memset(&cp, 0, sizeof(cp));
+ cp.handle = cpu_to_le16(past->le->handle);
+ cp.adv_handle = past->conn->iso_qos.bcast.bis;
+
+ hci_dev_unlock(hdev);
+
+ return __hci_cmd_sync_status(hdev, HCI_OP_LE_PAST_SET_INFO,
+ sizeof(cp), &cp, HCI_CMD_TIMEOUT);
+}
+
+static int hci_le_past_sync(struct hci_dev *hdev, void *data)
+{
+ struct past_data *past = data;
+ struct hci_cp_le_past cp;
+
+ hci_dev_lock(hdev);
+
+ if (!hci_conn_valid(hdev, past->conn) ||
+ !hci_conn_valid(hdev, past->le)) {
+ hci_dev_unlock(hdev);
+ return -ECANCELED;
+ }
+
+ memset(&cp, 0, sizeof(cp));
+ cp.handle = cpu_to_le16(past->le->handle);
+ cp.sync_handle = cpu_to_le16(past->conn->sync_handle);
+
+ hci_dev_unlock(hdev);
+
+ return __hci_cmd_sync_status(hdev, HCI_OP_LE_PAST,
+ sizeof(cp), &cp, HCI_CMD_TIMEOUT);
+}
+
+int hci_past_sync(struct hci_conn *conn, struct hci_conn *le)
+{
+ struct past_data *data;
+ int err;
+
+ if (conn->type != BIS_LINK && conn->type != PA_LINK)
+ return -EINVAL;
+
+ if (!past_sender_capable(conn->hdev))
+ return -EOPNOTSUPP;
+
+ data = kmalloc(sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->conn = conn;
+ data->le = le;
+
+ if (conn->role == HCI_ROLE_MASTER)
+ err = hci_cmd_sync_queue_once(conn->hdev,
+ hci_le_past_set_info_sync, data,
+ past_complete);
+ else
+ err = hci_cmd_sync_queue_once(conn->hdev, hci_le_past_sync,
+ data, past_complete);
+
+ if (err)
+ kfree(data);
+
+ return err;
+}
diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c
index 3c568d3d8ac9..607172b35267 100644
--- a/net/bluetooth/iso.c
+++ b/net/bluetooth/iso.c
@@ -987,20 +987,14 @@ static int iso_sock_bind_bc(struct socket *sock, struct sockaddr *addr,
return 0;
}
-static int iso_sock_bind_pa_sk(struct sock *sk, struct sockaddr_iso *sa,
+/* Must be called on the locked socket. */
+static int iso_sock_rebind_bis(struct sock *sk, struct sockaddr_iso *sa,
int addr_len)
{
int err = 0;
- if (sk->sk_type != SOCK_SEQPACKET) {
- err = -EINVAL;
- goto done;
- }
-
- if (addr_len != sizeof(*sa) + sizeof(*sa->iso_bc)) {
- err = -EINVAL;
- goto done;
- }
+ if (!test_bit(BT_SK_PA_SYNC, &iso_pi(sk)->flags))
+ return -EBADFD;
if (sa->iso_bc->bc_num_bis > ISO_MAX_NUM_BIS) {
err = -EINVAL;
@@ -1023,6 +1017,77 @@ static int iso_sock_bind_pa_sk(struct sock *sk, struct sockaddr_iso *sa,
return err;
}
+static struct hci_dev *iso_conn_get_hdev(struct iso_conn *conn)
+{
+ struct hci_dev *hdev = NULL;
+
+ iso_conn_lock(conn);
+ if (conn->hcon)
+ hdev = hci_dev_hold(conn->hcon->hdev);
+ iso_conn_unlock(conn);
+
+ return hdev;
+}
+
+/* Must be called on the locked socket. */
+static int iso_sock_rebind_bc(struct sock *sk, struct sockaddr_iso *sa,
+ int addr_len)
+{
+ struct hci_dev *hdev;
+ struct hci_conn *bis;
+ int err;
+
+ if (sk->sk_type != SOCK_SEQPACKET || !iso_pi(sk)->conn)
+ return -EINVAL;
+
+ /* Check if it is really a Broadcast address being requested */
+ if (addr_len != sizeof(*sa) + sizeof(*sa->iso_bc))
+ return -EINVAL;
+
+ /* Check if the address hasn't changed then perhaps only the number of
+ * bis has changed.
+ */
+ if (!bacmp(&iso_pi(sk)->dst, &sa->iso_bc->bc_bdaddr) ||
+ !bacmp(&sa->iso_bc->bc_bdaddr, BDADDR_ANY))
+ return iso_sock_rebind_bis(sk, sa, addr_len);
+
+ /* Check if the address type is of LE type */
+ if (!bdaddr_type_is_le(sa->iso_bc->bc_bdaddr_type))
+ return -EINVAL;
+
+ hdev = iso_conn_get_hdev(iso_pi(sk)->conn);
+ if (!hdev)
+ return -EINVAL;
+
+ bis = iso_pi(sk)->conn->hcon;
+
+ /* Release the socket before lookups since that requires hci_dev_lock
+ * which shall not be acquired while holding sock_lock for proper
+ * ordering.
+ */
+ release_sock(sk);
+ hci_dev_lock(bis->hdev);
+ lock_sock(sk);
+
+ if (!iso_pi(sk)->conn || iso_pi(sk)->conn->hcon != bis) {
+ /* raced with iso_conn_del() or iso_disconn_sock() */
+ err = -ENOTCONN;
+ goto unlock;
+ }
+
+ BT_DBG("sk %p %pMR type %u", sk, &sa->iso_bc->bc_bdaddr,
+ sa->iso_bc->bc_bdaddr_type);
+
+ err = hci_past_bis(bis, &sa->iso_bc->bc_bdaddr,
+ le_addr_type(sa->iso_bc->bc_bdaddr_type));
+
+unlock:
+ hci_dev_unlock(hdev);
+ hci_dev_put(hdev);
+
+ return err;
+}
+
static int iso_sock_bind(struct socket *sock, struct sockaddr *addr,
int addr_len)
{
@@ -1038,13 +1103,12 @@ static int iso_sock_bind(struct socket *sock, struct sockaddr *addr,
lock_sock(sk);
- /* Allow the user to bind a PA sync socket to a number
- * of BISes to sync to.
- */
- if ((sk->sk_state == BT_CONNECT2 ||
- sk->sk_state == BT_CONNECTED) &&
- test_bit(BT_SK_PA_SYNC, &iso_pi(sk)->flags)) {
- err = iso_sock_bind_pa_sk(sk, sa, addr_len);
+ if ((sk->sk_state == BT_CONNECT2 || sk->sk_state == BT_CONNECTED) &&
+ addr_len > sizeof(*sa)) {
+ /* Allow the user to rebind to a different address using
+ * PAST procedures.
+ */
+ err = iso_sock_rebind_bc(sk, sa, addr_len);
goto done;
}
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,437 @@
From 390744e4031283550f4dd646cff099d5952d3683 Mon Sep 17 00:00:00 2001
From: David Marlin <dmarlin@redhat.com>
Date: Wed, 15 Apr 2026 17:01:26 -0500
Subject: [PATCH] Bluetooth: HCI: Add support for LL Extended Feature Set
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
JIRA: https://issues.redhat.com/browse/RHEL-157827
commit a106e50be74b0896583f4d010a69f9806e4194f4
Author: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Date: Fri Nov 14 09:29:28 2025 -0500
Bluetooth: HCI: Add support for LL Extended Feature Set
This adds support for emulating LL Extended Feature Set introduced in 6.0
that adds the following:
Commands:
- HCI_LE_Read_All_Local_Supported_­Features(0x2087)(Feature:47,1)
- HCI_LE_Read_All_Remote_Features(0x2088)(Feature:47,2)
Events:
- HCI_LE_Read_All_Remote_Features_Complete(0x2b)(Mask bit:42)
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Signed-off-by: David Marlin <dmarlin@redhat.com>
diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h
index d883ad233ebc..a27cd3626b87 100644
--- a/include/net/bluetooth/hci.h
+++ b/include/net/bluetooth/hci.h
@@ -653,6 +653,7 @@ enum {
#define HCI_LE_CIS_PERIPHERAL 0x20
#define HCI_LE_ISO_BROADCASTER 0x40
#define HCI_LE_ISO_SYNC_RECEIVER 0x80
+#define HCI_LE_LL_EXT_FEATURE 0x80
/* Connection modes */
#define HCI_CM_ACTIVE 0x0000
@@ -2255,6 +2256,19 @@ struct hci_cp_le_set_host_feature {
__u8 bit_value;
} __packed;
+#define HCI_OP_LE_READ_ALL_LOCAL_FEATURES 0x2087
+struct hci_rp_le_read_all_local_features {
+ __u8 status;
+ __u8 page;
+ __u8 features[248];
+} __packed;
+
+#define HCI_OP_LE_READ_ALL_REMOTE_FEATURES 0x2088
+struct hci_cp_le_read_all_remote_features {
+ __le16 handle;
+ __u8 pages;
+} __packed;
+
/* ---- HCI Events ---- */
struct hci_ev_status {
__u8 status;
@@ -2937,6 +2951,15 @@ struct hci_evt_le_big_info_adv_report {
__u8 encryption;
} __packed;
+#define HCI_EVT_LE_ALL_REMOTE_FEATURES_COMPLETE 0x2b
+struct hci_evt_le_read_all_remote_features_complete {
+ __u8 status;
+ __le16 handle;
+ __u8 max_pages;
+ __u8 valid_pages;
+ __u8 features[248];
+} __packed;
+
#define HCI_EV_VENDOR 0xff
/* Internal events generated by Bluetooth stack */
diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
index 858b58206e80..4263e71a23ef 100644
--- a/include/net/bluetooth/hci_core.h
+++ b/include/net/bluetooth/hci_core.h
@@ -378,7 +378,7 @@ struct hci_dev {
__u8 minor_class;
__u8 max_page;
__u8 features[HCI_MAX_PAGES][8];
- __u8 le_features[8];
+ __u8 le_features[248];
__u8 le_accept_list_size;
__u8 le_resolv_list_size;
__u8 le_num_of_adv_sets;
@@ -702,6 +702,7 @@ struct hci_conn {
__u8 attempt;
__u8 dev_class[3];
__u8 features[HCI_MAX_PAGES][8];
+ __u8 le_features[248];
__u16 pkt_type;
__u16 link_policy;
__u8 key_type;
@@ -2067,6 +2068,8 @@ void hci_conn_del_sysfs(struct hci_conn *conn);
(le_enabled(dev) && past_receiver_capable(dev))
#define past_enabled(dev) \
(past_sender_enabled(dev) || past_receiver_enabled(dev))
+#define ll_ext_feature_capable(dev) \
+ ((dev)->le_features[7] & HCI_LE_LL_EXT_FEATURE)
#define mws_transport_config_capable(dev) (((dev)->commands[30] & 0x08) && \
(!hci_test_quirk((dev), HCI_QUIRK_BROKEN_MWS_TRANSPORT_CONFIG)))
diff --git a/include/net/bluetooth/hci_sync.h b/include/net/bluetooth/hci_sync.h
index 3133f40fa9f9..56076bbc981d 100644
--- a/include/net/bluetooth/hci_sync.h
+++ b/include/net/bluetooth/hci_sync.h
@@ -189,3 +189,5 @@ int hci_le_conn_update_sync(struct hci_dev *hdev, struct hci_conn *conn,
int hci_connect_pa_sync(struct hci_dev *hdev, struct hci_conn *conn);
int hci_connect_big_sync(struct hci_dev *hdev, struct hci_conn *conn);
int hci_past_sync(struct hci_conn *conn, struct hci_conn *le);
+
+int hci_le_read_remote_features(struct hci_conn *conn);
diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c
index 7c4ca14f13e5..a9868f17ef40 100644
--- a/net/bluetooth/hci_event.c
+++ b/net/bluetooth/hci_event.c
@@ -2886,12 +2886,8 @@ static void hci_cs_le_read_remote_features(struct hci_dev *hdev, u8 status)
hci_dev_lock(hdev);
conn = hci_conn_hash_lookup_handle(hdev, __le16_to_cpu(cp->handle));
- if (conn) {
- if (conn->state == BT_CONFIG) {
- hci_connect_cfm(conn, status);
- hci_conn_drop(conn);
- }
- }
+ if (conn && conn->state == BT_CONFIG)
+ hci_connect_cfm(conn, status);
hci_dev_unlock(hdev);
}
@@ -3915,11 +3911,49 @@ static u8 hci_cc_le_setup_iso_path(struct hci_dev *hdev, void *data,
return rp->status;
}
+static u8 hci_cc_le_read_all_local_features(struct hci_dev *hdev, void *data,
+ struct sk_buff *skb)
+{
+ struct hci_rp_le_read_all_local_features *rp = data;
+
+ bt_dev_dbg(hdev, "status 0x%2.2x", rp->status);
+
+ if (rp->status)
+ return rp->status;
+
+ memcpy(hdev->le_features, rp->features, 248);
+
+ return rp->status;
+}
+
static void hci_cs_le_create_big(struct hci_dev *hdev, u8 status)
{
bt_dev_dbg(hdev, "status 0x%2.2x", status);
}
+static void hci_cs_le_read_all_remote_features(struct hci_dev *hdev, u8 status)
+{
+ struct hci_cp_le_read_remote_features *cp;
+ struct hci_conn *conn;
+
+ bt_dev_dbg(hdev, "status 0x%2.2x", status);
+
+ if (!status)
+ return;
+
+ cp = hci_sent_cmd_data(hdev, HCI_OP_LE_READ_ALL_REMOTE_FEATURES);
+ if (!cp)
+ return;
+
+ hci_dev_lock(hdev);
+
+ conn = hci_conn_hash_lookup_handle(hdev, __le16_to_cpu(cp->handle));
+ if (conn && conn->state == BT_CONFIG)
+ hci_connect_cfm(conn, status);
+
+ hci_dev_unlock(hdev);
+}
+
static u8 hci_cc_set_per_adv_param(struct hci_dev *hdev, void *data,
struct sk_buff *skb)
{
@@ -4171,6 +4205,9 @@ static const struct hci_cc {
sizeof(struct hci_rp_le_set_cig_params), HCI_MAX_EVENT_SIZE),
HCI_CC(HCI_OP_LE_SETUP_ISO_PATH, hci_cc_le_setup_iso_path,
sizeof(struct hci_rp_le_setup_iso_path)),
+ HCI_CC(HCI_OP_LE_READ_ALL_LOCAL_FEATURES,
+ hci_cc_le_read_all_local_features,
+ sizeof(struct hci_rp_le_read_all_local_features)),
};
static u8 hci_cc_func(struct hci_dev *hdev, const struct hci_cc *cc,
@@ -4325,6 +4362,8 @@ static const struct hci_cs {
HCI_CS(HCI_OP_LE_EXT_CREATE_CONN, hci_cs_le_ext_create_conn),
HCI_CS(HCI_OP_LE_CREATE_CIS, hci_cs_le_create_cis),
HCI_CS(HCI_OP_LE_CREATE_BIG, hci_cs_le_create_big),
+ HCI_CS(HCI_OP_LE_READ_ALL_REMOTE_FEATURES,
+ hci_cs_le_read_all_remote_features),
};
static void hci_cmd_status_evt(struct hci_dev *hdev, void *data,
@@ -5645,6 +5684,7 @@ static void le_conn_complete_evt(struct hci_dev *hdev, u8 status,
struct hci_conn *conn;
struct smp_irk *irk;
u8 addr_type;
+ int err;
hci_dev_lock(hdev);
@@ -5775,26 +5815,8 @@ static void le_conn_complete_evt(struct hci_dev *hdev, u8 status,
hci_debugfs_create_conn(conn);
hci_conn_add_sysfs(conn);
- /* The remote features procedure is defined for central
- * role only. So only in case of an initiated connection
- * request the remote features.
- *
- * If the local controller supports peripheral-initiated features
- * exchange, then requesting the remote features in peripheral
- * role is possible. Otherwise just transition into the
- * connected state without requesting the remote features.
- */
- if (conn->out ||
- (hdev->le_features[0] & HCI_LE_PERIPHERAL_FEATURES)) {
- struct hci_cp_le_read_remote_features cp;
-
- cp.handle = __cpu_to_le16(conn->handle);
-
- hci_send_cmd(hdev, HCI_OP_LE_READ_REMOTE_FEATURES,
- sizeof(cp), &cp);
-
- hci_conn_hold(conn);
- } else {
+ err = hci_le_read_remote_features(conn);
+ if (err) {
conn->state = BT_CONNECTED;
hci_connect_cfm(conn, status);
}
@@ -6608,7 +6630,6 @@ static void hci_le_remote_feat_complete_evt(struct hci_dev *hdev, void *data,
conn->state = BT_CONNECTED;
hci_connect_cfm(conn, status);
- hci_conn_drop(conn);
}
}
@@ -7186,6 +7207,50 @@ static void hci_le_big_info_adv_report_evt(struct hci_dev *hdev, void *data,
hci_dev_unlock(hdev);
}
+static void hci_le_read_all_remote_features_evt(struct hci_dev *hdev,
+ void *data, struct sk_buff *skb)
+{
+ struct hci_evt_le_read_all_remote_features_complete *ev = data;
+ struct hci_conn *conn;
+
+ bt_dev_dbg(hdev, "status 0x%2.2x", ev->status);
+
+ hci_dev_lock(hdev);
+
+ conn = hci_conn_hash_lookup_handle(hdev, __le16_to_cpu(ev->handle));
+ if (!conn)
+ goto unlock;
+
+ if (!ev->status)
+ memcpy(conn->le_features, ev->features, 248);
+
+ if (conn->state == BT_CONFIG) {
+ __u8 status;
+
+ /* If the local controller supports peripheral-initiated
+ * features exchange, but the remote controller does
+ * not, then it is possible that the error code 0x1a
+ * for unsupported remote feature gets returned.
+ *
+ * In this specific case, allow the connection to
+ * transition into connected state and mark it as
+ * successful.
+ */
+ if (!conn->out &&
+ ev->status == HCI_ERROR_UNSUPPORTED_REMOTE_FEATURE &&
+ (hdev->le_features[0] & HCI_LE_PERIPHERAL_FEATURES))
+ status = 0x00;
+ else
+ status = ev->status;
+
+ conn->state = BT_CONNECTED;
+ hci_connect_cfm(conn, status);
+ }
+
+unlock:
+ hci_dev_unlock(hdev);
+}
+
#define HCI_LE_EV_VL(_op, _func, _min_len, _max_len) \
[_op] = { \
.func = _func, \
@@ -7291,6 +7356,12 @@ static const struct hci_le_ev {
hci_le_big_info_adv_report_evt,
sizeof(struct hci_evt_le_big_info_adv_report),
HCI_MAX_EVENT_SIZE),
+ /* [0x2b = HCI_EVT_LE_ALL_REMOTE_FEATURES_COMPLETE] */
+ HCI_LE_EV_VL(HCI_EVT_LE_ALL_REMOTE_FEATURES_COMPLETE,
+ hci_le_read_all_remote_features_evt,
+ sizeof(struct
+ hci_evt_le_read_all_remote_features_complete),
+ HCI_MAX_EVENT_SIZE),
};
static void hci_le_meta_evt(struct hci_dev *hdev, void *data,
diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c
index 742ac6ab9755..13b2e9608fdb 100644
--- a/net/bluetooth/hci_sync.c
+++ b/net/bluetooth/hci_sync.c
@@ -4011,8 +4011,19 @@ static int hci_le_read_buffer_size_sync(struct hci_dev *hdev)
/* Read LE Local Supported Features */
static int hci_le_read_local_features_sync(struct hci_dev *hdev)
{
- return __hci_cmd_sync_status(hdev, HCI_OP_LE_READ_LOCAL_FEATURES,
- 0, NULL, HCI_CMD_TIMEOUT);
+ int err;
+
+ err = __hci_cmd_sync_status(hdev, HCI_OP_LE_READ_LOCAL_FEATURES,
+ 0, NULL, HCI_CMD_TIMEOUT);
+ if (err)
+ return err;
+
+ if (ll_ext_feature_capable(hdev) && hdev->commands[47] & BIT(2))
+ return __hci_cmd_sync_status(hdev,
+ HCI_OP_LE_READ_ALL_LOCAL_FEATURES,
+ 0, NULL, HCI_CMD_TIMEOUT);
+
+ return err;
}
/* Read LE Supported States */
@@ -7321,3 +7332,90 @@ int hci_past_sync(struct hci_conn *conn, struct hci_conn *le)
return err;
}
+
+static void le_read_features_complete(struct hci_dev *hdev, void *data, int err)
+{
+ struct hci_conn *conn = data;
+
+ bt_dev_dbg(hdev, "err %d", err);
+
+ if (err == -ECANCELED)
+ return;
+
+ hci_conn_drop(conn);
+}
+
+static int hci_le_read_all_remote_features_sync(struct hci_dev *hdev,
+ void *data)
+{
+ struct hci_conn *conn = data;
+ struct hci_cp_le_read_all_remote_features cp;
+
+ memset(&cp, 0, sizeof(cp));
+ cp.handle = cpu_to_le16(conn->handle);
+ cp.pages = 10; /* Attempt to read all pages */
+
+ /* Wait for HCI_EVT_LE_ALL_REMOTE_FEATURES_COMPLETE event otherwise
+ * hci_conn_drop may run prematurely causing a disconnection.
+ */
+ return __hci_cmd_sync_status_sk(hdev,
+ HCI_OP_LE_READ_ALL_REMOTE_FEATURES,
+ sizeof(cp), &cp,
+ HCI_EVT_LE_ALL_REMOTE_FEATURES_COMPLETE,
+ HCI_CMD_TIMEOUT, NULL);
+
+ return __hci_cmd_sync_status(hdev, HCI_OP_LE_READ_ALL_REMOTE_FEATURES,
+ sizeof(cp), &cp, HCI_CMD_TIMEOUT);
+}
+
+static int hci_le_read_remote_features_sync(struct hci_dev *hdev, void *data)
+{
+ struct hci_conn *conn = data;
+ struct hci_cp_le_read_remote_features cp;
+
+ if (!hci_conn_valid(hdev, conn))
+ return -ECANCELED;
+
+ /* Check if LL Extended Feature Set is supported and
+ * HCI_OP_LE_READ_ALL_REMOTE_FEATURES is supported then use that to read
+ * all features.
+ */
+ if (ll_ext_feature_capable(hdev) && hdev->commands[47] & BIT(3))
+ return hci_le_read_all_remote_features_sync(hdev, data);
+
+ memset(&cp, 0, sizeof(cp));
+ cp.handle = cpu_to_le16(conn->handle);
+
+ /* Wait for HCI_EV_LE_REMOTE_FEAT_COMPLETE event otherwise
+ * hci_conn_drop may run prematurely causing a disconnection.
+ */
+ return __hci_cmd_sync_status_sk(hdev, HCI_OP_LE_READ_REMOTE_FEATURES,
+ sizeof(cp), &cp,
+ HCI_EV_LE_REMOTE_FEAT_COMPLETE,
+ HCI_CMD_TIMEOUT, NULL);
+}
+
+int hci_le_read_remote_features(struct hci_conn *conn)
+{
+ struct hci_dev *hdev = conn->hdev;
+ int err;
+
+ /* The remote features procedure is defined for central
+ * role only. So only in case of an initiated connection
+ * request the remote features.
+ *
+ * If the local controller supports peripheral-initiated features
+ * exchange, then requesting the remote features in peripheral
+ * role is possible. Otherwise just transition into the
+ * connected state without requesting the remote features.
+ */
+ if (conn->out || (hdev->le_features[0] & HCI_LE_PERIPHERAL_FEATURES))
+ err = hci_cmd_sync_queue_once(hdev,
+ hci_le_read_remote_features_sync,
+ hci_conn_hold(conn),
+ le_read_features_complete);
+ else
+ err = -EOPNOTSUPP;
+
+ return err;
+}
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,136 @@
From 8c647364b6263e4d076a7c96f55989b5c0be9598 Mon Sep 17 00:00:00 2001
From: David Marlin <dmarlin@redhat.com>
Date: Wed, 15 Apr 2026 19:43:05 -0500
Subject: [PATCH] Bluetooth: hci_conn: Fix using conn->le_{tx,rx}_phy as
supported PHYs
JIRA: https://issues.redhat.com/browse/RHEL-157827
commit 129d1ef3c5e60d51678e6359beaba85771a49e46
Author: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Date: Wed Dec 10 11:38:08 2025 -0500
Bluetooth: hci_conn: Fix using conn->le_{tx,rx}_phy as supported PHYs
conn->le_{tx,rx}_phy is not actually a bitfield as it set by
HCI_EV_LE_PHY_UPDATE_COMPLETE it is actually correspond to the current
PHY in use not what is supported by the controller, so this introduces
different fields (conn->le_{tx,rx}_def_phys) to track what PHYs are
supported by the connection.
Fixes: eab2404ba798 ("Bluetooth: Add BT_PHY socket option")
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Signed-off-by: David Marlin <dmarlin@redhat.com>
diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
index 4263e71a23ef..8aadf4cdead2 100644
--- a/include/net/bluetooth/hci_core.h
+++ b/include/net/bluetooth/hci_core.h
@@ -730,6 +730,8 @@ struct hci_conn {
__u16 le_per_adv_data_offset;
__u8 le_adv_phy;
__u8 le_adv_sec_phy;
+ __u8 le_tx_def_phys;
+ __u8 le_rx_def_phys;
__u8 le_tx_phy;
__u8 le_rx_phy;
__s8 rssi;
diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c
index c3f7828bf9d5..5a4374ccf8e8 100644
--- a/net/bluetooth/hci_conn.c
+++ b/net/bluetooth/hci_conn.c
@@ -1008,6 +1008,11 @@ static struct hci_conn *__hci_conn_add(struct hci_dev *hdev, int type,
/* conn->src should reflect the local identity address */
hci_copy_identity_address(hdev, &conn->src, &conn->src_type);
conn->mtu = hdev->le_mtu ? hdev->le_mtu : hdev->acl_mtu;
+ /* Use the controller supported PHYS as default until the
+ * remote features are resolved.
+ */
+ conn->le_tx_def_phys = hdev->le_tx_def_phys;
+ conn->le_rx_def_phys = hdev->le_tx_def_phys;
break;
case CIS_LINK:
/* conn->src should reflect the local identity address */
@@ -2928,22 +2933,22 @@ u32 hci_conn_get_phy(struct hci_conn *conn)
break;
case LE_LINK:
- if (conn->le_tx_phy & HCI_LE_SET_PHY_1M)
+ if (conn->le_tx_def_phys & HCI_LE_SET_PHY_1M)
phys |= BT_PHY_LE_1M_TX;
- if (conn->le_rx_phy & HCI_LE_SET_PHY_1M)
+ if (conn->le_rx_def_phys & HCI_LE_SET_PHY_1M)
phys |= BT_PHY_LE_1M_RX;
- if (conn->le_tx_phy & HCI_LE_SET_PHY_2M)
+ if (conn->le_tx_def_phys & HCI_LE_SET_PHY_2M)
phys |= BT_PHY_LE_2M_TX;
- if (conn->le_rx_phy & HCI_LE_SET_PHY_2M)
+ if (conn->le_rx_def_phys & HCI_LE_SET_PHY_2M)
phys |= BT_PHY_LE_2M_RX;
- if (conn->le_tx_phy & HCI_LE_SET_PHY_CODED)
+ if (conn->le_tx_def_phys & HCI_LE_SET_PHY_CODED)
phys |= BT_PHY_LE_CODED_TX;
- if (conn->le_rx_phy & HCI_LE_SET_PHY_CODED)
+ if (conn->le_rx_def_phys & HCI_LE_SET_PHY_CODED)
phys |= BT_PHY_LE_CODED_RX;
break;
diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c
index a9868f17ef40..58075bf72055 100644
--- a/net/bluetooth/hci_event.c
+++ b/net/bluetooth/hci_event.c
@@ -6607,8 +6607,20 @@ static void hci_le_remote_feat_complete_evt(struct hci_dev *hdev, void *data,
conn = hci_conn_hash_lookup_handle(hdev, __le16_to_cpu(ev->handle));
if (conn) {
- if (!ev->status)
- memcpy(conn->features[0], ev->features, 8);
+ if (!ev->status) {
+ memcpy(conn->le_features, ev->features, 8);
+
+ /* Update supported PHYs */
+ if (!(conn->le_features[1] & HCI_LE_PHY_2M)) {
+ conn->le_tx_def_phys &= ~HCI_LE_SET_PHY_2M;
+ conn->le_rx_def_phys &= ~HCI_LE_SET_PHY_2M;
+ }
+
+ if (!(conn->le_features[1] & HCI_LE_PHY_CODED)) {
+ conn->le_tx_def_phys &= ~HCI_LE_SET_PHY_CODED;
+ conn->le_rx_def_phys &= ~HCI_LE_SET_PHY_CODED;
+ }
+ }
if (conn->state == BT_CONFIG) {
__u8 status;
@@ -7221,9 +7233,21 @@ static void hci_le_read_all_remote_features_evt(struct hci_dev *hdev,
if (!conn)
goto unlock;
- if (!ev->status)
+ if (!ev->status) {
memcpy(conn->le_features, ev->features, 248);
+ /* Update supported PHYs */
+ if (!(conn->le_features[1] & HCI_LE_PHY_2M)) {
+ conn->le_tx_def_phys &= ~HCI_LE_SET_PHY_2M;
+ conn->le_rx_def_phys &= ~HCI_LE_SET_PHY_2M;
+ }
+
+ if (!(conn->le_features[1] & HCI_LE_PHY_CODED)) {
+ conn->le_tx_def_phys &= ~HCI_LE_SET_PHY_CODED;
+ conn->le_rx_def_phys &= ~HCI_LE_SET_PHY_CODED;
+ }
+ }
+
if (conn->state == BT_CONFIG) {
__u8 status;
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,442 @@
From 81102db981020f3c766c0e7c88bdd2bf7d12ea30 Mon Sep 17 00:00:00 2001
From: David Marlin <dmarlin@redhat.com>
Date: Wed, 15 Apr 2026 22:50:00 -0500
Subject: [PATCH] Bluetooth: L2CAP: Add support for setting BT_PHY
JIRA: https://issues.redhat.com/browse/RHEL-157827
commit 132c0779d4a2d08541519cf04783bca52c6ec85c
Author: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Date: Wed Dec 17 10:50:51 2025 -0500
Bluetooth: L2CAP: Add support for setting BT_PHY
This enables client to use setsockopt(BT_PHY) to set the connection
packet type/PHY:
Example setting BT_PHY_BR_1M_1SLOT:
< HCI Command: Change Conne.. (0x01|0x000f) plen 4
Handle: 1 Address: 00:AA:01:01:00:00 (Intel Corporation)
Packet type: 0x331e
2-DH1 may not be used
3-DH1 may not be used
DM1 may be used
DH1 may be used
2-DH3 may not be used
3-DH3 may not be used
2-DH5 may not be used
3-DH5 may not be used
> HCI Event: Command Status (0x0f) plen 4
Change Connection Packet Type (0x01|0x000f) ncmd 1
Status: Success (0x00)
> HCI Event: Connection Packet Typ.. (0x1d) plen 5
Status: Success (0x00)
Handle: 1 Address: 00:AA:01:01:00:00 (Intel Corporation)
Packet type: 0x331e
2-DH1 may not be used
3-DH1 may not be used
DM1 may be used
DH1 may be used
2-DH3 may not be used
3-DH3 may not be used
2-DH5 may not be used
Example setting BT_PHY_LE_1M_TX and BT_PHY_LE_1M_RX:
< HCI Command: LE Set PHY (0x08|0x0032) plen 7
Handle: 1 Address: 00:AA:01:01:00:00 (Intel Corporation)
All PHYs preference: 0x00
TX PHYs preference: 0x01
LE 1M
RX PHYs preference: 0x01
LE 1M
PHY options preference: Reserved (0x0000)
> HCI Event: Command Status (0x0f) plen 4
LE Set PHY (0x08|0x0032) ncmd 1
Status: Success (0x00)
> HCI Event: LE Meta Event (0x3e) plen 6
LE PHY Update Complete (0x0c)
Status: Success (0x00)
Handle: 1 Address: 00:AA:01:01:00:00 (Intel Corporation)
TX PHY: LE 1M (0x01)
RX PHY: LE 1M (0x01)
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Signed-off-by: David Marlin <dmarlin@redhat.com>
diff --git a/include/net/bluetooth/bluetooth.h b/include/net/bluetooth/bluetooth.h
index d46ed9011ee5..89a60919050b 100644
--- a/include/net/bluetooth/bluetooth.h
+++ b/include/net/bluetooth/bluetooth.h
@@ -130,21 +130,30 @@ struct bt_voice {
#define BT_RCVMTU 13
#define BT_PHY 14
-#define BT_PHY_BR_1M_1SLOT 0x00000001
-#define BT_PHY_BR_1M_3SLOT 0x00000002
-#define BT_PHY_BR_1M_5SLOT 0x00000004
-#define BT_PHY_EDR_2M_1SLOT 0x00000008
-#define BT_PHY_EDR_2M_3SLOT 0x00000010
-#define BT_PHY_EDR_2M_5SLOT 0x00000020
-#define BT_PHY_EDR_3M_1SLOT 0x00000040
-#define BT_PHY_EDR_3M_3SLOT 0x00000080
-#define BT_PHY_EDR_3M_5SLOT 0x00000100
-#define BT_PHY_LE_1M_TX 0x00000200
-#define BT_PHY_LE_1M_RX 0x00000400
-#define BT_PHY_LE_2M_TX 0x00000800
-#define BT_PHY_LE_2M_RX 0x00001000
-#define BT_PHY_LE_CODED_TX 0x00002000
-#define BT_PHY_LE_CODED_RX 0x00004000
+#define BT_PHY_BR_1M_1SLOT BIT(0)
+#define BT_PHY_BR_1M_3SLOT BIT(1)
+#define BT_PHY_BR_1M_5SLOT BIT(2)
+#define BT_PHY_EDR_2M_1SLOT BIT(3)
+#define BT_PHY_EDR_2M_3SLOT BIT(4)
+#define BT_PHY_EDR_2M_5SLOT BIT(5)
+#define BT_PHY_EDR_3M_1SLOT BIT(6)
+#define BT_PHY_EDR_3M_3SLOT BIT(7)
+#define BT_PHY_EDR_3M_5SLOT BIT(8)
+#define BT_PHY_LE_1M_TX BIT(9)
+#define BT_PHY_LE_1M_RX BIT(10)
+#define BT_PHY_LE_2M_TX BIT(11)
+#define BT_PHY_LE_2M_RX BIT(12)
+#define BT_PHY_LE_CODED_TX BIT(13)
+#define BT_PHY_LE_CODED_RX BIT(14)
+
+#define BT_PHY_BREDR_MASK (BT_PHY_BR_1M_1SLOT | BT_PHY_BR_1M_3SLOT | \
+ BT_PHY_BR_1M_5SLOT | BT_PHY_EDR_2M_1SLOT | \
+ BT_PHY_EDR_2M_3SLOT | BT_PHY_EDR_2M_5SLOT | \
+ BT_PHY_EDR_3M_1SLOT | BT_PHY_EDR_3M_3SLOT | \
+ BT_PHY_EDR_3M_5SLOT)
+#define BT_PHY_LE_MASK (BT_PHY_LE_1M_TX | BT_PHY_LE_1M_RX | \
+ BT_PHY_LE_2M_TX | BT_PHY_LE_2M_RX | \
+ BT_PHY_LE_CODED_TX | BT_PHY_LE_CODED_RX)
#define BT_MODE 15
diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h
index 4a731e1bec53..db76c2d1eeaa 100644
--- a/include/net/bluetooth/hci.h
+++ b/include/net/bluetooth/hci.h
@@ -1885,6 +1885,15 @@ struct hci_cp_le_set_default_phy {
#define HCI_LE_SET_PHY_2M 0x02
#define HCI_LE_SET_PHY_CODED 0x04
+#define HCI_OP_LE_SET_PHY 0x2032
+struct hci_cp_le_set_phy {
+ __le16 handle;
+ __u8 all_phys;
+ __u8 tx_phys;
+ __u8 rx_phys;
+ __le16 phy_opts;
+} __packed;
+
#define HCI_OP_LE_SET_EXT_SCAN_PARAMS 0x2041
struct hci_cp_le_set_ext_scan_params {
__u8 own_addr_type;
diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
index a7d5beb01b69..a7bffb908c1e 100644
--- a/include/net/bluetooth/hci_core.h
+++ b/include/net/bluetooth/hci_core.h
@@ -2342,6 +2342,7 @@ void *hci_sent_cmd_data(struct hci_dev *hdev, __u16 opcode);
void *hci_recv_event_data(struct hci_dev *hdev, __u8 event);
u32 hci_conn_get_phy(struct hci_conn *conn);
+int hci_conn_set_phy(struct hci_conn *conn, u32 phys);
/* ----- HCI Sockets ----- */
void hci_send_to_sock(struct hci_dev *hdev, struct sk_buff *skb);
diff --git a/include/net/bluetooth/hci_sync.h b/include/net/bluetooth/hci_sync.h
index 56076bbc981d..73e494b2591d 100644
--- a/include/net/bluetooth/hci_sync.h
+++ b/include/net/bluetooth/hci_sync.h
@@ -191,3 +191,6 @@ int hci_connect_big_sync(struct hci_dev *hdev, struct hci_conn *conn);
int hci_past_sync(struct hci_conn *conn, struct hci_conn *le);
int hci_le_read_remote_features(struct hci_conn *conn);
+
+int hci_acl_change_pkt_type(struct hci_conn *conn, u16 pkt_type);
+int hci_le_set_phy(struct hci_conn *conn, u8 tx_phys, u8 rx_phys);
diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c
index 0f512c2c2fd3..48aaccd35954 100644
--- a/net/bluetooth/hci_conn.c
+++ b/net/bluetooth/hci_conn.c
@@ -2958,6 +2958,111 @@ u32 hci_conn_get_phy(struct hci_conn *conn)
return phys;
}
+static u16 bt_phy_pkt_type(struct hci_conn *conn, u32 phys)
+{
+ u16 pkt_type = conn->pkt_type;
+
+ if (phys & BT_PHY_BR_1M_3SLOT)
+ pkt_type |= HCI_DM3 | HCI_DH3;
+ else
+ pkt_type &= ~(HCI_DM3 | HCI_DH3);
+
+ if (phys & BT_PHY_BR_1M_5SLOT)
+ pkt_type |= HCI_DM5 | HCI_DH5;
+ else
+ pkt_type &= ~(HCI_DM5 | HCI_DH5);
+
+ if (phys & BT_PHY_EDR_2M_1SLOT)
+ pkt_type &= ~HCI_2DH1;
+ else
+ pkt_type |= HCI_2DH1;
+
+ if (phys & BT_PHY_EDR_2M_3SLOT)
+ pkt_type &= ~HCI_2DH3;
+ else
+ pkt_type |= HCI_2DH3;
+
+ if (phys & BT_PHY_EDR_2M_5SLOT)
+ pkt_type &= ~HCI_2DH5;
+ else
+ pkt_type |= HCI_2DH5;
+
+ if (phys & BT_PHY_EDR_3M_1SLOT)
+ pkt_type &= ~HCI_3DH1;
+ else
+ pkt_type |= HCI_3DH1;
+
+ if (phys & BT_PHY_EDR_3M_3SLOT)
+ pkt_type &= ~HCI_3DH3;
+ else
+ pkt_type |= HCI_3DH3;
+
+ if (phys & BT_PHY_EDR_3M_5SLOT)
+ pkt_type &= ~HCI_3DH5;
+ else
+ pkt_type |= HCI_3DH5;
+
+ return pkt_type;
+}
+
+static int bt_phy_le_phy(u32 phys, u8 *tx_phys, u8 *rx_phys)
+{
+ if (!tx_phys || !rx_phys)
+ return -EINVAL;
+
+ *tx_phys = 0;
+ *rx_phys = 0;
+
+ if (phys & BT_PHY_LE_1M_TX)
+ *tx_phys |= HCI_LE_SET_PHY_1M;
+
+ if (phys & BT_PHY_LE_1M_RX)
+ *rx_phys |= HCI_LE_SET_PHY_1M;
+
+ if (phys & BT_PHY_LE_2M_TX)
+ *tx_phys |= HCI_LE_SET_PHY_2M;
+
+ if (phys & BT_PHY_LE_2M_RX)
+ *rx_phys |= HCI_LE_SET_PHY_2M;
+
+ if (phys & BT_PHY_LE_CODED_TX)
+ *tx_phys |= HCI_LE_SET_PHY_CODED;
+
+ if (phys & BT_PHY_LE_CODED_RX)
+ *rx_phys |= HCI_LE_SET_PHY_CODED;
+
+ return 0;
+}
+
+int hci_conn_set_phy(struct hci_conn *conn, u32 phys)
+{
+ u8 tx_phys, rx_phys;
+
+ switch (conn->type) {
+ case SCO_LINK:
+ case ESCO_LINK:
+ return -EINVAL;
+ case ACL_LINK:
+ /* Only allow setting BR/EDR PHYs if link type is ACL */
+ if (phys & ~BT_PHY_BREDR_MASK)
+ return -EINVAL;
+
+ return hci_acl_change_pkt_type(conn,
+ bt_phy_pkt_type(conn, phys));
+ case LE_LINK:
+ /* Only allow setting LE PHYs if link type is LE */
+ if (phys & ~BT_PHY_LE_MASK)
+ return -EINVAL;
+
+ if (bt_phy_le_phy(phys, &tx_phys, &rx_phys))
+ return -EINVAL;
+
+ return hci_le_set_phy(conn, tx_phys, rx_phys);
+ default:
+ return -EINVAL;
+ }
+}
+
static int abort_conn_sync(struct hci_dev *hdev, void *data)
{
struct hci_conn *conn = data;
diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c
index 58075bf72055..467710a42d45 100644
--- a/net/bluetooth/hci_event.c
+++ b/net/bluetooth/hci_event.c
@@ -2869,6 +2869,31 @@ static void hci_cs_le_ext_create_conn(struct hci_dev *hdev, u8 status)
hci_dev_unlock(hdev);
}
+static void hci_cs_le_set_phy(struct hci_dev *hdev, u8 status)
+{
+ struct hci_cp_le_set_phy *cp;
+ struct hci_conn *conn;
+
+ bt_dev_dbg(hdev, "status 0x%2.2x", status);
+
+ if (status)
+ return;
+
+ cp = hci_sent_cmd_data(hdev, HCI_OP_LE_SET_PHY);
+ if (!cp)
+ return;
+
+ hci_dev_lock(hdev);
+
+ conn = hci_conn_hash_lookup_handle(hdev, __le16_to_cpu(cp->handle));
+ if (conn) {
+ conn->le_tx_def_phys = cp->tx_phys;
+ conn->le_rx_def_phys = cp->rx_phys;
+ }
+
+ hci_dev_unlock(hdev);
+}
+
static void hci_cs_le_read_remote_features(struct hci_dev *hdev, u8 status)
{
struct hci_cp_le_read_remote_features *cp;
@@ -4359,6 +4384,7 @@ static const struct hci_cs {
HCI_CS(HCI_OP_LE_CREATE_CONN, hci_cs_le_create_conn),
HCI_CS(HCI_OP_LE_READ_REMOTE_FEATURES, hci_cs_le_read_remote_features),
HCI_CS(HCI_OP_LE_START_ENC, hci_cs_le_start_enc),
+ HCI_CS(HCI_OP_LE_SET_PHY, hci_cs_le_set_phy),
HCI_CS(HCI_OP_LE_EXT_CREATE_CONN, hci_cs_le_ext_create_conn),
HCI_CS(HCI_OP_LE_CREATE_CIS, hci_cs_le_create_cis),
HCI_CS(HCI_OP_LE_CREATE_BIG, hci_cs_le_create_big),
diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c
index c5356a6a3588..4f7f15544393 100644
--- a/net/bluetooth/hci_sync.c
+++ b/net/bluetooth/hci_sync.c
@@ -7448,3 +7448,75 @@ int hci_le_read_remote_features(struct hci_conn *conn)
return err;
}
+
+static void pkt_type_changed(struct hci_dev *hdev, void *data, int err)
+{
+ struct hci_cp_change_conn_ptype *cp = data;
+
+ bt_dev_dbg(hdev, "err %d", err);
+
+ kfree(cp);
+}
+
+static int hci_change_conn_ptype_sync(struct hci_dev *hdev, void *data)
+{
+ struct hci_cp_change_conn_ptype *cp = data;
+
+ return __hci_cmd_sync_status_sk(hdev, HCI_OP_CHANGE_CONN_PTYPE,
+ sizeof(*cp), cp,
+ HCI_EV_PKT_TYPE_CHANGE,
+ HCI_CMD_TIMEOUT, NULL);
+}
+
+int hci_acl_change_pkt_type(struct hci_conn *conn, u16 pkt_type)
+{
+ struct hci_dev *hdev = conn->hdev;
+ struct hci_cp_change_conn_ptype *cp;
+
+ cp = kmalloc(sizeof(*cp), GFP_KERNEL);
+ if (!cp)
+ return -ENOMEM;
+
+ cp->handle = cpu_to_le16(conn->handle);
+ cp->pkt_type = cpu_to_le16(pkt_type);
+
+ return hci_cmd_sync_queue_once(hdev, hci_change_conn_ptype_sync, cp,
+ pkt_type_changed);
+}
+
+static void le_phy_update_complete(struct hci_dev *hdev, void *data, int err)
+{
+ struct hci_cp_le_set_phy *cp = data;
+
+ bt_dev_dbg(hdev, "err %d", err);
+
+ kfree(cp);
+}
+
+static int hci_le_set_phy_sync(struct hci_dev *hdev, void *data)
+{
+ struct hci_cp_le_set_phy *cp = data;
+
+ return __hci_cmd_sync_status_sk(hdev, HCI_OP_LE_SET_PHY,
+ sizeof(*cp), cp,
+ HCI_EV_LE_PHY_UPDATE_COMPLETE,
+ HCI_CMD_TIMEOUT, NULL);
+}
+
+int hci_le_set_phy(struct hci_conn *conn, u8 tx_phys, u8 rx_phys)
+{
+ struct hci_dev *hdev = conn->hdev;
+ struct hci_cp_le_set_phy *cp;
+
+ cp = kmalloc(sizeof(*cp), GFP_KERNEL);
+ if (!cp)
+ return -ENOMEM;
+
+ memset(cp, 0, sizeof(*cp));
+ cp->handle = cpu_to_le16(conn->handle);
+ cp->tx_phys = tx_phys;
+ cp->rx_phys = rx_phys;
+
+ return hci_cmd_sync_queue_once(hdev, hci_le_set_phy_sync, cp,
+ le_phy_update_complete);
+}
diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
index bca8f9b62fac..82dab62616d0 100644
--- a/net/bluetooth/l2cap_sock.c
+++ b/net/bluetooth/l2cap_sock.c
@@ -885,7 +885,7 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname,
struct bt_power pwr;
struct l2cap_conn *conn;
int err = 0;
- u32 opt;
+ u32 opt, phys;
u16 mtu;
u8 mode;
@@ -1066,6 +1066,24 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname,
break;
+ case BT_PHY:
+ if (sk->sk_state != BT_CONNECTED) {
+ err = -ENOTCONN;
+ break;
+ }
+
+ err = copy_safe_from_sockptr(&phys, sizeof(phys), optval,
+ optlen);
+ if (err)
+ break;
+
+ if (!chan->conn)
+ break;
+
+ conn = chan->conn;
+ err = hci_conn_set_phy(conn->hcon, phys);
+ break;
+
case BT_MODE:
if (!enable_ecred) {
err = -ENOPROTOOPT;
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,180 @@
From 73744f2131d220b7c5f7571863c45381764f4559 Mon Sep 17 00:00:00 2001
From: David Marlin <dmarlin@redhat.com>
Date: Wed, 15 Apr 2026 23:57:05 -0500
Subject: [PATCH] Bluetooth: hci_sync: hci_cmd_sync_queue_once() return -EEXIST
if exists
JIRA: https://issues.redhat.com/browse/RHEL-157827
commit 2969554bcfccb5c609f6b6cd4a014933f3a66dd0
Author: Pauli Virtanen <pav@iki.fi>
Date: Wed Mar 25 21:07:43 2026 +0200
Bluetooth: hci_sync: hci_cmd_sync_queue_once() return -EEXIST if exists
hci_cmd_sync_queue_once() needs to indicate whether a queue item was
added, so caller can know if callbacks are called, so it can avoid
leaking resources.
Change the function to return -EEXIST if queue item already exists.
Modify all callsites to handle that.
Signed-off-by: Pauli Virtanen <pav@iki.fi>
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Conflicts: Context difference due to missing tree-wide changes:
bf4afc53b77ae Convert 'alloc_obj' family to use the new default GFP_KERNEL argument
69050f8d6d075 treewide: Replace kmalloc with kmalloc_obj for non-scalar types
Signed-off-by: David Marlin <dmarlin@redhat.com>
diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c
index 4f7f15544393..6291e1ce61f4 100644
--- a/net/bluetooth/hci_sync.c
+++ b/net/bluetooth/hci_sync.c
@@ -780,7 +780,7 @@ int hci_cmd_sync_queue_once(struct hci_dev *hdev, hci_cmd_sync_work_func_t func,
void *data, hci_cmd_sync_work_destroy_t destroy)
{
if (hci_cmd_sync_lookup_entry(hdev, func, data, destroy))
- return 0;
+ return -EEXIST;
return hci_cmd_sync_queue(hdev, func, data, destroy);
}
@@ -3255,6 +3255,8 @@ static int update_passive_scan_sync(struct hci_dev *hdev, void *data)
int hci_update_passive_scan(struct hci_dev *hdev)
{
+ int err;
+
/* Only queue if it would have any effect */
if (!test_bit(HCI_UP, &hdev->flags) ||
test_bit(HCI_INIT, &hdev->flags) ||
@@ -3264,8 +3266,9 @@ int hci_update_passive_scan(struct hci_dev *hdev)
hci_dev_test_flag(hdev, HCI_UNREGISTER))
return 0;
- return hci_cmd_sync_queue_once(hdev, update_passive_scan_sync, NULL,
- NULL);
+ err = hci_cmd_sync_queue_once(hdev, update_passive_scan_sync, NULL,
+ NULL);
+ return (err == -EEXIST) ? 0 : err;
}
int hci_write_sc_support_sync(struct hci_dev *hdev, u8 val)
@@ -6958,8 +6961,11 @@ static int hci_acl_create_conn_sync(struct hci_dev *hdev, void *data)
int hci_connect_acl_sync(struct hci_dev *hdev, struct hci_conn *conn)
{
- return hci_cmd_sync_queue_once(hdev, hci_acl_create_conn_sync, conn,
- NULL);
+ int err;
+
+ err = hci_cmd_sync_queue_once(hdev, hci_acl_create_conn_sync, conn,
+ NULL);
+ return (err == -EEXIST) ? 0 : err;
}
static void create_le_conn_complete(struct hci_dev *hdev, void *data, int err)
@@ -6995,8 +7001,11 @@ static void create_le_conn_complete(struct hci_dev *hdev, void *data, int err)
int hci_connect_le_sync(struct hci_dev *hdev, struct hci_conn *conn)
{
- return hci_cmd_sync_queue_once(hdev, hci_le_create_conn_sync, conn,
- create_le_conn_complete);
+ int err;
+
+ err = hci_cmd_sync_queue_once(hdev, hci_le_create_conn_sync, conn,
+ create_le_conn_complete);
+ return (err == -EEXIST) ? 0 : err;
}
int hci_cancel_connect_sync(struct hci_dev *hdev, struct hci_conn *conn)
@@ -7203,8 +7212,11 @@ static int hci_le_pa_create_sync(struct hci_dev *hdev, void *data)
int hci_connect_pa_sync(struct hci_dev *hdev, struct hci_conn *conn)
{
- return hci_cmd_sync_queue_once(hdev, hci_le_pa_create_sync, conn,
- create_pa_complete);
+ int err;
+
+ err = hci_cmd_sync_queue_once(hdev, hci_le_pa_create_sync, conn,
+ create_pa_complete);
+ return (err == -EEXIST) ? 0 : err;
}
static void create_big_complete(struct hci_dev *hdev, void *data, int err)
@@ -7266,8 +7278,11 @@ static int hci_le_big_create_sync(struct hci_dev *hdev, void *data)
int hci_connect_big_sync(struct hci_dev *hdev, struct hci_conn *conn)
{
- return hci_cmd_sync_queue_once(hdev, hci_le_big_create_sync, conn,
- create_big_complete);
+ int err;
+
+ err = hci_cmd_sync_queue_once(hdev, hci_le_big_create_sync, conn,
+ create_big_complete);
+ return (err == -EEXIST) ? 0 : err;
}
struct past_data {
@@ -7359,7 +7374,7 @@ int hci_past_sync(struct hci_conn *conn, struct hci_conn *le)
if (err)
kfree(data);
- return err;
+ return (err == -EEXIST) ? 0 : err;
}
static void le_read_features_complete(struct hci_dev *hdev, void *data, int err)
@@ -7446,7 +7461,7 @@ int hci_le_read_remote_features(struct hci_conn *conn)
else
err = -EOPNOTSUPP;
- return err;
+ return (err == -EEXIST) ? 0 : err;
}
static void pkt_type_changed(struct hci_dev *hdev, void *data, int err)
@@ -7472,6 +7487,7 @@ int hci_acl_change_pkt_type(struct hci_conn *conn, u16 pkt_type)
{
struct hci_dev *hdev = conn->hdev;
struct hci_cp_change_conn_ptype *cp;
+ int err;
cp = kmalloc(sizeof(*cp), GFP_KERNEL);
if (!cp)
@@ -7480,8 +7496,9 @@ int hci_acl_change_pkt_type(struct hci_conn *conn, u16 pkt_type)
cp->handle = cpu_to_le16(conn->handle);
cp->pkt_type = cpu_to_le16(pkt_type);
- return hci_cmd_sync_queue_once(hdev, hci_change_conn_ptype_sync, cp,
- pkt_type_changed);
+ err = hci_cmd_sync_queue_once(hdev, hci_change_conn_ptype_sync, cp,
+ pkt_type_changed);
+ return (err == -EEXIST) ? 0 : err;
}
static void le_phy_update_complete(struct hci_dev *hdev, void *data, int err)
@@ -7507,6 +7524,7 @@ int hci_le_set_phy(struct hci_conn *conn, u8 tx_phys, u8 rx_phys)
{
struct hci_dev *hdev = conn->hdev;
struct hci_cp_le_set_phy *cp;
+ int err;
cp = kmalloc(sizeof(*cp), GFP_KERNEL);
if (!cp)
@@ -7517,6 +7535,7 @@ int hci_le_set_phy(struct hci_conn *conn, u8 tx_phys, u8 rx_phys)
cp->tx_phys = tx_phys;
cp->rx_phys = rx_phys;
- return hci_cmd_sync_queue_once(hdev, hci_le_set_phy_sync, cp,
- le_phy_update_complete);
+ err = hci_cmd_sync_queue_once(hdev, hci_le_set_phy_sync, cp,
+ le_phy_update_complete);
+ return (err == -EEXIST) ? 0 : err;
}
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,69 @@
From 3eed784fc3a9e5477b2948a4fa33f6ce32c33319 Mon Sep 17 00:00:00 2001
From: David Marlin <dmarlin@redhat.com>
Date: Thu, 16 Apr 2026 00:04:46 -0500
Subject: [PATCH] Bluetooth: hci_sync: fix leaks when hci_cmd_sync_queue_once
fails
JIRA: https://issues.redhat.com/browse/RHEL-157827
commit aca377208e7f7322bf4e107cdec6e7d7e8aa7a88
Author: Pauli Virtanen <pav@iki.fi>
Date: Wed Mar 25 21:07:44 2026 +0200
Bluetooth: hci_sync: fix leaks when hci_cmd_sync_queue_once fails
When hci_cmd_sync_queue_once() returns with error, the destroy callback
will not be called.
Fix leaking references / memory on these failures.
Signed-off-by: Pauli Virtanen <pav@iki.fi>
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Signed-off-by: David Marlin <dmarlin@redhat.com>
diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c
index 6291e1ce61f4..cac7a8078795 100644
--- a/net/bluetooth/hci_sync.c
+++ b/net/bluetooth/hci_sync.c
@@ -7453,13 +7453,16 @@ int hci_le_read_remote_features(struct hci_conn *conn)
* role is possible. Otherwise just transition into the
* connected state without requesting the remote features.
*/
- if (conn->out || (hdev->le_features[0] & HCI_LE_PERIPHERAL_FEATURES))
+ if (conn->out || (hdev->le_features[0] & HCI_LE_PERIPHERAL_FEATURES)) {
err = hci_cmd_sync_queue_once(hdev,
hci_le_read_remote_features_sync,
hci_conn_hold(conn),
le_read_features_complete);
- else
+ if (err)
+ hci_conn_drop(conn);
+ } else {
err = -EOPNOTSUPP;
+ }
return (err == -EEXIST) ? 0 : err;
}
@@ -7498,6 +7501,9 @@ int hci_acl_change_pkt_type(struct hci_conn *conn, u16 pkt_type)
err = hci_cmd_sync_queue_once(hdev, hci_change_conn_ptype_sync, cp,
pkt_type_changed);
+ if (err)
+ kfree(cp);
+
return (err == -EEXIST) ? 0 : err;
}
@@ -7537,5 +7543,8 @@ int hci_le_set_phy(struct hci_conn *conn, u8 tx_phys, u8 rx_phys)
err = hci_cmd_sync_queue_once(hdev, hci_le_set_phy_sync, cp,
le_phy_update_complete);
+ if (err)
+ kfree(cp);
+
return (err == -EEXIST) ? 0 : err;
}
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,243 @@
From b4a37eb54b3d79e24a8ad9cd4a58baecef81189a Mon Sep 17 00:00:00 2001
From: David Marlin <dmarlin@redhat.com>
Date: Thu, 16 Apr 2026 00:04:53 -0500
Subject: [PATCH] Bluetooth: hci_sync: Fix UAF in le_read_features_complete
JIRA: https://issues.redhat.com/browse/RHEL-157827
commit 035c25007c9e698bef3826070ee34bb6d778020c
Author: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Date: Wed Mar 25 11:11:46 2026 -0400
Bluetooth: hci_sync: Fix UAF in le_read_features_complete
This fixes the following backtrace caused by hci_conn being freed
before le_read_features_complete but after
hci_le_read_remote_features_sync so hci_conn_del -> hci_cmd_sync_dequeue
is not able to prevent it:
==================================================================
BUG: KASAN: slab-use-after-free in instrument_atomic_read_write include/linux/instrumented.h:96 [inline]
BUG: KASAN: slab-use-after-free in atomic_dec_and_test include/linux/atomic/atomic-instrumented.h:1383 [inline]
BUG: KASAN: slab-use-after-free in hci_conn_drop include/net/bluetooth/hci_core.h:1688 [inline]
BUG: KASAN: slab-use-after-free in le_read_features_complete+0x5b/0x340 net/bluetooth/hci_sync.c:7344
Write of size 4 at addr ffff8880796b0010 by task kworker/u9:0/52
CPU: 0 UID: 0 PID: 52 Comm: kworker/u9:0 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 10/25/2025
Workqueue: hci0 hci_cmd_sync_work
Call Trace:
<TASK>
__dump_stack lib/dump_stack.c:94 [inline]
dump_stack_lvl+0x116/0x1f0 lib/dump_stack.c:120
print_address_description mm/kasan/report.c:378 [inline]
print_report+0xcd/0x630 mm/kasan/report.c:482
kasan_report+0xe0/0x110 mm/kasan/report.c:595
check_region_inline mm/kasan/generic.c:194 [inline]
kasan_check_range+0x100/0x1b0 mm/kasan/generic.c:200
instrument_atomic_read_write include/linux/instrumented.h:96 [inline]
atomic_dec_and_test include/linux/atomic/atomic-instrumented.h:1383 [inline]
hci_conn_drop include/net/bluetooth/hci_core.h:1688 [inline]
le_read_features_complete+0x5b/0x340 net/bluetooth/hci_sync.c:7344
hci_cmd_sync_work+0x1ff/0x430 net/bluetooth/hci_sync.c:334
process_one_work+0x9ba/0x1b20 kernel/workqueue.c:3257
process_scheduled_works kernel/workqueue.c:3340 [inline]
worker_thread+0x6c8/0xf10 kernel/workqueue.c:3421
kthread+0x3c5/0x780 kernel/kthread.c:463
ret_from_fork+0x983/0xb10 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:246
</TASK>
Allocated by task 5932:
kasan_save_stack+0x33/0x60 mm/kasan/common.c:56
kasan_save_track+0x14/0x30 mm/kasan/common.c:77
poison_kmalloc_redzone mm/kasan/common.c:400 [inline]
__kasan_kmalloc+0xaa/0xb0 mm/kasan/common.c:417
kmalloc_noprof include/linux/slab.h:957 [inline]
kzalloc_noprof include/linux/slab.h:1094 [inline]
__hci_conn_add+0xf8/0x1c70 net/bluetooth/hci_conn.c:963
hci_conn_add_unset+0x76/0x100 net/bluetooth/hci_conn.c:1084
le_conn_complete_evt+0x639/0x1f20 net/bluetooth/hci_event.c:5714
hci_le_enh_conn_complete_evt+0x23d/0x380 net/bluetooth/hci_event.c:5861
hci_le_meta_evt+0x357/0x5e0 net/bluetooth/hci_event.c:7408
hci_event_func net/bluetooth/hci_event.c:7716 [inline]
hci_event_packet+0x685/0x11c0 net/bluetooth/hci_event.c:7773
hci_rx_work+0x2c9/0xeb0 net/bluetooth/hci_core.c:4076
process_one_work+0x9ba/0x1b20 kernel/workqueue.c:3257
process_scheduled_works kernel/workqueue.c:3340 [inline]
worker_thread+0x6c8/0xf10 kernel/workqueue.c:3421
kthread+0x3c5/0x780 kernel/kthread.c:463
ret_from_fork+0x983/0xb10 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:246
Freed by task 5932:
kasan_save_stack+0x33/0x60 mm/kasan/common.c:56
kasan_save_track+0x14/0x30 mm/kasan/common.c:77
__kasan_save_free_info+0x3b/0x60 mm/kasan/generic.c:587
kasan_save_free_info mm/kasan/kasan.h:406 [inline]
poison_slab_object mm/kasan/common.c:252 [inline]
__kasan_slab_free+0x5f/0x80 mm/kasan/common.c:284
kasan_slab_free include/linux/kasan.h:234 [inline]
slab_free_hook mm/slub.c:2540 [inline]
slab_free mm/slub.c:6663 [inline]
kfree+0x2f8/0x6e0 mm/slub.c:6871
device_release+0xa4/0x240 drivers/base/core.c:2565
kobject_cleanup lib/kobject.c:689 [inline]
kobject_release lib/kobject.c:720 [inline]
kref_put include/linux/kref.h:65 [inline]
kobject_put+0x1e7/0x590 lib/kobject.c:737
put_device drivers/base/core.c:3797 [inline]
device_unregister+0x2f/0xc0 drivers/base/core.c:3920
hci_conn_del_sysfs+0xb4/0x180 net/bluetooth/hci_sysfs.c:79
hci_conn_cleanup net/bluetooth/hci_conn.c:173 [inline]
hci_conn_del+0x657/0x1180 net/bluetooth/hci_conn.c:1234
hci_disconn_complete_evt+0x410/0xa00 net/bluetooth/hci_event.c:3451
hci_event_func net/bluetooth/hci_event.c:7719 [inline]
hci_event_packet+0xa10/0x11c0 net/bluetooth/hci_event.c:7773
hci_rx_work+0x2c9/0xeb0 net/bluetooth/hci_core.c:4076
process_one_work+0x9ba/0x1b20 kernel/workqueue.c:3257
process_scheduled_works kernel/workqueue.c:3340 [inline]
worker_thread+0x6c8/0xf10 kernel/workqueue.c:3421
kthread+0x3c5/0x780 kernel/kthread.c:463
ret_from_fork+0x983/0xb10 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:246
The buggy address belongs to the object at ffff8880796b0000
which belongs to the cache kmalloc-8k of size 8192
The buggy address is located 16 bytes inside of
freed 8192-byte region [ffff8880796b0000, ffff8880796b2000)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x796b0
head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
anon flags: 0xfff00000000040(head|node=0|zone=1|lastcpupid=0x7ff)
page_type: f5(slab)
raw: 00fff00000000040 ffff88813ff27280 0000000000000000 0000000000000001
raw: 0000000000000000 0000000000020002 00000000f5000000 0000000000000000
head: 00fff00000000040 ffff88813ff27280 0000000000000000 0000000000000001
head: 0000000000000000 0000000000020002 00000000f5000000 0000000000000000
head: 00fff00000000003 ffffea0001e5ac01 00000000ffffffff 00000000ffffffff
head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000008
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 3, migratetype Unmovable, gfp_mask 0xd2040(__GFP_IO|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 5657, tgid 5657 (dhcpcd-run-hook), ts 79819636908, free_ts 79814310558
set_page_owner include/linux/page_owner.h:32 [inline]
post_alloc_hook+0x1af/0x220 mm/page_alloc.c:1845
prep_new_page mm/page_alloc.c:1853 [inline]
get_page_from_freelist+0xd0b/0x31a0 mm/page_alloc.c:3879
__alloc_frozen_pages_noprof+0x25f/0x2440 mm/page_alloc.c:5183
alloc_pages_mpol+0x1fb/0x550 mm/mempolicy.c:2416
alloc_slab_page mm/slub.c:3075 [inline]
allocate_slab mm/slub.c:3248 [inline]
new_slab+0x2c3/0x430 mm/slub.c:3302
___slab_alloc+0xe18/0x1c90 mm/slub.c:4651
__slab_alloc.constprop.0+0x63/0x110 mm/slub.c:4774
__slab_alloc_node mm/slub.c:4850 [inline]
slab_alloc_node mm/slub.c:5246 [inline]
__kmalloc_cache_noprof+0x477/0x800 mm/slub.c:5766
kmalloc_noprof include/linux/slab.h:957 [inline]
kzalloc_noprof include/linux/slab.h:1094 [inline]
tomoyo_print_bprm security/tomoyo/audit.c:26 [inline]
tomoyo_init_log+0xc8a/0x2140 security/tomoyo/audit.c:264
tomoyo_supervisor+0x302/0x13b0 security/tomoyo/common.c:2198
tomoyo_audit_env_log security/tomoyo/environ.c:36 [inline]
tomoyo_env_perm+0x191/0x200 security/tomoyo/environ.c:63
tomoyo_environ security/tomoyo/domain.c:672 [inline]
tomoyo_find_next_domain+0xec1/0x20b0 security/tomoyo/domain.c:888
tomoyo_bprm_check_security security/tomoyo/tomoyo.c:102 [inline]
tomoyo_bprm_check_security+0x12d/0x1d0 security/tomoyo/tomoyo.c:92
security_bprm_check+0x1b9/0x1e0 security/security.c:794
search_binary_handler fs/exec.c:1659 [inline]
exec_binprm fs/exec.c:1701 [inline]
bprm_execve fs/exec.c:1753 [inline]
bprm_execve+0x81e/0x1620 fs/exec.c:1729
do_execveat_common.isra.0+0x4a5/0x610 fs/exec.c:1859
page last free pid 5657 tgid 5657 stack trace:
reset_page_owner include/linux/page_owner.h:25 [inline]
free_pages_prepare mm/page_alloc.c:1394 [inline]
__free_frozen_pages+0x7df/0x1160 mm/page_alloc.c:2901
discard_slab mm/slub.c:3346 [inline]
__put_partials+0x130/0x170 mm/slub.c:3886
qlink_free mm/kasan/quarantine.c:163 [inline]
qlist_free_all+0x4c/0xf0 mm/kasan/quarantine.c:179
kasan_quarantine_reduce+0x195/0x1e0 mm/kasan/quarantine.c:286
__kasan_slab_alloc+0x69/0x90 mm/kasan/common.c:352
kasan_slab_alloc include/linux/kasan.h:252 [inline]
slab_post_alloc_hook mm/slub.c:4948 [inline]
slab_alloc_node mm/slub.c:5258 [inline]
__kmalloc_cache_noprof+0x274/0x800 mm/slub.c:5766
kmalloc_noprof include/linux/slab.h:957 [inline]
tomoyo_print_header security/tomoyo/audit.c:156 [inline]
tomoyo_init_log+0x197/0x2140 security/tomoyo/audit.c:255
tomoyo_supervisor+0x302/0x13b0 security/tomoyo/common.c:2198
tomoyo_audit_env_log security/tomoyo/environ.c:36 [inline]
tomoyo_env_perm+0x191/0x200 security/tomoyo/environ.c:63
tomoyo_environ security/tomoyo/domain.c:672 [inline]
tomoyo_find_next_domain+0xec1/0x20b0 security/tomoyo/domain.c:888
tomoyo_bprm_check_security security/tomoyo/tomoyo.c:102 [inline]
tomoyo_bprm_check_security+0x12d/0x1d0 security/tomoyo/tomoyo.c:92
security_bprm_check+0x1b9/0x1e0 security/security.c:794
search_binary_handler fs/exec.c:1659 [inline]
exec_binprm fs/exec.c:1701 [inline]
bprm_execve fs/exec.c:1753 [inline]
bprm_execve+0x81e/0x1620 fs/exec.c:1729
do_execveat_common.isra.0+0x4a5/0x610 fs/exec.c:1859
do_execve fs/exec.c:1933 [inline]
__do_sys_execve fs/exec.c:2009 [inline]
__se_sys_execve fs/exec.c:2004 [inline]
__x64_sys_execve+0x8e/0xb0 fs/exec.c:2004
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0xcd/0xf80 arch/x86/entry/syscall_64.c:94
Memory state around the buggy address:
ffff8880796aff00: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
ffff8880796aff80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
>ffff8880796b0000: fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
^
ffff8880796b0080: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff8880796b0100: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
==================================================================
Fixes: a106e50be74b ("Bluetooth: HCI: Add support for LL Extended Feature Set")
Reported-by: syzbot+87badbb9094e008e0685@syzkaller.appspotmail.com
Tested-by: syzbot+87badbb9094e008e0685@syzkaller.appspotmail.com
Closes: https://syzbot.org/bug?extid=87badbb9094e008e0685
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Signed-off-by: Pauli Virtanen <pav@iki.fi>
Signed-off-by: David Marlin <dmarlin@redhat.com>
diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c
index cac7a8078795..5101efc6a0e4 100644
--- a/net/bluetooth/hci_sync.c
+++ b/net/bluetooth/hci_sync.c
@@ -7383,10 +7383,8 @@ static void le_read_features_complete(struct hci_dev *hdev, void *data, int err)
bt_dev_dbg(hdev, "err %d", err);
- if (err == -ECANCELED)
- return;
-
hci_conn_drop(conn);
+ hci_conn_put(conn);
}
static int hci_le_read_all_remote_features_sync(struct hci_dev *hdev,
@@ -7456,10 +7454,12 @@ int hci_le_read_remote_features(struct hci_conn *conn)
if (conn->out || (hdev->le_features[0] & HCI_LE_PERIPHERAL_FEATURES)) {
err = hci_cmd_sync_queue_once(hdev,
hci_le_read_remote_features_sync,
- hci_conn_hold(conn),
+ hci_conn_hold(hci_conn_get(conn)),
le_read_features_complete);
- if (err)
+ if (err) {
hci_conn_drop(conn);
+ hci_conn_put(conn);
+ }
} else {
err = -EOPNOTSUPP;
}
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,63 @@
From 1f16a5989467930fa2a1bf222fdc20207b917c50 Mon Sep 17 00:00:00 2001
From: Olga Kornievskaia <okorniev@redhat.com>
Date: Thu, 19 Mar 2026 10:00:42 -0400
Subject: [PATCH] pNFS: fix a missing wake up while waiting on NFS_LAYOUT_DRAIN
JIRA: https://issues.redhat.com/browse/RHEL-157444
commit 5248d8474e594d156bee1ed10339cc16e207a28b
Author: Olga Kornievskaia <okorniev@redhat.com>
Date: Mon Jan 26 14:15:39 2026 -0500
pNFS: fix a missing wake up while waiting on NFS_LAYOUT_DRAIN
It is possible to have a task get stuck on waiting on the
NFS_LAYOUT_DRAIN in the following scenario
1. cpu a: waiter test NFS_LAYOUT_DRAIN (1) and plh_outstanding (1)
2. cpu b: atomic_dec_and_test() -> clear bit -> wake up
3. cpu c: sets NFS_LAYOUT_DRAIN again
4. cpu a: calls wait_on_bit() sleeps forever.
To expand on this we have say 2 outstanding pnfs write IO that get
ESTALE which causes both to call pnfs_destroy_layout() and set the
NFS_LAYOUT_DRAIN bit but the 1st one doesn't call the
pnfs_put_layout_hdr() yet (as that would prevent the 2nd ESTALE write
from trying to call pnfs_destroy_layout()). If the 1st ESTALE write
is the one that initially sets the NFS_LAYOUT_DRAIN so that new IO
on this file initiates new LAYOUTGET. Another new write would find
NFS_LAYOUT_DRAIN set and phl_outstanding>0 (step 1) and would
wait_on_bit(). LAYOUTGET completes doing step 2. Now, the 2nd of
ESTALE writes is calling pnfs_destory_layout() and set the
NFS_LAYOUT_DRAIN bit (step 3). Finally, the waiting write wakes up
to check the bit and goes back to sleep.
The problem revolves around the fact that if NFS_LAYOUT_INVALID_STID
was already set, it should not do the work of
pnfs_mark_layout_stateid_invalid(), thus NFS_LAYOUT_DRAIN will not
be set more than once for an invalid layout.
Suggested-by: Trond Myklebust <trond.myklebust@hammerspace.com>
Fixes: 880265c77ac4 ("pNFS: Avoid a live lock condition in pnfs_update_layout()")
Signed-off-by: Olga Kornievskaia <okorniev@redhat.com>
Signed-off-by: Anna Schumaker <anna.schumaker@oracle.com>
Signed-off-by: Olga Kornievskaia <okorniev@redhat.com>
diff --git a/fs/nfs/pnfs.c b/fs/nfs/pnfs.c
index 33bc6db0dc92..b3cb5ee9d821 100644
--- a/fs/nfs/pnfs.c
+++ b/fs/nfs/pnfs.c
@@ -463,7 +463,8 @@ pnfs_mark_layout_stateid_invalid(struct pnfs_layout_hdr *lo,
};
struct pnfs_layout_segment *lseg, *next;
- set_bit(NFS_LAYOUT_INVALID_STID, &lo->plh_flags);
+ if (test_and_set_bit(NFS_LAYOUT_INVALID_STID, &lo->plh_flags))
+ return !list_empty(&lo->plh_segs);
clear_bit(NFS_INO_LAYOUTCOMMIT, &NFS_I(lo->plh_inode)->flags);
list_for_each_entry_safe(lseg, next, &lo->plh_segs, pls_list)
pnfs_clear_lseg_state(lseg, lseg_list);
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,123 @@
From 33e2b5cdb5bd48ccb14dd2e4db98bc07d209dc70 Mon Sep 17 00:00:00 2001
From: CKI Backport Bot <cki-ci-bot+cki-gitlab-backport-bot@redhat.com>
Date: Tue, 28 Apr 2026 12:03:58 +0000
Subject: [PATCH] smb: client: fix OOB reads parsing symlink error response
JIRA: https://redhat.atlassian.net/browse/RHEL-171476
CVE: CVE-2026-31613
commit 3df690bba28edec865cf7190be10708ad0ddd67e
Author: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Date: Mon Apr 6 15:49:38 2026 +0200
smb: client: fix OOB reads parsing symlink error response
When a CREATE returns STATUS_STOPPED_ON_SYMLINK, smb2_check_message()
returns success without any length validation, leaving the symlink
parsers as the only defense against an untrusted server.
symlink_data() walks SMB 3.1.1 error contexts with the loop test "p <
end", but reads p->ErrorId at offset 4 and p->ErrorDataLength at offset
0. When the server-controlled ErrorDataLength advances p to within 1-7
bytes of end, the next iteration will read past it. When the matching
context is found, sym->SymLinkErrorTag is read at offset 4 from
p->ErrorContextData with no check that the symlink header itself fits.
smb2_parse_symlink_response() then bounds-checks the substitute name
using SMB2_SYMLINK_STRUCT_SIZE as the offset of PathBuffer from
iov_base. That value is computed as sizeof(smb2_err_rsp) +
sizeof(smb2_symlink_err_rsp), which is correct only when
ErrorContextCount == 0.
With at least one error context the symlink data sits 8 bytes deeper,
and each skipped non-matching context shifts it further by 8 +
ALIGN(ErrorDataLength, 8). The check is too short, allowing the
substitute name read to run past iov_len. The out-of-bound heap bytes
are UTF-16-decoded into the symlink target and returned to userspace via
readlink(2).
Fix this all up by making the loops test require the full context header
to fit, rejecting sym if its header runs past end, and bound the
substitute name against the actual position of sym->PathBuffer rather
than a fixed offset.
Because sub_offs and sub_len are 16bits, the pointer math will not
overflow here with the new greater-than.
Cc: Ronnie Sahlberg <ronniesahlberg@gmail.com>
Cc: Shyam Prasad N <sprasad@microsoft.com>
Cc: Tom Talpey <tom@talpey.com>
Cc: Bharath SM <bharathsm@microsoft.com>
Cc: linux-cifs@vger.kernel.org
Cc: samba-technical@lists.samba.org
Cc: stable <stable@kernel.org>
Reviewed-by: Paulo Alcantara (Red Hat) <pc@manguebit.org>
Assisted-by: gregkh_clanker_t1000
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Steve French <stfrench@microsoft.com>
Signed-off-by: CKI Backport Bot <cki-ci-bot+cki-gitlab-backport-bot@redhat.com>
diff --git a/fs/smb/client/smb2file.c b/fs/smb/client/smb2file.c
index 1f7f284a7844..b2ddcecd00b9 100644
--- a/fs/smb/client/smb2file.c
+++ b/fs/smb/client/smb2file.c
@@ -27,10 +27,11 @@ static struct smb2_symlink_err_rsp *symlink_data(const struct kvec *iov)
{
struct smb2_err_rsp *err = iov->iov_base;
struct smb2_symlink_err_rsp *sym = ERR_PTR(-EINVAL);
+ u8 *end = (u8 *)err + iov->iov_len;
u32 len;
if (err->ErrorContextCount) {
- struct smb2_error_context_rsp *p, *end;
+ struct smb2_error_context_rsp *p;
len = (u32)err->ErrorContextCount * (offsetof(struct smb2_error_context_rsp,
ErrorContextData) +
@@ -39,8 +40,7 @@ static struct smb2_symlink_err_rsp *symlink_data(const struct kvec *iov)
return ERR_PTR(-EINVAL);
p = (struct smb2_error_context_rsp *)err->ErrorData;
- end = (struct smb2_error_context_rsp *)((u8 *)err + iov->iov_len);
- do {
+ while ((u8 *)p + sizeof(*p) <= end) {
if (le32_to_cpu(p->ErrorId) == SMB2_ERROR_ID_DEFAULT) {
sym = (struct smb2_symlink_err_rsp *)p->ErrorContextData;
break;
@@ -50,14 +50,16 @@ static struct smb2_symlink_err_rsp *symlink_data(const struct kvec *iov)
len = ALIGN(le32_to_cpu(p->ErrorDataLength), 8);
p = (struct smb2_error_context_rsp *)(p->ErrorContextData + len);
- } while (p < end);
+ }
} else if (le32_to_cpu(err->ByteCount) >= sizeof(*sym) &&
iov->iov_len >= SMB2_SYMLINK_STRUCT_SIZE) {
sym = (struct smb2_symlink_err_rsp *)err->ErrorData;
}
- if (!IS_ERR(sym) && (le32_to_cpu(sym->SymLinkErrorTag) != SYMLINK_ERROR_TAG ||
- le32_to_cpu(sym->ReparseTag) != IO_REPARSE_TAG_SYMLINK))
+ if (!IS_ERR(sym) &&
+ ((u8 *)sym + sizeof(*sym) > end ||
+ le32_to_cpu(sym->SymLinkErrorTag) != SYMLINK_ERROR_TAG ||
+ le32_to_cpu(sym->ReparseTag) != IO_REPARSE_TAG_SYMLINK))
sym = ERR_PTR(-EINVAL);
return sym;
@@ -128,8 +130,10 @@ int smb2_parse_symlink_response(struct cifs_sb_info *cifs_sb, const struct kvec
print_len = le16_to_cpu(sym->PrintNameLength);
print_offs = le16_to_cpu(sym->PrintNameOffset);
- if (iov->iov_len < SMB2_SYMLINK_STRUCT_SIZE + sub_offs + sub_len ||
- iov->iov_len < SMB2_SYMLINK_STRUCT_SIZE + print_offs + print_len)
+ if ((char *)sym->PathBuffer + sub_offs + sub_len >
+ (char *)iov->iov_base + iov->iov_len ||
+ (char *)sym->PathBuffer + print_offs + print_len >
+ (char *)iov->iov_base + iov->iov_len)
return -EINVAL;
return smb2_parse_native_symlink(path,
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,164 @@
diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c
index 350c202..3645a12 100644
--- a/kernel/sched/deadline.c
+++ b/kernel/sched/deadline.c
@@ -1166,8 +1166,12 @@ static enum hrtimer_restart dl_server_timer(struct hrtimer *timer, struct sched_
sched_clock_tick();
update_rq_clock(rq);
- if (!dl_se->dl_runtime)
- return HRTIMER_NORESTART;
+ /*
+ * Make sure current has propagated its pending runtime into
+ * any relevant server through calling dl_server_update() and
+ * friends.
+ */
+ rq->donor->sched_class->update_curr(rq);
if (dl_se->dl_defer_armed) {
/*
@@ -1543,35 +1547,16 @@ static void update_curr_dl_se(struct rq *rq, struct sched_dl_entity *dl_se, s64
* as time available for the fair server, avoiding a penalty for the
* rt scheduler that did not consumed that time.
*/
-void dl_server_update_idle_time(struct rq *rq, struct task_struct *p)
+void dl_server_update_idle(struct sched_dl_entity *dl_se, s64 delta_exec)
{
- s64 delta_exec;
-
- if (!rq->fair_server.dl_defer)
- return;
-
- /* no need to discount more */
- if (rq->fair_server.runtime < 0)
- return;
-
- delta_exec = rq_clock_task(rq) - p->se.exec_start;
- if (delta_exec < 0)
- return;
-
- rq->fair_server.runtime -= delta_exec;
-
- if (rq->fair_server.runtime < 0) {
- rq->fair_server.dl_defer_running = 0;
- rq->fair_server.runtime = 0;
- }
-
- p->se.exec_start = rq_clock_task(rq);
+ if (dl_se->dl_server_active && dl_se->dl_runtime && dl_se->dl_defer)
+ update_curr_dl_se(dl_se->rq, dl_se, delta_exec);
}
void dl_server_update(struct sched_dl_entity *dl_se, s64 delta_exec)
{
/* 0 runtime = fair server disabled */
- if (dl_se->dl_runtime)
+ if (dl_se->dl_server_active && dl_se->dl_runtime)
update_curr_dl_se(dl_se->rq, dl_se, delta_exec);
}
@@ -1582,6 +1567,11 @@ void dl_server_start(struct sched_dl_entity *dl_se)
if (!dl_server(dl_se) || dl_se->dl_server_active)
return;
+ /*
+ * Update the current task to 'now'.
+ */
+ rq->donor->sched_class->update_curr(rq);
+
if (WARN_ON_ONCE(!cpu_online(cpu_of(rq))))
return;
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index a135883..6e4e7b5 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -1233,8 +1233,7 @@ static void update_curr(struct cfs_rq *cfs_rq)
* against fair_server such that it can account for this time
* and possibly avoid running this period.
*/
- if (dl_server_active(&rq->fair_server))
- dl_server_update(&rq->fair_server, delta_exec);
+ dl_server_update(&rq->fair_server, delta_exec);
}
account_cfs_rq_runtime(cfs_rq, delta_exec);
@@ -7001,12 +7000,8 @@ enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags)
h_nr_idle = 1;
}
- if (!rq_h_nr_queued && rq->cfs.h_nr_queued) {
- /* Account for idle runtime */
- if (!rq->nr_running)
- dl_server_update_idle_time(rq, rq->curr);
+ if (!rq_h_nr_queued && rq->cfs.h_nr_queued)
dl_server_start(&rq->fair_server);
- }
/* At this point se is NULL and we are at root level*/
add_nr_running(rq, 1);
diff --git a/kernel/sched/idle.c b/kernel/sched/idle.c
index c39b089..e8cbba8 100644
--- a/kernel/sched/idle.c
+++ b/kernel/sched/idle.c
@@ -452,9 +452,11 @@ static void wakeup_preempt_idle(struct rq *rq, struct task_struct *p, int flags)
resched_curr(rq);
}
+static void update_curr_idle(struct rq *rq);
+
static void put_prev_task_idle(struct rq *rq, struct task_struct *prev, struct task_struct *next)
{
- dl_server_update_idle_time(rq, prev);
+ update_curr_idle(rq);
scx_update_idle(rq, false, true);
}
@@ -496,6 +498,7 @@ dequeue_task_idle(struct rq *rq, struct task_struct *p, int flags)
*/
static void task_tick_idle(struct rq *rq, struct task_struct *curr, int queued)
{
+ update_curr_idle(rq);
}
static void switched_to_idle(struct rq *rq, struct task_struct *p)
@@ -511,6 +514,17 @@ prio_changed_idle(struct rq *rq, struct task_struct *p, int oldprio)
static void update_curr_idle(struct rq *rq)
{
+ struct sched_entity *se = &rq->idle->se;
+ u64 now = rq_clock_task(rq);
+ s64 delta_exec;
+
+ delta_exec = now - se->exec_start;
+ if (unlikely(delta_exec <= 0))
+ return;
+
+ se->exec_start = now;
+
+ dl_server_update_idle(&rq->fair_server, delta_exec);
}
/*
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
index 5461e9a..34813e3 100644
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -406,6 +406,7 @@ extern s64 dl_scaled_delta_exec(struct rq *rq, struct sched_dl_entity *dl_se, s6
* naturally thottled to once per period, avoiding high context switch
* workloads from spamming the hrtimer program/cancel paths.
*/
+extern void dl_server_update_idle(struct sched_dl_entity *dl_se, s64 delta_exec);
extern void dl_server_update(struct sched_dl_entity *dl_se, s64 delta_exec);
extern void dl_server_start(struct sched_dl_entity *dl_se);
extern void dl_server_stop(struct sched_dl_entity *dl_se);
@@ -413,8 +414,6 @@ extern void dl_server_init(struct sched_dl_entity *dl_se, struct rq *rq,
dl_server_pick_f pick_task);
extern void sched_init_dl_servers(void);
-extern void dl_server_update_idle_time(struct rq *rq,
- struct task_struct *p);
extern void fair_server_init(struct rq *rq);
extern void __dl_server_attach_root(struct sched_dl_entity *dl_se, struct rq *rq);
extern int dl_server_apply_params(struct sched_dl_entity *dl_se,

View File

@ -0,0 +1,30 @@
From 65d0f5264af85b67546a34dc94b9b7778ed50e89 Mon Sep 17 00:00:00 2001
From: Brian Masney <bmasney@redhat.com>
Date: Wed, 20 May 2026 13:50:28 -0400
Subject: [PATCH] redhat/configs: automotive: disable CONFIG_IO_URING
JIRA: https://redhat.atlassian.net/browse/RHEL-178183
Upstream-status: RHEL-only
Disable CONFIG_IO_URING, and the dependency CONFIG_BLK_DEV_UBLK, since
IO uring is currently not in supported at the moment in automotive.
Signed-off-by: Brian Masney <bmasney@redhat.com>
diff --git a/redhat/configs/rhel/automotive/generic/CONFIG_BLK_DEV_UBLK b/redhat/configs/rhel/automotive/generic/CONFIG_BLK_DEV_UBLK
new file mode 100644
index 000000000000..f773891a920f
--- /dev/null
+++ b/redhat/configs/rhel/automotive/generic/CONFIG_BLK_DEV_UBLK
@@ -0,0 +1 @@
+# CONFIG_BLK_DEV_UBLK is not set
diff --git a/redhat/configs/rhel/automotive/generic/CONFIG_IO_URING b/redhat/configs/rhel/automotive/generic/CONFIG_IO_URING
new file mode 100644
index 000000000000..dcae2b3a1327
--- /dev/null
+++ b/redhat/configs/rhel/automotive/generic/CONFIG_IO_URING
@@ -0,0 +1 @@
+# CONFIG_IO_URING is not set
--
2.50.1 (Apple Git-155)

View File

@ -0,0 +1,121 @@
Subject: [PATCH] dpll: RHEL 10.2 kABI adaptation (RH_KABI_USE/REPLACE + ffo backward-compat)
# RHEL-z-stream-specific kABI wrapping of the dpll feature additions (0015/0026/0028).
# No CS10/upstream commit produces this exact kABI form; reconstructed to match RHEL.
diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c
index 00e8696..edd5967 100644
--- a/drivers/dpll/dpll_netlink.c
+++ b/drivers/dpll/dpll_netlink.c
@@ -424,7 +424,13 @@ static int dpll_msg_add_ffo(struct sk_buff *msg, struct dpll_pin *pin,
struct dpll_ffo_param ffo = { .type = type };
int ret;
- if (!ops->ffo_get || !(ops->supported_ffo & BIT(type)))
+ /* RHEL: To maintain backward compatibility with older binary modules,
+ * we must accept a value of zero for .supported_ffo when the type is
+ * DPLL_FFO_PORT_RXTX_RATE.
+ */
+ if (!ops->ffo_get ||
+ !((ops->supported_ffo & BIT(type)) ||
+ (!ops->supported_ffo && type == DPLL_FFO_PORT_RXTX_RATE)))
return 0;
ret = ops->ffo_get(pin, dpll_pin_on_dpll_priv(ref->dpll, pin),
ref->dpll, dpll_priv(ref->dpll), &ffo, extack);
diff --git a/include/linux/dpll.h b/include/linux/dpll.h
index cd21b1f..d988c09 100644
--- a/include/linux/dpll.h
+++ b/include/linux/dpll.h
@@ -54,15 +54,13 @@ struct dpll_device_ops {
int (*phase_offset_avg_factor_get)(const struct dpll_device *dpll,
void *dpll_priv, u32 *factor,
struct netlink_ext_ack *extack);
- int (*freq_monitor_set)(const struct dpll_device *dpll, void *dpll_priv,
+
+ RH_KABI_USE(1, int (*freq_monitor_set)(const struct dpll_device *dpll, void *dpll_priv,
enum dpll_feature_state state,
- struct netlink_ext_ack *extack);
- int (*freq_monitor_get)(const struct dpll_device *dpll, void *dpll_priv,
+ struct netlink_ext_ack *extack))
+ RH_KABI_USE(2, int (*freq_monitor_get)(const struct dpll_device *dpll, void *dpll_priv,
enum dpll_feature_state *state,
- struct netlink_ext_ack *extack);
-
- RH_KABI_RESERVE(1)
- RH_KABI_RESERVE(2)
+ struct netlink_ext_ack *extack))
RH_KABI_RESERVE(3)
RH_KABI_RESERVE(4)
RH_KABI_RESERVE(5)
@@ -80,13 +78,16 @@ enum dpll_ffo_type {
__DPLL_FFO_TYPE_MAX,
};
+/* RHEL: we have to keep 'ffo' field to be first to preserve compatibility
+ * with older .ffo_get() callbacks that accepts 's64 *' as parameter instead
+ * of 'struct dpll_ffo_param *'
+ */
struct dpll_ffo_param {
- enum dpll_ffo_type type;
s64 ffo;
+ enum dpll_ffo_type type;
};
struct dpll_pin_ops {
- unsigned long supported_ffo;
int (*frequency_set)(const struct dpll_pin *pin, void *pin_priv,
const struct dpll_device *dpll, void *dpll_priv,
const u64 frequency,
@@ -111,12 +112,6 @@ struct dpll_pin_ops {
const struct dpll_device *dpll,
void *dpll_priv, enum dpll_pin_state *state,
struct netlink_ext_ack *extack);
- int (*operstate_on_dpll_get)(const struct dpll_pin *pin,
- void *pin_priv,
- const struct dpll_device *dpll,
- void *dpll_priv,
- enum dpll_pin_operstate *operstate,
- struct netlink_ext_ack *extack);
int (*state_on_pin_set)(const struct dpll_pin *pin, void *pin_priv,
const struct dpll_pin *parent_pin,
void *parent_pin_priv,
@@ -145,14 +140,13 @@ struct dpll_pin_ops {
const struct dpll_device *dpll, void *dpll_priv,
const s32 phase_adjust,
struct netlink_ext_ack *extack);
- int (*ffo_get)(const struct dpll_pin *pin, void *pin_priv,
+ RH_KABI_REPLACE(int (*ffo_get)(const struct dpll_pin *pin, void *pin_priv,
+ const struct dpll_device *dpll, void *dpll_priv,
+ s64 *ffo, struct netlink_ext_ack *extack),
+ int (*ffo_get)(const struct dpll_pin *pin, void *pin_priv,
const struct dpll_device *dpll, void *dpll_priv,
struct dpll_ffo_param *ffo,
- struct netlink_ext_ack *extack);
- int (*measured_freq_get)(const struct dpll_pin *pin, void *pin_priv,
- const struct dpll_device *dpll,
- void *dpll_priv, u64 *measured_freq,
- struct netlink_ext_ack *extack);
+ struct netlink_ext_ack *extack))
int (*esync_set)(const struct dpll_pin *pin, void *pin_priv,
const struct dpll_device *dpll, void *dpll_priv,
u64 freq, struct netlink_ext_ack *extack);
@@ -171,9 +165,17 @@ struct dpll_pin_ops {
enum dpll_pin_state *state,
struct netlink_ext_ack *extack);
- RH_KABI_RESERVE(1)
- RH_KABI_RESERVE(2)
- RH_KABI_RESERVE(3)
+ RH_KABI_USE(1, int (*measured_freq_get)(const struct dpll_pin *pin, void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv, u64 *measured_freq,
+ struct netlink_ext_ack *extack))
+ RH_KABI_USE(2, int (*operstate_on_dpll_get)(const struct dpll_pin *pin,
+ void *pin_priv,
+ const struct dpll_device *dpll,
+ void *dpll_priv,
+ enum dpll_pin_operstate *operstate,
+ struct netlink_ext_ack *extack))
+ RH_KABI_USE(3, unsigned long supported_ffo)
RH_KABI_RESERVE(4)
RH_KABI_RESERVE(5)
RH_KABI_RESERVE(6)

View File

@ -182,7 +182,7 @@ Summary: The Linux kernel
# This is needed to do merge window version magic
%define patchlevel 12
# This allows pkg_release to have configurable %%{?dist} tag
%define specrelease 211.18.1%{?buildid}%{?dist}
%define specrelease 211.20.1%{?buildid}%{?dist}
# This defines the kabi tarball version
%define kabiversion 6.12.0-211.7.1.el10_2
@ -1279,6 +1279,49 @@ Patch1241: 1241-bluetooth-mgmt-validate-ltk-enc-size-on-load.patch
Patch1242: 1242-bluetooth-sco-fix-race-conditions-in-sco-sock-connect.patch
Patch1243: 1243-xfs-delete-attr-leaf-freemap-entries-when-empty.patch
Patch1244: 1244-xfs-fix-freemap-adjustments-when-adding-xattrs-to-leaf-block.patch
Patch1245: 1245-proc-use-the-same-treatment-to-check-proc-lseek-as-ones-for-.patch
Patch1246: 1246-proc-fix-missing-pde-set-flags-for-net-proc-files.patch
Patch1247: 1247-proc-fix-type-confusion-in-pde-set-flags.patch
Patch1248: 1248-nbd-defer-config-unlock-in-nbd-genl-connect.patch
Patch1249: 1249-crypto-authenc-correctly-pass-einprogress-back-up-to-the-cal.patch
Patch1250: 1250-dpll-zl3073x-detect-dpll-channel-count-from-chip-id-at-runti.patch
Patch1251: 1251-dpll-zl3073x-add-die-temperature-reporting-for-supported-chi.patch
Patch1252: 1252-dpll-zl3073x-use-struct-group-to-partition-states.patch
Patch1253: 1253-dpll-zl3073x-add-zl3073x-ref-state-update-helper.patch
Patch1254: 1254-dpll-zl3073x-introduce-zl3073x-chan-for-dpll-channel-state.patch
Patch1255: 1255-dpll-zl3073x-add-dpll-channel-status-fields-to-zl3073x-chan.patch
Patch1256: 1256-dpll-zl3073x-add-reference-priority-to-zl3073x-chan.patch
Patch1257: 1257-dpll-zl3073x-drop-selected-and-simplify-connected-ref-getter.patch
Patch1258: 1258-dpll-add-frequency-monitoring-to-netlink-spec.patch
Patch1259: 1259-dpll-add-frequency-monitoring-callback-ops.patch
Patch1260: 1260-dpll-zl3073x-implement-frequency-monitoring.patch
Patch1261: 1261-dpll-zl3073x-clean-up-esync-get-set-and-use-zl3073x-out-is-n.patch
Patch1262: 1262-dpll-zl3073x-use-field-modify-for-clear-and-set-patterns.patch
Patch1263: 1263-dpll-zl3073x-add-ref-sync-and-output-clock-type-helpers.patch
Patch1264: 1264-dpll-zl3073x-add-ref-sync-pair-support.patch
Patch1265: 1265-smb-client-validate-the-whole-dacl-before-rewriting-it-in-ci.patch
Patch1266: 1266-smb-client-require-a-full-nfs-mode-sid-before-reading-mode-b.patch
Patch1267: 1267-smb-client-scope-end-of-dacl-to-cifs-debug2-use-in-parse-dac.patch
Patch1268: 1268-smb-client-use-kzalloc-to-zero-initialize-security-descripto.patch
Patch1269: 1269-smb-client-validate-dacloffset-before-building-dacl-pointers.patch
Patch1270: 1270-dpll-add-pin-operational-state.patch
Patch1271: 1271-dpll-zl3073x-implement-pin-operational-state-reporting.patch
Patch1272: 1272-dpll-add-fractional-frequency-offset-to-pin-parent-device.patch
Patch1273: 1273-dpll-zl3073x-report-ffo-as-dpll-vs-input-reference-offset.patch
Patch1274: 1274-netfilter-flowtable-strictly-check-for-maximum-number-of-act.patch
Patch1275: 1275-bluetooth-hci-add-initial-support-for-past.patch
Patch1276: 1276-bluetooth-iso-add-support-to-bind-to-trigger-past.patch
Patch1277: 1277-bluetooth-hci-add-support-for-ll-extended-feature-set.patch
Patch1278: 1278-bluetooth-hci-conn-fix-using-conn-le-tx-rx-phy-as-supported-.patch
Patch1279: 1279-bluetooth-l2cap-add-support-for-setting-bt-phy.patch
Patch1280: 1280-bluetooth-hci-sync-hci-cmd-sync-queue-once-return-eexist-if-.patch
Patch1281: 1281-bluetooth-hci-sync-fix-leaks-when-hci-cmd-sync-queue-once-fa.patch
Patch1282: 1282-bluetooth-hci-sync-fix-uaf-in-le-read-features-complete.patch
Patch1283: 1283-pnfs-fix-a-missing-wake-up-while-waiting-on-nfs-layout-drain.patch
Patch1284: 1284-smb-client-fix-oob-reads-parsing-symlink-error-response.patch
Patch1285: 1285-sched-deadline-fix-dl-server-time-accounting.patch
Patch1286: 1286-redhat-configs-automotive-disable-config-io-uring.patch
Patch1287: 1287-rhel-kabi-dpll-adaptation.patch
# END OF PATCH DEFINITIONS
%description
@ -2280,6 +2323,49 @@ ApplyPatch 1241-bluetooth-mgmt-validate-ltk-enc-size-on-load.patch
ApplyPatch 1242-bluetooth-sco-fix-race-conditions-in-sco-sock-connect.patch
ApplyPatch 1243-xfs-delete-attr-leaf-freemap-entries-when-empty.patch
ApplyPatch 1244-xfs-fix-freemap-adjustments-when-adding-xattrs-to-leaf-block.patch
ApplyPatch 1245-proc-use-the-same-treatment-to-check-proc-lseek-as-ones-for-.patch
ApplyPatch 1246-proc-fix-missing-pde-set-flags-for-net-proc-files.patch
ApplyPatch 1247-proc-fix-type-confusion-in-pde-set-flags.patch
ApplyPatch 1248-nbd-defer-config-unlock-in-nbd-genl-connect.patch
ApplyPatch 1249-crypto-authenc-correctly-pass-einprogress-back-up-to-the-cal.patch
ApplyPatch 1250-dpll-zl3073x-detect-dpll-channel-count-from-chip-id-at-runti.patch
ApplyPatch 1251-dpll-zl3073x-add-die-temperature-reporting-for-supported-chi.patch
ApplyPatch 1252-dpll-zl3073x-use-struct-group-to-partition-states.patch
ApplyPatch 1253-dpll-zl3073x-add-zl3073x-ref-state-update-helper.patch
ApplyPatch 1254-dpll-zl3073x-introduce-zl3073x-chan-for-dpll-channel-state.patch
ApplyPatch 1255-dpll-zl3073x-add-dpll-channel-status-fields-to-zl3073x-chan.patch
ApplyPatch 1256-dpll-zl3073x-add-reference-priority-to-zl3073x-chan.patch
ApplyPatch 1257-dpll-zl3073x-drop-selected-and-simplify-connected-ref-getter.patch
ApplyPatch 1258-dpll-add-frequency-monitoring-to-netlink-spec.patch
ApplyPatch 1259-dpll-add-frequency-monitoring-callback-ops.patch
ApplyPatch 1260-dpll-zl3073x-implement-frequency-monitoring.patch
ApplyPatch 1261-dpll-zl3073x-clean-up-esync-get-set-and-use-zl3073x-out-is-n.patch
ApplyPatch 1262-dpll-zl3073x-use-field-modify-for-clear-and-set-patterns.patch
ApplyPatch 1263-dpll-zl3073x-add-ref-sync-and-output-clock-type-helpers.patch
ApplyPatch 1264-dpll-zl3073x-add-ref-sync-pair-support.patch
ApplyPatch 1265-smb-client-validate-the-whole-dacl-before-rewriting-it-in-ci.patch
ApplyPatch 1266-smb-client-require-a-full-nfs-mode-sid-before-reading-mode-b.patch
ApplyPatch 1267-smb-client-scope-end-of-dacl-to-cifs-debug2-use-in-parse-dac.patch
ApplyPatch 1268-smb-client-use-kzalloc-to-zero-initialize-security-descripto.patch
ApplyPatch 1269-smb-client-validate-dacloffset-before-building-dacl-pointers.patch
ApplyPatch 1270-dpll-add-pin-operational-state.patch
ApplyPatch 1271-dpll-zl3073x-implement-pin-operational-state-reporting.patch
ApplyPatch 1272-dpll-add-fractional-frequency-offset-to-pin-parent-device.patch
ApplyPatch 1273-dpll-zl3073x-report-ffo-as-dpll-vs-input-reference-offset.patch
ApplyPatch 1274-netfilter-flowtable-strictly-check-for-maximum-number-of-act.patch
ApplyPatch 1275-bluetooth-hci-add-initial-support-for-past.patch
ApplyPatch 1276-bluetooth-iso-add-support-to-bind-to-trigger-past.patch
ApplyPatch 1277-bluetooth-hci-add-support-for-ll-extended-feature-set.patch
ApplyPatch 1278-bluetooth-hci-conn-fix-using-conn-le-tx-rx-phy-as-supported-.patch
ApplyPatch 1279-bluetooth-l2cap-add-support-for-setting-bt-phy.patch
ApplyPatch 1280-bluetooth-hci-sync-hci-cmd-sync-queue-once-return-eexist-if-.patch
ApplyPatch 1281-bluetooth-hci-sync-fix-leaks-when-hci-cmd-sync-queue-once-fa.patch
ApplyPatch 1282-bluetooth-hci-sync-fix-uaf-in-le-read-features-complete.patch
ApplyPatch 1283-pnfs-fix-a-missing-wake-up-while-waiting-on-nfs-layout-drain.patch
ApplyPatch 1284-smb-client-fix-oob-reads-parsing-symlink-error-response.patch
ApplyPatch 1285-sched-deadline-fix-dl-server-time-accounting.patch
ApplyPatch 1286-redhat-configs-automotive-disable-config-io-uring.patch
ApplyPatch 1287-rhel-kabi-dpll-adaptation.patch
# END OF PATCH APPLICATIONS
# Any further pre-build tree manipulations happen here.
@ -4784,6 +4870,58 @@ fi\
#
#
%changelog
* Sun Jun 07 2026 Andrew Lukoshko <alukoshko@almalinux.org> - 6.12.0-211.20.1
- Recreate RHEL 6.12.0-211.20.1 from CentOS Stream 10 and upstream stable backports (1245-1287)
- smb cifs.spnego now shipped by RHEL too; existing ahead-fix 1105 is identical (RHEL's redundant copy omitted)
- RHEL changelog for 211.19.1..211.20.1 follows:
* Tue Jun 02 2026 CKI KWF Bot <cki-ci-bot+kwf-gitlab-com@redhat.com> [6.12.0-211.20.1.el10_2]
- smb: client: reject userspace cifs.spnego descriptions (Paulo Alcantara) [RHEL-178932] {CVE-2026-46243}
- redhat/configs: automotive: disable CONFIG_IO_URING (Brian Masney) [RHEL-179469]
- sched/deadline: Fix dl_server time accounting (Phil Auld) [RHEL-173950]
- smb: client: fix OOB reads parsing symlink error response (CKI Backport Bot) [RHEL-171475] {CVE-2026-31613}
- pNFS: fix a missing wake up while waiting on NFS_LAYOUT_DRAIN (Olga Kornievskaia) [RHEL-157466]
* Thu May 28 2026 CKI KWF Bot <cki-ci-bot+kwf-gitlab-com@redhat.com> [6.12.0-211.19.1.el10_2]
- Bluetooth: hci_sync: Fix UAF in le_read_features_complete (David Marlin) [RHEL-176903] {CVE-2026-43322}
- Bluetooth: hci_sync: fix leaks when hci_cmd_sync_queue_once fails (David Marlin) [RHEL-176903]
- Bluetooth: hci_sync: hci_cmd_sync_queue_once() return -EEXIST if exists (David Marlin) [RHEL-176903]
- Bluetooth: L2CAP: Add support for setting BT_PHY (David Marlin) [RHEL-176903]
- Bluetooth: hci_conn: Fix using conn->le_{tx,rx}_phy as supported PHYs (David Marlin) [RHEL-176903]
- Bluetooth: HCI: Add support for LL Extended Feature Set (David Marlin) [RHEL-176903]
- Bluetooth: ISO: Add support to bind to trigger PAST (David Marlin) [RHEL-176903]
- Bluetooth: HCI: Add initial support for PAST (David Marlin) [RHEL-176903]
- netfilter: flowtable: strictly check for maximum number of actions (CKI Backport Bot) [RHEL-176915] {CVE-2026-43329}
- dpll: zl3073x: report FFO as DPLL vs input reference offset (Ivan Vecera) [RHEL-175824]
- dpll: add fractional frequency offset to pin-parent-device (Ivan Vecera) [RHEL-175824]
- dpll: zl3073x: implement pin operational state reporting (Ivan Vecera) [RHEL-175821]
- dpll: add pin operational state (Ivan Vecera) [RHEL-175821]
- smb: client: validate dacloffset before building DACL pointers (Paulo Alcantara) [RHEL-172827]
- smb: client: use kzalloc to zero-initialize security descriptor buffer (Paulo Alcantara) [RHEL-172827]
- smb: client: scope end_of_dacl to CIFS_DEBUG2 use in parse_dacl (Paulo Alcantara) [RHEL-172827]
- smb: client: require a full NFS mode SID before reading mode bits (Paulo Alcantara) [RHEL-172827]
- smb: client: validate the whole DACL before rewriting it in cifsacl (Paulo Alcantara) [RHEL-172827] {CVE-2026-31709}
- dpll: zl3073x: add ref-sync pair support (Ivan Vecera) [RHEL-167277]
- dpll: zl3073x: add ref sync and output clock type helpers (Ivan Vecera) [RHEL-167277]
- dpll: zl3073x: use FIELD_MODIFY() for clear-and-set patterns (Ivan Vecera) [RHEL-167277]
- dpll: zl3073x: clean up esync get/set and use zl3073x_out_is_ndiv() (Ivan Vecera) [RHEL-167277]
- dpll: zl3073x: implement frequency monitoring (Ivan Vecera) [RHEL-167837]
- dpll: add frequency monitoring callback ops (Ivan Vecera) [RHEL-167837]
- dpll: add frequency monitoring to netlink spec (Ivan Vecera) [RHEL-167837]
- dpll: zl3073x: drop selected and simplify connected ref getter (Ivan Vecera) [RHEL-172930]
- dpll: zl3073x: add reference priority to zl3073x_chan (Ivan Vecera) [RHEL-172930]
- dpll: zl3073x: add DPLL channel status fields to zl3073x_chan (Ivan Vecera) [RHEL-172930]
- dpll: zl3073x: introduce zl3073x_chan for DPLL channel state (Ivan Vecera) [RHEL-172930]
- dpll: zl3073x: add zl3073x_ref_state_update helper (Ivan Vecera) [RHEL-172930]
- dpll: zl3073x: use struct_group to partition states (Ivan Vecera) [RHEL-172930]
- dpll: zl3073x: add die temperature reporting for supported chips (Ivan Vecera) [RHEL-172930]
- dpll: zl3073x: detect DPLL channel count from chip ID at runtime (Ivan Vecera) [RHEL-172930]
- crypto: authenc - Correctly pass EINPROGRESS back up to the caller (CKI Backport Bot) [RHEL-171302]
- nbd: defer config unlock in nbd_genl_connect (CKI Backport Bot) [RHEL-166953] {CVE-2025-68366}
- proc: fix type confusion in pde_set_flags() (Abhi Das) [RHEL-163345] {CVE-2025-38653}
- proc: fix missing pde_set_flags() for net proc files (Abhi Das) [RHEL-163345] {CVE-2025-38653}
- proc: use the same treatment to check proc_lseek as ones for proc_read_iter et.al (CKI Backport Bot) [RHEL-163345] {CVE-2025-38653}
* Sun Jun 07 2026 Andrew Lukoshko <alukoshko@almalinux.org> - 6.12.0-211.18.1
- Recreate RHEL 6.12.0-211.18.1 from CentOS Stream 10 and upstream stable backports (1162-1244)
- RHEL changelog for 211.17.1..211.18.1 follows: