From: Greg Kroah-Hartman Date: Sat, 27 Jan 2024 00:20:44 +0000 (-0800) Subject: 6.6-stable patches X-Git-Tag: v6.1.76~96 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f74d2b9e1dae407897f0e94217ec2473869d9849;p=thirdparty%2Fkernel%2Fstable-queue.git 6.6-stable patches added patches: kexec-do-syscore_shutdown-in-kernel_kexec.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-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 --- diff --git a/queue-6.6/kexec-do-syscore_shutdown-in-kernel_kexec.patch b/queue-6.6/kexec-do-syscore_shutdown-in-kernel_kexec.patch new file mode 100644 index 00000000000..d14c89a3af6 --- /dev/null +++ b/queue-6.6/kexec-do-syscore_shutdown-in-kernel_kexec.patch @@ -0,0 +1,94 @@ +From 7bb943806ff61e83ae4cceef8906b7fe52453e8a Mon Sep 17 00:00:00 2001 +From: James Gowans +Date: Wed, 13 Dec 2023 08:40:04 +0200 +Subject: kexec: do syscore_shutdown() in kernel_kexec + +From: James Gowans + +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 +Cc: Baoquan He +Cc: Eric Biederman +Cc: Paolo Bonzini +Cc: Sean Christopherson +Cc: Marc Zyngier +Cc: Arnd Bergmann +Cc: Tony Luck +Cc: Borislav Petkov +Cc: Thomas Gleixner +Cc: Ingo Molnar +Cc: Chen-Yu Tsai +Cc: Jernej Skrabec +Cc: Samuel Holland +Cc: Pavel Machek +Cc: Sebastian Reichel +Cc: Orson Zhai +Cc: Alexander Graf +Cc: Jan H. Schoenherr +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Greg Kroah-Hartman +--- + kernel/kexec_core.c | 1 + + 1 file changed, 1 insertion(+) + +--- a/kernel/kexec_core.c ++++ b/kernel/kexec_core.c +@@ -1271,6 +1271,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.6/mm-rmap-fix-misplaced-parenthesis-of-a-likely.patch b/queue-6.6/mm-rmap-fix-misplaced-parenthesis-of-a-likely.patch new file mode 100644 index 00000000000..cf988874baa --- /dev/null +++ b/queue-6.6/mm-rmap-fix-misplaced-parenthesis-of-a-likely.patch @@ -0,0 +1,64 @@ +From f67f8d4a8c1e1ebc85a6cbdb9a7266f14863461c Mon Sep 17 00:00:00 2001 +From: "Steven Rostedt (Google)" +Date: Fri, 1 Dec 2023 14:59:36 -0500 +Subject: mm/rmap: fix misplaced parenthesis of a likely() + +From: Steven Rostedt (Google) + +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) +Acked-by: Vlastimil Babka +Cc: David Hildenbrand +Cc: Vlastimil Babka +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Greg Kroah-Hartman +--- + 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.6/mm-sparsemem-fix-race-in-accessing-memory_section-usage.patch b/queue-6.6/mm-sparsemem-fix-race-in-accessing-memory_section-usage.patch new file mode 100644 index 00000000000..998e76653a6 --- /dev/null +++ b/queue-6.6/mm-sparsemem-fix-race-in-accessing-memory_section-usage.patch @@ -0,0 +1,206 @@ +From 5ec8e8ea8b7783fab150cf86404fc38cb4db8800 Mon Sep 17 00:00:00 2001 +From: Charan Teja Kalla +Date: Fri, 13 Oct 2023 18:34:27 +0530 +Subject: mm/sparsemem: fix race in accessing memory_section->usage + +From: Charan Teja Kalla + +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 +Cc: Aneesh Kumar K.V +Cc: Dan Williams +Cc: David Hildenbrand +Cc: Mel Gorman +Cc: Oscar Salvador +Cc: Vlastimil Babka +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Greg Kroah-Hartman +--- + 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 +@@ -1774,6 +1774,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 +@@ -1967,7 +1968,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) +@@ -1991,6 +1992,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 +@@ -2004,13 +2006,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.6/rename-fix-the-locking-of-subdirectories.patch b/queue-6.6/rename-fix-the-locking-of-subdirectories.patch new file mode 100644 index 00000000000..986544e0a48 --- /dev/null +++ b/queue-6.6/rename-fix-the-locking-of-subdirectories.patch @@ -0,0 +1,276 @@ +From 22e111ed6c83dcde3037fc81176012721bc34c0b Mon Sep 17 00:00:00 2001 +From: Al Viro +Date: Sun, 19 Nov 2023 20:25:58 -0500 +Subject: rename(): fix the locking of subdirectories + +From: Al Viro + +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 +Signed-off-by: Al Viro +Signed-off-by: Greg Kroah-Hartman +--- + 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 +@@ -1045,3 +1045,21 @@ filesystem type is now moved to a later + As this is a VFS level change it has no practical consequences for filesystems + other than that all of them must use one of the provided kill_litter_super(), + kill_anon_super(), or kill_block_super() helpers. ++ ++--- ++ ++**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; + } + + /* +@@ -4733,11 +4727,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 +@@ -4769,6 +4764,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; +@@ -4822,15 +4818,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))) +@@ -4878,8 +4891,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.6/selftests-mm-hugepage-vmemmap-fails-on-64k-page-size-systems.patch b/queue-6.6/selftests-mm-hugepage-vmemmap-fails-on-64k-page-size-systems.patch new file mode 100644 index 00000000000..7ca82d26db3 --- /dev/null +++ b/queue-6.6/selftests-mm-hugepage-vmemmap-fails-on-64k-page-size-systems.patch @@ -0,0 +1,137 @@ +From 00bcfcd47a52f50f07a2e88d730d7931384cb073 Mon Sep 17 00:00:00 2001 +From: Donet Tom +Date: Wed, 10 Jan 2024 14:03:35 +0530 +Subject: selftests: mm: hugepage-vmemmap fails on 64K page size systems + +From: Donet Tom + +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 +Reported-by: Geetika Moolchandani +Tested-by: Geetika Moolchandani +Acked-by: Muchun Song +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Greg Kroah-Hartman +--- + 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 + #include + #include +- +-#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.6/serial-sc16is7xx-change-efr-lock-to-operate-on-each-channels.patch b/queue-6.6/serial-sc16is7xx-change-efr-lock-to-operate-on-each-channels.patch new file mode 100644 index 00000000000..99174943ade --- /dev/null +++ b/queue-6.6/serial-sc16is7xx-change-efr-lock-to-operate-on-each-channels.patch @@ -0,0 +1,206 @@ +From 4409df5866b7ff7686ba27e449ca97a92ee063c9 Mon Sep 17 00:00:00 2001 +From: Hugo Villeneuve +Date: Mon, 11 Dec 2023 12:13:51 -0500 +Subject: serial: sc16is7xx: change EFR lock to operate on each channels + +From: Hugo Villeneuve + +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: # 6.1.x: 3837a03 serial: sc16is7xx: improve regmap debugfs by using one regmap per port +Signed-off-by: Hugo Villeneuve +Link: https://lore.kernel.org/r/20231211171353.2901416-5-hugo@hugovil.com +Signed-off-by: Greg Kroah-Hartman +--- + 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[]; + }; + +@@ -496,7 +496,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; +@@ -520,7 +519,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); + +@@ -539,7 +538,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, +@@ -707,11 +706,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; +@@ -737,15 +735,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; + +@@ -785,15 +788,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; +@@ -804,24 +809,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); + + spin_lock_irqsave(&port->lock, flags); + sc16is7xx_ier_set(port, SC16IS7XX_IER_THRI_BIT); +@@ -928,9 +931,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); + } +@@ -1014,7 +1017,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; +@@ -1073,7 +1075,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); +@@ -1103,7 +1105,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, +@@ -1514,7 +1516,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, +@@ -1557,6 +1558,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.6/serial-sc16is7xx-convert-from-_raw_-to-_noinc_-regmap-functions-for-fifo.patch b/queue-6.6/serial-sc16is7xx-convert-from-_raw_-to-_noinc_-regmap-functions-for-fifo.patch new file mode 100644 index 00000000000..9fb175f2745 --- /dev/null +++ b/queue-6.6/serial-sc16is7xx-convert-from-_raw_-to-_noinc_-regmap-functions-for-fifo.patch @@ -0,0 +1,83 @@ +From dbf4ab821804df071c8b566d9813083125e6d97b Mon Sep 17 00:00:00 2001 +From: Hugo Villeneuve +Date: Mon, 11 Dec 2023 12:13:52 -0500 +Subject: serial: sc16is7xx: convert from _raw_ to _noinc_ regmap functions for FIFO + +From: Hugo Villeneuve + +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: +Signed-off-by: Hugo Villeneuve +Link: https://lore.kernel.org/r/20231211171353.2901416-6-hugo@hugovil.com +Signed-off-by: Greg Kroah-Hartman +--- + 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 +@@ -383,9 +383,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) +@@ -400,9 +398,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, +@@ -494,6 +490,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); +@@ -1697,6 +1698,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.6/serial-sc16is7xx-fix-invalid-sc16is7xx_lines-bitfield-in-case-of-probe-error.patch b/queue-6.6/serial-sc16is7xx-fix-invalid-sc16is7xx_lines-bitfield-in-case-of-probe-error.patch new file mode 100644 index 00000000000..0eb0b3cbe9b --- /dev/null +++ b/queue-6.6/serial-sc16is7xx-fix-invalid-sc16is7xx_lines-bitfield-in-case-of-probe-error.patch @@ -0,0 +1,128 @@ +From 8a1060ce974919f2a79807527ad82ac39336eda2 Mon Sep 17 00:00:00 2001 +From: Hugo Villeneuve +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 + +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: +Cc: Yury Norov +Signed-off-by: Hugo Villeneuve +Link: https://lore.kernel.org/r/20231221231823.2327894-2-hugo@hugovil.com +Signed-off-by: Greg Kroah-Hartman +--- + 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 +@@ -409,19 +409,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, +@@ -1532,6 +1519,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; +@@ -1551,14 +1545,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); +@@ -1576,8 +1564,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, +@@ -1644,10 +1637,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); + +@@ -1669,8 +1661,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.6/serial-sc16is7xx-improve-do-while-loop-in-sc16is7xx_irq.patch b/queue-6.6/serial-sc16is7xx-improve-do-while-loop-in-sc16is7xx_irq.patch new file mode 100644 index 00000000000..25a5f0e24ba --- /dev/null +++ b/queue-6.6/serial-sc16is7xx-improve-do-while-loop-in-sc16is7xx_irq.patch @@ -0,0 +1,49 @@ +From d5078509c8b06c5c472a60232815e41af81c6446 Mon Sep 17 00:00:00 2001 +From: Hugo Villeneuve +Date: Thu, 21 Dec 2023 18:18:12 -0500 +Subject: serial: sc16is7xx: improve do/while loop in sc16is7xx_irq() + +From: Hugo Villeneuve + +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: +Suggested-by: Andy Shevchenko +Signed-off-by: Hugo Villeneuve +Link: https://lore.kernel.org/r/20231221231823.2327894-6-hugo@hugovil.com +Signed-off-by: Greg Kroah-Hartman +--- + 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 +@@ -782,17 +782,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.6/serial-sc16is7xx-improve-regmap-debugfs-by-using-one-regmap-per-port.patch b/queue-6.6/serial-sc16is7xx-improve-regmap-debugfs-by-using-one-regmap-per-port.patch new file mode 100644 index 00000000000..1a18d8ff233 --- /dev/null +++ b/queue-6.6/serial-sc16is7xx-improve-regmap-debugfs-by-using-one-regmap-per-port.patch @@ -0,0 +1,437 @@ +From 3837a0379533aabb9e4483677077479f7c6aa910 Mon Sep 17 00:00:00 2001 +From: Hugo Villeneuve +Date: Mon, 30 Oct 2023 17:14:47 -0400 +Subject: serial: sc16is7xx: improve regmap debugfs by using one regmap per port + +From: Hugo Villeneuve + +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 +Link: https://lore.kernel.org/r/20231030211447.974779-1-hugo@hugovil.com +Signed-off-by: Greg Kroah-Hartman +--- + 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; +@@ -362,48 +363,37 @@ static void sc16is7xx_stop_tx(struct uar + #define to_sc16is7xx_port(p,e) ((container_of((p), struct sc16is7xx_port, e))) + #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 +@@ -412,19 +402,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) +@@ -479,7 +467,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: +@@ -498,7 +486,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: +@@ -511,6 +499,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; +@@ -542,12 +531,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); +@@ -563,10 +552,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); +@@ -1092,7 +1081,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]); + +@@ -1111,7 +1100,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); +@@ -1162,7 +1151,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; + +@@ -1179,7 +1167,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, +@@ -1197,7 +1185,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); +@@ -1455,7 +1443,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); + +@@ -1470,7 +1458,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; +@@ -1478,16 +1466,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; + +@@ -1521,7 +1509,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); +@@ -1536,8 +1524,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; +@@ -1561,6 +1549,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; +@@ -1589,13 +1578,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); +@@ -1698,19 +1687,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 */ +@@ -1735,11 +1741,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, ®cfg); ++ 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, ®cfg); ++ } + +- 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) +@@ -1778,7 +1793,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); +@@ -1788,11 +1804,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, ®cfg); ++ 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, ®cfg); ++ } + +- 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.6/serial-sc16is7xx-remove-global-regmap-from-struct-sc16is7xx_port.patch b/queue-6.6/serial-sc16is7xx-remove-global-regmap-from-struct-sc16is7xx_port.patch new file mode 100644 index 00000000000..f382e30b11d --- /dev/null +++ b/queue-6.6/serial-sc16is7xx-remove-global-regmap-from-struct-sc16is7xx_port.patch @@ -0,0 +1,90 @@ +From f6959c5217bd799bcb770b95d3c09b3244e175c6 Mon Sep 17 00:00:00 2001 +From: Hugo Villeneuve +Date: Mon, 11 Dec 2023 12:13:49 -0500 +Subject: serial: sc16is7xx: remove global regmap from struct sc16is7xx_port + +From: Hugo Villeneuve + +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: +Suggested-by: Andy Shevchenko +Signed-off-by: Hugo Villeneuve +Link: https://lore.kernel.org/r/20231211171353.2901416-3-hugo@hugovil.com +Signed-off-by: Greg Kroah-Hartman +--- + 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; +@@ -1413,7 +1412,8 @@ static int sc16is7xx_setup_gpio_chip(str + /* + * 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; +@@ -1442,7 +1442,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); +@@ -1474,6 +1474,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) +@@ -1509,7 +1513,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); +@@ -1524,7 +1527,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) { +@@ -1604,7 +1607,7 @@ static int sc16is7xx_probe(struct device + s->p[u].irda_mode = true; + } + +- ret = sc16is7xx_setup_mctrl_ports(s); ++ ret = sc16is7xx_setup_mctrl_ports(s, regmaps[0]); + if (ret) + goto out_ports; + diff --git a/queue-6.6/serial-sc16is7xx-remove-obsolete-loop-in-sc16is7xx_port_irq.patch b/queue-6.6/serial-sc16is7xx-remove-obsolete-loop-in-sc16is7xx_port_irq.patch new file mode 100644 index 00000000000..2cc13d2563c --- /dev/null +++ b/queue-6.6/serial-sc16is7xx-remove-obsolete-loop-in-sc16is7xx_port_irq.patch @@ -0,0 +1,130 @@ +From ed647256e8f226241ecff7baaecdb8632ffc7ec1 Mon Sep 17 00:00:00 2001 +From: Hugo Villeneuve +Date: Thu, 21 Dec 2023 18:18:11 -0500 +Subject: serial: sc16is7xx: remove obsolete loop in sc16is7xx_port_irq() + +From: Hugo Villeneuve + +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: +Suggested-by: Andy Shevchenko +Signed-off-by: Hugo Villeneuve +Link: https://lore.kernel.org/r/20231221231823.2327894-5-hugo@hugovil.com +Signed-off-by: Greg Kroah-Hartman +--- + 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 +@@ -724,58 +724,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.6/serial-sc16is7xx-remove-unused-line-structure-member.patch b/queue-6.6/serial-sc16is7xx-remove-unused-line-structure-member.patch new file mode 100644 index 00000000000..abefe86f178 --- /dev/null +++ b/queue-6.6/serial-sc16is7xx-remove-unused-line-structure-member.patch @@ -0,0 +1,39 @@ +From 41a308cbedb2a68a6831f0f2e992e296c4b8aff0 Mon Sep 17 00:00:00 2001 +From: Hugo Villeneuve +Date: Mon, 11 Dec 2023 12:13:50 -0500 +Subject: serial: sc16is7xx: remove unused line structure member + +From: Hugo Villeneuve + +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: +Signed-off-by: Hugo Villeneuve +Link: https://lore.kernel.org/r/20231211171353.2901416-4-hugo@hugovil.com +Signed-off-by: Greg Kroah-Hartman +--- + 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; +@@ -1531,7 +1530,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.6/serial-sc16is7xx-remove-wasteful-static-buffer-in-sc16is7xx_regmap_name.patch b/queue-6.6/serial-sc16is7xx-remove-wasteful-static-buffer-in-sc16is7xx_regmap_name.patch new file mode 100644 index 00000000000..aa29d5fcce6 --- /dev/null +++ b/queue-6.6/serial-sc16is7xx-remove-wasteful-static-buffer-in-sc16is7xx_regmap_name.patch @@ -0,0 +1,53 @@ +From 6bcab3c8acc88e265c570dea969fd04f137c8a4c Mon Sep 17 00:00:00 2001 +From: Hugo Villeneuve +Date: Mon, 11 Dec 2023 12:13:48 -0500 +Subject: serial: sc16is7xx: remove wasteful static buffer in sc16is7xx_regmap_name() + +From: Hugo Villeneuve + +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: # 6.1.x: 3837a03 serial: sc16is7xx: improve regmap debugfs by using one regmap per port +Suggested-by: Andy Shevchenko +Signed-off-by: Hugo Villeneuve +Link: https://lore.kernel.org/r/20231211171353.2901416-2-hugo@hugovil.com +Signed-off-by: Greg Kroah-Hartman +--- + 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 +@@ -1696,13 +1696,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) diff --git a/queue-6.6/series b/queue-6.6/series index 3d65c6abc4c..98ef2790a67 100644 --- a/queue-6.6/series +++ b/queue-6.6/series @@ -120,3 +120,17 @@ rtc-extend-timeout-for-waiting-for-uip-to-clear-to-1s.patch nouveau-vmm-don-t-set-addr-on-the-fail-path-to-avoid-warning.patch efi-disable-mirror-feature-during-crashkernel.patch ubifs-ubifs_symlink-fix-memleak-of-inode-i_link-in-error-path.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-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-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