]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/commitdiff
6.7-stable patches
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sat, 27 Jan 2024 00:21:09 +0000 (16:21 -0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sat, 27 Jan 2024 00:21:09 +0000 (16:21 -0800)
added patches:
fs-proc-task_mmu-move-mmu-notification-mechanism-inside-mm-lock.patch
kexec-do-syscore_shutdown-in-kernel_kexec.patch
mm-migrate-fix-getting-incorrect-page-mapping-during-page-migration.patch
mm-rmap-fix-misplaced-parenthesis-of-a-likely.patch
mm-sparsemem-fix-race-in-accessing-memory_section-usage.patch
rename-fix-the-locking-of-subdirectories.patch
selftests-mm-hugepage-vmemmap-fails-on-64k-page-size-systems.patch
serial-sc16is7xx-change-efr-lock-to-operate-on-each-channels.patch
serial-sc16is7xx-convert-from-_raw_-to-_noinc_-regmap-functions-for-fifo.patch
serial-sc16is7xx-fix-invalid-sc16is7xx_lines-bitfield-in-case-of-probe-error.patch
serial-sc16is7xx-fix-unconditional-activation-of-thri-interrupt.patch
serial-sc16is7xx-improve-do-while-loop-in-sc16is7xx_irq.patch
serial-sc16is7xx-improve-regmap-debugfs-by-using-one-regmap-per-port.patch
serial-sc16is7xx-remove-global-regmap-from-struct-sc16is7xx_port.patch
serial-sc16is7xx-remove-obsolete-loop-in-sc16is7xx_port_irq.patch
serial-sc16is7xx-remove-unused-line-structure-member.patch
serial-sc16is7xx-remove-wasteful-static-buffer-in-sc16is7xx_regmap_name.patch
thermal-gov_power_allocator-avoid-inability-to-reset-a-cdev.patch

19 files changed:
queue-6.7/fs-proc-task_mmu-move-mmu-notification-mechanism-inside-mm-lock.patch [new file with mode: 0644]
queue-6.7/kexec-do-syscore_shutdown-in-kernel_kexec.patch [new file with mode: 0644]
queue-6.7/mm-migrate-fix-getting-incorrect-page-mapping-during-page-migration.patch [new file with mode: 0644]
queue-6.7/mm-rmap-fix-misplaced-parenthesis-of-a-likely.patch [new file with mode: 0644]
queue-6.7/mm-sparsemem-fix-race-in-accessing-memory_section-usage.patch [new file with mode: 0644]
queue-6.7/rename-fix-the-locking-of-subdirectories.patch [new file with mode: 0644]
queue-6.7/selftests-mm-hugepage-vmemmap-fails-on-64k-page-size-systems.patch [new file with mode: 0644]
queue-6.7/serial-sc16is7xx-change-efr-lock-to-operate-on-each-channels.patch [new file with mode: 0644]
queue-6.7/serial-sc16is7xx-convert-from-_raw_-to-_noinc_-regmap-functions-for-fifo.patch [new file with mode: 0644]
queue-6.7/serial-sc16is7xx-fix-invalid-sc16is7xx_lines-bitfield-in-case-of-probe-error.patch [new file with mode: 0644]
queue-6.7/serial-sc16is7xx-fix-unconditional-activation-of-thri-interrupt.patch [new file with mode: 0644]
queue-6.7/serial-sc16is7xx-improve-do-while-loop-in-sc16is7xx_irq.patch [new file with mode: 0644]
queue-6.7/serial-sc16is7xx-improve-regmap-debugfs-by-using-one-regmap-per-port.patch [new file with mode: 0644]
queue-6.7/serial-sc16is7xx-remove-global-regmap-from-struct-sc16is7xx_port.patch [new file with mode: 0644]
queue-6.7/serial-sc16is7xx-remove-obsolete-loop-in-sc16is7xx_port_irq.patch [new file with mode: 0644]
queue-6.7/serial-sc16is7xx-remove-unused-line-structure-member.patch [new file with mode: 0644]
queue-6.7/serial-sc16is7xx-remove-wasteful-static-buffer-in-sc16is7xx_regmap_name.patch [new file with mode: 0644]
queue-6.7/series
queue-6.7/thermal-gov_power_allocator-avoid-inability-to-reset-a-cdev.patch [new file with mode: 0644]

diff --git a/queue-6.7/fs-proc-task_mmu-move-mmu-notification-mechanism-inside-mm-lock.patch b/queue-6.7/fs-proc-task_mmu-move-mmu-notification-mechanism-inside-mm-lock.patch
new file mode 100644 (file)
index 0000000..6e78cfd
--- /dev/null
@@ -0,0 +1,106 @@
+From 4cccb6221cae6d020270606b9e52b1678fc8b71a Mon Sep 17 00:00:00 2001
+From: Muhammad Usama Anjum <usama.anjum@collabora.com>
+Date: Tue, 9 Jan 2024 16:24:42 +0500
+Subject: fs/proc/task_mmu: move mmu notification mechanism inside mm lock
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+From: Muhammad Usama Anjum <usama.anjum@collabora.com>
+
+commit 4cccb6221cae6d020270606b9e52b1678fc8b71a upstream.
+
+Move mmu notification mechanism inside mm lock to prevent race condition
+in other components which depend on it.  The notifier will invalidate
+memory range.  Depending upon the number of iterations, different memory
+ranges would be invalidated.
+
+The following warning would be removed by this patch:
+WARNING: CPU: 0 PID: 5067 at arch/x86/kvm/../../../virt/kvm/kvm_main.c:734 kvm_mmu_notifier_change_pte+0x860/0x960 arch/x86/kvm/../../../virt/kvm/kvm_main.c:734
+
+There is no behavioural and performance change with this patch when
+there is no component registered with the mmu notifier.
+
+[akpm@linux-foundation.org: narrow the scope of `range', per Sean]
+Link: https://lkml.kernel.org/r/20240109112445.590736-1-usama.anjum@collabora.com
+Fixes: 52526ca7fdb9 ("fs/proc/task_mmu: implement IOCTL to get and optionally clear info about PTEs")
+Signed-off-by: Muhammad Usama Anjum <usama.anjum@collabora.com>
+Reported-by: syzbot+81227d2bd69e9dedb802@syzkaller.appspotmail.com
+Link: https://lore.kernel.org/all/000000000000f6d051060c6785bc@google.com/
+Reviewed-by: Sean Christopherson <seanjc@google.com>
+Cc: Andrei Vagin <avagin@google.com>
+Cc: Arnd Bergmann <arnd@arndb.de>
+Cc: David Hildenbrand <david@redhat.com>
+Cc: Hugh Dickins <hughd@google.com>
+Cc: Kefeng Wang <wangkefeng.wang@huawei.com>
+Cc: Liam R. Howlett <Liam.Howlett@oracle.com>
+Cc: Michał Mirosław <mirq-linux@rere.qmqm.pl>
+Cc: Peter Xu <peterx@redhat.com>
+Cc: Ryan Roberts <ryan.roberts@arm.com>
+Cc: Stephen Rothwell <sfr@canb.auug.org.au>
+Cc: Suren Baghdasaryan <surenb@google.com>
+Cc: <stable@vger.kernel.org>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/proc/task_mmu.c |   24 +++++++++++++-----------
+ 1 file changed, 13 insertions(+), 11 deletions(-)
+
+--- a/fs/proc/task_mmu.c
++++ b/fs/proc/task_mmu.c
+@@ -2415,7 +2415,6 @@ static long pagemap_scan_flush_buffer(st
+ static long do_pagemap_scan(struct mm_struct *mm, unsigned long uarg)
+ {
+-      struct mmu_notifier_range range;
+       struct pagemap_scan_private p = {0};
+       unsigned long walk_start;
+       size_t n_ranges_out = 0;
+@@ -2431,15 +2430,9 @@ static long do_pagemap_scan(struct mm_st
+       if (ret)
+               return ret;
+-      /* Protection change for the range is going to happen. */
+-      if (p.arg.flags & PM_SCAN_WP_MATCHING) {
+-              mmu_notifier_range_init(&range, MMU_NOTIFY_PROTECTION_VMA, 0,
+-                                      mm, p.arg.start, p.arg.end);
+-              mmu_notifier_invalidate_range_start(&range);
+-      }
+-
+       for (walk_start = p.arg.start; walk_start < p.arg.end;
+                       walk_start = p.arg.walk_end) {
++              struct mmu_notifier_range range;
+               long n_out;
+               if (fatal_signal_pending(current)) {
+@@ -2450,8 +2443,20 @@ static long do_pagemap_scan(struct mm_st
+               ret = mmap_read_lock_killable(mm);
+               if (ret)
+                       break;
++
++              /* Protection change for the range is going to happen. */
++              if (p.arg.flags & PM_SCAN_WP_MATCHING) {
++                      mmu_notifier_range_init(&range, MMU_NOTIFY_PROTECTION_VMA, 0,
++                                              mm, walk_start, p.arg.end);
++                      mmu_notifier_invalidate_range_start(&range);
++              }
++
+               ret = walk_page_range(mm, walk_start, p.arg.end,
+                                     &pagemap_scan_ops, &p);
++
++              if (p.arg.flags & PM_SCAN_WP_MATCHING)
++                      mmu_notifier_invalidate_range_end(&range);
++
+               mmap_read_unlock(mm);
+               n_out = pagemap_scan_flush_buffer(&p);
+@@ -2477,9 +2482,6 @@ static long do_pagemap_scan(struct mm_st
+       if (pagemap_scan_writeback_args(&p.arg, uarg))
+               ret = -EFAULT;
+-      if (p.arg.flags & PM_SCAN_WP_MATCHING)
+-              mmu_notifier_invalidate_range_end(&range);
+-
+       kfree(p.vec_buf);
+       return ret;
+ }
diff --git a/queue-6.7/kexec-do-syscore_shutdown-in-kernel_kexec.patch b/queue-6.7/kexec-do-syscore_shutdown-in-kernel_kexec.patch
new file mode 100644 (file)
index 0000000..42fb758
--- /dev/null
@@ -0,0 +1,94 @@
+From 7bb943806ff61e83ae4cceef8906b7fe52453e8a Mon Sep 17 00:00:00 2001
+From: James Gowans <jgowans@amazon.com>
+Date: Wed, 13 Dec 2023 08:40:04 +0200
+Subject: kexec: do syscore_shutdown() in kernel_kexec
+
+From: James Gowans <jgowans@amazon.com>
+
+commit 7bb943806ff61e83ae4cceef8906b7fe52453e8a upstream.
+
+syscore_shutdown() runs driver and module callbacks to get the system into
+a state where it can be correctly shut down.  In commit 6f389a8f1dd2 ("PM
+/ reboot: call syscore_shutdown() after disable_nonboot_cpus()")
+syscore_shutdown() was removed from kernel_restart_prepare() and hence got
+(incorrectly?) removed from the kexec flow.  This was innocuous until
+commit 6735150b6997 ("KVM: Use syscore_ops instead of reboot_notifier to
+hook restart/shutdown") changed the way that KVM registered its shutdown
+callbacks, switching from reboot notifiers to syscore_ops.shutdown.  As
+syscore_shutdown() is missing from kexec, KVM's shutdown hook is not run
+and virtualisation is left enabled on the boot CPU which results in triple
+faults when switching to the new kernel on Intel x86 VT-x with VMXE
+enabled.
+
+Fix this by adding syscore_shutdown() to the kexec sequence.  In terms of
+where to add it, it is being added after migrating the kexec task to the
+boot CPU, but before APs are shut down.  It is not totally clear if this
+is the best place: in commit 6f389a8f1dd2 ("PM / reboot: call
+syscore_shutdown() after disable_nonboot_cpus()") it is stated that
+"syscore_ops operations should be carried with one CPU on-line and
+interrupts disabled." APs are only offlined later in machine_shutdown(),
+so this syscore_shutdown() is being run while APs are still online.  This
+seems to be the correct place as it matches where syscore_shutdown() is
+run in the reboot and halt flows - they also run it before APs are shut
+down.  The assumption is that the commit message in commit 6f389a8f1dd2
+("PM / reboot: call syscore_shutdown() after disable_nonboot_cpus()") is
+no longer valid.
+
+KVM has been discussed here as it is what broke loudly by not having
+syscore_shutdown() in kexec, but this change impacts more than just KVM;
+all drivers/modules which register a syscore_ops.shutdown callback will
+now be invoked in the kexec flow.  Looking at some of them like x86 MCE it
+is probably more correct to also shut these down during kexec.
+Maintainers of all drivers which use syscore_ops.shutdown are added on CC
+for visibility.  They are:
+
+arch/powerpc/platforms/cell/spu_base.c  .shutdown = spu_shutdown,
+arch/x86/kernel/cpu/mce/core.c         .shutdown = mce_syscore_shutdown,
+arch/x86/kernel/i8259.c                 .shutdown = i8259A_shutdown,
+drivers/irqchip/irq-i8259.c            .shutdown = i8259A_shutdown,
+drivers/irqchip/irq-sun6i-r.c          .shutdown = sun6i_r_intc_shutdown,
+drivers/leds/trigger/ledtrig-cpu.c     .shutdown = ledtrig_cpu_syscore_shutdown,
+drivers/power/reset/sc27xx-poweroff.c  .shutdown = sc27xx_poweroff_shutdown,
+kernel/irq/generic-chip.c              .shutdown = irq_gc_shutdown,
+virt/kvm/kvm_main.c                    .shutdown = kvm_shutdown,
+
+This has been tested by doing a kexec on x86_64 and aarch64.
+
+Link: https://lkml.kernel.org/r/20231213064004.2419447-1-jgowans@amazon.com
+Fixes: 6735150b6997 ("KVM: Use syscore_ops instead of reboot_notifier to hook restart/shutdown")
+Signed-off-by: James Gowans <jgowans@amazon.com>
+Cc: Baoquan He <bhe@redhat.com>
+Cc: Eric Biederman <ebiederm@xmission.com>
+Cc: Paolo Bonzini <pbonzini@redhat.com>
+Cc: Sean Christopherson <seanjc@google.com>
+Cc: Marc Zyngier <maz@kernel.org>
+Cc: Arnd Bergmann <arnd@arndb.de>
+Cc: Tony Luck <tony.luck@intel.com>
+Cc: Borislav Petkov <bp@alien8.de>
+Cc: Thomas Gleixner <tglx@linutronix.de>
+Cc: Ingo Molnar <mingo@redhat.com>
+Cc: Chen-Yu Tsai <wens@csie.org>
+Cc: Jernej Skrabec <jernej.skrabec@gmail.com>
+Cc: Samuel Holland <samuel@sholland.org>
+Cc: Pavel Machek <pavel@ucw.cz>
+Cc: Sebastian Reichel <sre@kernel.org>
+Cc: Orson Zhai <orsonzhai@gmail.com>
+Cc: Alexander Graf <graf@amazon.de>
+Cc: Jan H. Schoenherr <jschoenh@amazon.de>
+Cc: <stable@vger.kernel.org>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ kernel/kexec_core.c |    1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/kernel/kexec_core.c
++++ b/kernel/kexec_core.c
+@@ -1254,6 +1254,7 @@ int kernel_kexec(void)
+               kexec_in_progress = true;
+               kernel_restart_prepare("kexec reboot");
+               migrate_to_reboot_cpu();
++              syscore_shutdown();
+               /*
+                * migrate_to_reboot_cpu() disables CPU hotplug assuming that
diff --git a/queue-6.7/mm-migrate-fix-getting-incorrect-page-mapping-during-page-migration.patch b/queue-6.7/mm-migrate-fix-getting-incorrect-page-mapping-during-page-migration.patch
new file mode 100644 (file)
index 0000000..4b02aa6
--- /dev/null
@@ -0,0 +1,129 @@
+From d1adb25df7111de83b64655a80b5a135adbded61 Mon Sep 17 00:00:00 2001
+From: Baolin Wang <baolin.wang@linux.alibaba.com>
+Date: Fri, 15 Dec 2023 20:07:52 +0800
+Subject: mm: migrate: fix getting incorrect page mapping during page migration
+
+From: Baolin Wang <baolin.wang@linux.alibaba.com>
+
+commit d1adb25df7111de83b64655a80b5a135adbded61 upstream.
+
+When running stress-ng testing, we found below kernel crash after a few hours:
+
+Unable to handle kernel NULL pointer dereference at virtual address 0000000000000000
+pc : dentry_name+0xd8/0x224
+lr : pointer+0x22c/0x370
+sp : ffff800025f134c0
+......
+Call trace:
+  dentry_name+0xd8/0x224
+  pointer+0x22c/0x370
+  vsnprintf+0x1ec/0x730
+  vscnprintf+0x2c/0x60
+  vprintk_store+0x70/0x234
+  vprintk_emit+0xe0/0x24c
+  vprintk_default+0x3c/0x44
+  vprintk_func+0x84/0x2d0
+  printk+0x64/0x88
+  __dump_page+0x52c/0x530
+  dump_page+0x14/0x20
+  set_migratetype_isolate+0x110/0x224
+  start_isolate_page_range+0xc4/0x20c
+  offline_pages+0x124/0x474
+  memory_block_offline+0x44/0xf4
+  memory_subsys_offline+0x3c/0x70
+  device_offline+0xf0/0x120
+  ......
+
+After analyzing the vmcore, I found this issue is caused by page migration.
+The scenario is that, one thread is doing page migration, and we will use the
+target page's ->mapping field to save 'anon_vma' pointer between page unmap and
+page move, and now the target page is locked and refcount is 1.
+
+Currently, there is another stress-ng thread performing memory hotplug,
+attempting to offline the target page that is being migrated. It discovers that
+the refcount of this target page is 1, preventing the offline operation, thus
+proceeding to dump the page. However, page_mapping() of the target page may
+return an incorrect file mapping to crash the system in dump_mapping(), since
+the target page->mapping only saves 'anon_vma' pointer without setting
+PAGE_MAPPING_ANON flag.
+
+There are seveval ways to fix this issue:
+(1) Setting the PAGE_MAPPING_ANON flag for target page's ->mapping when saving
+'anon_vma', but this can confuse PageAnon() for PFN walkers, since the target
+page has not built mappings yet.
+(2) Getting the page lock to call page_mapping() in __dump_page() to avoid crashing
+the system, however, there are still some PFN walkers that call page_mapping()
+without holding the page lock, such as compaction.
+(3) Using target page->private field to save the 'anon_vma' pointer and 2 bits
+page state, just as page->mapping records an anonymous page, which can remove
+the page_mapping() impact for PFN walkers and also seems a simple way.
+
+So I choose option 3 to fix this issue, and this can also fix other potential
+issues for PFN walkers, such as compaction.
+
+Link: https://lkml.kernel.org/r/e60b17a88afc38cb32f84c3e30837ec70b343d2b.1702641709.git.baolin.wang@linux.alibaba.com
+Fixes: 64c8902ed441 ("migrate_pages: split unmap_and_move() to _unmap() and _move()")
+Signed-off-by: Baolin Wang <baolin.wang@linux.alibaba.com>
+Reviewed-by: "Huang, Ying" <ying.huang@intel.com>
+Cc: Matthew Wilcox <willy@infradead.org>
+Cc: David Hildenbrand <david@redhat.com>
+Cc: Xu Yu <xuyu@linux.alibaba.com>
+Cc: Zi Yan <ziy@nvidia.com>
+Cc: <stable@vger.kernel.org>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ mm/migrate.c |   27 ++++++++++-----------------
+ 1 file changed, 10 insertions(+), 17 deletions(-)
+
+--- a/mm/migrate.c
++++ b/mm/migrate.c
+@@ -1025,38 +1025,31 @@ out:
+ }
+ /*
+- * To record some information during migration, we use some unused
+- * fields (mapping and private) of struct folio of the newly allocated
+- * destination folio.  This is safe because nobody is using them
+- * except us.
++ * To record some information during migration, we use unused private
++ * field of struct folio of the newly allocated destination folio.
++ * This is safe because nobody is using it except us.
+  */
+-union migration_ptr {
+-      struct anon_vma *anon_vma;
+-      struct address_space *mapping;
+-};
+-
+ enum {
+       PAGE_WAS_MAPPED = BIT(0),
+       PAGE_WAS_MLOCKED = BIT(1),
++      PAGE_OLD_STATES = PAGE_WAS_MAPPED | PAGE_WAS_MLOCKED,
+ };
+ static void __migrate_folio_record(struct folio *dst,
+-                                 unsigned long old_page_state,
++                                 int old_page_state,
+                                  struct anon_vma *anon_vma)
+ {
+-      union migration_ptr ptr = { .anon_vma = anon_vma };
+-      dst->mapping = ptr.mapping;
+-      dst->private = (void *)old_page_state;
++      dst->private = (void *)anon_vma + old_page_state;
+ }
+ static void __migrate_folio_extract(struct folio *dst,
+                                  int *old_page_state,
+                                  struct anon_vma **anon_vmap)
+ {
+-      union migration_ptr ptr = { .mapping = dst->mapping };
+-      *anon_vmap = ptr.anon_vma;
+-      *old_page_state = (unsigned long)dst->private;
+-      dst->mapping = NULL;
++      unsigned long private = (unsigned long)dst->private;
++
++      *anon_vmap = (struct anon_vma *)(private & ~PAGE_OLD_STATES);
++      *old_page_state = private & PAGE_OLD_STATES;
+       dst->private = NULL;
+ }
diff --git a/queue-6.7/mm-rmap-fix-misplaced-parenthesis-of-a-likely.patch b/queue-6.7/mm-rmap-fix-misplaced-parenthesis-of-a-likely.patch
new file mode 100644 (file)
index 0000000..cf98887
--- /dev/null
@@ -0,0 +1,64 @@
+From f67f8d4a8c1e1ebc85a6cbdb9a7266f14863461c Mon Sep 17 00:00:00 2001
+From: "Steven Rostedt (Google)" <rostedt@goodmis.org>
+Date: Fri, 1 Dec 2023 14:59:36 -0500
+Subject: mm/rmap: fix misplaced parenthesis of a likely()
+
+From: Steven Rostedt (Google) <rostedt@goodmis.org>
+
+commit f67f8d4a8c1e1ebc85a6cbdb9a7266f14863461c upstream.
+
+Running my yearly branch profiler to see where likely/unlikely annotation
+may be added or removed, I discovered this:
+
+correct incorrect  %        Function                  File              Line
+ ------- ---------  -        --------                  ----              ----
+       0   457918 100 page_try_dup_anon_rmap         rmap.h               264
+[..]
+  458021        0   0 page_try_dup_anon_rmap         rmap.h               265
+
+I thought it was interesting that line 264 of rmap.h had a 100% incorrect
+annotation, but the line directly below it was 100% correct. Looking at the
+code:
+
+       if (likely(!is_device_private_page(page) &&
+           unlikely(page_needs_cow_for_dma(vma, page))))
+
+It didn't make sense. The "likely()" was around the entire if statement
+(not just the "!is_device_private_page(page)"), which also included the
+"unlikely()" portion of that if condition.
+
+If the unlikely portion is unlikely to be true, that would make the entire
+if condition unlikely to be true, so it made no sense at all to say the
+entire if condition is true.
+
+What is more likely to be likely is just the first part of the if statement
+before the && operation. It's likely to be a misplaced parenthesis. And
+after making the if condition broken into a likely() && unlikely(), both
+now appear to be correct!
+
+Link: https://lkml.kernel.org/r/20231201145936.5ddfdb50@gandalf.local.home
+Fixes:fb3d824d1a46c ("mm/rmap: split page_dup_rmap() into page_dup_file_rmap() and page_try_dup_anon_rmap()")
+Signed-off-by: Steven Rostedt (Google) <rostedt@goodmis.org>
+Acked-by: Vlastimil Babka <vbabka@suse.cz>
+Cc: David Hildenbrand <david@redhat.com>
+Cc: Vlastimil Babka <vbabka@suse.cz>
+Cc: <stable@vger.kernel.org>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/rmap.h |    4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/include/linux/rmap.h
++++ b/include/linux/rmap.h
+@@ -261,8 +261,8 @@ static inline int page_try_dup_anon_rmap
+        * guarantee the pinned page won't be randomly replaced in the
+        * future on write faults.
+        */
+-      if (likely(!is_device_private_page(page) &&
+-          unlikely(page_needs_cow_for_dma(vma, page))))
++      if (likely(!is_device_private_page(page)) &&
++          unlikely(page_needs_cow_for_dma(vma, page)))
+               return -EBUSY;
+       ClearPageAnonExclusive(page);
diff --git a/queue-6.7/mm-sparsemem-fix-race-in-accessing-memory_section-usage.patch b/queue-6.7/mm-sparsemem-fix-race-in-accessing-memory_section-usage.patch
new file mode 100644 (file)
index 0000000..9264b47
--- /dev/null
@@ -0,0 +1,206 @@
+From 5ec8e8ea8b7783fab150cf86404fc38cb4db8800 Mon Sep 17 00:00:00 2001
+From: Charan Teja Kalla <quic_charante@quicinc.com>
+Date: Fri, 13 Oct 2023 18:34:27 +0530
+Subject: mm/sparsemem: fix race in accessing memory_section->usage
+
+From: Charan Teja Kalla <quic_charante@quicinc.com>
+
+commit 5ec8e8ea8b7783fab150cf86404fc38cb4db8800 upstream.
+
+The below race is observed on a PFN which falls into the device memory
+region with the system memory configuration where PFN's are such that
+[ZONE_NORMAL ZONE_DEVICE ZONE_NORMAL].  Since normal zone start and end
+pfn contains the device memory PFN's as well, the compaction triggered
+will try on the device memory PFN's too though they end up in NOP(because
+pfn_to_online_page() returns NULL for ZONE_DEVICE memory sections).  When
+from other core, the section mappings are being removed for the
+ZONE_DEVICE region, that the PFN in question belongs to, on which
+compaction is currently being operated is resulting into the kernel crash
+with CONFIG_SPASEMEM_VMEMAP enabled.  The crash logs can be seen at [1].
+
+compact_zone()                 memunmap_pages
+-------------                  ---------------
+__pageblock_pfn_to_page
+   ......
+ (a)pfn_valid():
+     valid_section()//return true
+                             (b)__remove_pages()->
+                                 sparse_remove_section()->
+                                   section_deactivate():
+                                   [Free the array ms->usage and set
+                                    ms->usage = NULL]
+     pfn_section_valid()
+     [Access ms->usage which
+     is NULL]
+
+NOTE: From the above it can be said that the race is reduced to between
+the pfn_valid()/pfn_section_valid() and the section deactivate with
+SPASEMEM_VMEMAP enabled.
+
+The commit b943f045a9af("mm/sparse: fix kernel crash with
+pfn_section_valid check") tried to address the same problem by clearing
+the SECTION_HAS_MEM_MAP with the expectation of valid_section() returns
+false thus ms->usage is not accessed.
+
+Fix this issue by the below steps:
+
+a) Clear SECTION_HAS_MEM_MAP before freeing the ->usage.
+
+b) RCU protected read side critical section will either return NULL
+   when SECTION_HAS_MEM_MAP is cleared or can successfully access ->usage.
+
+c) Free the ->usage with kfree_rcu() and set ms->usage = NULL.  No
+   attempt will be made to access ->usage after this as the
+   SECTION_HAS_MEM_MAP is cleared thus valid_section() return false.
+
+Thanks to David/Pavan for their inputs on this patch.
+
+[1] https://lore.kernel.org/linux-mm/994410bb-89aa-d987-1f50-f514903c55aa@quicinc.com/
+
+On Snapdragon SoC, with the mentioned memory configuration of PFN's as
+[ZONE_NORMAL ZONE_DEVICE ZONE_NORMAL], we are able to see bunch of
+issues daily while testing on a device farm.
+
+For this particular issue below is the log.  Though the below log is
+not directly pointing to the pfn_section_valid(){ ms->usage;}, when we
+loaded this dump on T32 lauterbach tool, it is pointing.
+
+[  540.578056] Unable to handle kernel NULL pointer dereference at
+virtual address 0000000000000000
+[  540.578068] Mem abort info:
+[  540.578070]   ESR = 0x0000000096000005
+[  540.578073]   EC = 0x25: DABT (current EL), IL = 32 bits
+[  540.578077]   SET = 0, FnV = 0
+[  540.578080]   EA = 0, S1PTW = 0
+[  540.578082]   FSC = 0x05: level 1 translation fault
+[  540.578085] Data abort info:
+[  540.578086]   ISV = 0, ISS = 0x00000005
+[  540.578088]   CM = 0, WnR = 0
+[  540.579431] pstate: 82400005 (Nzcv daif +PAN -UAO +TCO -DIT -SSBSBTYPE=--)
+[  540.579436] pc : __pageblock_pfn_to_page+0x6c/0x14c
+[  540.579454] lr : compact_zone+0x994/0x1058
+[  540.579460] sp : ffffffc03579b510
+[  540.579463] x29: ffffffc03579b510 x28: 0000000000235800 x27:000000000000000c
+[  540.579470] x26: 0000000000235c00 x25: 0000000000000068 x24:ffffffc03579b640
+[  540.579477] x23: 0000000000000001 x22: ffffffc03579b660 x21:0000000000000000
+[  540.579483] x20: 0000000000235bff x19: ffffffdebf7e3940 x18:ffffffdebf66d140
+[  540.579489] x17: 00000000739ba063 x16: 00000000739ba063 x15:00000000009f4bff
+[  540.579495] x14: 0000008000000000 x13: 0000000000000000 x12:0000000000000001
+[  540.579501] x11: 0000000000000000 x10: 0000000000000000 x9 :ffffff897d2cd440
+[  540.579507] x8 : 0000000000000000 x7 : 0000000000000000 x6 :ffffffc03579b5b4
+[  540.579512] x5 : 0000000000027f25 x4 : ffffffc03579b5b8 x3 :0000000000000001
+[  540.579518] x2 : ffffffdebf7e3940 x1 : 0000000000235c00 x0 :0000000000235800
+[  540.579524] Call trace:
+[  540.579527]  __pageblock_pfn_to_page+0x6c/0x14c
+[  540.579533]  compact_zone+0x994/0x1058
+[  540.579536]  try_to_compact_pages+0x128/0x378
+[  540.579540]  __alloc_pages_direct_compact+0x80/0x2b0
+[  540.579544]  __alloc_pages_slowpath+0x5c0/0xe10
+[  540.579547]  __alloc_pages+0x250/0x2d0
+[  540.579550]  __iommu_dma_alloc_noncontiguous+0x13c/0x3fc
+[  540.579561]  iommu_dma_alloc+0xa0/0x320
+[  540.579565]  dma_alloc_attrs+0xd4/0x108
+
+[quic_charante@quicinc.com: use kfree_rcu() in place of synchronize_rcu(), per David]
+  Link: https://lkml.kernel.org/r/1698403778-20938-1-git-send-email-quic_charante@quicinc.com
+Link: https://lkml.kernel.org/r/1697202267-23600-1-git-send-email-quic_charante@quicinc.com
+Fixes: f46edbd1b151 ("mm/sparsemem: add helpers track active portions of a section at boot")
+Signed-off-by: Charan Teja Kalla <quic_charante@quicinc.com>
+Cc: Aneesh Kumar K.V <aneesh.kumar@linux.ibm.com>
+Cc: Dan Williams <dan.j.williams@intel.com>
+Cc: David Hildenbrand <david@redhat.com>
+Cc: Mel Gorman <mgorman@techsingularity.net>
+Cc: Oscar Salvador <osalvador@suse.de>
+Cc: Vlastimil Babka <vbabka@suse.cz>
+Cc: <stable@vger.kernel.org>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/mmzone.h |   14 +++++++++++---
+ mm/sparse.c            |   17 +++++++++--------
+ 2 files changed, 20 insertions(+), 11 deletions(-)
+
+--- a/include/linux/mmzone.h
++++ b/include/linux/mmzone.h
+@@ -1793,6 +1793,7 @@ static inline unsigned long section_nr_t
+ #define SUBSECTION_ALIGN_DOWN(pfn) ((pfn) & PAGE_SUBSECTION_MASK)
+ struct mem_section_usage {
++      struct rcu_head rcu;
+ #ifdef CONFIG_SPARSEMEM_VMEMMAP
+       DECLARE_BITMAP(subsection_map, SUBSECTIONS_PER_SECTION);
+ #endif
+@@ -1986,7 +1987,7 @@ static inline int pfn_section_valid(stru
+ {
+       int idx = subsection_map_index(pfn);
+-      return test_bit(idx, ms->usage->subsection_map);
++      return test_bit(idx, READ_ONCE(ms->usage)->subsection_map);
+ }
+ #else
+ static inline int pfn_section_valid(struct mem_section *ms, unsigned long pfn)
+@@ -2010,6 +2011,7 @@ static inline int pfn_section_valid(stru
+ static inline int pfn_valid(unsigned long pfn)
+ {
+       struct mem_section *ms;
++      int ret;
+       /*
+        * Ensure the upper PAGE_SHIFT bits are clear in the
+@@ -2023,13 +2025,19 @@ static inline int pfn_valid(unsigned lon
+       if (pfn_to_section_nr(pfn) >= NR_MEM_SECTIONS)
+               return 0;
+       ms = __pfn_to_section(pfn);
+-      if (!valid_section(ms))
++      rcu_read_lock();
++      if (!valid_section(ms)) {
++              rcu_read_unlock();
+               return 0;
++      }
+       /*
+        * Traditionally early sections always returned pfn_valid() for
+        * the entire section-sized span.
+        */
+-      return early_section(ms) || pfn_section_valid(ms, pfn);
++      ret = early_section(ms) || pfn_section_valid(ms, pfn);
++      rcu_read_unlock();
++
++      return ret;
+ }
+ #endif
+--- a/mm/sparse.c
++++ b/mm/sparse.c
+@@ -792,6 +792,13 @@ static void section_deactivate(unsigned
+               unsigned long section_nr = pfn_to_section_nr(pfn);
+               /*
++               * Mark the section invalid so that valid_section()
++               * return false. This prevents code from dereferencing
++               * ms->usage array.
++               */
++              ms->section_mem_map &= ~SECTION_HAS_MEM_MAP;
++
++              /*
+                * When removing an early section, the usage map is kept (as the
+                * usage maps of other sections fall into the same page). It
+                * will be re-used when re-adding the section - which is then no
+@@ -799,16 +806,10 @@ static void section_deactivate(unsigned
+                * was allocated during boot.
+                */
+               if (!PageReserved(virt_to_page(ms->usage))) {
+-                      kfree(ms->usage);
+-                      ms->usage = NULL;
++                      kfree_rcu(ms->usage, rcu);
++                      WRITE_ONCE(ms->usage, NULL);
+               }
+               memmap = sparse_decode_mem_map(ms->section_mem_map, section_nr);
+-              /*
+-               * Mark the section invalid so that valid_section()
+-               * return false. This prevents code from dereferencing
+-               * ms->usage array.
+-               */
+-              ms->section_mem_map &= ~SECTION_HAS_MEM_MAP;
+       }
+       /*
diff --git a/queue-6.7/rename-fix-the-locking-of-subdirectories.patch b/queue-6.7/rename-fix-the-locking-of-subdirectories.patch
new file mode 100644 (file)
index 0000000..7d7985f
--- /dev/null
@@ -0,0 +1,276 @@
+From 22e111ed6c83dcde3037fc81176012721bc34c0b Mon Sep 17 00:00:00 2001
+From: Al Viro <viro@zeniv.linux.org.uk>
+Date: Sun, 19 Nov 2023 20:25:58 -0500
+Subject: rename(): fix the locking of subdirectories
+
+From: Al Viro <viro@zeniv.linux.org.uk>
+
+commit 22e111ed6c83dcde3037fc81176012721bc34c0b upstream.
+
+       We should never lock two subdirectories without having taken
+->s_vfs_rename_mutex; inode pointer order or not, the "order" proposed
+in 28eceeda130f "fs: Lock moved directories" is not transitive, with
+the usual consequences.
+
+       The rationale for locking renamed subdirectory in all cases was
+the possibility of race between rename modifying .. in a subdirectory to
+reflect the new parent and another thread modifying the same subdirectory.
+For a lot of filesystems that's not a problem, but for some it can lead
+to trouble (e.g. the case when short directory contents is kept in the
+inode, but creating a file in it might push it across the size limit
+and copy its contents into separate data block(s)).
+
+       However, we need that only in case when the parent does change -
+otherwise ->rename() doesn't need to do anything with .. entry in the
+first place.  Some instances are lazy and do a tautological update anyway,
+but it's really not hard to avoid.
+
+Amended locking rules for rename():
+       find the parent(s) of source and target
+       if source and target have the same parent
+               lock the common parent
+       else
+               lock ->s_vfs_rename_mutex
+               lock both parents, in ancestor-first order; if neither
+               is an ancestor of another, lock the parent of source
+               first.
+       find the source and target.
+       if source and target have the same parent
+               if operation is an overwriting rename of a subdirectory
+                       lock the target subdirectory
+       else
+               if source is a subdirectory
+                       lock the source
+               if target is a subdirectory
+                       lock the target
+       lock non-directories involved, in inode pointer order if both
+       source and target are such.
+
+That way we are guaranteed that parents are locked (for obvious reasons),
+that any renamed non-directory is locked (nfsd relies upon that),
+that any victim is locked (emptiness check needs that, among other things)
+and subdirectory that changes parent is locked (needed to protect the update
+of .. entries).  We are also guaranteed that any operation locking more
+than one directory either takes ->s_vfs_rename_mutex or locks a parent
+followed by its child.
+
+Cc: stable@vger.kernel.org
+Fixes: 28eceeda130f "fs: Lock moved directories"
+Reviewed-by: Jan Kara <jack@suse.cz>
+Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ Documentation/filesystems/directory-locking.rst |   29 ++++++-----
+ Documentation/filesystems/locking.rst           |    5 +-
+ Documentation/filesystems/porting.rst           |   18 +++++++
+ fs/namei.c                                      |   60 ++++++++++++++----------
+ 4 files changed, 74 insertions(+), 38 deletions(-)
+
+--- a/Documentation/filesystems/directory-locking.rst
++++ b/Documentation/filesystems/directory-locking.rst
+@@ -22,13 +22,16 @@ exclusive.
+ 3) object removal.  Locking rules: caller locks parent, finds victim,
+ locks victim and calls the method.  Locks are exclusive.
+-4) rename() that is _not_ cross-directory.  Locking rules: caller locks the
+-parent and finds source and target.  We lock both (provided they exist).  If we
+-need to lock two inodes of different type (dir vs non-dir), we lock directory
+-first.  If we need to lock two inodes of the same type, lock them in inode
+-pointer order.  Then call the method.  All locks are exclusive.
+-NB: we might get away with locking the source (and target in exchange
+-case) shared.
++4) rename() that is _not_ cross-directory.  Locking rules: caller locks
++the parent and finds source and target.  Then we decide which of the
++source and target need to be locked.  Source needs to be locked if it's a
++non-directory; target - if it's a non-directory or about to be removed.
++Take the locks that need to be taken, in inode pointer order if need
++to take both (that can happen only when both source and target are
++non-directories - the source because it wouldn't be locked otherwise
++and the target because mixing directory and non-directory is allowed
++only with RENAME_EXCHANGE, and that won't be removing the target).
++After the locks had been taken, call the method.  All locks are exclusive.
+ 5) link creation.  Locking rules:
+@@ -44,20 +47,17 @@ rules:
+       * lock the filesystem
+       * lock parents in "ancestors first" order. If one is not ancestor of
+-        the other, lock them in inode pointer order.
++        the other, lock the parent of source first.
+       * find source and target.
+       * if old parent is equal to or is a descendent of target
+         fail with -ENOTEMPTY
+       * if new parent is equal to or is a descendent of source
+         fail with -ELOOP
+-      * Lock both the source and the target provided they exist. If we
+-        need to lock two inodes of different type (dir vs non-dir), we lock
+-        the directory first. If we need to lock two inodes of the same type,
+-        lock them in inode pointer order.
++      * Lock subdirectories involved (source before target).
++      * Lock non-directories involved, in inode pointer order.
+       * call the method.
+-All ->i_rwsem are taken exclusive.  Again, we might get away with locking
+-the source (and target in exchange case) shared.
++All ->i_rwsem are taken exclusive.
+ The rules above obviously guarantee that all directories that are going to be
+ read, modified or removed by method will be locked by caller.
+@@ -67,6 +67,7 @@ If no directory is its own ancestor, the
+ Proof:
++[XXX: will be updated once we are done massaging the lock_rename()]
+       First of all, at any moment we have a linear ordering of the
+       objects - A < B iff (A is an ancestor of B) or (B is not an ancestor
+         of A and ptr(A) < ptr(B)).
+--- a/Documentation/filesystems/locking.rst
++++ b/Documentation/filesystems/locking.rst
+@@ -101,7 +101,7 @@ symlink:   exclusive
+ mkdir:                exclusive
+ unlink:               exclusive (both)
+ rmdir:                exclusive (both)(see below)
+-rename:               exclusive (all) (see below)
++rename:               exclusive (both parents, some children) (see below)
+ readlink:     no
+ get_link:     no
+ setattr:      exclusive
+@@ -123,6 +123,9 @@ get_offset_ctx  no
+       Additionally, ->rmdir(), ->unlink() and ->rename() have ->i_rwsem
+       exclusive on victim.
+       cross-directory ->rename() has (per-superblock) ->s_vfs_rename_sem.
++      ->unlink() and ->rename() have ->i_rwsem exclusive on all non-directories
++      involved.
++      ->rename() has ->i_rwsem exclusive on any subdirectory that changes parent.
+ See Documentation/filesystems/directory-locking.rst for more detailed discussion
+ of the locking scheme for directory operations.
+--- a/Documentation/filesystems/porting.rst
++++ b/Documentation/filesystems/porting.rst
+@@ -1061,3 +1061,21 @@ export_operations ->encode_fh() no longe
+ encode FILEID_INO32_GEN* file handles.
+ Filesystems that used the default implementation may use the generic helper
+ generic_encode_ino32_fh() explicitly.
++
++---
++
++**mandatory**
++
++If ->rename() update of .. on cross-directory move needs an exclusion with
++directory modifications, do *not* lock the subdirectory in question in your
++->rename() - it's done by the caller now [that item should've been added in
++28eceeda130f "fs: Lock moved directories"].
++
++---
++
++**mandatory**
++
++On same-directory ->rename() the (tautological) update of .. is not protected
++by any locks; just don't do it if the old parent is the same as the new one.
++We really can't lock two subdirectories in same-directory rename - not without
++deadlocks.
+--- a/fs/namei.c
++++ b/fs/namei.c
+@@ -3021,20 +3021,14 @@ static struct dentry *lock_two_directori
+       p = d_ancestor(p2, p1);
+       if (p) {
+               inode_lock_nested(p2->d_inode, I_MUTEX_PARENT);
+-              inode_lock_nested(p1->d_inode, I_MUTEX_CHILD);
++              inode_lock_nested(p1->d_inode, I_MUTEX_PARENT2);
+               return p;
+       }
+       p = d_ancestor(p1, p2);
+-      if (p) {
+-              inode_lock_nested(p1->d_inode, I_MUTEX_PARENT);
+-              inode_lock_nested(p2->d_inode, I_MUTEX_CHILD);
+-              return p;
+-      }
+-
+-      lock_two_inodes(p1->d_inode, p2->d_inode,
+-                      I_MUTEX_PARENT, I_MUTEX_PARENT2);
+-      return NULL;
++      inode_lock_nested(p1->d_inode, I_MUTEX_PARENT);
++      inode_lock_nested(p2->d_inode, I_MUTEX_PARENT2);
++      return p;
+ }
+ /*
+@@ -4716,11 +4710,12 @@ SYSCALL_DEFINE2(link, const char __user
+  *
+  *    a) we can get into loop creation.
+  *    b) race potential - two innocent renames can create a loop together.
+- *       That's where 4.4 screws up. Current fix: serialization on
++ *       That's where 4.4BSD screws up. Current fix: serialization on
+  *       sb->s_vfs_rename_mutex. We might be more accurate, but that's another
+  *       story.
+- *    c) we have to lock _four_ objects - parents and victim (if it exists),
+- *       and source.
++ *    c) we may have to lock up to _four_ objects - parents and victim (if it exists),
++ *       and source (if it's a non-directory or a subdirectory that moves to
++ *       different parent).
+  *       And that - after we got ->i_mutex on parents (until then we don't know
+  *       whether the target exists).  Solution: try to be smart with locking
+  *       order for inodes.  We rely on the fact that tree topology may change
+@@ -4752,6 +4747,7 @@ int vfs_rename(struct renamedata *rd)
+       bool new_is_dir = false;
+       unsigned max_links = new_dir->i_sb->s_max_links;
+       struct name_snapshot old_name;
++      bool lock_old_subdir, lock_new_subdir;
+       if (source == target)
+               return 0;
+@@ -4805,15 +4801,32 @@ int vfs_rename(struct renamedata *rd)
+       take_dentry_name_snapshot(&old_name, old_dentry);
+       dget(new_dentry);
+       /*
+-       * Lock all moved children. Moved directories may need to change parent
+-       * pointer so they need the lock to prevent against concurrent
+-       * directory changes moving parent pointer. For regular files we've
+-       * historically always done this. The lockdep locking subclasses are
+-       * somewhat arbitrary but RENAME_EXCHANGE in particular can swap
+-       * regular files and directories so it's difficult to tell which
+-       * subclasses to use.
++       * Lock children.
++       * The source subdirectory needs to be locked on cross-directory
++       * rename or cross-directory exchange since its parent changes.
++       * The target subdirectory needs to be locked on cross-directory
++       * exchange due to parent change and on any rename due to becoming
++       * a victim.
++       * Non-directories need locking in all cases (for NFS reasons);
++       * they get locked after any subdirectories (in inode address order).
++       *
++       * NOTE: WE ONLY LOCK UNRELATED DIRECTORIES IN CROSS-DIRECTORY CASE.
++       * NEVER, EVER DO THAT WITHOUT ->s_vfs_rename_mutex.
+        */
+-      lock_two_inodes(source, target, I_MUTEX_NORMAL, I_MUTEX_NONDIR2);
++      lock_old_subdir = new_dir != old_dir;
++      lock_new_subdir = new_dir != old_dir || !(flags & RENAME_EXCHANGE);
++      if (is_dir) {
++              if (lock_old_subdir)
++                      inode_lock_nested(source, I_MUTEX_CHILD);
++              if (target && (!new_is_dir || lock_new_subdir))
++                      inode_lock(target);
++      } else if (new_is_dir) {
++              if (lock_new_subdir)
++                      inode_lock_nested(target, I_MUTEX_CHILD);
++              inode_lock(source);
++      } else {
++              lock_two_nondirectories(source, target);
++      }
+       error = -EPERM;
+       if (IS_SWAPFILE(source) || (target && IS_SWAPFILE(target)))
+@@ -4861,8 +4874,9 @@ int vfs_rename(struct renamedata *rd)
+                       d_exchange(old_dentry, new_dentry);
+       }
+ out:
+-      inode_unlock(source);
+-      if (target)
++      if (!is_dir || lock_old_subdir)
++              inode_unlock(source);
++      if (target && (!new_is_dir || lock_new_subdir))
+               inode_unlock(target);
+       dput(new_dentry);
+       if (!error) {
diff --git a/queue-6.7/selftests-mm-hugepage-vmemmap-fails-on-64k-page-size-systems.patch b/queue-6.7/selftests-mm-hugepage-vmemmap-fails-on-64k-page-size-systems.patch
new file mode 100644 (file)
index 0000000..7ca82d2
--- /dev/null
@@ -0,0 +1,137 @@
+From 00bcfcd47a52f50f07a2e88d730d7931384cb073 Mon Sep 17 00:00:00 2001
+From: Donet Tom <donettom@linux.vnet.ibm.com>
+Date: Wed, 10 Jan 2024 14:03:35 +0530
+Subject: selftests: mm: hugepage-vmemmap fails on 64K page size systems
+
+From: Donet Tom <donettom@linux.vnet.ibm.com>
+
+commit 00bcfcd47a52f50f07a2e88d730d7931384cb073 upstream.
+
+The kernel sefltest mm/hugepage-vmemmap fails on architectures which has
+different page size other than 4K.  In hugepage-vmemmap page size used is
+4k so the pfn calculation will go wrong on systems which has different
+page size .The length of MAP_HUGETLB memory must be hugepage aligned but
+in hugepage-vmemmap map length is 2M so this will not get aligned if the
+system has differnet hugepage size.
+
+Added  psize() to get the page size and default_huge_page_size() to
+get the default hugepage size at run time, hugepage-vmemmap test pass
+on powerpc with 64K page size and x86 with 4K page size.
+
+Result on powerpc without patch (page size 64K)
+*# ./hugepage-vmemmap
+Returned address is 0x7effff000000 whose pfn is 0
+Head page flags (100000000) is invalid
+check_page_flags: Invalid argument
+*#
+
+Result on powerpc with patch (page size 64K)
+*# ./hugepage-vmemmap
+Returned address is 0x7effff000000 whose pfn is 600
+*#
+
+Result on x86 with patch (page size 4K)
+*# ./hugepage-vmemmap
+Returned address is 0x7fc7c2c00000 whose pfn is 1dac00
+*#
+
+Link: https://lkml.kernel.org/r/3b3a3ae37ba21218481c482a872bbf7526031600.1704865754.git.donettom@linux.vnet.ibm.com
+Fixes: b147c89cd429 ("selftests: vm: add a hugetlb test case")
+Signed-off-by: Donet Tom <donettom@linux.vnet.ibm.com>
+Reported-by: Geetika Moolchandani <geetika@linux.ibm.com>
+Tested-by: Geetika Moolchandani <geetika@linux.ibm.com>
+Acked-by: Muchun Song <muchun.song@linux.dev>
+Cc: <stable@vger.kernel.org>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ tools/testing/selftests/mm/hugepage-vmemmap.c |   29 ++++++++++++++++----------
+ 1 file changed, 18 insertions(+), 11 deletions(-)
+
+--- a/tools/testing/selftests/mm/hugepage-vmemmap.c
++++ b/tools/testing/selftests/mm/hugepage-vmemmap.c
+@@ -10,10 +10,7 @@
+ #include <unistd.h>
+ #include <sys/mman.h>
+ #include <fcntl.h>
+-
+-#define MAP_LENGTH            (2UL * 1024 * 1024)
+-
+-#define PAGE_SIZE             4096
++#include "vm_util.h"
+ #define PAGE_COMPOUND_HEAD    (1UL << 15)
+ #define PAGE_COMPOUND_TAIL    (1UL << 16)
+@@ -39,6 +36,9 @@
+ #define MAP_FLAGS             (MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB)
+ #endif
++static size_t pagesize;
++static size_t maplength;
++
+ static void write_bytes(char *addr, size_t length)
+ {
+       unsigned long i;
+@@ -56,7 +56,7 @@ static unsigned long virt_to_pfn(void *a
+       if (fd < 0)
+               return -1UL;
+-      lseek(fd, (unsigned long)addr / PAGE_SIZE * sizeof(pagemap), SEEK_SET);
++      lseek(fd, (unsigned long)addr / pagesize * sizeof(pagemap), SEEK_SET);
+       read(fd, &pagemap, sizeof(pagemap));
+       close(fd);
+@@ -86,7 +86,7 @@ static int check_page_flags(unsigned lon
+        * this also verifies kernel has correctly set the fake page_head to tail
+        * while hugetlb_free_vmemmap is enabled.
+        */
+-      for (i = 1; i < MAP_LENGTH / PAGE_SIZE; i++) {
++      for (i = 1; i < maplength / pagesize; i++) {
+               read(fd, &pageflags, sizeof(pageflags));
+               if ((pageflags & TAIL_PAGE_FLAGS) != TAIL_PAGE_FLAGS ||
+                   (pageflags & HEAD_PAGE_FLAGS) == HEAD_PAGE_FLAGS) {
+@@ -106,18 +106,25 @@ int main(int argc, char **argv)
+       void *addr;
+       unsigned long pfn;
+-      addr = mmap(MAP_ADDR, MAP_LENGTH, PROT_READ | PROT_WRITE, MAP_FLAGS, -1, 0);
++      pagesize  = psize();
++      maplength = default_huge_page_size();
++      if (!maplength) {
++              printf("Unable to determine huge page size\n");
++              exit(1);
++      }
++
++      addr = mmap(MAP_ADDR, maplength, PROT_READ | PROT_WRITE, MAP_FLAGS, -1, 0);
+       if (addr == MAP_FAILED) {
+               perror("mmap");
+               exit(1);
+       }
+       /* Trigger allocation of HugeTLB page. */
+-      write_bytes(addr, MAP_LENGTH);
++      write_bytes(addr, maplength);
+       pfn = virt_to_pfn(addr);
+       if (pfn == -1UL) {
+-              munmap(addr, MAP_LENGTH);
++              munmap(addr, maplength);
+               perror("virt_to_pfn");
+               exit(1);
+       }
+@@ -125,13 +132,13 @@ int main(int argc, char **argv)
+       printf("Returned address is %p whose pfn is %lx\n", addr, pfn);
+       if (check_page_flags(pfn) < 0) {
+-              munmap(addr, MAP_LENGTH);
++              munmap(addr, maplength);
+               perror("check_page_flags");
+               exit(1);
+       }
+       /* munmap() length of MAP_HUGETLB memory must be hugepage aligned */
+-      if (munmap(addr, MAP_LENGTH)) {
++      if (munmap(addr, maplength)) {
+               perror("munmap");
+               exit(1);
+       }
diff --git a/queue-6.7/serial-sc16is7xx-change-efr-lock-to-operate-on-each-channels.patch b/queue-6.7/serial-sc16is7xx-change-efr-lock-to-operate-on-each-channels.patch
new file mode 100644 (file)
index 0000000..5fd1029
--- /dev/null
@@ -0,0 +1,206 @@
+From 4409df5866b7ff7686ba27e449ca97a92ee063c9 Mon Sep 17 00:00:00 2001
+From: Hugo Villeneuve <hvilleneuve@dimonoff.com>
+Date: Mon, 11 Dec 2023 12:13:51 -0500
+Subject: serial: sc16is7xx: change EFR lock to operate on each channels
+
+From: Hugo Villeneuve <hvilleneuve@dimonoff.com>
+
+commit 4409df5866b7ff7686ba27e449ca97a92ee063c9 upstream.
+
+Now that the driver has been converted to use one regmap per port, change
+efr locking to operate on a channel basis instead of on the whole IC.
+
+Fixes: 3837a0379533 ("serial: sc16is7xx: improve regmap debugfs by using one regmap per port")
+Cc:  <stable@vger.kernel.org> # 6.1.x: 3837a03 serial: sc16is7xx: improve regmap debugfs by using one regmap per port
+Signed-off-by: Hugo Villeneuve <hvilleneuve@dimonoff.com>
+Link: https://lore.kernel.org/r/20231211171353.2901416-5-hugo@hugovil.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/tty/serial/sc16is7xx.c |   49 +++++++++++++++++++++--------------------
+ 1 file changed, 26 insertions(+), 23 deletions(-)
+
+--- a/drivers/tty/serial/sc16is7xx.c
++++ b/drivers/tty/serial/sc16is7xx.c
+@@ -324,6 +324,7 @@ struct sc16is7xx_one_config {
+ struct sc16is7xx_one {
+       struct uart_port                port;
+       struct regmap                   *regmap;
++      struct mutex                    efr_lock; /* EFR registers access */
+       struct kthread_work             tx_work;
+       struct kthread_work             reg_work;
+       struct kthread_delayed_work     ms_work;
+@@ -343,7 +344,6 @@ struct sc16is7xx_port {
+       unsigned char                   buf[SC16IS7XX_FIFO_SIZE];
+       struct kthread_worker           kworker;
+       struct task_struct              *kworker_task;
+-      struct mutex                    efr_lock;
+       struct sc16is7xx_one            p[];
+ };
+@@ -495,7 +495,6 @@ static bool sc16is7xx_regmap_precious(st
+ static int sc16is7xx_set_baud(struct uart_port *port, int baud)
+ {
+-      struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
+       struct sc16is7xx_one *one = to_sc16is7xx_one(port, port);
+       u8 lcr;
+       u8 prescaler = 0;
+@@ -519,7 +518,7 @@ static int sc16is7xx_set_baud(struct uar
+        * because the bulk of the interrupt processing is run as a workqueue
+        * job in thread context.
+        */
+-      mutex_lock(&s->efr_lock);
++      mutex_lock(&one->efr_lock);
+       lcr = sc16is7xx_port_read(port, SC16IS7XX_LCR_REG);
+@@ -538,7 +537,7 @@ static int sc16is7xx_set_baud(struct uar
+       /* Put LCR back to the normal mode */
+       sc16is7xx_port_write(port, SC16IS7XX_LCR_REG, lcr);
+-      mutex_unlock(&s->efr_lock);
++      mutex_unlock(&one->efr_lock);
+       sc16is7xx_port_update(port, SC16IS7XX_MCR_REG,
+                             SC16IS7XX_MCR_CLKSEL_BIT,
+@@ -706,11 +705,10 @@ static unsigned int sc16is7xx_get_hwmctr
+ static void sc16is7xx_update_mlines(struct sc16is7xx_one *one)
+ {
+       struct uart_port *port = &one->port;
+-      struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
+       unsigned long flags;
+       unsigned int status, changed;
+-      lockdep_assert_held_once(&s->efr_lock);
++      lockdep_assert_held_once(&one->efr_lock);
+       status = sc16is7xx_get_hwmctrl(port);
+       changed = status ^ one->old_mctrl;
+@@ -736,15 +734,20 @@ static void sc16is7xx_update_mlines(stru
+ static bool sc16is7xx_port_irq(struct sc16is7xx_port *s, int portno)
+ {
++      bool rc = true;
+       struct uart_port *port = &s->p[portno].port;
++      struct sc16is7xx_one *one = to_sc16is7xx_one(port, port);
++
++      mutex_lock(&one->efr_lock);
+       do {
+               unsigned int iir, rxlen;
+-              struct sc16is7xx_one *one = to_sc16is7xx_one(port, port);
+               iir = sc16is7xx_port_read(port, SC16IS7XX_IIR_REG);
+-              if (iir & SC16IS7XX_IIR_NO_INT_BIT)
+-                      return false;
++              if (iir & SC16IS7XX_IIR_NO_INT_BIT) {
++                      rc = false;
++                      goto out_port_irq;
++              }
+               iir &= SC16IS7XX_IIR_ID_MASK;
+@@ -784,15 +787,17 @@ static bool sc16is7xx_port_irq(struct sc
+                       break;
+               }
+       } while (0);
+-      return true;
++
++out_port_irq:
++      mutex_unlock(&one->efr_lock);
++
++      return rc;
+ }
+ static irqreturn_t sc16is7xx_irq(int irq, void *dev_id)
+ {
+       struct sc16is7xx_port *s = (struct sc16is7xx_port *)dev_id;
+-      mutex_lock(&s->efr_lock);
+-
+       while (1) {
+               bool keep_polling = false;
+               int i;
+@@ -803,24 +808,22 @@ static irqreturn_t sc16is7xx_irq(int irq
+                       break;
+       }
+-      mutex_unlock(&s->efr_lock);
+-
+       return IRQ_HANDLED;
+ }
+ static void sc16is7xx_tx_proc(struct kthread_work *ws)
+ {
+       struct uart_port *port = &(to_sc16is7xx_one(ws, tx_work)->port);
+-      struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
++      struct sc16is7xx_one *one = to_sc16is7xx_one(port, port);
+       unsigned long flags;
+       if ((port->rs485.flags & SER_RS485_ENABLED) &&
+           (port->rs485.delay_rts_before_send > 0))
+               msleep(port->rs485.delay_rts_before_send);
+-      mutex_lock(&s->efr_lock);
++      mutex_lock(&one->efr_lock);
+       sc16is7xx_handle_tx(port);
+-      mutex_unlock(&s->efr_lock);
++      mutex_unlock(&one->efr_lock);
+       uart_port_lock_irqsave(port, &flags);
+       sc16is7xx_ier_set(port, SC16IS7XX_IER_THRI_BIT);
+@@ -927,9 +930,9 @@ static void sc16is7xx_ms_proc(struct kth
+       struct sc16is7xx_port *s = dev_get_drvdata(one->port.dev);
+       if (one->port.state) {
+-              mutex_lock(&s->efr_lock);
++              mutex_lock(&one->efr_lock);
+               sc16is7xx_update_mlines(one);
+-              mutex_unlock(&s->efr_lock);
++              mutex_unlock(&one->efr_lock);
+               kthread_queue_delayed_work(&s->kworker, &one->ms_work, HZ);
+       }
+@@ -1013,7 +1016,6 @@ static void sc16is7xx_set_termios(struct
+                                 struct ktermios *termios,
+                                 const struct ktermios *old)
+ {
+-      struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
+       struct sc16is7xx_one *one = to_sc16is7xx_one(port, port);
+       unsigned int lcr, flow = 0;
+       int baud;
+@@ -1072,7 +1074,7 @@ static void sc16is7xx_set_termios(struct
+               port->ignore_status_mask |= SC16IS7XX_LSR_BRK_ERROR_MASK;
+       /* As above, claim the mutex while accessing the EFR. */
+-      mutex_lock(&s->efr_lock);
++      mutex_lock(&one->efr_lock);
+       sc16is7xx_port_write(port, SC16IS7XX_LCR_REG,
+                            SC16IS7XX_LCR_CONF_MODE_B);
+@@ -1102,7 +1104,7 @@ static void sc16is7xx_set_termios(struct
+       /* Update LCR register */
+       sc16is7xx_port_write(port, SC16IS7XX_LCR_REG, lcr);
+-      mutex_unlock(&s->efr_lock);
++      mutex_unlock(&one->efr_lock);
+       /* Get baud rate generator configuration */
+       baud = uart_get_baud_rate(port, termios, old,
+@@ -1536,7 +1538,6 @@ static int sc16is7xx_probe(struct device
+       s->devtype = devtype;
+       dev_set_drvdata(dev, s);
+-      mutex_init(&s->efr_lock);
+       kthread_init_worker(&s->kworker);
+       s->kworker_task = kthread_run(kthread_worker_fn, &s->kworker,
+@@ -1579,6 +1580,8 @@ static int sc16is7xx_probe(struct device
+                       goto out_ports;
+               }
++              mutex_init(&s->p[i].efr_lock);
++
+               ret = uart_get_rs485_mode(&s->p[i].port);
+               if (ret)
+                       goto out_ports;
diff --git a/queue-6.7/serial-sc16is7xx-convert-from-_raw_-to-_noinc_-regmap-functions-for-fifo.patch b/queue-6.7/serial-sc16is7xx-convert-from-_raw_-to-_noinc_-regmap-functions-for-fifo.patch
new file mode 100644 (file)
index 0000000..8d773dd
--- /dev/null
@@ -0,0 +1,83 @@
+From dbf4ab821804df071c8b566d9813083125e6d97b Mon Sep 17 00:00:00 2001
+From: Hugo Villeneuve <hvilleneuve@dimonoff.com>
+Date: Mon, 11 Dec 2023 12:13:52 -0500
+Subject: serial: sc16is7xx: convert from _raw_ to _noinc_ regmap functions for FIFO
+
+From: Hugo Villeneuve <hvilleneuve@dimonoff.com>
+
+commit dbf4ab821804df071c8b566d9813083125e6d97b upstream.
+
+The SC16IS7XX IC supports a burst mode to access the FIFOs where the
+initial register address is sent ($00), followed by all the FIFO data
+without having to resend the register address each time. In this mode, the
+IC doesn't increment the register address for each R/W byte.
+
+The regmap_raw_read() and regmap_raw_write() are functions which can
+perform IO over multiple registers. They are currently used to read/write
+from/to the FIFO, and although they operate correctly in this burst mode on
+the SPI bus, they would corrupt the regmap cache if it was not disabled
+manually. The reason is that when the R/W size is more than 1 byte, these
+functions assume that the register address is incremented and handle the
+cache accordingly.
+
+Convert FIFO R/W functions to use the regmap _noinc_ versions in order to
+remove the manual cache control which was a workaround when using the
+_raw_ versions. FIFO registers are properly declared as volatile so
+cache will not be used/updated for FIFO accesses.
+
+Fixes: dfeae619d781 ("serial: sc16is7xx")
+Cc:  <stable@vger.kernel.org>
+Signed-off-by: Hugo Villeneuve <hvilleneuve@dimonoff.com>
+Link: https://lore.kernel.org/r/20231211171353.2901416-6-hugo@hugovil.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/tty/serial/sc16is7xx.c |   17 +++++++++++------
+ 1 file changed, 11 insertions(+), 6 deletions(-)
+
+--- a/drivers/tty/serial/sc16is7xx.c
++++ b/drivers/tty/serial/sc16is7xx.c
+@@ -382,9 +382,7 @@ static void sc16is7xx_fifo_read(struct u
+       struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
+       struct sc16is7xx_one *one = to_sc16is7xx_one(port, port);
+-      regcache_cache_bypass(one->regmap, true);
+-      regmap_raw_read(one->regmap, SC16IS7XX_RHR_REG, s->buf, rxlen);
+-      regcache_cache_bypass(one->regmap, false);
++      regmap_noinc_read(one->regmap, SC16IS7XX_RHR_REG, s->buf, rxlen);
+ }
+ static void sc16is7xx_fifo_write(struct uart_port *port, u8 to_send)
+@@ -399,9 +397,7 @@ static void sc16is7xx_fifo_write(struct
+       if (unlikely(!to_send))
+               return;
+-      regcache_cache_bypass(one->regmap, true);
+-      regmap_raw_write(one->regmap, SC16IS7XX_THR_REG, s->buf, to_send);
+-      regcache_cache_bypass(one->regmap, false);
++      regmap_noinc_write(one->regmap, SC16IS7XX_THR_REG, s->buf, to_send);
+ }
+ static void sc16is7xx_port_update(struct uart_port *port, u8 reg,
+@@ -493,6 +489,11 @@ static bool sc16is7xx_regmap_precious(st
+       return false;
+ }
++static bool sc16is7xx_regmap_noinc(struct device *dev, unsigned int reg)
++{
++      return reg == SC16IS7XX_RHR_REG;
++}
++
+ static int sc16is7xx_set_baud(struct uart_port *port, int baud)
+ {
+       struct sc16is7xx_one *one = to_sc16is7xx_one(port, port);
+@@ -1710,6 +1711,10 @@ static struct regmap_config regcfg = {
+       .cache_type = REGCACHE_RBTREE,
+       .volatile_reg = sc16is7xx_regmap_volatile,
+       .precious_reg = sc16is7xx_regmap_precious,
++      .writeable_noinc_reg = sc16is7xx_regmap_noinc,
++      .readable_noinc_reg = sc16is7xx_regmap_noinc,
++      .max_raw_read = SC16IS7XX_FIFO_SIZE,
++      .max_raw_write = SC16IS7XX_FIFO_SIZE,
+       .max_register = SC16IS7XX_EFCR_REG,
+ };
diff --git a/queue-6.7/serial-sc16is7xx-fix-invalid-sc16is7xx_lines-bitfield-in-case-of-probe-error.patch b/queue-6.7/serial-sc16is7xx-fix-invalid-sc16is7xx_lines-bitfield-in-case-of-probe-error.patch
new file mode 100644 (file)
index 0000000..c86a22d
--- /dev/null
@@ -0,0 +1,128 @@
+From 8a1060ce974919f2a79807527ad82ac39336eda2 Mon Sep 17 00:00:00 2001
+From: Hugo Villeneuve <hvilleneuve@dimonoff.com>
+Date: Thu, 21 Dec 2023 18:18:08 -0500
+Subject: serial: sc16is7xx: fix invalid sc16is7xx_lines bitfield in case of probe error
+
+From: Hugo Villeneuve <hvilleneuve@dimonoff.com>
+
+commit 8a1060ce974919f2a79807527ad82ac39336eda2 upstream.
+
+If an error occurs during probing, the sc16is7xx_lines bitfield may be left
+in a state that doesn't represent the correct state of lines allocation.
+
+For example, in a system with two SC16 devices, if an error occurs only
+during probing of channel (port) B of the second device, sc16is7xx_lines
+final state will be 00001011b instead of the expected 00000011b.
+
+This is caused in part because of the "i--" in the for/loop located in
+the out_ports: error path.
+
+Fix this by checking the return value of uart_add_one_port() and set line
+allocation bit only if this was successful. This allows the refactor of
+the obfuscated for(i--...) loop in the error path, and properly call
+uart_remove_one_port() only when needed, and properly unset line allocation
+bits.
+
+Also use same mechanism in remove() when calling uart_remove_one_port().
+
+Fixes: c64349722d14 ("sc16is7xx: support multiple devices")
+Cc:  <stable@vger.kernel.org>
+Cc: Yury Norov <yury.norov@gmail.com>
+Signed-off-by: Hugo Villeneuve <hvilleneuve@dimonoff.com>
+Link: https://lore.kernel.org/r/20231221231823.2327894-2-hugo@hugovil.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/tty/serial/sc16is7xx.c |   44 ++++++++++++++++-------------------------
+ 1 file changed, 18 insertions(+), 26 deletions(-)
+
+--- a/drivers/tty/serial/sc16is7xx.c
++++ b/drivers/tty/serial/sc16is7xx.c
+@@ -408,19 +408,6 @@ static void sc16is7xx_port_update(struct
+       regmap_update_bits(one->regmap, reg, mask, val);
+ }
+-static int sc16is7xx_alloc_line(void)
+-{
+-      int i;
+-
+-      BUILD_BUG_ON(SC16IS7XX_MAX_DEVS > BITS_PER_LONG);
+-
+-      for (i = 0; i < SC16IS7XX_MAX_DEVS; i++)
+-              if (!test_and_set_bit(i, &sc16is7xx_lines))
+-                      break;
+-
+-      return i;
+-}
+-
+ static void sc16is7xx_power(struct uart_port *port, int on)
+ {
+       sc16is7xx_port_update(port, SC16IS7XX_IER_REG,
+@@ -1551,6 +1538,13 @@ static int sc16is7xx_probe(struct device
+                    SC16IS7XX_IOCONTROL_SRESET_BIT);
+       for (i = 0; i < devtype->nr_uart; ++i) {
++              s->p[i].port.line = find_first_zero_bit(&sc16is7xx_lines,
++                                                      SC16IS7XX_MAX_DEVS);
++              if (s->p[i].port.line >= SC16IS7XX_MAX_DEVS) {
++                      ret = -ERANGE;
++                      goto out_ports;
++              }
++
+               /* Initialize port data */
+               s->p[i].port.dev        = dev;
+               s->p[i].port.irq        = irq;
+@@ -1570,14 +1564,8 @@ static int sc16is7xx_probe(struct device
+               s->p[i].port.rs485_supported = sc16is7xx_rs485_supported;
+               s->p[i].port.ops        = &sc16is7xx_ops;
+               s->p[i].old_mctrl       = 0;
+-              s->p[i].port.line       = sc16is7xx_alloc_line();
+               s->p[i].regmap          = regmaps[i];
+-              if (s->p[i].port.line >= SC16IS7XX_MAX_DEVS) {
+-                      ret = -ENOMEM;
+-                      goto out_ports;
+-              }
+-
+               mutex_init(&s->p[i].efr_lock);
+               ret = uart_get_rs485_mode(&s->p[i].port);
+@@ -1595,8 +1583,13 @@ static int sc16is7xx_probe(struct device
+               kthread_init_work(&s->p[i].tx_work, sc16is7xx_tx_proc);
+               kthread_init_work(&s->p[i].reg_work, sc16is7xx_reg_proc);
+               kthread_init_delayed_work(&s->p[i].ms_work, sc16is7xx_ms_proc);
++
+               /* Register port */
+-              uart_add_one_port(&sc16is7xx_uart, &s->p[i].port);
++              ret = uart_add_one_port(&sc16is7xx_uart, &s->p[i].port);
++              if (ret)
++                      goto out_ports;
++
++              set_bit(s->p[i].port.line, &sc16is7xx_lines);
+               /* Enable EFR */
+               sc16is7xx_port_write(&s->p[i].port, SC16IS7XX_LCR_REG,
+@@ -1654,10 +1647,9 @@ static int sc16is7xx_probe(struct device
+ #endif
+ out_ports:
+-      for (i--; i >= 0; i--) {
+-              uart_remove_one_port(&sc16is7xx_uart, &s->p[i].port);
+-              clear_bit(s->p[i].port.line, &sc16is7xx_lines);
+-      }
++      for (i = 0; i < devtype->nr_uart; i++)
++              if (test_and_clear_bit(s->p[i].port.line, &sc16is7xx_lines))
++                      uart_remove_one_port(&sc16is7xx_uart, &s->p[i].port);
+       kthread_stop(s->kworker_task);
+@@ -1679,8 +1671,8 @@ static void sc16is7xx_remove(struct devi
+       for (i = 0; i < s->devtype->nr_uart; i++) {
+               kthread_cancel_delayed_work_sync(&s->p[i].ms_work);
+-              uart_remove_one_port(&sc16is7xx_uart, &s->p[i].port);
+-              clear_bit(s->p[i].port.line, &sc16is7xx_lines);
++              if (test_and_clear_bit(s->p[i].port.line, &sc16is7xx_lines))
++                      uart_remove_one_port(&sc16is7xx_uart, &s->p[i].port);
+               sc16is7xx_power(&s->p[i].port, 0);
+       }
diff --git a/queue-6.7/serial-sc16is7xx-fix-unconditional-activation-of-thri-interrupt.patch b/queue-6.7/serial-sc16is7xx-fix-unconditional-activation-of-thri-interrupt.patch
new file mode 100644 (file)
index 0000000..fc7dba4
--- /dev/null
@@ -0,0 +1,70 @@
+From 9915753037eba7135b209fef4f2afeca841af816 Mon Sep 17 00:00:00 2001
+From: Hugo Villeneuve <hvilleneuve@dimonoff.com>
+Date: Mon, 11 Dec 2023 12:13:53 -0500
+Subject: serial: sc16is7xx: fix unconditional activation of THRI interrupt
+
+From: Hugo Villeneuve <hvilleneuve@dimonoff.com>
+
+commit 9915753037eba7135b209fef4f2afeca841af816 upstream.
+
+Commit cc4c1d05eb10 ("sc16is7xx: Properly resume TX after stop") changed
+behavior to unconditionnaly set the THRI interrupt in sc16is7xx_tx_proc().
+
+For example when sending a 65 bytes message, and assuming the Tx FIFO is
+initially empty, sc16is7xx_handle_tx() will write the first 64 bytes of the
+message to the FIFO and sc16is7xx_tx_proc() will then activate THRI. When
+the THRI IRQ is fired, the driver will write the remaining byte of the
+message to the FIFO, and disable THRI by calling sc16is7xx_stop_tx().
+
+When sending a 2 bytes message, sc16is7xx_handle_tx() will write the 2
+bytes of the message to the FIFO and call sc16is7xx_stop_tx(), disabling
+THRI. After sc16is7xx_handle_tx() exits, control returns to
+sc16is7xx_tx_proc() which will unconditionally set THRI. When the THRI IRQ
+is fired, the driver simply acknowledges the interrupt and does nothing
+more, since all the data has already been written to the FIFO. This results
+in 2 register writes and 4 register reads all for nothing and taking
+precious cycles from the I2C/SPI bus.
+
+Fix this by enabling the THRI interrupt only when we fill the Tx FIFO to
+its maximum capacity and there are remaining bytes to send in the message.
+
+Fixes: cc4c1d05eb10 ("sc16is7xx: Properly resume TX after stop")
+Cc:  <stable@vger.kernel.org>
+Signed-off-by: Hugo Villeneuve <hvilleneuve@dimonoff.com>
+Link: https://lore.kernel.org/r/20231211171353.2901416-7-hugo@hugovil.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/tty/serial/sc16is7xx.c |    7 ++-----
+ 1 file changed, 2 insertions(+), 5 deletions(-)
+
+--- a/drivers/tty/serial/sc16is7xx.c
++++ b/drivers/tty/serial/sc16is7xx.c
+@@ -688,6 +688,8 @@ static void sc16is7xx_handle_tx(struct u
+       if (uart_circ_empty(xmit))
+               sc16is7xx_stop_tx(port);
++      else
++              sc16is7xx_ier_set(port, SC16IS7XX_IER_THRI_BIT);
+       uart_port_unlock_irqrestore(port, flags);
+ }
+@@ -816,7 +818,6 @@ static void sc16is7xx_tx_proc(struct kth
+ {
+       struct uart_port *port = &(to_sc16is7xx_one(ws, tx_work)->port);
+       struct sc16is7xx_one *one = to_sc16is7xx_one(port, port);
+-      unsigned long flags;
+       if ((port->rs485.flags & SER_RS485_ENABLED) &&
+           (port->rs485.delay_rts_before_send > 0))
+@@ -825,10 +826,6 @@ static void sc16is7xx_tx_proc(struct kth
+       mutex_lock(&one->efr_lock);
+       sc16is7xx_handle_tx(port);
+       mutex_unlock(&one->efr_lock);
+-
+-      uart_port_lock_irqsave(port, &flags);
+-      sc16is7xx_ier_set(port, SC16IS7XX_IER_THRI_BIT);
+-      uart_port_unlock_irqrestore(port, flags);
+ }
+ static void sc16is7xx_reconf_rs485(struct uart_port *port)
diff --git a/queue-6.7/serial-sc16is7xx-improve-do-while-loop-in-sc16is7xx_irq.patch b/queue-6.7/serial-sc16is7xx-improve-do-while-loop-in-sc16is7xx_irq.patch
new file mode 100644 (file)
index 0000000..d4d2a66
--- /dev/null
@@ -0,0 +1,49 @@
+From d5078509c8b06c5c472a60232815e41af81c6446 Mon Sep 17 00:00:00 2001
+From: Hugo Villeneuve <hvilleneuve@dimonoff.com>
+Date: Thu, 21 Dec 2023 18:18:12 -0500
+Subject: serial: sc16is7xx: improve do/while loop in sc16is7xx_irq()
+
+From: Hugo Villeneuve <hvilleneuve@dimonoff.com>
+
+commit d5078509c8b06c5c472a60232815e41af81c6446 upstream.
+
+Simplify and improve readability by replacing while(1) loop with
+do {} while, and by using the keep_polling variable as the exit
+condition, making it more explicit.
+
+Fixes: 834449872105 ("sc16is7xx: Fix for multi-channel stall")
+Cc:  <stable@vger.kernel.org>
+Suggested-by: Andy Shevchenko <andy.shevchenko@gmail.com>
+Signed-off-by: Hugo Villeneuve <hvilleneuve@dimonoff.com>
+Link: https://lore.kernel.org/r/20231221231823.2327894-6-hugo@hugovil.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/tty/serial/sc16is7xx.c |   11 ++++++-----
+ 1 file changed, 6 insertions(+), 5 deletions(-)
+
+--- a/drivers/tty/serial/sc16is7xx.c
++++ b/drivers/tty/serial/sc16is7xx.c
+@@ -783,17 +783,18 @@ out_port_irq:
+ static irqreturn_t sc16is7xx_irq(int irq, void *dev_id)
+ {
++      bool keep_polling;
++
+       struct sc16is7xx_port *s = (struct sc16is7xx_port *)dev_id;
+-      while (1) {
+-              bool keep_polling = false;
++      do {
+               int i;
++              keep_polling = false;
++
+               for (i = 0; i < s->devtype->nr_uart; ++i)
+                       keep_polling |= sc16is7xx_port_irq(s, i);
+-              if (!keep_polling)
+-                      break;
+-      }
++      } while (keep_polling);
+       return IRQ_HANDLED;
+ }
diff --git a/queue-6.7/serial-sc16is7xx-improve-regmap-debugfs-by-using-one-regmap-per-port.patch b/queue-6.7/serial-sc16is7xx-improve-regmap-debugfs-by-using-one-regmap-per-port.patch
new file mode 100644 (file)
index 0000000..81484cc
--- /dev/null
@@ -0,0 +1,437 @@
+From 3837a0379533aabb9e4483677077479f7c6aa910 Mon Sep 17 00:00:00 2001
+From: Hugo Villeneuve <hvilleneuve@dimonoff.com>
+Date: Mon, 30 Oct 2023 17:14:47 -0400
+Subject: serial: sc16is7xx: improve regmap debugfs by using one regmap per port
+
+From: Hugo Villeneuve <hvilleneuve@dimonoff.com>
+
+commit 3837a0379533aabb9e4483677077479f7c6aa910 upstream.
+
+With this current driver regmap implementation, it is hard to make sense
+of the register addresses displayed using the regmap debugfs interface,
+because they do not correspond to the actual register addresses documented
+in the datasheet. For example, register 1 is displayed as registers 04 thru
+07:
+
+$ cat /sys/kernel/debug/regmap/spi0.0/registers
+  04: 10 -> Port 0, register offset 1
+  05: 10 -> Port 1, register offset 1
+  06: 00 -> Port 2, register offset 1 -> invalid
+  07: 00 -> port 3, register offset 1 -> invalid
+  ...
+
+The reason is that bits 0 and 1 of the register address correspond to the
+channel (port) bits, so the register address itself starts at bit 2, and we
+must 'mentally' shift each register address by 2 bits to get its real
+address/offset.
+
+Also, only channels 0 and 1 are supported by the chip, so channel mask
+combinations of 10b and 11b are invalid, and the display of these
+registers is useless.
+
+This patch adds a separate regmap configuration for each port, similar to
+what is done in the max310x driver, so that register addresses displayed
+match the register addresses in the chip datasheet. Also, each port now has
+its own debugfs entry.
+
+Example with new regmap implementation:
+
+$ cat /sys/kernel/debug/regmap/spi0.0-port0/registers
+1: 10
+2: 01
+3: 00
+...
+
+$ cat /sys/kernel/debug/regmap/spi0.0-port1/registers
+1: 10
+2: 01
+3: 00
+
+As an added bonus, this also simplifies some operations (read/write/modify)
+because it is no longer necessary to manually shift register addresses.
+
+Signed-off-by: Hugo Villeneuve <hvilleneuve@dimonoff.com>
+Link: https://lore.kernel.org/r/20231030211447.974779-1-hugo@hugovil.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/tty/serial/sc16is7xx.c |  143 +++++++++++++++++++++++------------------
+ 1 file changed, 81 insertions(+), 62 deletions(-)
+
+--- a/drivers/tty/serial/sc16is7xx.c
++++ b/drivers/tty/serial/sc16is7xx.c
+@@ -301,8 +301,8 @@
+ /* Misc definitions */
++#define SC16IS7XX_SPI_READ_BIT                BIT(7)
+ #define SC16IS7XX_FIFO_SIZE           (64)
+-#define SC16IS7XX_REG_SHIFT           2
+ #define SC16IS7XX_GPIOS_PER_BANK      4
+ struct sc16is7xx_devtype {
+@@ -324,6 +324,7 @@ struct sc16is7xx_one_config {
+ struct sc16is7xx_one {
+       struct uart_port                port;
+       u8                              line;
++      struct regmap                   *regmap;
+       struct kthread_work             tx_work;
+       struct kthread_work             reg_work;
+       struct kthread_delayed_work     ms_work;
+@@ -361,48 +362,37 @@ static void sc16is7xx_stop_tx(struct uar
+ #define to_sc16is7xx_one(p,e) ((container_of((p), struct sc16is7xx_one, e)))
+-static int sc16is7xx_line(struct uart_port *port)
+-{
+-      struct sc16is7xx_one *one = to_sc16is7xx_one(port, port);
+-
+-      return one->line;
+-}
+-
+ static u8 sc16is7xx_port_read(struct uart_port *port, u8 reg)
+ {
+-      struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
++      struct sc16is7xx_one *one = to_sc16is7xx_one(port, port);
+       unsigned int val = 0;
+-      const u8 line = sc16is7xx_line(port);
+-      regmap_read(s->regmap, (reg << SC16IS7XX_REG_SHIFT) | line, &val);
++      regmap_read(one->regmap, reg, &val);
+       return val;
+ }
+ static void sc16is7xx_port_write(struct uart_port *port, u8 reg, u8 val)
+ {
+-      struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
+-      const u8 line = sc16is7xx_line(port);
++      struct sc16is7xx_one *one = to_sc16is7xx_one(port, port);
+-      regmap_write(s->regmap, (reg << SC16IS7XX_REG_SHIFT) | line, val);
++      regmap_write(one->regmap, reg, val);
+ }
+ static void sc16is7xx_fifo_read(struct uart_port *port, unsigned int rxlen)
+ {
+       struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
+-      const u8 line = sc16is7xx_line(port);
+-      u8 addr = (SC16IS7XX_RHR_REG << SC16IS7XX_REG_SHIFT) | line;
++      struct sc16is7xx_one *one = to_sc16is7xx_one(port, port);
+-      regcache_cache_bypass(s->regmap, true);
+-      regmap_raw_read(s->regmap, addr, s->buf, rxlen);
+-      regcache_cache_bypass(s->regmap, false);
++      regcache_cache_bypass(one->regmap, true);
++      regmap_raw_read(one->regmap, SC16IS7XX_RHR_REG, s->buf, rxlen);
++      regcache_cache_bypass(one->regmap, false);
+ }
+ static void sc16is7xx_fifo_write(struct uart_port *port, u8 to_send)
+ {
+       struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
+-      const u8 line = sc16is7xx_line(port);
+-      u8 addr = (SC16IS7XX_THR_REG << SC16IS7XX_REG_SHIFT) | line;
++      struct sc16is7xx_one *one = to_sc16is7xx_one(port, port);
+       /*
+        * Don't send zero-length data, at least on SPI it confuses the chip
+@@ -411,19 +401,17 @@ static void sc16is7xx_fifo_write(struct
+       if (unlikely(!to_send))
+               return;
+-      regcache_cache_bypass(s->regmap, true);
+-      regmap_raw_write(s->regmap, addr, s->buf, to_send);
+-      regcache_cache_bypass(s->regmap, false);
++      regcache_cache_bypass(one->regmap, true);
++      regmap_raw_write(one->regmap, SC16IS7XX_THR_REG, s->buf, to_send);
++      regcache_cache_bypass(one->regmap, false);
+ }
+ static void sc16is7xx_port_update(struct uart_port *port, u8 reg,
+                                 u8 mask, u8 val)
+ {
+-      struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
+-      const u8 line = sc16is7xx_line(port);
++      struct sc16is7xx_one *one = to_sc16is7xx_one(port, port);
+-      regmap_update_bits(s->regmap, (reg << SC16IS7XX_REG_SHIFT) | line,
+-                         mask, val);
++      regmap_update_bits(one->regmap, reg, mask, val);
+ }
+ static int sc16is7xx_alloc_line(void)
+@@ -478,7 +466,7 @@ static const struct sc16is7xx_devtype sc
+ static bool sc16is7xx_regmap_volatile(struct device *dev, unsigned int reg)
+ {
+-      switch (reg >> SC16IS7XX_REG_SHIFT) {
++      switch (reg) {
+       case SC16IS7XX_RHR_REG:
+       case SC16IS7XX_IIR_REG:
+       case SC16IS7XX_LSR_REG:
+@@ -497,7 +485,7 @@ static bool sc16is7xx_regmap_volatile(st
+ static bool sc16is7xx_regmap_precious(struct device *dev, unsigned int reg)
+ {
+-      switch (reg >> SC16IS7XX_REG_SHIFT) {
++      switch (reg) {
+       case SC16IS7XX_RHR_REG:
+               return true;
+       default:
+@@ -510,6 +498,7 @@ static bool sc16is7xx_regmap_precious(st
+ static int sc16is7xx_set_baud(struct uart_port *port, int baud)
+ {
+       struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
++      struct sc16is7xx_one *one = to_sc16is7xx_one(port, port);
+       u8 lcr;
+       u8 prescaler = 0;
+       unsigned long clk = port->uartclk, div = clk / 16 / baud;
+@@ -541,12 +530,12 @@ static int sc16is7xx_set_baud(struct uar
+                            SC16IS7XX_LCR_CONF_MODE_B);
+       /* Enable enhanced features */
+-      regcache_cache_bypass(s->regmap, true);
++      regcache_cache_bypass(one->regmap, true);
+       sc16is7xx_port_update(port, SC16IS7XX_EFR_REG,
+                             SC16IS7XX_EFR_ENABLE_BIT,
+                             SC16IS7XX_EFR_ENABLE_BIT);
+-      regcache_cache_bypass(s->regmap, false);
++      regcache_cache_bypass(one->regmap, false);
+       /* Put LCR back to the normal mode */
+       sc16is7xx_port_write(port, SC16IS7XX_LCR_REG, lcr);
+@@ -562,10 +551,10 @@ static int sc16is7xx_set_baud(struct uar
+                            SC16IS7XX_LCR_CONF_MODE_A);
+       /* Write the new divisor */
+-      regcache_cache_bypass(s->regmap, true);
++      regcache_cache_bypass(one->regmap, true);
+       sc16is7xx_port_write(port, SC16IS7XX_DLH_REG, div / 256);
+       sc16is7xx_port_write(port, SC16IS7XX_DLL_REG, div % 256);
+-      regcache_cache_bypass(s->regmap, false);
++      regcache_cache_bypass(one->regmap, false);
+       /* Put LCR back to the normal mode */
+       sc16is7xx_port_write(port, SC16IS7XX_LCR_REG, lcr);
+@@ -1091,7 +1080,7 @@ static void sc16is7xx_set_termios(struct
+                            SC16IS7XX_LCR_CONF_MODE_B);
+       /* Configure flow control */
+-      regcache_cache_bypass(s->regmap, true);
++      regcache_cache_bypass(one->regmap, true);
+       sc16is7xx_port_write(port, SC16IS7XX_XON1_REG, termios->c_cc[VSTART]);
+       sc16is7xx_port_write(port, SC16IS7XX_XOFF1_REG, termios->c_cc[VSTOP]);
+@@ -1110,7 +1099,7 @@ static void sc16is7xx_set_termios(struct
+                             SC16IS7XX_EFR_REG,
+                             SC16IS7XX_EFR_FLOWCTRL_BITS,
+                             flow);
+-      regcache_cache_bypass(s->regmap, false);
++      regcache_cache_bypass(one->regmap, false);
+       /* Update LCR register */
+       sc16is7xx_port_write(port, SC16IS7XX_LCR_REG, lcr);
+@@ -1161,7 +1150,6 @@ static int sc16is7xx_config_rs485(struct
+ static int sc16is7xx_startup(struct uart_port *port)
+ {
+       struct sc16is7xx_one *one = to_sc16is7xx_one(port, port);
+-      struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
+       unsigned int val;
+       unsigned long flags;
+@@ -1178,7 +1166,7 @@ static int sc16is7xx_startup(struct uart
+       sc16is7xx_port_write(port, SC16IS7XX_LCR_REG,
+                            SC16IS7XX_LCR_CONF_MODE_B);
+-      regcache_cache_bypass(s->regmap, true);
++      regcache_cache_bypass(one->regmap, true);
+       /* Enable write access to enhanced features and internal clock div */
+       sc16is7xx_port_update(port, SC16IS7XX_EFR_REG,
+@@ -1196,7 +1184,7 @@ static int sc16is7xx_startup(struct uart
+                            SC16IS7XX_TCR_RX_RESUME(24) |
+                            SC16IS7XX_TCR_RX_HALT(48));
+-      regcache_cache_bypass(s->regmap, false);
++      regcache_cache_bypass(one->regmap, false);
+       /* Now, initialize the UART */
+       sc16is7xx_port_write(port, SC16IS7XX_LCR_REG, SC16IS7XX_LCR_WORD_LEN_8);
+@@ -1477,7 +1465,7 @@ static int sc16is7xx_setup_mctrl_ports(s
+       if (s->mctrl_mask)
+               regmap_update_bits(
+                       s->regmap,
+-                      SC16IS7XX_IOCONTROL_REG << SC16IS7XX_REG_SHIFT,
++                      SC16IS7XX_IOCONTROL_REG,
+                       SC16IS7XX_IOCONTROL_MODEM_A_BIT |
+                       SC16IS7XX_IOCONTROL_MODEM_B_BIT, s->mctrl_mask);
+@@ -1492,7 +1480,7 @@ static const struct serial_rs485 sc16is7
+ static int sc16is7xx_probe(struct device *dev,
+                          const struct sc16is7xx_devtype *devtype,
+-                         struct regmap *regmap, int irq)
++                         struct regmap *regmaps[], int irq)
+ {
+       unsigned long freq = 0, *pfreq = dev_get_platdata(dev);
+       unsigned int val;
+@@ -1500,16 +1488,16 @@ static int sc16is7xx_probe(struct device
+       int i, ret;
+       struct sc16is7xx_port *s;
+-      if (IS_ERR(regmap))
+-              return PTR_ERR(regmap);
++      for (i = 0; i < devtype->nr_uart; i++)
++              if (IS_ERR(regmaps[i]))
++                      return PTR_ERR(regmaps[i]);
+       /*
+        * This device does not have an identification register that would
+        * tell us if we are really connected to the correct device.
+        * The best we can do is to check if communication is at all possible.
+        */
+-      ret = regmap_read(regmap,
+-                        SC16IS7XX_LSR_REG << SC16IS7XX_REG_SHIFT, &val);
++      ret = regmap_read(regmaps[0], SC16IS7XX_LSR_REG, &val);
+       if (ret < 0)
+               return -EPROBE_DEFER;
+@@ -1543,7 +1531,7 @@ static int sc16is7xx_probe(struct device
+                       return -EINVAL;
+       }
+-      s->regmap = regmap;
++      s->regmap = regmaps[0];
+       s->devtype = devtype;
+       dev_set_drvdata(dev, s);
+       mutex_init(&s->efr_lock);
+@@ -1558,8 +1546,8 @@ static int sc16is7xx_probe(struct device
+       sched_set_fifo(s->kworker_task);
+       /* reset device, purging any pending irq / data */
+-      regmap_write(s->regmap, SC16IS7XX_IOCONTROL_REG << SC16IS7XX_REG_SHIFT,
+-                      SC16IS7XX_IOCONTROL_SRESET_BIT);
++      regmap_write(s->regmap, SC16IS7XX_IOCONTROL_REG,
++                   SC16IS7XX_IOCONTROL_SRESET_BIT);
+       for (i = 0; i < devtype->nr_uart; ++i) {
+               s->p[i].line            = i;
+@@ -1583,6 +1571,7 @@ static int sc16is7xx_probe(struct device
+               s->p[i].port.ops        = &sc16is7xx_ops;
+               s->p[i].old_mctrl       = 0;
+               s->p[i].port.line       = sc16is7xx_alloc_line();
++              s->p[i].regmap          = regmaps[i];
+               if (s->p[i].port.line >= SC16IS7XX_MAX_DEVS) {
+                       ret = -ENOMEM;
+@@ -1611,13 +1600,13 @@ static int sc16is7xx_probe(struct device
+               sc16is7xx_port_write(&s->p[i].port, SC16IS7XX_LCR_REG,
+                                    SC16IS7XX_LCR_CONF_MODE_B);
+-              regcache_cache_bypass(s->regmap, true);
++              regcache_cache_bypass(regmaps[i], true);
+               /* Enable write access to enhanced features */
+               sc16is7xx_port_write(&s->p[i].port, SC16IS7XX_EFR_REG,
+                                    SC16IS7XX_EFR_ENABLE_BIT);
+-              regcache_cache_bypass(s->regmap, false);
++              regcache_cache_bypass(regmaps[i], false);
+               /* Restore access to general registers */
+               sc16is7xx_port_write(&s->p[i].port, SC16IS7XX_LCR_REG, 0x00);
+@@ -1711,19 +1700,36 @@ static const struct of_device_id __maybe
+ MODULE_DEVICE_TABLE(of, sc16is7xx_dt_ids);
+ static struct regmap_config regcfg = {
+-      .reg_bits = 7,
+-      .pad_bits = 1,
++      .reg_bits = 5,
++      .pad_bits = 3,
+       .val_bits = 8,
+       .cache_type = REGCACHE_RBTREE,
+       .volatile_reg = sc16is7xx_regmap_volatile,
+       .precious_reg = sc16is7xx_regmap_precious,
++      .max_register = SC16IS7XX_EFCR_REG,
+ };
++static const char *sc16is7xx_regmap_name(unsigned int port_id)
++{
++      static char buf[6];
++
++      snprintf(buf, sizeof(buf), "port%d", port_id);
++
++      return buf;
++}
++
++static unsigned int sc16is7xx_regmap_port_mask(unsigned int port_id)
++{
++      /* CH1,CH0 are at bits 2:1. */
++      return port_id << 1;
++}
++
+ #ifdef CONFIG_SERIAL_SC16IS7XX_SPI
+ static int sc16is7xx_spi_probe(struct spi_device *spi)
+ {
+       const struct sc16is7xx_devtype *devtype;
+-      struct regmap *regmap;
++      struct regmap *regmaps[2];
++      unsigned int i;
+       int ret;
+       /* Setup SPI bus */
+@@ -1748,11 +1754,20 @@ static int sc16is7xx_spi_probe(struct sp
+               devtype = (struct sc16is7xx_devtype *)id_entry->driver_data;
+       }
+-      regcfg.max_register = (0xf << SC16IS7XX_REG_SHIFT) |
+-                            (devtype->nr_uart - 1);
+-      regmap = devm_regmap_init_spi(spi, &regcfg);
++      for (i = 0; i < devtype->nr_uart; i++) {
++              regcfg.name = sc16is7xx_regmap_name(i);
++              /*
++               * If read_flag_mask is 0, the regmap code sets it to a default
++               * of 0x80. Since we specify our own mask, we must add the READ
++               * bit ourselves:
++               */
++              regcfg.read_flag_mask = sc16is7xx_regmap_port_mask(i) |
++                      SC16IS7XX_SPI_READ_BIT;
++              regcfg.write_flag_mask = sc16is7xx_regmap_port_mask(i);
++              regmaps[i] = devm_regmap_init_spi(spi, &regcfg);
++      }
+-      return sc16is7xx_probe(&spi->dev, devtype, regmap, spi->irq);
++      return sc16is7xx_probe(&spi->dev, devtype, regmaps, spi->irq);
+ }
+ static void sc16is7xx_spi_remove(struct spi_device *spi)
+@@ -1791,7 +1806,8 @@ static int sc16is7xx_i2c_probe(struct i2
+ {
+       const struct i2c_device_id *id = i2c_client_get_device_id(i2c);
+       const struct sc16is7xx_devtype *devtype;
+-      struct regmap *regmap;
++      struct regmap *regmaps[2];
++      unsigned int i;
+       if (i2c->dev.of_node) {
+               devtype = device_get_match_data(&i2c->dev);
+@@ -1801,11 +1817,14 @@ static int sc16is7xx_i2c_probe(struct i2
+               devtype = (struct sc16is7xx_devtype *)id->driver_data;
+       }
+-      regcfg.max_register = (0xf << SC16IS7XX_REG_SHIFT) |
+-                            (devtype->nr_uart - 1);
+-      regmap = devm_regmap_init_i2c(i2c, &regcfg);
++      for (i = 0; i < devtype->nr_uart; i++) {
++              regcfg.name = sc16is7xx_regmap_name(i);
++              regcfg.read_flag_mask = sc16is7xx_regmap_port_mask(i);
++              regcfg.write_flag_mask = sc16is7xx_regmap_port_mask(i);
++              regmaps[i] = devm_regmap_init_i2c(i2c, &regcfg);
++      }
+-      return sc16is7xx_probe(&i2c->dev, devtype, regmap, i2c->irq);
++      return sc16is7xx_probe(&i2c->dev, devtype, regmaps, i2c->irq);
+ }
+ static void sc16is7xx_i2c_remove(struct i2c_client *client)
diff --git a/queue-6.7/serial-sc16is7xx-remove-global-regmap-from-struct-sc16is7xx_port.patch b/queue-6.7/serial-sc16is7xx-remove-global-regmap-from-struct-sc16is7xx_port.patch
new file mode 100644 (file)
index 0000000..0de85eb
--- /dev/null
@@ -0,0 +1,90 @@
+From f6959c5217bd799bcb770b95d3c09b3244e175c6 Mon Sep 17 00:00:00 2001
+From: Hugo Villeneuve <hvilleneuve@dimonoff.com>
+Date: Mon, 11 Dec 2023 12:13:49 -0500
+Subject: serial: sc16is7xx: remove global regmap from struct sc16is7xx_port
+
+From: Hugo Villeneuve <hvilleneuve@dimonoff.com>
+
+commit f6959c5217bd799bcb770b95d3c09b3244e175c6 upstream.
+
+Remove global struct regmap so that it is more obvious that this
+regmap is to be used only in the probe function.
+
+Also add a comment to that effect in probe function.
+
+Fixes: 3837a0379533 ("serial: sc16is7xx: improve regmap debugfs by using one regmap per port")
+Cc:  <stable@vger.kernel.org>
+Suggested-by: Andy Shevchenko <andy.shevchenko@gmail.com>
+Signed-off-by: Hugo Villeneuve <hvilleneuve@dimonoff.com>
+Link: https://lore.kernel.org/r/20231211171353.2901416-3-hugo@hugovil.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/tty/serial/sc16is7xx.c |   15 +++++++++------
+ 1 file changed, 9 insertions(+), 6 deletions(-)
+
+--- a/drivers/tty/serial/sc16is7xx.c
++++ b/drivers/tty/serial/sc16is7xx.c
+@@ -335,7 +335,6 @@ struct sc16is7xx_one {
+ struct sc16is7xx_port {
+       const struct sc16is7xx_devtype  *devtype;
+-      struct regmap                   *regmap;
+       struct clk                      *clk;
+ #ifdef CONFIG_GPIOLIB
+       struct gpio_chip                gpio;
+@@ -1435,7 +1434,8 @@ static void sc16is7xx_setup_irda_ports(s
+ /*
+  * Configure ports designated to operate as modem control lines.
+  */
+-static int sc16is7xx_setup_mctrl_ports(struct sc16is7xx_port *s)
++static int sc16is7xx_setup_mctrl_ports(struct sc16is7xx_port *s,
++                                     struct regmap *regmap)
+ {
+       int i;
+       int ret;
+@@ -1464,7 +1464,7 @@ static int sc16is7xx_setup_mctrl_ports(s
+       if (s->mctrl_mask)
+               regmap_update_bits(
+-                      s->regmap,
++                      regmap,
+                       SC16IS7XX_IOCONTROL_REG,
+                       SC16IS7XX_IOCONTROL_MODEM_A_BIT |
+                       SC16IS7XX_IOCONTROL_MODEM_B_BIT, s->mctrl_mask);
+@@ -1496,6 +1496,10 @@ static int sc16is7xx_probe(struct device
+        * This device does not have an identification register that would
+        * tell us if we are really connected to the correct device.
+        * The best we can do is to check if communication is at all possible.
++       *
++       * Note: regmap[0] is used in the probe function to access registers
++       * common to all channels/ports, as it is guaranteed to be present on
++       * all variants.
+        */
+       ret = regmap_read(regmaps[0], SC16IS7XX_LSR_REG, &val);
+       if (ret < 0)
+@@ -1531,7 +1535,6 @@ static int sc16is7xx_probe(struct device
+                       return -EINVAL;
+       }
+-      s->regmap = regmaps[0];
+       s->devtype = devtype;
+       dev_set_drvdata(dev, s);
+       mutex_init(&s->efr_lock);
+@@ -1546,7 +1549,7 @@ static int sc16is7xx_probe(struct device
+       sched_set_fifo(s->kworker_task);
+       /* reset device, purging any pending irq / data */
+-      regmap_write(s->regmap, SC16IS7XX_IOCONTROL_REG,
++      regmap_write(regmaps[0], SC16IS7XX_IOCONTROL_REG,
+                    SC16IS7XX_IOCONTROL_SRESET_BIT);
+       for (i = 0; i < devtype->nr_uart; ++i) {
+@@ -1617,7 +1620,7 @@ static int sc16is7xx_probe(struct device
+       sc16is7xx_setup_irda_ports(s);
+-      ret = sc16is7xx_setup_mctrl_ports(s);
++      ret = sc16is7xx_setup_mctrl_ports(s, regmaps[0]);
+       if (ret)
+               goto out_ports;
diff --git a/queue-6.7/serial-sc16is7xx-remove-obsolete-loop-in-sc16is7xx_port_irq.patch b/queue-6.7/serial-sc16is7xx-remove-obsolete-loop-in-sc16is7xx_port_irq.patch
new file mode 100644 (file)
index 0000000..18f8eaa
--- /dev/null
@@ -0,0 +1,130 @@
+From ed647256e8f226241ecff7baaecdb8632ffc7ec1 Mon Sep 17 00:00:00 2001
+From: Hugo Villeneuve <hvilleneuve@dimonoff.com>
+Date: Thu, 21 Dec 2023 18:18:11 -0500
+Subject: serial: sc16is7xx: remove obsolete loop in sc16is7xx_port_irq()
+
+From: Hugo Villeneuve <hvilleneuve@dimonoff.com>
+
+commit ed647256e8f226241ecff7baaecdb8632ffc7ec1 upstream.
+
+Commit 834449872105 ("sc16is7xx: Fix for multi-channel stall") changed
+sc16is7xx_port_irq() from looping multiple times when there was still
+interrupts to serve. It simply changed the do {} while(1) loop to a
+do {} while(0) loop, which makes the loop itself now obsolete.
+
+Clean the code by removing this obsolete do {} while(0) loop.
+
+Fixes: 834449872105 ("sc16is7xx: Fix for multi-channel stall")
+Cc:  <stable@vger.kernel.org>
+Suggested-by: Andy Shevchenko <andy.shevchenko@gmail.com>
+Signed-off-by: Hugo Villeneuve <hvilleneuve@dimonoff.com>
+Link: https://lore.kernel.org/r/20231221231823.2327894-5-hugo@hugovil.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/tty/serial/sc16is7xx.c |   89 +++++++++++++++++++----------------------
+ 1 file changed, 43 insertions(+), 46 deletions(-)
+
+--- a/drivers/tty/serial/sc16is7xx.c
++++ b/drivers/tty/serial/sc16is7xx.c
+@@ -725,58 +725,55 @@ static void sc16is7xx_update_mlines(stru
+ static bool sc16is7xx_port_irq(struct sc16is7xx_port *s, int portno)
+ {
+       bool rc = true;
++      unsigned int iir, rxlen;
+       struct uart_port *port = &s->p[portno].port;
+       struct sc16is7xx_one *one = to_sc16is7xx_one(port, port);
+       mutex_lock(&one->efr_lock);
+-      do {
+-              unsigned int iir, rxlen;
+-
+-              iir = sc16is7xx_port_read(port, SC16IS7XX_IIR_REG);
+-              if (iir & SC16IS7XX_IIR_NO_INT_BIT) {
+-                      rc = false;
+-                      goto out_port_irq;
+-              }
+-
+-              iir &= SC16IS7XX_IIR_ID_MASK;
+-
+-              switch (iir) {
+-              case SC16IS7XX_IIR_RDI_SRC:
+-              case SC16IS7XX_IIR_RLSE_SRC:
+-              case SC16IS7XX_IIR_RTOI_SRC:
+-              case SC16IS7XX_IIR_XOFFI_SRC:
+-                      rxlen = sc16is7xx_port_read(port, SC16IS7XX_RXLVL_REG);
+-
+-                      /*
+-                       * There is a silicon bug that makes the chip report a
+-                       * time-out interrupt but no data in the FIFO. This is
+-                       * described in errata section 18.1.4.
+-                       *
+-                       * When this happens, read one byte from the FIFO to
+-                       * clear the interrupt.
+-                       */
+-                      if (iir == SC16IS7XX_IIR_RTOI_SRC && !rxlen)
+-                              rxlen = 1;
+-
+-                      if (rxlen)
+-                              sc16is7xx_handle_rx(port, rxlen, iir);
+-                      break;
++      iir = sc16is7xx_port_read(port, SC16IS7XX_IIR_REG);
++      if (iir & SC16IS7XX_IIR_NO_INT_BIT) {
++              rc = false;
++              goto out_port_irq;
++      }
++
++      iir &= SC16IS7XX_IIR_ID_MASK;
++
++      switch (iir) {
++      case SC16IS7XX_IIR_RDI_SRC:
++      case SC16IS7XX_IIR_RLSE_SRC:
++      case SC16IS7XX_IIR_RTOI_SRC:
++      case SC16IS7XX_IIR_XOFFI_SRC:
++              rxlen = sc16is7xx_port_read(port, SC16IS7XX_RXLVL_REG);
++
++              /*
++               * There is a silicon bug that makes the chip report a
++               * time-out interrupt but no data in the FIFO. This is
++               * described in errata section 18.1.4.
++               *
++               * When this happens, read one byte from the FIFO to
++               * clear the interrupt.
++               */
++              if (iir == SC16IS7XX_IIR_RTOI_SRC && !rxlen)
++                      rxlen = 1;
++
++              if (rxlen)
++                      sc16is7xx_handle_rx(port, rxlen, iir);
++              break;
+               /* CTSRTS interrupt comes only when CTS goes inactive */
+-              case SC16IS7XX_IIR_CTSRTS_SRC:
+-              case SC16IS7XX_IIR_MSI_SRC:
+-                      sc16is7xx_update_mlines(one);
+-                      break;
+-              case SC16IS7XX_IIR_THRI_SRC:
+-                      sc16is7xx_handle_tx(port);
+-                      break;
+-              default:
+-                      dev_err_ratelimited(port->dev,
+-                                          "ttySC%i: Unexpected interrupt: %x",
+-                                          port->line, iir);
+-                      break;
+-              }
+-      } while (0);
++      case SC16IS7XX_IIR_CTSRTS_SRC:
++      case SC16IS7XX_IIR_MSI_SRC:
++              sc16is7xx_update_mlines(one);
++              break;
++      case SC16IS7XX_IIR_THRI_SRC:
++              sc16is7xx_handle_tx(port);
++              break;
++      default:
++              dev_err_ratelimited(port->dev,
++                                  "ttySC%i: Unexpected interrupt: %x",
++                                  port->line, iir);
++              break;
++      }
+ out_port_irq:
+       mutex_unlock(&one->efr_lock);
diff --git a/queue-6.7/serial-sc16is7xx-remove-unused-line-structure-member.patch b/queue-6.7/serial-sc16is7xx-remove-unused-line-structure-member.patch
new file mode 100644 (file)
index 0000000..c9c2140
--- /dev/null
@@ -0,0 +1,39 @@
+From 41a308cbedb2a68a6831f0f2e992e296c4b8aff0 Mon Sep 17 00:00:00 2001
+From: Hugo Villeneuve <hvilleneuve@dimonoff.com>
+Date: Mon, 11 Dec 2023 12:13:50 -0500
+Subject: serial: sc16is7xx: remove unused line structure member
+
+From: Hugo Villeneuve <hvilleneuve@dimonoff.com>
+
+commit 41a308cbedb2a68a6831f0f2e992e296c4b8aff0 upstream.
+
+Now that the driver has been converted to use one regmap per port, the line
+structure member is no longer used, so remove it.
+
+Fixes: 3837a0379533 ("serial: sc16is7xx: improve regmap debugfs by using one regmap per port")
+Cc:  <stable@vger.kernel.org>
+Signed-off-by: Hugo Villeneuve <hvilleneuve@dimonoff.com>
+Link: https://lore.kernel.org/r/20231211171353.2901416-4-hugo@hugovil.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/tty/serial/sc16is7xx.c |    2 --
+ 1 file changed, 2 deletions(-)
+
+--- a/drivers/tty/serial/sc16is7xx.c
++++ b/drivers/tty/serial/sc16is7xx.c
+@@ -323,7 +323,6 @@ struct sc16is7xx_one_config {
+ struct sc16is7xx_one {
+       struct uart_port                port;
+-      u8                              line;
+       struct regmap                   *regmap;
+       struct kthread_work             tx_work;
+       struct kthread_work             reg_work;
+@@ -1553,7 +1552,6 @@ static int sc16is7xx_probe(struct device
+                    SC16IS7XX_IOCONTROL_SRESET_BIT);
+       for (i = 0; i < devtype->nr_uart; ++i) {
+-              s->p[i].line            = i;
+               /* Initialize port data */
+               s->p[i].port.dev        = dev;
+               s->p[i].port.irq        = irq;
diff --git a/queue-6.7/serial-sc16is7xx-remove-wasteful-static-buffer-in-sc16is7xx_regmap_name.patch b/queue-6.7/serial-sc16is7xx-remove-wasteful-static-buffer-in-sc16is7xx_regmap_name.patch
new file mode 100644 (file)
index 0000000..5f82a25
--- /dev/null
@@ -0,0 +1,53 @@
+From 6bcab3c8acc88e265c570dea969fd04f137c8a4c Mon Sep 17 00:00:00 2001
+From: Hugo Villeneuve <hvilleneuve@dimonoff.com>
+Date: Mon, 11 Dec 2023 12:13:48 -0500
+Subject: serial: sc16is7xx: remove wasteful static buffer in sc16is7xx_regmap_name()
+
+From: Hugo Villeneuve <hvilleneuve@dimonoff.com>
+
+commit 6bcab3c8acc88e265c570dea969fd04f137c8a4c upstream.
+
+Using a static buffer inside sc16is7xx_regmap_name() was a convenient and
+simple way to set the regmap name without having to allocate and free a
+buffer each time it is called. The drawback is that the static buffer
+wastes memory for nothing once regmap is fully initialized.
+
+Remove static buffer and use constant strings instead.
+
+This also avoids a truncation warning when using "%d" or "%u" in snprintf
+which was flagged by kernel test robot.
+
+Fixes: 3837a0379533 ("serial: sc16is7xx: improve regmap debugfs by using one regmap per port")
+Cc:  <stable@vger.kernel.org> # 6.1.x: 3837a03 serial: sc16is7xx: improve regmap debugfs by using one regmap per port
+Suggested-by: Andy Shevchenko <andy.shevchenko@gmail.com>
+Signed-off-by: Hugo Villeneuve <hvilleneuve@dimonoff.com>
+Link: https://lore.kernel.org/r/20231211171353.2901416-2-hugo@hugovil.com
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/tty/serial/sc16is7xx.c |   14 ++++++++------
+ 1 file changed, 8 insertions(+), 6 deletions(-)
+
+--- a/drivers/tty/serial/sc16is7xx.c
++++ b/drivers/tty/serial/sc16is7xx.c
+@@ -1709,13 +1709,15 @@ static struct regmap_config regcfg = {
+       .max_register = SC16IS7XX_EFCR_REG,
+ };
+-static const char *sc16is7xx_regmap_name(unsigned int port_id)
++static const char *sc16is7xx_regmap_name(u8 port_id)
+ {
+-      static char buf[6];
+-
+-      snprintf(buf, sizeof(buf), "port%d", port_id);
+-
+-      return buf;
++      switch (port_id) {
++      case 0: return "port0";
++      case 1: return "port1";
++      default:
++              WARN_ON(true);
++              return NULL;
++      }
+ }
+ static unsigned int sc16is7xx_regmap_port_mask(unsigned int port_id)
index 94779deda5d32f6eb0ac32383a7ca2ab45dddf68..3a7dc7ace78e0386228ec0ea3a56cc1557572391 100644 (file)
@@ -116,3 +116,21 @@ nouveau-gsp-handle-engines-in-runl-without-nonstall-interrupts.patch
 efi-disable-mirror-feature-during-crashkernel.patch
 kdump-defer-the-insertion-of-crashkernel-resources.patch
 ubifs-ubifs_symlink-fix-memleak-of-inode-i_link-in-error-path.patch
+thermal-gov_power_allocator-avoid-inability-to-reset-a-cdev.patch
+fs-proc-task_mmu-move-mmu-notification-mechanism-inside-mm-lock.patch
+kexec-do-syscore_shutdown-in-kernel_kexec.patch
+selftests-mm-hugepage-vmemmap-fails-on-64k-page-size-systems.patch
+mm-rmap-fix-misplaced-parenthesis-of-a-likely.patch
+mm-migrate-fix-getting-incorrect-page-mapping-during-page-migration.patch
+mm-sparsemem-fix-race-in-accessing-memory_section-usage.patch
+rename-fix-the-locking-of-subdirectories.patch
+serial-sc16is7xx-improve-regmap-debugfs-by-using-one-regmap-per-port.patch
+serial-sc16is7xx-remove-wasteful-static-buffer-in-sc16is7xx_regmap_name.patch
+serial-sc16is7xx-remove-global-regmap-from-struct-sc16is7xx_port.patch
+serial-sc16is7xx-remove-unused-line-structure-member.patch
+serial-sc16is7xx-change-efr-lock-to-operate-on-each-channels.patch
+serial-sc16is7xx-convert-from-_raw_-to-_noinc_-regmap-functions-for-fifo.patch
+serial-sc16is7xx-fix-unconditional-activation-of-thri-interrupt.patch
+serial-sc16is7xx-fix-invalid-sc16is7xx_lines-bitfield-in-case-of-probe-error.patch
+serial-sc16is7xx-remove-obsolete-loop-in-sc16is7xx_port_irq.patch
+serial-sc16is7xx-improve-do-while-loop-in-sc16is7xx_irq.patch
diff --git a/queue-6.7/thermal-gov_power_allocator-avoid-inability-to-reset-a-cdev.patch b/queue-6.7/thermal-gov_power_allocator-avoid-inability-to-reset-a-cdev.patch
new file mode 100644 (file)
index 0000000..6ad35f1
--- /dev/null
@@ -0,0 +1,67 @@
+From e95fa7404716f6e25021e66067271a4ad8eb1486 Mon Sep 17 00:00:00 2001
+From: Di Shen <di.shen@unisoc.com>
+Date: Wed, 10 Jan 2024 19:55:26 +0800
+Subject: thermal: gov_power_allocator: avoid inability to reset a cdev
+
+From: Di Shen <di.shen@unisoc.com>
+
+commit e95fa7404716f6e25021e66067271a4ad8eb1486 upstream.
+
+Commit 0952177f2a1f ("thermal/core/power_allocator: Update once
+cooling devices when temp is low") adds an update flag to avoid
+triggering a thermal event when there is no need, and the thermal
+cdev is updated once when the temperature is low.
+
+But when the trips are writable, and switch_on_temp is set to be a
+higher value, the cooling device state may not be reset to 0,
+because last_temperature is smaller than switch_on_temp.
+
+For example:
+First:
+switch_on_temp=70 control_temp=85;
+Then userspace change the trip_temp:
+switch_on_temp=45 control_temp=55 cur_temp=54
+
+Then userspace reset the trip_temp:
+switch_on_temp=70 control_temp=85 cur_temp=57 last_temp=54
+
+At this time, the cooling device state should be reset to 0.
+However, because cur_temp(57) < switch_on_temp(70)
+last_temp(54) < switch_on_temp(70)  ---->  update = false,
+update is false, the cooling device state can not be reset.
+
+Using the observation that tz->passive can also be regarded as the
+temperature status, set the update flag to the tz->passive value.
+
+When the temperature drops below switch_on for the first time, the
+states of cooling devices can be reset once, and tz->passive is updated
+to 0. In the next round, because tz->passive is 0, cdev->state will not
+be updated.
+
+By using the tz->passive value as the "update" flag, the issue above
+can be solved, and the cooling devices can be updated only once when the
+temperature is low.
+
+Fixes: 0952177f2a1f ("thermal/core/power_allocator: Update once cooling devices when temp is low")
+Cc: 5.13+ <stable@vger.kernel.org> # 5.13+
+Suggested-by: Wei Wang <wvw@google.com>
+Signed-off-by: Di Shen <di.shen@unisoc.com>
+Reviewed-by: Lukasz Luba <lukasz.luba@arm.com>
+[ rjw: Subject and changelog edits ]
+Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/thermal/gov_power_allocator.c |    2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/thermal/gov_power_allocator.c
++++ b/drivers/thermal/gov_power_allocator.c
+@@ -693,7 +693,7 @@ static int power_allocator_throttle(stru
+       trip = params->trip_switch_on;
+       if (trip && tz->temperature < trip->temperature) {
+-              update = tz->last_temperature >= trip->temperature;
++              update = tz->passive;
+               tz->passive = 0;
+               reset_pid_controller(params);
+               allow_maximum_power(tz, update);