From f6916a8ae1fca867028cbe7384908f3cdf8ba63d Mon Sep 17 00:00:00 2001 From: Andrew Lukoshko Date: Sun, 7 Jun 2026 00:04:20 +0000 Subject: [PATCH] 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. --- ...ent-to-check-proc-lseek-as-ones-for-.patch | 89 +++ ...ing-pde-set-flags-for-net-proc-files.patch | 140 ++++ ...-fix-type-confusion-in-pde-set-flags.patch | 59 ++ ...er-config-unlock-in-nbd-genl-connect.patch | 74 ++ ...-pass-einprogress-back-up-to-the-cal.patch | 205 +++++ ...-channel-count-from-chip-id-at-runti.patch | 426 +++++++++++ ...perature-reporting-for-supported-chi.patch | 183 +++++ ...use-struct-group-to-partition-states.patch | 305 ++++++++ ...-add-zl3073x-ref-state-update-helper.patch | 115 +++ ...-zl3073x-chan-for-dpll-channel-state.patch | 563 ++++++++++++++ ...hannel-status-fields-to-zl3073x-chan.patch | 288 +++++++ ...d-reference-priority-to-zl3073x-chan.patch | 713 ++++++++++++++++++ ...ed-and-simplify-connected-ref-getter.patch | 188 +++++ ...frequency-monitoring-to-netlink-spec.patch | 211 ++++++ ...dd-frequency-monitoring-callback-ops.patch | 224 ++++++ ...3073x-implement-frequency-monitoring.patch | 399 ++++++++++ ...ync-get-set-and-use-zl3073x-out-is-n.patch | 181 +++++ ...ld-modify-for-clear-and-set-patterns.patch | 99 +++ ...f-sync-and-output-clock-type-helpers.patch | 219 ++++++ ...ll-zl3073x-add-ref-sync-pair-support.patch | 325 ++++++++ ...whole-dacl-before-rewriting-it-in-ci.patch | 233 ++++++ ...l-nfs-mode-sid-before-reading-mode-b.patch | 50 ++ ...dacl-to-cifs-debug2-use-in-parse-dac.patch | 69 ++ ...o-zero-initialize-security-descripto.patch | 57 ++ ...offset-before-building-dacl-pointers.patch | 123 +++ 1270-dpll-add-pin-operational-state.patch | 294 ++++++++ ...ment-pin-operational-state-reporting.patch | 254 +++++++ ...requency-offset-to-pin-parent-device.patch | 325 ++++++++ ...fo-as-dpll-vs-input-reference-offset.patch | 362 +++++++++ ...ctly-check-for-maximum-number-of-act.patch | 500 ++++++++++++ ...oth-hci-add-initial-support-for-past.patch | 355 +++++++++ ...-add-support-to-bind-to-trigger-past.patch | 300 ++++++++ ...-support-for-ll-extended-feature-set.patch | 437 +++++++++++ ...sing-conn-le-tx-rx-phy-as-supported-.patch | 136 ++++ ...l2cap-add-support-for-setting-bt-phy.patch | 442 +++++++++++ ...md-sync-queue-once-return-eexist-if-.patch | 180 +++++ ...eaks-when-hci-cmd-sync-queue-once-fa.patch | 69 ++ ...fix-uaf-in-le-read-features-complete.patch | 243 ++++++ ...up-while-waiting-on-nfs-layout-drain.patch | 63 ++ ...reads-parsing-symlink-error-response.patch | 123 +++ ...adline-fix-dl-server-time-accounting.patch | 164 ++++ ...s-automotive-disable-config-io-uring.patch | 30 + 1287-rhel-kabi-dpll-adaptation.patch | 121 +++ kernel.spec | 140 +++- 44 files changed, 10075 insertions(+), 1 deletion(-) create mode 100644 1245-proc-use-the-same-treatment-to-check-proc-lseek-as-ones-for-.patch create mode 100644 1246-proc-fix-missing-pde-set-flags-for-net-proc-files.patch create mode 100644 1247-proc-fix-type-confusion-in-pde-set-flags.patch create mode 100644 1248-nbd-defer-config-unlock-in-nbd-genl-connect.patch create mode 100644 1249-crypto-authenc-correctly-pass-einprogress-back-up-to-the-cal.patch create mode 100644 1250-dpll-zl3073x-detect-dpll-channel-count-from-chip-id-at-runti.patch create mode 100644 1251-dpll-zl3073x-add-die-temperature-reporting-for-supported-chi.patch create mode 100644 1252-dpll-zl3073x-use-struct-group-to-partition-states.patch create mode 100644 1253-dpll-zl3073x-add-zl3073x-ref-state-update-helper.patch create mode 100644 1254-dpll-zl3073x-introduce-zl3073x-chan-for-dpll-channel-state.patch create mode 100644 1255-dpll-zl3073x-add-dpll-channel-status-fields-to-zl3073x-chan.patch create mode 100644 1256-dpll-zl3073x-add-reference-priority-to-zl3073x-chan.patch create mode 100644 1257-dpll-zl3073x-drop-selected-and-simplify-connected-ref-getter.patch create mode 100644 1258-dpll-add-frequency-monitoring-to-netlink-spec.patch create mode 100644 1259-dpll-add-frequency-monitoring-callback-ops.patch create mode 100644 1260-dpll-zl3073x-implement-frequency-monitoring.patch create mode 100644 1261-dpll-zl3073x-clean-up-esync-get-set-and-use-zl3073x-out-is-n.patch create mode 100644 1262-dpll-zl3073x-use-field-modify-for-clear-and-set-patterns.patch create mode 100644 1263-dpll-zl3073x-add-ref-sync-and-output-clock-type-helpers.patch create mode 100644 1264-dpll-zl3073x-add-ref-sync-pair-support.patch create mode 100644 1265-smb-client-validate-the-whole-dacl-before-rewriting-it-in-ci.patch create mode 100644 1266-smb-client-require-a-full-nfs-mode-sid-before-reading-mode-b.patch create mode 100644 1267-smb-client-scope-end-of-dacl-to-cifs-debug2-use-in-parse-dac.patch create mode 100644 1268-smb-client-use-kzalloc-to-zero-initialize-security-descripto.patch create mode 100644 1269-smb-client-validate-dacloffset-before-building-dacl-pointers.patch create mode 100644 1270-dpll-add-pin-operational-state.patch create mode 100644 1271-dpll-zl3073x-implement-pin-operational-state-reporting.patch create mode 100644 1272-dpll-add-fractional-frequency-offset-to-pin-parent-device.patch create mode 100644 1273-dpll-zl3073x-report-ffo-as-dpll-vs-input-reference-offset.patch create mode 100644 1274-netfilter-flowtable-strictly-check-for-maximum-number-of-act.patch create mode 100644 1275-bluetooth-hci-add-initial-support-for-past.patch create mode 100644 1276-bluetooth-iso-add-support-to-bind-to-trigger-past.patch create mode 100644 1277-bluetooth-hci-add-support-for-ll-extended-feature-set.patch create mode 100644 1278-bluetooth-hci-conn-fix-using-conn-le-tx-rx-phy-as-supported-.patch create mode 100644 1279-bluetooth-l2cap-add-support-for-setting-bt-phy.patch create mode 100644 1280-bluetooth-hci-sync-hci-cmd-sync-queue-once-return-eexist-if-.patch create mode 100644 1281-bluetooth-hci-sync-fix-leaks-when-hci-cmd-sync-queue-once-fa.patch create mode 100644 1282-bluetooth-hci-sync-fix-uaf-in-le-read-features-complete.patch create mode 100644 1283-pnfs-fix-a-missing-wake-up-while-waiting-on-nfs-layout-drain.patch create mode 100644 1284-smb-client-fix-oob-reads-parsing-symlink-error-response.patch create mode 100644 1285-sched-deadline-fix-dl-server-time-accounting.patch create mode 100644 1286-redhat-configs-automotive-disable-config-io-uring.patch create mode 100644 1287-rhel-kabi-dpll-adaptation.patch diff --git a/1245-proc-use-the-same-treatment-to-check-proc-lseek-as-ones-for-.patch b/1245-proc-use-the-same-treatment-to-check-proc-lseek-as-ones-for-.patch new file mode 100644 index 000000000..95d3a236f --- /dev/null +++ b/1245-proc-use-the-same-treatment-to-check-proc-lseek-as-ones-for-.patch @@ -0,0 +1,89 @@ +From 72ad184f05e7a77a7043b9e71943ba3031cc683a Mon Sep 17 00:00:00 2001 +From: CKI Backport Bot +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 +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 + Reviewed-by: Alexey Dobriyan + Cc: Alexei Starovoitov + Cc: Al Viro + Cc: "Edgecombe, Rick P" + Cc: Kirill A. Shuemov + Signed-off-by: Andrew Morton + +Signed-off-by: CKI Backport Bot + +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) + diff --git a/1246-proc-fix-missing-pde-set-flags-for-net-proc-files.patch b/1246-proc-fix-missing-pde-set-flags-for-net-proc-files.patch new file mode 100644 index 000000000..8ee90dcba --- /dev/null +++ b/1246-proc-fix-missing-pde-set-flags-for-net-proc-files.patch @@ -0,0 +1,140 @@ +From 5b2ab6d1f5e5488a9faa61f4d560b239dce0e08b Mon Sep 17 00:00:00 2001 +From: Abhi Das +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 +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 + Reported-by: Lars Wendler + Tested-by: Stefano Brivio + Tested-by: Petr Vaněk + Tested by: Lars Wendler + Cc: Alexei Starovoitov + Cc: Alexey Dobriyan + Cc: Al Viro + Cc: "Edgecombe, Rick P" + Cc: Greg Kroah-Hartman + Cc: Jiri Slaby + Cc: Kirill A. Shutemov + Cc: wangzijie + Cc: + Signed-off-by: Andrew Morton + +Signed-off-by: Abhi Das + +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) + diff --git a/1247-proc-fix-type-confusion-in-pde-set-flags.patch b/1247-proc-fix-type-confusion-in-pde-set-flags.patch new file mode 100644 index 000000000..a4354cad7 --- /dev/null +++ b/1247-proc-fix-type-confusion-in-pde-set-flags.patch @@ -0,0 +1,59 @@ +From 51903c441e999d6c7afdf651f0724c26627950d8 Mon Sep 17 00:00:00 2001 +From: Abhi Das +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 +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 + Reported-by: Brad Spengler + Closes: https://lore.kernel.org/all/20250903065758.3678537-1-wangzijie1@honor.com/ + Cc: Alexey Dobriyan + Cc: Al Viro + Cc: Christian Brauner + Cc: Jiri Slaby + Cc: Stefano Brivio + Cc: + Signed-off-by: Andrew Morton + +Signed-off-by: Abhi Das + +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) + diff --git a/1248-nbd-defer-config-unlock-in-nbd-genl-connect.patch b/1248-nbd-defer-config-unlock-in-nbd-genl-connect.patch new file mode 100644 index 000000000..f606280b7 --- /dev/null +++ b/1248-nbd-defer-config-unlock-in-nbd-genl-connect.patch @@ -0,0 +1,74 @@ +From f78e5b8575baca940843081e029427cfb2679d32 Mon Sep 17 00:00:00 2001 +From: Ming Lei +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 +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 + Reviewed-by: Yu Kuai + Signed-off-by: Jens Axboe + +Signed-off-by: Ming Lei + +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) + diff --git a/1249-crypto-authenc-correctly-pass-einprogress-back-up-to-the-cal.patch b/1249-crypto-authenc-correctly-pass-einprogress-back-up-to-the-cal.patch new file mode 100644 index 000000000..b7a16cdb6 --- /dev/null +++ b/1249-crypto-authenc-correctly-pass-einprogress-back-up-to-the-cal.patch @@ -0,0 +1,205 @@ +From 70b8cf8679755c2013b28e9f61da1576eab4c707 Mon Sep 17 00:00:00 2001 +From: CKI Backport Bot +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 +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 + Reported-by: Mikulas Patocka + Fixes: 180ce7e81030 ("crypto: authenc - Add EINPROGRESS check") + Signed-off-by: Herbert Xu + +Signed-off-by: CKI Backport Bot + +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) + diff --git a/1250-dpll-zl3073x-detect-dpll-channel-count-from-chip-id-at-runti.patch b/1250-dpll-zl3073x-detect-dpll-channel-count-from-chip-id-at-runti.patch new file mode 100644 index 000000000..09ddef885 --- /dev/null +++ b/1250-dpll-zl3073x-detect-dpll-channel-count-from-chip-id-at-runti.patch @@ -0,0 +1,426 @@ +From bf31f0732fbaa881f28bf71e012d2aba97e59316 Mon Sep 17 00:00:00 2001 +From: Ivan Vecera +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 +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 + Link: https://patch.msgid.link/20260227105300.710272-2-ivecera@redhat.com + Reviewed-by: Simon Horman + Signed-off-by: Paolo Abeni + +(cherry picked from commit 4845f2fff730f0cdf8f7fe6401c8b871891cf1cb) +Assisted-by: Patchpal +Signed-off-by: Ivan Vecera + +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) + diff --git a/1251-dpll-zl3073x-add-die-temperature-reporting-for-supported-chi.patch b/1251-dpll-zl3073x-add-die-temperature-reporting-for-supported-chi.patch new file mode 100644 index 000000000..9293f7344 --- /dev/null +++ b/1251-dpll-zl3073x-add-die-temperature-reporting-for-supported-chi.patch @@ -0,0 +1,183 @@ +From 0320baa880dd1e760bf8d15d5d8da898b265f4ed Mon Sep 17 00:00:00 2001 +From: Ivan Vecera +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 +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 + Link: https://patch.msgid.link/20260227105300.710272-3-ivecera@redhat.com + Reviewed-by: Simon Horman + Signed-off-by: Paolo Abeni + +(cherry picked from commit 3a97e02b3e91e4d40095ad9bb6e466d8d7c1a1bc) +Assisted-by: Patchpal +Signed-off-by: Ivan Vecera + +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) + diff --git a/1252-dpll-zl3073x-use-struct-group-to-partition-states.patch b/1252-dpll-zl3073x-use-struct-group-to-partition-states.patch new file mode 100644 index 000000000..07eee22eb --- /dev/null +++ b/1252-dpll-zl3073x-use-struct-group-to-partition-states.patch @@ -0,0 +1,305 @@ +From f222542cbe20b882e741d32029bd9d3a2136cb1c Mon Sep 17 00:00:00 2001 +From: Ivan Vecera +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 +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 + Link: https://patch.msgid.link/20260315174224.399074-2-ivecera@redhat.com + Signed-off-by: Jakub Kicinski + +(cherry picked from commit f327f5a8115e80d954598cf2d5c461873042b7f6) +Assisted-by: Patchpal +Signed-off-by: Ivan Vecera + +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 ++#include + #include + + #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 + #include ++#include + #include + + #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 + #include ++#include + #include + + #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) + diff --git a/1253-dpll-zl3073x-add-zl3073x-ref-state-update-helper.patch b/1253-dpll-zl3073x-add-zl3073x-ref-state-update-helper.patch new file mode 100644 index 000000000..55ea91c14 --- /dev/null +++ b/1253-dpll-zl3073x-add-zl3073x-ref-state-update-helper.patch @@ -0,0 +1,115 @@ +From 98b442e23ddaf8448ee6e7ed30dcbeb137a4f7ad Mon Sep 17 00:00:00 2001 +From: Ivan Vecera +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 +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 + Link: https://patch.msgid.link/20260315174224.399074-3-ivecera@redhat.com + Signed-off-by: Jakub Kicinski + +(cherry picked from commit 05ea2ab3b10075e8fcfcd5e283e486df7055de5e) +Assisted-by: Patchpal +Signed-off-by: Ivan Vecera + +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) + diff --git a/1254-dpll-zl3073x-introduce-zl3073x-chan-for-dpll-channel-state.patch b/1254-dpll-zl3073x-introduce-zl3073x-chan-for-dpll-channel-state.patch new file mode 100644 index 000000000..bd31dc929 --- /dev/null +++ b/1254-dpll-zl3073x-introduce-zl3073x-chan-for-dpll-channel-state.patch @@ -0,0 +1,563 @@ +From 99589ed2ea279188527603207e5beeeeb232c7ed Mon Sep 17 00:00:00 2001 +From: Ivan Vecera +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 +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 + Link: https://patch.msgid.link/20260315174224.399074-4-ivecera@redhat.com + Signed-off-by: Jakub Kicinski + +(cherry picked from commit 3032e95987fa0da656ce3a5eb454674e7cc60a12) +Assisted-by: Patchpal +Signed-off-by: Ivan Vecera + +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 ++#include ++#include ++ ++#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 ++#include ++#include ++ ++#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 + #include + ++#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) + diff --git a/1255-dpll-zl3073x-add-dpll-channel-status-fields-to-zl3073x-chan.patch b/1255-dpll-zl3073x-add-dpll-channel-status-fields-to-zl3073x-chan.patch new file mode 100644 index 000000000..13eb44b1a --- /dev/null +++ b/1255-dpll-zl3073x-add-dpll-channel-status-fields-to-zl3073x-chan.patch @@ -0,0 +1,288 @@ +From 1325513643b0d024227fc1ef13470e2a84989622 Mon Sep 17 00:00:00 2001 +From: Ivan Vecera +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 +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 + Link: https://patch.msgid.link/20260315174224.399074-5-ivecera@redhat.com + Signed-off-by: Jakub Kicinski + +(cherry picked from commit 41bab554d7e9840e17e4c8c7e93647161c6596bf) +Assisted-by: Patchpal +Signed-off-by: Ivan Vecera + +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) + diff --git a/1256-dpll-zl3073x-add-reference-priority-to-zl3073x-chan.patch b/1256-dpll-zl3073x-add-reference-priority-to-zl3073x-chan.patch new file mode 100644 index 000000000..4bd741a93 --- /dev/null +++ b/1256-dpll-zl3073x-add-reference-priority-to-zl3073x-chan.patch @@ -0,0 +1,713 @@ +From 4b33e0ad68ce2edfb7cdce8dc91d166eebf7f54b Mon Sep 17 00:00:00 2001 +From: Ivan Vecera +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 +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 + Link: https://patch.msgid.link/20260315174224.399074-6-ivecera@redhat.com + Signed-off-by: Jakub Kicinski + +(cherry picked from commit f6b075bc3ad545d1c91e181c3f8ea6193d01602f) +Assisted-by: Patchpal +Signed-off-by: Ivan Vecera + +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 + #include + #include + #include +@@ -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 + #include + ++/* ++ * 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) + diff --git a/1257-dpll-zl3073x-drop-selected-and-simplify-connected-ref-getter.patch b/1257-dpll-zl3073x-drop-selected-and-simplify-connected-ref-getter.patch new file mode 100644 index 000000000..55c50a5c2 --- /dev/null +++ b/1257-dpll-zl3073x-drop-selected-and-simplify-connected-ref-getter.patch @@ -0,0 +1,188 @@ +From 6d8e25eab47b0e42b2df4fee21761986644d4f7b Mon Sep 17 00:00:00 2001 +From: Ivan Vecera +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 +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 + Link: https://patch.msgid.link/20260315174224.399074-7-ivecera@redhat.com + Signed-off-by: Jakub Kicinski + +(cherry picked from commit acee049a6af2a7b4baabd28f16fb53628ea6e7a5) +Assisted-by: Patchpal +Signed-off-by: Ivan Vecera + +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) + diff --git a/1258-dpll-add-frequency-monitoring-to-netlink-spec.patch b/1258-dpll-add-frequency-monitoring-to-netlink-spec.patch new file mode 100644 index 000000000..189cb6678 --- /dev/null +++ b/1258-dpll-add-frequency-monitoring-to-netlink-spec.patch @@ -0,0 +1,211 @@ +From cf443396fbf1d5230af6f3e6ff9429f897ac1e2e Mon Sep 17 00:00:00 2001 +From: Ivan Vecera +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 +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 + Signed-off-by: Ivan Vecera + Link: https://patch.msgid.link/20260402184057.1890514-2-ivecera@redhat.com + Signed-off-by: Jakub Kicinski + +(cherry picked from commit 3fdea79c09d169b6ea172b8d36232c3773f39973) +Assisted-by: Patchpal +Signed-off-by: Ivan Vecera + +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) + diff --git a/1259-dpll-add-frequency-monitoring-callback-ops.patch b/1259-dpll-add-frequency-monitoring-callback-ops.patch new file mode 100644 index 000000000..0f4291409 --- /dev/null +++ b/1259-dpll-add-frequency-monitoring-callback-ops.patch @@ -0,0 +1,224 @@ +From c549611ebab6cedb1f8d0981320051d4dfb86ccc Mon Sep 17 00:00:00 2001 +From: Ivan Vecera +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 +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 + Signed-off-by: Ivan Vecera + Link: https://patch.msgid.link/20260402184057.1890514-3-ivecera@redhat.com + Signed-off-by: Jakub Kicinski + +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 + +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) + diff --git a/1260-dpll-zl3073x-implement-frequency-monitoring.patch b/1260-dpll-zl3073x-implement-frequency-monitoring.patch new file mode 100644 index 000000000..a47b1f127 --- /dev/null +++ b/1260-dpll-zl3073x-implement-frequency-monitoring.patch @@ -0,0 +1,399 @@ +From 7f9d57090a5680a3a47f6695f886ca8feb0e5bac Mon Sep 17 00:00:00 2001 +From: Ivan Vecera +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 +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 + Signed-off-by: Ivan Vecera + Link: https://patch.msgid.link/20260402184057.1890514-4-ivecera@redhat.com + Signed-off-by: Jakub Kicinski + +(cherry picked from commit bfc923b642874ea6f94763d6060782072944ebd5) +Assisted-by: Patchpal +Signed-off-by: Ivan Vecera + +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) + diff --git a/1261-dpll-zl3073x-clean-up-esync-get-set-and-use-zl3073x-out-is-n.patch b/1261-dpll-zl3073x-clean-up-esync-get-set-and-use-zl3073x-out-is-n.patch new file mode 100644 index 000000000..c9010d5ce --- /dev/null +++ b/1261-dpll-zl3073x-clean-up-esync-get-set-and-use-zl3073x-out-is-n.patch @@ -0,0 +1,181 @@ +From 83c5fcf1f1b2adc253e0eb10ae85ef11ce0ded24 Mon Sep 17 00:00:00 2001 +From: Ivan Vecera +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 +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 + Reviewed-by: Prathosh Satish + Signed-off-by: Ivan Vecera + Link: https://patch.msgid.link/20260408102716.443099-2-ivecera@redhat.com + Signed-off-by: Jakub Kicinski + +(cherry picked from commit 3c8c39768b10867e4f630080785b602245f01760) +Assisted-by: Patchpal +Signed-off-by: Ivan Vecera + +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) + diff --git a/1262-dpll-zl3073x-use-field-modify-for-clear-and-set-patterns.patch b/1262-dpll-zl3073x-use-field-modify-for-clear-and-set-patterns.patch new file mode 100644 index 000000000..56292061b --- /dev/null +++ b/1262-dpll-zl3073x-use-field-modify-for-clear-and-set-patterns.patch @@ -0,0 +1,99 @@ +From f4680f5c34a8af844573d88340c8b625a0628d25 Mon Sep 17 00:00:00 2001 +From: Ivan Vecera +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 +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 + Reviewed-by: Prathosh Satish + Signed-off-by: Ivan Vecera + Link: https://patch.msgid.link/20260408102716.443099-3-ivecera@redhat.com + Signed-off-by: Jakub Kicinski + +(cherry picked from commit 737cb6195c40acf67c876f509e209158436cf287) +Assisted-by: Patchpal +Signed-off-by: Ivan Vecera + +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) + diff --git a/1263-dpll-zl3073x-add-ref-sync-and-output-clock-type-helpers.patch b/1263-dpll-zl3073x-add-ref-sync-and-output-clock-type-helpers.patch new file mode 100644 index 000000000..44beef3e5 --- /dev/null +++ b/1263-dpll-zl3073x-add-ref-sync-and-output-clock-type-helpers.patch @@ -0,0 +1,219 @@ +From ea098b764d85e9c992398b51e59f4fc69a7351ba Mon Sep 17 00:00:00 2001 +From: Ivan Vecera +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 +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 + Reviewed-by: Prathosh Satish + Signed-off-by: Ivan Vecera + Link: https://patch.msgid.link/20260408102716.443099-4-ivecera@redhat.com + Signed-off-by: Jakub Kicinski + +(cherry picked from commit 63009eb92b0f379afddbba8dfdf8df087f6d5b62) +Assisted-by: Patchpal +Signed-off-by: Ivan Vecera + +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) + diff --git a/1264-dpll-zl3073x-add-ref-sync-pair-support.patch b/1264-dpll-zl3073x-add-ref-sync-pair-support.patch new file mode 100644 index 000000000..29824e2c2 --- /dev/null +++ b/1264-dpll-zl3073x-add-ref-sync-pair-support.patch @@ -0,0 +1,325 @@ +From ef38c6a6c3f4e6f931e21f54c88741a1a0c84f27 Mon Sep 17 00:00:00 2001 +From: Ivan Vecera +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 +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 + Reviewed-by: Prathosh Satish + Signed-off-by: Ivan Vecera + Link: https://patch.msgid.link/20260408102716.443099-6-ivecera@redhat.com + Signed-off-by: Jakub Kicinski + +(cherry picked from commit 14f269ae699869ddaca7c29c9c6c52288e3bfb73) +Assisted-by: Patchpal +Signed-off-by: Ivan Vecera + +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 + #include + #include ++#include + #include + #include + +@@ -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) + diff --git a/1265-smb-client-validate-the-whole-dacl-before-rewriting-it-in-ci.patch b/1265-smb-client-validate-the-whole-dacl-before-rewriting-it-in-ci.patch new file mode 100644 index 000000000..f4f11acb9 --- /dev/null +++ b/1265-smb-client-validate-the-whole-dacl-before-rewriting-it-in-ci.patch @@ -0,0 +1,233 @@ +From 55e372bffed1f937fe3cbac3a2afe058eb4657da Mon Sep 17 00:00:00 2001 +From: Paulo Alcantara +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 +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 + Signed-off-by: Steve French + +Signed-off-by: Paulo Alcantara + +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) + diff --git a/1266-smb-client-require-a-full-nfs-mode-sid-before-reading-mode-b.patch b/1266-smb-client-require-a-full-nfs-mode-sid-before-reading-mode-b.patch new file mode 100644 index 000000000..41c7ef248 --- /dev/null +++ b/1266-smb-client-require-a-full-nfs-mode-sid-before-reading-mode-b.patch @@ -0,0 +1,50 @@ +From d8a6ff81310035796f62a3786a33b7ba906091b0 Mon Sep 17 00:00:00 2001 +From: Paulo Alcantara +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 +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 + Signed-off-by: Steve French + +Signed-off-by: Paulo Alcantara + +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) + diff --git a/1267-smb-client-scope-end-of-dacl-to-cifs-debug2-use-in-parse-dac.patch b/1267-smb-client-scope-end-of-dacl-to-cifs-debug2-use-in-parse-dac.patch new file mode 100644 index 000000000..ac8240202 --- /dev/null +++ b/1267-smb-client-scope-end-of-dacl-to-cifs-debug2-use-in-parse-dac.patch @@ -0,0 +1,69 @@ +From 59a296878f29e33031dad8c186168e8e5b50e95a Mon Sep 17 00:00:00 2001 +From: Paulo Alcantara +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 +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 + 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 + Assisted-by: Claude:claude-opus-4-7 + Signed-off-by: Steve French + +Signed-off-by: Paulo Alcantara + +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) + diff --git a/1268-smb-client-use-kzalloc-to-zero-initialize-security-descripto.patch b/1268-smb-client-use-kzalloc-to-zero-initialize-security-descripto.patch new file mode 100644 index 000000000..aeb35e307 --- /dev/null +++ b/1268-smb-client-use-kzalloc-to-zero-initialize-security-descripto.patch @@ -0,0 +1,57 @@ +From f1579c843ecda9218102033597e2bafe1ce8ec74 Mon Sep 17 00:00:00 2001 +From: Paulo Alcantara +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 +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 + 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 + +Signed-off-by: Paulo Alcantara + +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) + diff --git a/1269-smb-client-validate-dacloffset-before-building-dacl-pointers.patch b/1269-smb-client-validate-dacloffset-before-building-dacl-pointers.patch new file mode 100644 index 000000000..3ed5def6c --- /dev/null +++ b/1269-smb-client-validate-dacloffset-before-building-dacl-pointers.patch @@ -0,0 +1,123 @@ +From eff4c8b51d7c82746c2efa0e4ba30576d1082169 Mon Sep 17 00:00:00 2001 +From: Paulo Alcantara +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 +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 + Signed-off-by: Steve French + +Signed-off-by: Paulo Alcantara + +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) + diff --git a/1270-dpll-add-pin-operational-state.patch b/1270-dpll-add-pin-operational-state.patch new file mode 100644 index 000000000..029a39ba4 --- /dev/null +++ b/1270-dpll-add-pin-operational-state.patch @@ -0,0 +1,294 @@ +From 05ce58c7fda9c607fb135c297cbef74db41489c2 Mon Sep 17 00:00:00 2001 +From: Ivan Vecera +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 +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 + Reviewed-by: Jiri Pirko + Reviewed-by: Vadim Fedorenko + Reviewed-by: Petr Oros + Link: https://patch.msgid.link/20260428154907.2820654-2-ivecera@redhat.com + Signed-off-by: Paolo Abeni + +(cherry picked from commit 781c8893a5da8522ae4ded93e5ef3adbe9559300) +Assisted-by: Patchpal +Signed-off-by: Ivan Vecera + +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 + + /* 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 + + /* 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) + diff --git a/1271-dpll-zl3073x-implement-pin-operational-state-reporting.patch b/1271-dpll-zl3073x-implement-pin-operational-state-reporting.patch new file mode 100644 index 000000000..41686b64f --- /dev/null +++ b/1271-dpll-zl3073x-implement-pin-operational-state-reporting.patch @@ -0,0 +1,254 @@ +From 80d47d653c6ca35a7dbfc6c8abc9486445c1e068 Mon Sep 17 00:00:00 2001 +From: Ivan Vecera +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 +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 + Reviewed-by: Petr Oros + Link: https://patch.msgid.link/20260428154907.2820654-3-ivecera@redhat.com + Signed-off-by: Paolo Abeni + +(cherry picked from commit c53f8f8dce776e032b1a11fb4d9b6e12bb63d958) +Assisted-by: Patchpal +Signed-off-by: Ivan Vecera + +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) + diff --git a/1272-dpll-add-fractional-frequency-offset-to-pin-parent-device.patch b/1272-dpll-add-fractional-frequency-offset-to-pin-parent-device.patch new file mode 100644 index 000000000..a20359ac6 --- /dev/null +++ b/1272-dpll-add-fractional-frequency-offset-to-pin-parent-device.patch @@ -0,0 +1,325 @@ +From 071d49d03d957fe6ab483b8447328dc278cbf6f1 Mon Sep 17 00:00:00 2001 +From: Ivan Vecera +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 +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 + Signed-off-by: Ivan Vecera + Link: https://patch.msgid.link/20260511155816.99936-2-ivecera@redhat.com + Signed-off-by: Jakub Kicinski + +(cherry picked from commit 9c11fcb2e9a54d0f1467380831e2e4bb68f7498d) +Assisted-by: Patchpal +Signed-off-by: Ivan Vecera + +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) + diff --git a/1273-dpll-zl3073x-report-ffo-as-dpll-vs-input-reference-offset.patch b/1273-dpll-zl3073x-report-ffo-as-dpll-vs-input-reference-offset.patch new file mode 100644 index 000000000..38343d90a --- /dev/null +++ b/1273-dpll-zl3073x-report-ffo-as-dpll-vs-input-reference-offset.patch @@ -0,0 +1,362 @@ +From ba5cbd1742d290baabec9f9d37a9a8f3a10ab860 Mon Sep 17 00:00:00 2001 +From: Ivan Vecera +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 +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 + Signed-off-by: Ivan Vecera + Link: https://patch.msgid.link/20260511155816.99936-3-ivecera@redhat.com + Signed-off-by: Jakub Kicinski + +(cherry picked from commit 54e65df8cf18a741745645aed7ae91514d437b43) +Assisted-by: Patchpal +Signed-off-by: Ivan Vecera + +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 + #include + #include + #include +@@ -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) + diff --git a/1274-netfilter-flowtable-strictly-check-for-maximum-number-of-act.patch b/1274-netfilter-flowtable-strictly-check-for-maximum-number-of-act.patch new file mode 100644 index 000000000..9ee8a8a7d --- /dev/null +++ b/1274-netfilter-flowtable-strictly-check-for-maximum-number-of-act.patch @@ -0,0 +1,500 @@ +From 57c78bd2e2dd08897acd35b2bf8bcef322e36f5e Mon Sep 17 00:00:00 2001 +From: Pablo Neira Ayuso +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 +Signed-off-by: Pablo Neira Ayuso +Signed-off-by: Sasha Levin + +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 + #include + ++#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) + diff --git a/1275-bluetooth-hci-add-initial-support-for-past.patch b/1275-bluetooth-hci-add-initial-support-for-past.patch new file mode 100644 index 000000000..e682eb00c --- /dev/null +++ b/1275-bluetooth-hci-add-initial-support-for-past.patch @@ -0,0 +1,355 @@ +From c33ae38cff3d15c40384b4ecbaccc4c88d3cb741 Mon Sep 17 00:00:00 2001 +From: David Marlin +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 +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 + +Signed-off-by: David Marlin + +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) + diff --git a/1276-bluetooth-iso-add-support-to-bind-to-trigger-past.patch b/1276-bluetooth-iso-add-support-to-bind-to-trigger-past.patch new file mode 100644 index 000000000..e165f2899 --- /dev/null +++ b/1276-bluetooth-iso-add-support-to-bind-to-trigger-past.patch @@ -0,0 +1,300 @@ +From 414511072bcdf8a60a29bf43cca47260006df84b Mon Sep 17 00:00:00 2001 +From: David Marlin +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 +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 + +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 + +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) + diff --git a/1277-bluetooth-hci-add-support-for-ll-extended-feature-set.patch b/1277-bluetooth-hci-add-support-for-ll-extended-feature-set.patch new file mode 100644 index 000000000..fce45213d --- /dev/null +++ b/1277-bluetooth-hci-add-support-for-ll-extended-feature-set.patch @@ -0,0 +1,437 @@ +From 390744e4031283550f4dd646cff099d5952d3683 Mon Sep 17 00:00:00 2001 +From: David Marlin +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 +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 + +Signed-off-by: David Marlin + +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) + diff --git a/1278-bluetooth-hci-conn-fix-using-conn-le-tx-rx-phy-as-supported-.patch b/1278-bluetooth-hci-conn-fix-using-conn-le-tx-rx-phy-as-supported-.patch new file mode 100644 index 000000000..383c2f463 --- /dev/null +++ b/1278-bluetooth-hci-conn-fix-using-conn-le-tx-rx-phy-as-supported-.patch @@ -0,0 +1,136 @@ +From 8c647364b6263e4d076a7c96f55989b5c0be9598 Mon Sep 17 00:00:00 2001 +From: David Marlin +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 +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 + +Signed-off-by: David Marlin + +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) + diff --git a/1279-bluetooth-l2cap-add-support-for-setting-bt-phy.patch b/1279-bluetooth-l2cap-add-support-for-setting-bt-phy.patch new file mode 100644 index 000000000..e50b693d0 --- /dev/null +++ b/1279-bluetooth-l2cap-add-support-for-setting-bt-phy.patch @@ -0,0 +1,442 @@ +From 81102db981020f3c766c0e7c88bdd2bf7d12ea30 Mon Sep 17 00:00:00 2001 +From: David Marlin +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 +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 + +Signed-off-by: David Marlin + +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) + diff --git a/1280-bluetooth-hci-sync-hci-cmd-sync-queue-once-return-eexist-if-.patch b/1280-bluetooth-hci-sync-hci-cmd-sync-queue-once-return-eexist-if-.patch new file mode 100644 index 000000000..79c06383f --- /dev/null +++ b/1280-bluetooth-hci-sync-hci-cmd-sync-queue-once-return-eexist-if-.patch @@ -0,0 +1,180 @@ +From 73744f2131d220b7c5f7571863c45381764f4559 Mon Sep 17 00:00:00 2001 +From: David Marlin +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 +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 + Signed-off-by: Luiz Augusto von Dentz + +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 + +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) + diff --git a/1281-bluetooth-hci-sync-fix-leaks-when-hci-cmd-sync-queue-once-fa.patch b/1281-bluetooth-hci-sync-fix-leaks-when-hci-cmd-sync-queue-once-fa.patch new file mode 100644 index 000000000..17088d3b9 --- /dev/null +++ b/1281-bluetooth-hci-sync-fix-leaks-when-hci-cmd-sync-queue-once-fa.patch @@ -0,0 +1,69 @@ +From 3eed784fc3a9e5477b2948a4fa33f6ce32c33319 Mon Sep 17 00:00:00 2001 +From: David Marlin +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 +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 + Signed-off-by: Luiz Augusto von Dentz + +Signed-off-by: David Marlin + +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) + diff --git a/1282-bluetooth-hci-sync-fix-uaf-in-le-read-features-complete.patch b/1282-bluetooth-hci-sync-fix-uaf-in-le-read-features-complete.patch new file mode 100644 index 000000000..9fc9acdf4 --- /dev/null +++ b/1282-bluetooth-hci-sync-fix-uaf-in-le-read-features-complete.patch @@ -0,0 +1,243 @@ +From b4a37eb54b3d79e24a8ad9cd4a58baecef81189a Mon Sep 17 00:00:00 2001 +From: David Marlin +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 +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: + + __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 + + + 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 + Signed-off-by: Pauli Virtanen + +Signed-off-by: David Marlin + +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) + diff --git a/1283-pnfs-fix-a-missing-wake-up-while-waiting-on-nfs-layout-drain.patch b/1283-pnfs-fix-a-missing-wake-up-while-waiting-on-nfs-layout-drain.patch new file mode 100644 index 000000000..a66092e5e --- /dev/null +++ b/1283-pnfs-fix-a-missing-wake-up-while-waiting-on-nfs-layout-drain.patch @@ -0,0 +1,63 @@ +From 1f16a5989467930fa2a1bf222fdc20207b917c50 Mon Sep 17 00:00:00 2001 +From: Olga Kornievskaia +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 +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 + Fixes: 880265c77ac4 ("pNFS: Avoid a live lock condition in pnfs_update_layout()") + Signed-off-by: Olga Kornievskaia + Signed-off-by: Anna Schumaker + +Signed-off-by: Olga Kornievskaia + +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) + diff --git a/1284-smb-client-fix-oob-reads-parsing-symlink-error-response.patch b/1284-smb-client-fix-oob-reads-parsing-symlink-error-response.patch new file mode 100644 index 000000000..114aa5bac --- /dev/null +++ b/1284-smb-client-fix-oob-reads-parsing-symlink-error-response.patch @@ -0,0 +1,123 @@ +From 33e2b5cdb5bd48ccb14dd2e4db98bc07d209dc70 Mon Sep 17 00:00:00 2001 +From: CKI Backport Bot +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 +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 + Cc: Shyam Prasad N + Cc: Tom Talpey + Cc: Bharath SM + Cc: linux-cifs@vger.kernel.org + Cc: samba-technical@lists.samba.org + Cc: stable + Reviewed-by: Paulo Alcantara (Red Hat) + Assisted-by: gregkh_clanker_t1000 + Signed-off-by: Greg Kroah-Hartman + Signed-off-by: Steve French + +Signed-off-by: CKI Backport Bot + +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) + diff --git a/1285-sched-deadline-fix-dl-server-time-accounting.patch b/1285-sched-deadline-fix-dl-server-time-accounting.patch new file mode 100644 index 000000000..405ef2d28 --- /dev/null +++ b/1285-sched-deadline-fix-dl-server-time-accounting.patch @@ -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, diff --git a/1286-redhat-configs-automotive-disable-config-io-uring.patch b/1286-redhat-configs-automotive-disable-config-io-uring.patch new file mode 100644 index 000000000..b7c2785c3 --- /dev/null +++ b/1286-redhat-configs-automotive-disable-config-io-uring.patch @@ -0,0 +1,30 @@ +From 65d0f5264af85b67546a34dc94b9b7778ed50e89 Mon Sep 17 00:00:00 2001 +From: Brian Masney +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 + +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) + diff --git a/1287-rhel-kabi-dpll-adaptation.patch b/1287-rhel-kabi-dpll-adaptation.patch new file mode 100644 index 000000000..0f03229b0 --- /dev/null +++ b/1287-rhel-kabi-dpll-adaptation.patch @@ -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) diff --git a/kernel.spec b/kernel.spec index 87d91154a..29ff813e6 100644 --- a/kernel.spec +++ b/kernel.spec @@ -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 - 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 [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 [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 - 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: