Linux Serial subsystem development
 help / color / mirror / Atom feed
* [PATCH v8] Bluetooth: hci_uart: fix UAFs and race conditions in close and init paths
From: w15303746062 @ 2026-05-18  1:36 UTC (permalink / raw)
  To: luiz.dentz, pmenzel, marcel, linux-bluetooth
  Cc: linux-serial, linux-kernel, greg, stable, Mingyu Wang
In-Reply-To: <CABBYNZ+r3gm37FW5WqE79bRp+x9UZsaCtyvfz_FdixqEucAxGw@mail.gmail.com>

From: Mingyu Wang <25181214217@stu.xidian.edu.cn>

Vulnerabilities leading to Use-After-Free (UAF) and Null Pointer
Dereference (NPD) conditions were observed in the lifecycle management
of hci_uart.

The primary issue arises because the workqueues (init_ready and
write_work) are only flushed/cancelled if the HCI_UART_PROTO_READY
flag is set during TTY close. If a hangup occurs before setup completes,
hci_uart_tty_close() skips the teardown of these workqueues and
proceeds to free the `hu` struct. When the scheduled work executes
later, it blindly dereferences the freed `hu` struct.

Furthermore, several data races and UAFs were identified in the teardown
sequence:
1. Calling hci_uart_flush() from hci_uart_close() without canceling
   write_work causes a race condition where both can concurrently
   double-free hu->tx_skb. This occurs both in TTY hangup and when the
   HCI device is closed via the HCI core.
2. Calling hci_free_dev(hdev) before hu->proto->close(hu) causes a UAF
   when vendor specific protocol close callbacks dereference hu->hdev.
3. In the initialization error paths, failing to take the proto_lock
   write lock before clearing PROTO_READY leads to races with active
   readers. Additionally, hci_uart_tty_receive() accesses hu->hdev
   outside the read lock, leading to UAFs if the initialization error
   path frees hdev concurrently.

Fix these synchronization and lifecycle issues by:
1. Re-ordering hci_uart_tty_close() to unconditionally cancel init_ready
   and write_work first. This prevents the double-free race in
   hci_uart_flush(), while preserving the HCI_UART_PROTO_READY flag so
   underlying hu->proto->flush() callbacks can still execute safely.
2. Relocating hu->proto->close(hu) strictly prior to hci_free_dev(hdev)
   across all close and error paths to prevent vendor-level UAFs.
3. Moving the hdev->stat.byte_rx increment in hci_uart_tty_receive()
   inside the proto_lock read-side critical section to safely synchronize
   with device unregistration.
4. Adding cancel_work_sync(&hu->write_work) to hci_uart_close() to safely
   flush the workqueue before hci_uart_flush() is invoked.
5. Utilizing cancel_work_sync() instead of disable_work_sync() after
   flags are cleared to safely flush workqueues without permanently
   breaking user-space retry capabilities.

Fixes: 3b799254cf6f ("Bluetooth: hci_uart: Cancel init work before unregistering")
Cc: stable@vger.kernel.org
Signed-off-by: Mingyu Wang <25181214217@stu.xidian.edu.cn>
---
Changes in v8:
- Corrected the teardown sequence in hci_uart_tty_close() by unconditionally canceling write_work BEFORE hci_uart_close(). This completely prevents the tx_skb double-free without prematurely clearing PROTO_READY, ensuring underlying hu->proto->flush(hu) runs correctly.
- Moved hu->hdev->stat.byte_rx increment inside the proto_lock read-side critical section in hci_uart_tty_receive() to prevent read-side UAF against concurrent registration failures.
- Added cancel_work_sync(&hu->write_work) inside hci_uart_close() to eliminate the race condition between write_work and hci_uart_flush() when the interface is brought down via the HCI core.

Changes in v7:
- Reverted disable_work_sync() back to cancel_work_sync() across all error and close paths to preserve user-space retry capabilities.
- Synchronized workqueue teardown safely by atomically clearing PROTO_READY / PROTO_INIT under proto_lock prior to calling cancel_work_sync().
- Fixed a Use-After-Free (UAF) vulnerability in the teardown sequence by relocating hu->proto->close(hu) strictly prior to hci_free_dev(hdev).
- Added cancel_work_sync(&hu->init_ready) at the very beginning of hci_uart_tty_close() to serialize teardown against active asynchronous registration.

Changes in v6:
- Fixed missing `hu->proto_lock` write lock in hci_uart_init_work() error path to prevent race with readers (reported by Sashiko).
- Added disable_work_sync() instead of cancel_work_sync() for `hu->write_work` in hci_uart_init_work() and hci_uart_register_dev() error paths.

Changes in v5:
- Relocated disable_work_sync() to the very top of hci_uart_tty_close(), 
  before hci_uart_close(), to ensure no new work is submitted during device teardown.

Changes in v4:
- Adopted Luiz's suggestion to use disable_work_sync() instead of 
  cancel_work_sync() in close path to prevent new work submissions.

Changes in v3:
- Added 'Cc: stable' tag as requested by the stable bot.

Changes in v2:
- Added KASAN/ODEBUG crash trace.

 drivers/bluetooth/hci_ldisc.c | 33 ++++++++++++++++++++++++++++-----
 1 file changed, 28 insertions(+), 5 deletions(-)

diff --git a/drivers/bluetooth/hci_ldisc.c b/drivers/bluetooth/hci_ldisc.c
index 275ea865bc29..cb56194daad1 100644
--- a/drivers/bluetooth/hci_ldisc.c
+++ b/drivers/bluetooth/hci_ldisc.c
@@ -194,7 +194,15 @@ void hci_uart_init_work(struct work_struct *work)
 	err = hci_register_dev(hu->hdev);
 	if (err < 0) {
 		BT_ERR("Can't register HCI device");
+
+		percpu_down_write(&hu->proto_lock);
 		clear_bit(HCI_UART_PROTO_READY, &hu->flags);
+		percpu_up_write(&hu->proto_lock);
+
+		/* Safely cancel work after clearing flags */
+		cancel_work_sync(&hu->write_work);
+
+		/* Close protocol before freeing hdev */
 		hu->proto->close(hu);
 		hdev = hu->hdev;
 		hu->hdev = NULL;
@@ -263,8 +271,12 @@ static int hci_uart_open(struct hci_dev *hdev)
 /* Close device */
 static int hci_uart_close(struct hci_dev *hdev)
 {
+	struct hci_uart *hu = hci_get_drvdata(hdev);
+
 	BT_DBG("hdev %p", hdev);
 
+	cancel_work_sync(&hu->write_work);
+
 	hci_uart_flush(hdev);
 	hdev->flush = NULL;
 	return 0;
@@ -540,6 +552,12 @@ static void hci_uart_tty_close(struct tty_struct *tty)
 	if (!hu)
 		return;
 
+	/* Wait for init_ready to finish to prevent registration races */
+	cancel_work_sync(&hu->init_ready);
+
+	/* Unconditionally cancel write_work BEFORE hci_uart_close() to prevent double-free */
+	cancel_work_sync(&hu->write_work);
+
 	hdev = hu->hdev;
 	if (hdev)
 		hci_uart_close(hdev);
@@ -549,15 +567,15 @@ static void hci_uart_tty_close(struct tty_struct *tty)
 		clear_bit(HCI_UART_PROTO_READY, &hu->flags);
 		percpu_up_write(&hu->proto_lock);
 
-		cancel_work_sync(&hu->init_ready);
-		cancel_work_sync(&hu->write_work);
-
 		if (hdev) {
 			if (test_bit(HCI_UART_REGISTERED, &hu->flags))
 				hci_unregister_dev(hdev);
-			hci_free_dev(hdev);
 		}
+		/* Close protocol before freeing hdev */
 		hu->proto->close(hu);
+
+		if (hdev)
+			hci_free_dev(hdev);
 	}
 	clear_bit(HCI_UART_PROTO_SET, &hu->flags);
 
@@ -625,11 +643,12 @@ static void hci_uart_tty_receive(struct tty_struct *tty, const u8 *data,
 	 * tty caller
 	 */
 	hu->proto->recv(hu, data, count);
-	percpu_up_read(&hu->proto_lock);
 
 	if (hu->hdev)
 		hu->hdev->stat.byte_rx += count;
 
+	percpu_up_read(&hu->proto_lock);
+
 	tty_unthrottle(tty);
 }
 
@@ -695,6 +714,10 @@ static int hci_uart_register_dev(struct hci_uart *hu)
 		percpu_down_write(&hu->proto_lock);
 		clear_bit(HCI_UART_PROTO_INIT, &hu->flags);
 		percpu_up_write(&hu->proto_lock);
+		/* Cancel work after clearing flags */
+		cancel_work_sync(&hu->write_work);
+
+		/* Close protocol before freeing hdev */
 		hu->proto->close(hu);
 		hu->hdev = NULL;
 		hci_free_dev(hdev);
-- 
2.34.1


^ permalink raw reply related

* [PATCH] ARM: Move Footbridge-specific header into mach-footbridge
From: Ethan Nelson-Moore @ 2026-05-17  4:57 UTC (permalink / raw)
  To: linux-arm-kernel, linux-serial, linux-watchdog
  Cc: Ethan Nelson-Moore, Russell King, Arnd Bergmann,
	Greg Kroah-Hartman, Miquel Raynal, Richard Weinberger,
	Vignesh Raghavendra, Jiri Slaby, Wim Van Sebroeck, Guenter Roeck

arch/arm/include/asm/hardware/dec21285.h is specific to the DC21285
Footbridge chip and should not be in the global ARM include directory.
Move it into mach-footbridge where it belongs. It was included twice in
arch/arm/mach-footbridge/common.c; remove one of the includes.
Also remove the file path from the header (it is bad style and would
become outdated) and add missing include guards.

Tested by compiling footbridge_defconfig and netwinder_defconfig,
modified to additionally enable CONFIG_MTD_DC21285 and
CONFIG_DEBUG_FOOTBRIDGE_COM1 or CONFIG_DEBUG_DC21285_PORT, respectively
(these are the only Footbridge-related options not enabled by the
defconfigs).

Signed-off-by: Ethan Nelson-Moore <enelsonmoore@gmail.com>
---
This patch depends on my previous patch "ARM: clean up machine-specific
PCI code and move it into mach-footbridge" because it touches the same
area of arch/arm/mach-footbridge/dc21285.c.

 MAINTAINERS                                               | 1 -
 arch/arm/include/debug/dc21285.S                          | 2 +-
 arch/arm/mach-footbridge/common.c                         | 3 +--
 arch/arm/mach-footbridge/dc21285-timer.c                  | 2 +-
 arch/arm/mach-footbridge/dc21285.c                        | 2 +-
 arch/arm/mach-footbridge/dma-isa.c                        | 2 +-
 arch/arm/mach-footbridge/ebsa285.c                        | 2 +-
 .../hardware => mach-footbridge/include/mach}/dec21285.h  | 8 +++++---
 arch/arm/mach-footbridge/isa-irq.c                        | 2 +-
 arch/arm/mach-footbridge/isa.c                            | 2 +-
 arch/arm/mach-footbridge/netwinder-hw.c                   | 2 +-
 drivers/char/nwflash.c                                    | 2 +-
 drivers/mtd/maps/dc21285.c                                | 2 +-
 drivers/tty/serial/21285.c                                | 2 +-
 drivers/watchdog/wdt285.c                                 | 2 +-
 15 files changed, 18 insertions(+), 18 deletions(-)
 rename arch/arm/{include/asm/hardware => mach-footbridge/include/mach}/dec21285.h (98%)

diff --git a/MAINTAINERS b/MAINTAINERS
index c2c6d79275c6..37ecfe4bc4e4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2811,7 +2811,6 @@ M:	Russell King <linux@armlinux.org.uk>
 L:	linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
 S:	Maintained
 W:	http://www.armlinux.org.uk/
-F:	arch/arm/include/asm/hardware/dec21285.h
 F:	arch/arm/mach-footbridge/
 
 ARM/FREESCALE IMX / MXC ARM ARCHITECTURE
diff --git a/arch/arm/include/debug/dc21285.S b/arch/arm/include/debug/dc21285.S
index 4ec0e5e31704..c0eb58ba7d7e 100644
--- a/arch/arm/include/debug/dc21285.S
+++ b/arch/arm/include/debug/dc21285.S
@@ -7,7 +7,7 @@
  *  Moved from linux/arch/arm/kernel/debug.S by Ben Dooks
 */
 
-#include <asm/hardware/dec21285.h>
+#include <mach/dec21285.h>
 
 #include <mach/hardware.h>
 	/* For EBSA285 debugging */
diff --git a/arch/arm/mach-footbridge/common.c b/arch/arm/mach-footbridge/common.c
index 85c598708c10..d3076bf03875 100644
--- a/arch/arm/mach-footbridge/common.c
+++ b/arch/arm/mach-footbridge/common.c
@@ -20,7 +20,6 @@
 #include <asm/mach-types.h>
 #include <asm/setup.h>
 #include <asm/system_misc.h>
-#include <asm/hardware/dec21285.h>
 
 #include <asm/mach/irq.h>
 #include <asm/mach/map.h>
@@ -30,7 +29,7 @@
 
 #include <mach/hardware.h>
 #include <mach/irqs.h>
-#include <asm/hardware/dec21285.h>
+#include <mach/dec21285.h>
 
 static int dc21285_get_irq(void)
 {
diff --git a/arch/arm/mach-footbridge/dc21285-timer.c b/arch/arm/mach-footbridge/dc21285-timer.c
index 2908c9ef3c9b..f5d0024783e3 100644
--- a/arch/arm/mach-footbridge/dc21285-timer.c
+++ b/arch/arm/mach-footbridge/dc21285-timer.c
@@ -14,7 +14,7 @@
 
 #include <asm/irq.h>
 
-#include <asm/hardware/dec21285.h>
+#include <mach/dec21285.h>
 #include <asm/mach/time.h>
 #include <asm/system_info.h>
 
diff --git a/arch/arm/mach-footbridge/dc21285.c b/arch/arm/mach-footbridge/dc21285.c
index 5a68b6739ecf..923c808e8ba1 100644
--- a/arch/arm/mach-footbridge/dc21285.c
+++ b/arch/arm/mach-footbridge/dc21285.c
@@ -19,7 +19,7 @@
 
 #include <asm/irq.h>
 #include <asm/mach/pci.h>
-#include <asm/hardware/dec21285.h>
+#include <mach/dec21285.h>
 
 #include "pci.h"
 
diff --git a/arch/arm/mach-footbridge/dma-isa.c b/arch/arm/mach-footbridge/dma-isa.c
index 937f5376d5e7..300cdf6ef223 100644
--- a/arch/arm/mach-footbridge/dma-isa.c
+++ b/arch/arm/mach-footbridge/dma-isa.c
@@ -19,7 +19,7 @@
 
 #include <asm/dma.h>
 #include <asm/mach/dma.h>
-#include <asm/hardware/dec21285.h>
+#include <mach/dec21285.h>
 
 #define ISA_DMA_MASK		0
 #define ISA_DMA_MODE		1
diff --git a/arch/arm/mach-footbridge/ebsa285.c b/arch/arm/mach-footbridge/ebsa285.c
index 1cb7d674bc81..93ab333e3027 100644
--- a/arch/arm/mach-footbridge/ebsa285.c
+++ b/arch/arm/mach-footbridge/ebsa285.c
@@ -10,7 +10,7 @@
 #include <linux/slab.h>
 #include <linux/leds.h>
 
-#include <asm/hardware/dec21285.h>
+#include <mach/dec21285.h>
 #include <asm/mach-types.h>
 
 #include <asm/mach/arch.h>
diff --git a/arch/arm/include/asm/hardware/dec21285.h b/arch/arm/mach-footbridge/include/mach/dec21285.h
similarity index 98%
rename from arch/arm/include/asm/hardware/dec21285.h
rename to arch/arm/mach-footbridge/include/mach/dec21285.h
index 894f2a635cbb..35d10e2dcade 100644
--- a/arch/arm/include/asm/hardware/dec21285.h
+++ b/arch/arm/mach-footbridge/include/mach/dec21285.h
@@ -1,11 +1,13 @@
 /* SPDX-License-Identifier: GPL-2.0-only */
 /*
- *  arch/arm/include/asm/hardware/dec21285.h
- *
  *  Copyright (C) 1998 Russell King
  *
  *  DC21285 registers
  */
+
+#ifndef __MACH_DEC21285_H
+#define __MACH_DEC21285_H
+
 #define DC21285_PCI_IACK		0x79000000
 #define DC21285_ARMCSR_BASE		0x42000000
 #define DC21285_PCI_TYPE_0_CONFIG	0x7b000000
@@ -135,4 +137,4 @@
 #define TIMER_CNTL_DIV256	(2 << 2)
 #define TIMER_CNTL_CNTEXT	(3 << 2)
 
-
+#endif /* __MACH_DEC21285_H */
diff --git a/arch/arm/mach-footbridge/isa-irq.c b/arch/arm/mach-footbridge/isa-irq.c
index 842ddb4121ef..f9231e84028d 100644
--- a/arch/arm/mach-footbridge/isa-irq.c
+++ b/arch/arm/mach-footbridge/isa-irq.c
@@ -21,7 +21,7 @@
 #include <asm/mach/irq.h>
 
 #include <mach/hardware.h>
-#include <asm/hardware/dec21285.h>
+#include <mach/dec21285.h>
 #include <asm/irq.h>
 #include <asm/mach-types.h>
 
diff --git a/arch/arm/mach-footbridge/isa.c b/arch/arm/mach-footbridge/isa.c
index 84caccddce44..a028920e8f12 100644
--- a/arch/arm/mach-footbridge/isa.c
+++ b/arch/arm/mach-footbridge/isa.c
@@ -8,7 +8,7 @@
 #include <linux/serial_8250.h>
 
 #include <asm/irq.h>
-#include <asm/hardware/dec21285.h>
+#include <mach/dec21285.h>
 
 #include "common.h"
 
diff --git a/arch/arm/mach-footbridge/netwinder-hw.c b/arch/arm/mach-footbridge/netwinder-hw.c
index c024eefd4978..bd21c455a495 100644
--- a/arch/arm/mach-footbridge/netwinder-hw.c
+++ b/arch/arm/mach-footbridge/netwinder-hw.c
@@ -16,7 +16,7 @@
 #include <linux/slab.h>
 #include <linux/leds.h>
 
-#include <asm/hardware/dec21285.h>
+#include <mach/dec21285.h>
 #include <asm/mach-types.h>
 #include <asm/setup.h>
 #include <asm/system_misc.h>
diff --git a/drivers/char/nwflash.c b/drivers/char/nwflash.c
index 9f52f0306ef7..21ac9b2df42e 100644
--- a/drivers/char/nwflash.c
+++ b/drivers/char/nwflash.c
@@ -29,7 +29,7 @@
 #include <linux/mutex.h>
 #include <linux/jiffies.h>
 
-#include <asm/hardware/dec21285.h>
+#include <mach/dec21285.h>
 #include <asm/io.h>
 #include <asm/mach-types.h>
 #include <linux/uaccess.h>
diff --git a/drivers/mtd/maps/dc21285.c b/drivers/mtd/maps/dc21285.c
index 70a3db3ab856..8bcb40489f4f 100644
--- a/drivers/mtd/maps/dc21285.c
+++ b/drivers/mtd/maps/dc21285.c
@@ -17,7 +17,7 @@
 #include <linux/mtd/partitions.h>
 
 #include <asm/io.h>
-#include <asm/hardware/dec21285.h>
+#include <mach/dec21285.h>
 #include <asm/mach-types.h>
 
 
diff --git a/drivers/tty/serial/21285.c b/drivers/tty/serial/21285.c
index 4de0c975ebdc..f20c2092e4a5 100644
--- a/drivers/tty/serial/21285.c
+++ b/drivers/tty/serial/21285.c
@@ -18,7 +18,7 @@
 #include <asm/irq.h>
 #include <asm/mach-types.h>
 #include <asm/system_info.h>
-#include <asm/hardware/dec21285.h>
+#include <mach/dec21285.h>
 #include <mach/hardware.h>
 
 #define BAUD_BASE		(mem_fclk_21285/64)
diff --git a/drivers/watchdog/wdt285.c b/drivers/watchdog/wdt285.c
index 78681d9f7d53..347cb2892833 100644
--- a/drivers/watchdog/wdt285.c
+++ b/drivers/watchdog/wdt285.c
@@ -30,7 +30,7 @@
 
 #include <asm/mach-types.h>
 #include <asm/system_info.h>
-#include <asm/hardware/dec21285.h>
+#include <mach/dec21285.h>
 
 /*
  * Define this to stop the watchdog actually rebooting the machine.
-- 
2.43.0


^ permalink raw reply related

* [PATCH v7] Bluetooth: hci_uart: fix UAFs and race conditions in close and init paths
From: w15303746062 @ 2026-05-16  8:47 UTC (permalink / raw)
  To: luiz.dentz, pmenzel, marcel, linux-bluetooth
  Cc: linux-serial, linux-kernel, greg, stable, Mingyu Wang
In-Reply-To: <CABBYNZ+r3gm37FW5WqE79bRp+x9UZsaCtyvfz_FdixqEucAxGw@mail.gmail.com>

From: Mingyu Wang <25181214217@stu.xidian.edu.cn>

Vulnerabilities leading to Use-After-Free (UAF) and Null Pointer
Dereference (NPD) conditions were observed in the lifecycle management
of hci_uart.

The primary issue arises because the workqueues (init_ready and
write_work) are only flushed/cancelled if the HCI_UART_PROTO_READY
flag is set during TTY close. If a hangup occurs before setup completes,
hci_uart_tty_close() skips the teardown of these workqueues and
proceeds to free the `hu` struct. When the scheduled work executes
later, it blindly dereferences the freed `hu` struct.

Furthermore, several data races and UAFs were identified in the teardown
sequence:
1. Calling hci_uart_close(hdev) before cancel_work_sync(&hu->write_work)
   causes a race condition where hci_uart_flush() and write_work
   can concurrently double-free hu->tx_skb.
2. Calling hci_free_dev(hdev) before hu->proto->close(hu) causes a UAF
   when vendor specific protocol close callbacks dereference hu->hdev.
3. In the initialization error paths, failing to take the proto_lock
   write lock before clearing PROTO_READY leads to races with active
   readers (e.g., hci_uart_tty_receive).

Fix these synchronization and lifecycle issues by:
1. Re-ordering hci_uart_tty_close() to unconditionally cancel init_ready
   first, then atomically clear PROTO_READY under proto_lock, and safely
   cancel write_work before touching hdev.
2. Relocating hu->proto->close(hu) strictly prior to hci_free_dev(hdev)
   across all close and error paths to prevent vendor-level UAFs.
3. Utilizing cancel_work_sync() instead of disable_work_sync() after
   flags are cleared to safely flush workqueues without permanently
   breaking user-space retry capabilities.

Fixes: 3b799254cf6f ("Bluetooth: hci_uart: Cancel init work before unregistering")
Cc: stable@vger.kernel.org
Signed-off-by: Mingyu Wang <25181214217@stu.xidian.edu.cn>
---
Changes in v7:
- Reverted disable_work_sync() back to cancel_work_sync() across all error and close paths to preserve user-space retry capabilities, addressing the regression introduced in v4/v6 where work items were permanently disabled.
- Synchronized workqueue teardown safely by atomically clearing PROTO_READY / PROTO_INIT under proto_lock prior to calling cancel_work_sync(), preventing any concurrent work requeuing.
- Fixed a Use-After-Free (UAF) vulnerability in the teardown sequence by relocating hu->proto->close(hu) strictly prior to hci_free_dev(hdev) in all close and error paths, ensuring vendor specific callbacks safely access hu->hdev.
- Added cancel_work_sync(&hu->init_ready) at the very beginning of hci_uart_tty_close() to serialize teardown against active asynchronous registration, eliminating race-induced double-frees.

Changes in v6:
- Fixed missing `hu->proto_lock` write lock in hci_uart_init_work() error path to prevent race with readers (reported by Sashiko).
- Added disable_work_sync() instead of cancel_work_sync() for `hu->write_work` in hci_uart_init_work() and hci_uart_register_dev() error paths to completely block any concurrent re-queuing window before hdev is freed (reported by Sashiko).

Changes in v5:
- Relocated disable_work_sync() to the very top of hci_uart_tty_close(), 
  before hci_uart_close(), to ensure no new work is submitted during device teardown.

Changes in v4:
- Adopted Luiz's suggestion to use disable_work_sync() instead of 
  cancel_work_sync() in close path to prevent new work submissions.

Changes in v3:
- Added 'Cc: stable' tag as requested by the stable bot.

Changes in v2:
- Added KASAN/ODEBUG crash trace.

 drivers/bluetooth/hci_ldisc.c | 35 ++++++++++++++++++++++++++++-------
 1 file changed, 28 insertions(+), 7 deletions(-)

diff --git a/drivers/bluetooth/hci_ldisc.c b/drivers/bluetooth/hci_ldisc.c
index 275ea865bc29..46a080f77cb1 100644
--- a/drivers/bluetooth/hci_ldisc.c
+++ b/drivers/bluetooth/hci_ldisc.c
@@ -194,7 +194,15 @@ void hci_uart_init_work(struct work_struct *work)
 	err = hci_register_dev(hu->hdev);
 	if (err < 0) {
 		BT_ERR("Can't register HCI device");
+
+		percpu_down_write(&hu->proto_lock);
 		clear_bit(HCI_UART_PROTO_READY, &hu->flags);
+		percpu_up_write(&hu->proto_lock);
+
+		/* Safely cancel work after clearing flags */
+		cancel_work_sync(&hu->write_work);
+
+		/* Close protocol before freeing hdev */
 		hu->proto->close(hu);
 		hdev = hu->hdev;
 		hu->hdev = NULL;
@@ -531,6 +539,7 @@ static void hci_uart_tty_close(struct tty_struct *tty)
 {
 	struct hci_uart *hu = tty->disc_data;
 	struct hci_dev *hdev;
+	bool proto_ready;
 
 	BT_DBG("tty %p", tty);
 
@@ -540,24 +549,32 @@ static void hci_uart_tty_close(struct tty_struct *tty)
 	if (!hu)
 		return;
 
-	hdev = hu->hdev;
-	if (hdev)
-		hci_uart_close(hdev);
+	/* Wait for init_ready to finish to prevent registration races */
+	cancel_work_sync(&hu->init_ready);
 
-	if (test_bit(HCI_UART_PROTO_READY, &hu->flags)) {
+	proto_ready = test_bit(HCI_UART_PROTO_READY, &hu->flags);
+	if (proto_ready) {
 		percpu_down_write(&hu->proto_lock);
 		clear_bit(HCI_UART_PROTO_READY, &hu->flags);
 		percpu_up_write(&hu->proto_lock);
+	}
+	/* Unconditionally cancel write_work after clearing flags */
+	cancel_work_sync(&hu->write_work);
 
-		cancel_work_sync(&hu->init_ready);
-		cancel_work_sync(&hu->write_work);
+	hdev = hu->hdev;
+	if (hdev)
+		hci_uart_close(hdev);
 
+	if (proto_ready) {
 		if (hdev) {
 			if (test_bit(HCI_UART_REGISTERED, &hu->flags))
 				hci_unregister_dev(hdev);
-			hci_free_dev(hdev);
 		}
+		/* Close protocol before freeing hdev */
 		hu->proto->close(hu);
+
+		if (hdev)
+			hci_free_dev(hdev);
 	}
 	clear_bit(HCI_UART_PROTO_SET, &hu->flags);
 
@@ -695,6 +712,10 @@ static int hci_uart_register_dev(struct hci_uart *hu)
 		percpu_down_write(&hu->proto_lock);
 		clear_bit(HCI_UART_PROTO_INIT, &hu->flags);
 		percpu_up_write(&hu->proto_lock);
+		/* Cancel work after clearing flags */
+		cancel_work_sync(&hu->write_work);
+
+		/* Close protocol before freeing hdev */
 		hu->proto->close(hu);
 		hu->hdev = NULL;
 		hci_free_dev(hdev);
-- 
2.34.1


^ permalink raw reply related

* [PATCH v6] Bluetooth: hci_uart: fix UAFs and race conditions in close and init paths
From: w15303746062 @ 2026-05-16  5:30 UTC (permalink / raw)
  To: luiz.dentz, pmenzel, marcel, linux-bluetooth
  Cc: linux-serial, linux-kernel, greg, stable, Mingyu Wang
In-Reply-To: <CABBYNZ+r3gm37FW5WqE79bRp+x9UZsaCtyvfz_FdixqEucAxGw@mail.gmail.com>

From: Mingyu Wang <25181214217@stu.xidian.edu.cn>

Vulnerabilities leading to Use-After-Free (UAF) and Null Pointer
Dereference (NPD) conditions were observed in the lifecycle management
of hci_uart.

The primary issue arises because the workqueues (init_ready and write_work)
are only flushed/cancelled if the HCI_UART_PROTO_READY flag is set during
TTY close. If a hangup occurs before setup completes, hci_uart_tty_close()
skips the teardown of these workqueues and proceeds to free the `hu` struct.
When the scheduled work executes later, it blindly dereferences the freed
`hu` struct. This issue was triggered by our custom device emulation and
fuzzing framework (DevGen) on the v6.18 kernel.

The crash trace is as follows:
  ODEBUG: free active (active state 0) object: ffff88804024e870 object type: work_struct hint: hci_uart_write_work+0x0/0x940
  WARNING: CPU: 0 PID: 338273 at lib/debugobjects.c:612 debug_print_object+0x1a2/0x2b0
  ...
  Call Trace:
   <TASK>
   debug_check_no_obj_freed+0x3ec/0x520
   kfree+0x3f0/0x6c0
   hci_uart_tty_close+0x127/0x2a0
   k_ldisc_close+0x113/0x1a0
   tty_ldisc_kill+0x8e/0x150
   tty_ldisc_hangup+0x3c1/0x730
   __tty_hangup.part.0+0x3fd/0x8a0
   tty_ioctl+0x120f/0x1690
   __x64_sys_ioctl+0x18f/0x210
   do_syscall_64+0xcb/0xfa0
   entry_SYSCALL_64_after_hwframe+0x77/0x7f
   </TASK>

Additionally, sister bugs exist in the device registration error paths
within hci_uart_init_work() and hci_uart_register_dev(). If
hci_register_dev() fails, the code frees hdev and sets hu->hdev to NULL,
but leaves hu->write_work active if it was already scheduled during early
protocol setup phase. When the work executes later, it blindly fetches and
dereferences the NULL hu->hdev pointer, triggering an unconditioned kernel
panic.

Furthermore, the error path in hci_uart_init_work() clears the
HCI_UART_PROTO_READY flag and invokes hu->proto->close(hu) without
acquiring the hu->proto_lock write lock. Since concurrent callbacks like
hci_uart_tty_receive() execute under the read lock, this missing
synchronization allows the protocol state to be destroyed while active
readers are accessing it.

Fix these synchronization and lifecycle issues by:
1. Moving the workqueue teardown to the very beginning of
   hci_uart_tty_close() and using disable_work_sync() to unconditionally
   prevent new work submissions.
2. Wrapping the clearance of HCI_UART_PROTO_READY in hci_uart_init_work()
   with percpu_down_write/percpu_up_write of hu->proto_lock.
3. Explicitly invoking disable_work_sync(&hu->write_work) before resetting
   hu->hdev to NULL in both initialization error paths to safely prevent
   any concurrent re-queuing issues.

Fixes: 3b799254cf6f ("Bluetooth: hci_uart: Cancel init work before unregistering")
Cc: stable@vger.kernel.org
Signed-off-by: Mingyu Wang <25181214217@stu.xidian.edu.cn>
---
Changes in v6:
- Fixed missing `hu->proto_lock` write lock in hci_uart_init_work() error path to prevent race with readers (reported by Sashiko).
- Added disable_work_sync() instead of cancel_work_sync() for `hu->write_work` in hci_uart_init_work() and hci_uart_register_dev() error paths to completely block any concurrent re-queuing window before hdev is freed (reported by Sashiko).

Changes in v5:
- Relocated disable_work_sync() to the very top of hci_uart_tty_close(), 
  before hci_uart_close(), to ensure no new work is submitted during device teardown.

Changes in v4:
- Adopted Luiz's suggestion to use disable_work_sync() instead of 
  cancel_work_sync() in close path to prevent new work submissions.

Changes in v3:
- Added 'Cc: stable' tag as requested by the stable bot.

Changes in v2:
- Added KASAN/ODEBUG crash trace.

 drivers/bluetooth/hci_ldisc.c | 35 ++++++++++++++++++++++++++++++++---
 1 file changed, 32 insertions(+), 3 deletions(-)

diff --git a/drivers/bluetooth/hci_ldisc.c b/drivers/bluetooth/hci_ldisc.c
index 275ea865bc29..612fa2890cec 100644
--- a/drivers/bluetooth/hci_ldisc.c
+++ b/drivers/bluetooth/hci_ldisc.c
@@ -194,7 +194,22 @@ void hci_uart_init_work(struct work_struct *work)
 	err = hci_register_dev(hu->hdev);
 	if (err < 0) {
 		BT_ERR("Can't register HCI device");
+
+		/*
+		 * Acquire proto_lock before clearing PROTO_READY and closing
+		 * the protocol to synchronize with active readers.
+		 */
+		percpu_down_write(&hu->proto_lock);
 		clear_bit(HCI_UART_PROTO_READY, &hu->flags);
+		percpu_up_write(&hu->proto_lock);
+
+		/*
+		 * Disable any pending write_work that may have been queued
+		 * during the PROTO_INIT phase to prevent UAF/NPD when hdev
+		 * is freed.
+		 */
+		disable_work_sync(&hu->write_work);
+
 		hu->proto->close(hu);
 		hdev = hu->hdev;
 		hu->hdev = NULL;
@@ -540,6 +555,15 @@ static void hci_uart_tty_close(struct tty_struct *tty)
 	if (!hu)
 		return;
 
+	/*
+	 * Disable workqueues unconditionally before tearing down the
+	 * connection, as they might be active during the PROTO_INIT phase.
+	 * Using disable_work_sync() ensures no new submissions can occur
+	 * during or after hci_uart_close().
+	 */
+	disable_work_sync(&hu->init_ready);
+	disable_work_sync(&hu->write_work);
+
 	hdev = hu->hdev;
 	if (hdev)
 		hci_uart_close(hdev);
@@ -549,9 +573,6 @@ static void hci_uart_tty_close(struct tty_struct *tty)
 		clear_bit(HCI_UART_PROTO_READY, &hu->flags);
 		percpu_up_write(&hu->proto_lock);
 
-		cancel_work_sync(&hu->init_ready);
-		cancel_work_sync(&hu->write_work);
-
 		if (hdev) {
 			if (test_bit(HCI_UART_REGISTERED, &hu->flags))
 				hci_unregister_dev(hdev);
@@ -692,6 +713,14 @@ static int hci_uart_register_dev(struct hci_uart *hu)
 
 	if (hci_register_dev(hdev) < 0) {
 		BT_ERR("Can't register HCI device");
+
+		/*
+		 * Disable any pending write_work that may have been queued
+		 * during the proto->open() phase to prevent UAF/NPD when
+		 * hdev is freed.
+		 */
+		disable_work_sync(&hu->write_work);
+
 		percpu_down_write(&hu->proto_lock);
 		clear_bit(HCI_UART_PROTO_INIT, &hu->flags);
 		percpu_up_write(&hu->proto_lock);
-- 
2.34.1


^ permalink raw reply related

* [PATCH v5] Bluetooth: hci_uart: fix UAF in hci_uart_tty_close()
From: w15303746062 @ 2026-05-16  2:22 UTC (permalink / raw)
  To: luiz.dentz, pmenzel, marcel, linux-bluetooth
  Cc: linux-serial, linux-kernel, greg, stable, Mingyu Wang
In-Reply-To: <CABBYNZ+r3gm37FW5WqE79bRp+x9UZsaCtyvfz_FdixqEucAxGw@mail.gmail.com>

From: Mingyu Wang <25181214217@stu.xidian.edu.cn>

A Use-After-Free (UAF) vulnerability and a subsequent kernel panic were
observed in hci_uart_write_work() due to a race condition between the
initialization of the HCI UART line discipline and concurrent TTY hangup.

This issue was triggered by our custom device emulation and fuzzing
framework (DevGen) on the v6.18 kernel. Due to the highly timing-dependent
nature of this race condition (requiring a precise interleaving of
TIOCVHANGUP and protocol setup), Syzkaller failed to extract a reliable
standalone C reproducer (reproducer is too unreliable: 0.00).

The crash trace is as follows:
  ODEBUG: free active (active state 0) object: ffff88804024e870 object type: work_struct hint: hci_uart_write_work+0x0/0x940
  WARNING: CPU: 0 PID: 338273 at lib/debugobjects.c:612 debug_print_object+0x1a2/0x2b0
  ...
  Call Trace:
   <TASK>
   debug_check_no_obj_freed+0x3ec/0x520
   kfree+0x3f0/0x6c0
   hci_uart_tty_close+0x127/0x2a0
   k_ldisc_close+0x113/0x1a0
   tty_ldisc_kill+0x8e/0x150
   tty_ldisc_hangup+0x3c1/0x730
   __tty_hangup.part.0+0x3fd/0x8a0
   tty_ioctl+0x120f/0x1690
   __x64_sys_ioctl+0x18f/0x210
   do_syscall_64+0xcb/0xfa0
   entry_SYSCALL_64_after_hwframe+0x77/0x7f
   </TASK>

The issue arises because the workqueues (init_ready and write_work) are
only flushed/cancelled if the HCI_UART_PROTO_READY flag is set. However,
during the protocol initialization phase (HCI_UART_PROTO_INIT), the
underlying protocol may schedule work. If a hangup occurs before the setup
completes and the READY flag is set, hci_uart_tty_close() skips the
teardown of these workqueues and proceeds to free the `hu` struct. When
the scheduled work executes later, it blindly dereferences the freed `hu`
struct.

Fix this by moving the workqueue teardown to the very beginning of
hci_uart_tty_close(), outside the HCI_UART_PROTO_READY check and prior to
calling hci_uart_close(). Furthermore, use disable_work_sync() instead of
cancel_work_sync() to unconditionally disable the works. This ensures that
any pending works are cancelled and no new submissions can occur before or
during the teardown of the device connection. Note that hu->init_ready and
hu->write_work are initialized in hci_uart_tty_open(), so it is always safe
to call disable_work_sync() on them in hci_uart_tty_close(), even if the
protocol was never fully attached.

Fixes: 3b799254cf6f ("Bluetooth: hci_uart: Cancel init work before unregistering")
Cc: stable@vger.kernel.org
Signed-off-by: Mingyu Wang <25181214217@stu.xidian.edu.cn>
---
Changes in v5:
- Moved disable_work_sync() to the very top of hci_uart_tty_close(), 
  before hci_uart_close(), to prevent any concurrent re-queuing 
  during device shutdown and resolve Sashiko static analysis warnings.

Changes in v4:
- Adopted Luiz's suggestion to use disable_work_sync() instead of 
  cancel_work_sync() to prevent new work submissions during teardown.

Changes in v3:
- Added 'Cc: stable' tag as requested by the stable bot.

Changes in v2:
- Added KASAN/ODEBUG crash trace.

 drivers/bluetooth/hci_ldisc.c | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/drivers/bluetooth/hci_ldisc.c b/drivers/bluetooth/hci_ldisc.c
index 275ea865bc29..ebdbcd567cd2 100644
--- a/drivers/bluetooth/hci_ldisc.c
+++ b/drivers/bluetooth/hci_ldisc.c
@@ -540,6 +540,15 @@ static void hci_uart_tty_close(struct tty_struct *tty)
 	if (!hu)
 		return;
 
+	/*
+	 * Disable workqueues unconditionally before tearing down the
+	 * connection, as they might be active during the PROTO_INIT phase.
+	 * Using disable_work_sync() ensures no new submissions can occur
+	 * during or after hci_uart_close().
+	 */
+	disable_work_sync(&hu->init_ready);
+	disable_work_sync(&hu->write_work);
+
 	hdev = hu->hdev;
 	if (hdev)
 		hci_uart_close(hdev);
@@ -549,9 +558,6 @@ static void hci_uart_tty_close(struct tty_struct *tty)
 		clear_bit(HCI_UART_PROTO_READY, &hu->flags);
 		percpu_up_write(&hu->proto_lock);
 
-		cancel_work_sync(&hu->init_ready);
-		cancel_work_sync(&hu->write_work);
-
 		if (hdev) {
 			if (test_bit(HCI_UART_REGISTERED, &hu->flags))
 				hci_unregister_dev(hdev);
-- 
2.34.1


^ permalink raw reply related

* Re:Re: [PATCH v4] Bluetooth: hci_uart: fix UAF in hci_uart_tty_close()
From: w15303746062 @ 2026-05-16  1:41 UTC (permalink / raw)
  To: Luiz Augusto von Dentz
  Cc: pmenzel, marcel, linux-bluetooth, linux-serial, linux-kernel,
	greg, stable, Mingyu Wang
In-Reply-To: <CABBYNZ+r3gm37FW5WqE79bRp+x9UZsaCtyvfz_FdixqEucAxGw@mail.gmail.com>


Hi Luiz,

Thank you for checking it with Sashiko.


At 2026-05-16 00:08:05, "Luiz Augusto von Dentz" <luiz.dentz@gmail.com> wrote:
>Hi,
>
>On Fri, May 15, 2026 at 10:06 AM <w15303746062@163.com> wrote:
>>
>> From: Mingyu Wang <25181214217@stu.xidian.edu.cn>
>>
>> A Use-After-Free (UAF) vulnerability and a subsequent kernel panic were
>> observed in hci_uart_write_work() due to a race condition between the
>> initialization of the HCI UART line discipline and concurrent TTY hangup.
>>
>> This issue was triggered by our custom device emulation and fuzzing
>> framework (DevGen) on the v6.18 kernel. Due to the highly timing-dependent
>> nature of this race condition (requiring a precise interleaving of
>> TIOCVHANGUP and protocol setup), Syzkaller failed to extract a reliable
>> standalone C reproducer (reproducer is too unreliable: 0.00).
>>
>> The crash trace is as follows:
>>   ODEBUG: free active (active state 0) object: ffff88804024e870 object type: work_struct hint: hci_uart_write_work+0x0/0x940
>>   WARNING: CPU: 0 PID: 338273 at lib/debugobjects.c:612 debug_print_object+0x1a2/0x2b0
>>   ...
>>   Call Trace:
>>    <TASK>
>>    debug_check_no_obj_freed+0x3ec/0x520
>>    kfree+0x3f0/0x6c0
>>    hci_uart_tty_close+0x127/0x2a0
>>    tty_ldisc_close+0x113/0x1a0
>>    tty_ldisc_kill+0x8e/0x150
>>    tty_ldisc_hangup+0x3c1/0x730
>>    __tty_hangup.part.0+0x3fd/0x8a0
>>    tty_ioctl+0x120f/0x1690
>>    __x64_sys_ioctl+0x18f/0x210
>>    do_syscall_64+0xcb/0xfa0
>>    entry_SYSCALL_64_after_hwframe+0x77/0x7f
>>    </TASK>
>>
>> The issue arises because the workqueues (init_ready and write_work) are
>> only flushed/cancelled if the HCI_UART_PROTO_READY flag is set. However,
>> during the protocol initialization phase (HCI_UART_PROTO_INIT), the
>> underlying protocol may schedule work. If a hangup occurs before the setup
>> completes and the READY flag is set, hci_uart_tty_close() skips the
>> teardown of these workqueues and proceeds to free the `hu` struct. When
>> the scheduled work executes later, it blindly dereferences the freed `hu`
>> struct.
>>
>> Fix this by moving the workqueue teardown outside the HCI_UART_PROTO_READY
>> check. Furthermore, use disable_work_sync() instead of cancel_work_sync()
>> to unconditionally disable the works. This ensures that any pending works
>> are cancelled and no new submissions can occur before the hci_uart
>> structure is freed. Note that hu->init_ready and hu->write_work are
>> initialized in hci_uart_tty_open(), so it is always safe to call
>> disable_work_sync() on them in hci_uart_tty_close(), even if the protocol
>> was never fully attached.
>>
>> Fixes: 3b799254cf6f ("Bluetooth: hci_uart: Cancel init work before unregistering")
>> Cc: stable@vger.kernel.org
>> Signed-off-by: Mingyu Wang <25181214217@stu.xidian.edu.cn>
>> ---
>> Changes in v4:
>> - Adopted Luiz's suggestion to use disable_work_sync() instead of
>>   cancel_work_sync() to prevent new work submissions during teardown.
>>
>> Changes in v3:
>> - Added 'Cc: stable' tag as requested by the stable bot.
>>
>> Changes in v2:
>> - Added KASAN/ODEBUG crash trace.
>>
>>  drivers/bluetooth/hci_ldisc.c | 12 +++++++++---
>>  1 file changed, 9 insertions(+), 3 deletions(-)
>>
>> diff --git a/drivers/bluetooth/hci_ldisc.c b/drivers/bluetooth/hci_ldisc.c
>> index 275ea865bc29..333c1e1503e8 100644
>> --- a/drivers/bluetooth/hci_ldisc.c
>> +++ b/drivers/bluetooth/hci_ldisc.c
>> @@ -544,14 +544,20 @@ static void hci_uart_tty_close(struct tty_struct *tty)
>>         if (hdev)
>>                 hci_uart_close(hdev);
>>
>> +       /*
>> +        * Disable workqueues unconditionally before freeing the hu
>> +        * struct, as they might be active during the PROTO_INIT phase.
>> +        * Using disable_work_sync() instead of cancel_work_sync()
>> +        * ensures no new submissions can occur.
>> +        */
>> +       disable_work_sync(&hu->init_ready);
>> +       disable_work_sync(&hu->write_work);
>
>Looks like sashiko has a problem with these being after hci_uart_close:

I see the issue now. Placing `disable_work_sync()` after `hci_uart_close()`
could still leave a tiny window where the workqueues might race with the 
teardown of the `hdev` structure. 

The safest and most logical approach is to pull the `disable_work_sync()`
calls to the very top of `hci_uart_tty_close()`, before `hci_uart_close()` 
or any other teardown logic begins. This will completely choke off any 
asynchronous operations before we touch the connection state or hardware.

I will update the patch and send out v5 immediately.

>
>https://sashiko.dev/#/patchset/20260515140548.393865-1-w15303746062%40163.com
>
>>         if (test_bit(HCI_UART_PROTO_READY, &hu->flags)) {
>>                 percpu_down_write(&hu->proto_lock);
>>                 clear_bit(HCI_UART_PROTO_READY, &hu->flags);
>>                 percpu_up_write(&hu->proto_lock);
>>
>> -               cancel_work_sync(&hu->init_ready);
>> -               cancel_work_sync(&hu->write_work);
>> -
>>                 if (hdev) {
>>                         if (test_bit(HCI_UART_REGISTERED, &hu->flags))
>>                                 hci_unregister_dev(hdev);
>> --
>> 2.34.1
>>
>
>
>-- 
>Luiz Augusto von Dentz

Best regards,
Mingyu

^ permalink raw reply

* [PATCH v2] serial: max310x: fix compile errors if CONFIG_SPI_MASTER is disabled
From: Hugo Villeneuve @ 2026-05-15 18:30 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby, Hugo Villeneuve
  Cc: hugo, kernel test robot, linux-kernel, linux-serial

From: Hugo Villeneuve <hvilleneuve@dimonoff.com>

Since commit 20ffe4b3330a8 ("serial: max310x: allow driver to be built with
SPI or I2C"), if I2C is enabled and SPI_MASTER is disabled, we have these
compile errors:

  drivers/tty/serial/max310x.c: In function 'max310x_uart_init':
  drivers/tty/serial/max310x.c: error: 'max310x_spi_driver' undeclared...
  drivers/tty/serial/max310x.c: In function ‘max310x_uart_init’:
  drivers/tty/serial/max310x.c: error: label ‘err_spi_register’
  defined but not used...
  drivers/tty/serial/max310x.c: error: ‘regcfg’ defined but not used

Fix by properly encapsulating i2c/spi code/variables in their respective
context with IS_ENABLED() macros for CONFIG_I2C and CONFIG_SPI_MASTER.

Fixes: 20ffe4b3330a8 ("serial: max310x: allow driver to be built with SPI or I2C")
Reported-by: kernel test robot <lkp@intel.com>
Closes: https://lore.kernel.org/oe-kbuild-all/202605121847.N9DVLNg2-lkp@intel.com/
Signed-off-by: Hugo Villeneuve <hvilleneuve@dimonoff.com>
---
note: not Cc-ing stable as the commit is still in tty-next, and even if the
errors originate from original commit that added I2C support, they were not
trigerred because the driver could not be selected/compiled if
CONFIG_SPI_MASTER was disabled.

Changes for v2:
- replace #ifdef with #if IS_ENABLED() to suppoirt both built-in and modules
  options
---
 drivers/tty/serial/max310x.c | 48 +++++++++++++++++++-----------------
 1 file changed, 26 insertions(+), 22 deletions(-)

diff --git a/drivers/tty/serial/max310x.c b/drivers/tty/serial/max310x.c
index 9f423b3b4201d..bad5329a0c84c 100644
--- a/drivers/tty/serial/max310x.c
+++ b/drivers/tty/serial/max310x.c
@@ -16,6 +16,7 @@
 #include <linux/device.h>
 #include <linux/gpio/driver.h>
 #include <linux/i2c.h>
+#include <linux/kconfig.h>
 #include <linux/module.h>
 #include <linux/mod_devicetable.h>
 #include <linux/property.h>
@@ -1507,6 +1508,21 @@ static const struct of_device_id __maybe_unused max310x_dt_ids[] = {
 };
 MODULE_DEVICE_TABLE(of, max310x_dt_ids);
 
+static const char *max310x_regmap_name(u8 port_id)
+{
+	switch (port_id) {
+	case 0:	return "port0";
+	case 1:	return "port1";
+	case 2:	return "port2";
+	case 3:	return "port3";
+	default:
+		WARN_ON(true);
+		return NULL;
+	}
+}
+
+#if IS_ENABLED(CONFIG_SPI_MASTER)
+
 static struct regmap_config regcfg = {
 	.reg_bits = 8,
 	.val_bits = 8,
@@ -1522,20 +1538,6 @@ static struct regmap_config regcfg = {
 	.max_raw_write = MAX310X_FIFO_SIZE,
 };
 
-static const char *max310x_regmap_name(u8 port_id)
-{
-	switch (port_id) {
-	case 0:	return "port0";
-	case 1:	return "port1";
-	case 2:	return "port2";
-	case 3:	return "port3";
-	default:
-		WARN_ON(true);
-		return NULL;
-	}
-}
-
-#ifdef CONFIG_SPI_MASTER
 static int max310x_spi_extended_reg_enable(struct device *dev, bool enable)
 {
 	struct max310x_port *s = dev_get_drvdata(dev);
@@ -1606,7 +1608,8 @@ static struct spi_driver max310x_spi_driver = {
 };
 #endif
 
-#ifdef CONFIG_I2C
+#if IS_ENABLED(CONFIG_I2C)
+
 static int max310x_i2c_extended_reg_enable(struct device *dev, bool enable)
 {
 	return 0;
@@ -1726,13 +1729,13 @@ static int __init max310x_uart_init(void)
 	if (ret)
 		return ret;
 
-#ifdef CONFIG_SPI_MASTER
+#if IS_ENABLED(CONFIG_SPI_MASTER)
 	ret = spi_register_driver(&max310x_spi_driver);
 	if (ret)
 		goto err_spi_register;
 #endif
 
-#ifdef CONFIG_I2C
+#if IS_ENABLED(CONFIG_I2C)
 	ret = i2c_add_driver(&max310x_i2c_driver);
 	if (ret)
 		goto err_i2c_register;
@@ -1740,12 +1743,13 @@ static int __init max310x_uart_init(void)
 
 	return 0;
 
-#ifdef CONFIG_I2C
+#if IS_ENABLED(CONFIG_I2C)
 err_i2c_register:
-	spi_unregister_driver(&max310x_spi_driver);
 #endif
-
+#if IS_ENABLED(CONFIG_SPI_MASTER)
+	spi_unregister_driver(&max310x_spi_driver);
 err_spi_register:
+#endif
 	uart_unregister_driver(&max310x_uart);
 
 	return ret;
@@ -1754,11 +1758,11 @@ module_init(max310x_uart_init);
 
 static void __exit max310x_uart_exit(void)
 {
-#ifdef CONFIG_I2C
+#if IS_ENABLED(CONFIG_I2C)
 	i2c_del_driver(&max310x_i2c_driver);
 #endif
 
-#ifdef CONFIG_SPI_MASTER
+#if IS_ENABLED(CONFIG_SPI_MASTER)
 	spi_unregister_driver(&max310x_spi_driver);
 #endif
 

base-commit: 16e95bfb79b5d9d01dc7651d98caf3c2ace331cd
-- 
2.47.3


^ permalink raw reply related

* Re: [PATCH v4] Bluetooth: hci_uart: fix UAF in hci_uart_tty_close()
From: Luiz Augusto von Dentz @ 2026-05-15 16:08 UTC (permalink / raw)
  To: w15303746062
  Cc: pmenzel, marcel, linux-bluetooth, linux-serial, linux-kernel,
	greg, stable, Mingyu Wang
In-Reply-To: <20260515140548.393865-1-w15303746062@163.com>

Hi,

On Fri, May 15, 2026 at 10:06 AM <w15303746062@163.com> wrote:
>
> From: Mingyu Wang <25181214217@stu.xidian.edu.cn>
>
> A Use-After-Free (UAF) vulnerability and a subsequent kernel panic were
> observed in hci_uart_write_work() due to a race condition between the
> initialization of the HCI UART line discipline and concurrent TTY hangup.
>
> This issue was triggered by our custom device emulation and fuzzing
> framework (DevGen) on the v6.18 kernel. Due to the highly timing-dependent
> nature of this race condition (requiring a precise interleaving of
> TIOCVHANGUP and protocol setup), Syzkaller failed to extract a reliable
> standalone C reproducer (reproducer is too unreliable: 0.00).
>
> The crash trace is as follows:
>   ODEBUG: free active (active state 0) object: ffff88804024e870 object type: work_struct hint: hci_uart_write_work+0x0/0x940
>   WARNING: CPU: 0 PID: 338273 at lib/debugobjects.c:612 debug_print_object+0x1a2/0x2b0
>   ...
>   Call Trace:
>    <TASK>
>    debug_check_no_obj_freed+0x3ec/0x520
>    kfree+0x3f0/0x6c0
>    hci_uart_tty_close+0x127/0x2a0
>    tty_ldisc_close+0x113/0x1a0
>    tty_ldisc_kill+0x8e/0x150
>    tty_ldisc_hangup+0x3c1/0x730
>    __tty_hangup.part.0+0x3fd/0x8a0
>    tty_ioctl+0x120f/0x1690
>    __x64_sys_ioctl+0x18f/0x210
>    do_syscall_64+0xcb/0xfa0
>    entry_SYSCALL_64_after_hwframe+0x77/0x7f
>    </TASK>
>
> The issue arises because the workqueues (init_ready and write_work) are
> only flushed/cancelled if the HCI_UART_PROTO_READY flag is set. However,
> during the protocol initialization phase (HCI_UART_PROTO_INIT), the
> underlying protocol may schedule work. If a hangup occurs before the setup
> completes and the READY flag is set, hci_uart_tty_close() skips the
> teardown of these workqueues and proceeds to free the `hu` struct. When
> the scheduled work executes later, it blindly dereferences the freed `hu`
> struct.
>
> Fix this by moving the workqueue teardown outside the HCI_UART_PROTO_READY
> check. Furthermore, use disable_work_sync() instead of cancel_work_sync()
> to unconditionally disable the works. This ensures that any pending works
> are cancelled and no new submissions can occur before the hci_uart
> structure is freed. Note that hu->init_ready and hu->write_work are
> initialized in hci_uart_tty_open(), so it is always safe to call
> disable_work_sync() on them in hci_uart_tty_close(), even if the protocol
> was never fully attached.
>
> Fixes: 3b799254cf6f ("Bluetooth: hci_uart: Cancel init work before unregistering")
> Cc: stable@vger.kernel.org
> Signed-off-by: Mingyu Wang <25181214217@stu.xidian.edu.cn>
> ---
> Changes in v4:
> - Adopted Luiz's suggestion to use disable_work_sync() instead of
>   cancel_work_sync() to prevent new work submissions during teardown.
>
> Changes in v3:
> - Added 'Cc: stable' tag as requested by the stable bot.
>
> Changes in v2:
> - Added KASAN/ODEBUG crash trace.
>
>  drivers/bluetooth/hci_ldisc.c | 12 +++++++++---
>  1 file changed, 9 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/bluetooth/hci_ldisc.c b/drivers/bluetooth/hci_ldisc.c
> index 275ea865bc29..333c1e1503e8 100644
> --- a/drivers/bluetooth/hci_ldisc.c
> +++ b/drivers/bluetooth/hci_ldisc.c
> @@ -544,14 +544,20 @@ static void hci_uart_tty_close(struct tty_struct *tty)
>         if (hdev)
>                 hci_uart_close(hdev);
>
> +       /*
> +        * Disable workqueues unconditionally before freeing the hu
> +        * struct, as they might be active during the PROTO_INIT phase.
> +        * Using disable_work_sync() instead of cancel_work_sync()
> +        * ensures no new submissions can occur.
> +        */
> +       disable_work_sync(&hu->init_ready);
> +       disable_work_sync(&hu->write_work);

Looks like sashiko has a problem with these being after hci_uart_close:

https://sashiko.dev/#/patchset/20260515140548.393865-1-w15303746062%40163.com

>         if (test_bit(HCI_UART_PROTO_READY, &hu->flags)) {
>                 percpu_down_write(&hu->proto_lock);
>                 clear_bit(HCI_UART_PROTO_READY, &hu->flags);
>                 percpu_up_write(&hu->proto_lock);
>
> -               cancel_work_sync(&hu->init_ready);
> -               cancel_work_sync(&hu->write_work);
> -
>                 if (hdev) {
>                         if (test_bit(HCI_UART_REGISTERED, &hu->flags))
>                                 hci_unregister_dev(hdev);
> --
> 2.34.1
>


-- 
Luiz Augusto von Dentz

^ permalink raw reply

* [PATCH v4] Bluetooth: hci_uart: fix UAF in hci_uart_tty_close()
From: w15303746062 @ 2026-05-15 14:05 UTC (permalink / raw)
  To: luiz.dentz, pmenzel, marcel, linux-bluetooth
  Cc: linux-serial, linux-kernel, greg, stable, Mingyu Wang
In-Reply-To: <CABBYNZLjreYY_BczAQr2G6L=iJjBYKksFp53CairG-6V0Cb0EA@mail.gmail.com>

From: Mingyu Wang <25181214217@stu.xidian.edu.cn>

A Use-After-Free (UAF) vulnerability and a subsequent kernel panic were
observed in hci_uart_write_work() due to a race condition between the
initialization of the HCI UART line discipline and concurrent TTY hangup.

This issue was triggered by our custom device emulation and fuzzing
framework (DevGen) on the v6.18 kernel. Due to the highly timing-dependent
nature of this race condition (requiring a precise interleaving of
TIOCVHANGUP and protocol setup), Syzkaller failed to extract a reliable
standalone C reproducer (reproducer is too unreliable: 0.00).

The crash trace is as follows:
  ODEBUG: free active (active state 0) object: ffff88804024e870 object type: work_struct hint: hci_uart_write_work+0x0/0x940
  WARNING: CPU: 0 PID: 338273 at lib/debugobjects.c:612 debug_print_object+0x1a2/0x2b0
  ...
  Call Trace:
   <TASK>
   debug_check_no_obj_freed+0x3ec/0x520
   kfree+0x3f0/0x6c0
   hci_uart_tty_close+0x127/0x2a0
   tty_ldisc_close+0x113/0x1a0
   tty_ldisc_kill+0x8e/0x150
   tty_ldisc_hangup+0x3c1/0x730
   __tty_hangup.part.0+0x3fd/0x8a0
   tty_ioctl+0x120f/0x1690
   __x64_sys_ioctl+0x18f/0x210
   do_syscall_64+0xcb/0xfa0
   entry_SYSCALL_64_after_hwframe+0x77/0x7f
   </TASK>

The issue arises because the workqueues (init_ready and write_work) are
only flushed/cancelled if the HCI_UART_PROTO_READY flag is set. However,
during the protocol initialization phase (HCI_UART_PROTO_INIT), the
underlying protocol may schedule work. If a hangup occurs before the setup
completes and the READY flag is set, hci_uart_tty_close() skips the
teardown of these workqueues and proceeds to free the `hu` struct. When
the scheduled work executes later, it blindly dereferences the freed `hu`
struct.

Fix this by moving the workqueue teardown outside the HCI_UART_PROTO_READY
check. Furthermore, use disable_work_sync() instead of cancel_work_sync()
to unconditionally disable the works. This ensures that any pending works
are cancelled and no new submissions can occur before the hci_uart
structure is freed. Note that hu->init_ready and hu->write_work are
initialized in hci_uart_tty_open(), so it is always safe to call
disable_work_sync() on them in hci_uart_tty_close(), even if the protocol
was never fully attached.

Fixes: 3b799254cf6f ("Bluetooth: hci_uart: Cancel init work before unregistering")
Cc: stable@vger.kernel.org
Signed-off-by: Mingyu Wang <25181214217@stu.xidian.edu.cn>
---
Changes in v4:
- Adopted Luiz's suggestion to use disable_work_sync() instead of 
  cancel_work_sync() to prevent new work submissions during teardown.

Changes in v3:
- Added 'Cc: stable' tag as requested by the stable bot.

Changes in v2:
- Added KASAN/ODEBUG crash trace.

 drivers/bluetooth/hci_ldisc.c | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/drivers/bluetooth/hci_ldisc.c b/drivers/bluetooth/hci_ldisc.c
index 275ea865bc29..333c1e1503e8 100644
--- a/drivers/bluetooth/hci_ldisc.c
+++ b/drivers/bluetooth/hci_ldisc.c
@@ -544,14 +544,20 @@ static void hci_uart_tty_close(struct tty_struct *tty)
 	if (hdev)
 		hci_uart_close(hdev);
 
+	/*
+	 * Disable workqueues unconditionally before freeing the hu
+	 * struct, as they might be active during the PROTO_INIT phase.
+	 * Using disable_work_sync() instead of cancel_work_sync()
+	 * ensures no new submissions can occur.
+	 */
+	disable_work_sync(&hu->init_ready);
+	disable_work_sync(&hu->write_work);
+
 	if (test_bit(HCI_UART_PROTO_READY, &hu->flags)) {
 		percpu_down_write(&hu->proto_lock);
 		clear_bit(HCI_UART_PROTO_READY, &hu->flags);
 		percpu_up_write(&hu->proto_lock);
 
-		cancel_work_sync(&hu->init_ready);
-		cancel_work_sync(&hu->write_work);
-
 		if (hdev) {
 			if (test_bit(HCI_UART_REGISTERED, &hu->flags))
 				hci_unregister_dev(hdev);
-- 
2.34.1


^ permalink raw reply related

* Re:Re: [PATCH] Bluetooth: hci_uart: fix UAF in hci_uart_tty_close()
From: w15303746062 @ 2026-05-15 13:39 UTC (permalink / raw)
  To: Luiz Augusto von Dentz
  Cc: marcel, linux-bluetooth, linux-serial, linux-kernel, Mingyu Wang
In-Reply-To: <CABBYNZLjreYY_BczAQr2G6L=iJjBYKksFp53CairG-6V0Cb0EA@mail.gmail.com>


Hi Luiz,

Thank you for the review.

That is an excellent suggestion. You are absolutely right. Since the
`hu` structure is being torn down and freed immediately afterward, 
using `disable_work_sync()` provides a much stronger guarantee by 
preventing any concurrent threads from re-queuing the works, thus 
eliminating the risk of a lingering UAF.

Both `init_ready` and `write_work` are standard `struct work_struct`,
so `disable_work_sync()` applies perfectly here.

I will send out a v4 patch shortly adopting this change. 
Thank you for pointing this out!

Best regards,
Mingyu


At 2026-05-15 20:37:57, "Luiz Augusto von Dentz" <luiz.dentz@gmail.com> wrote:
>Hi,
>
>On Wed, May 13, 2026 at 2:46 AM <w15303746062@163.com> wrote:
>>
>> From: Mingyu Wang <25181214217@stu.xidian.edu.cn>
>>
>> A Use-After-Free (UAF) vulnerability and a subsequent General Protection
>> Fault (GPF) were observed in h5_recv() due to a race condition between
>> the initialization of the HCI UART line discipline and concurrent TTY
>> hangup via TIOCVHANGUP.
>>
>> The issue arises because the workqueues (init_ready and write_work) are
>> only cancelled if the HCI_UART_PROTO_READY flag is set. However, during
>> the protocol initialization phase (HCI_UART_PROTO_INIT), the underlying
>> protocol (e.g., H5) may schedule work (such as sending sync/config
>> packets). If a hangup occurs before the setup completes and the READY
>> flag is set, hci_uart_tty_close() skips the cancel_work_sync() calls
>> and proceeds to free the `hu` struct.
>>
>> When the delayed workqueue finally executes, it blindly dereferences
>> the freed `hu` struct, causing ODEBUG warnings and kernel panics.
>>
>> Fix this by moving the cancel_work_sync() calls outside the
>> HCI_UART_PROTO_READY check, ensuring that any pending works are
>> unconditionally cancelled before the hci_uart structure is freed.
>>
>> Signed-off-by: Mingyu Wang <25181214217@stu.xidian.edu.cn>
>> ---
>>  drivers/bluetooth/hci_ldisc.c | 10 +++++++---
>>  1 file changed, 7 insertions(+), 3 deletions(-)
>>
>> diff --git a/drivers/bluetooth/hci_ldisc.c b/drivers/bluetooth/hci_ldisc.c
>> index 275ea865bc29..566e1c525ee2 100644
>> --- a/drivers/bluetooth/hci_ldisc.c
>> +++ b/drivers/bluetooth/hci_ldisc.c
>> @@ -544,14 +544,18 @@ static void hci_uart_tty_close(struct tty_struct *tty)
>>         if (hdev)
>>                 hci_uart_close(hdev);
>>
>> +       /*
>> +        * Always cancel workqueues unconditionally before freeing the hu
>> +        * struct, as they might be active during the PROTO_INIT phase.
>> +        */
>> +       cancel_work_sync(&hu->init_ready);
>> +       cancel_work_sync(&hu->write_work);
>
>Can't we use disable_work_sync? If it frees up at the end, it's
>probably best to disable it so it doesn't allow new submissions.
>
>>         if (test_bit(HCI_UART_PROTO_READY, &hu->flags)) {
>>                 percpu_down_write(&hu->proto_lock);
>>                 clear_bit(HCI_UART_PROTO_READY, &hu->flags);
>>                 percpu_up_write(&hu->proto_lock);
>>
>> -               cancel_work_sync(&hu->init_ready);
>> -               cancel_work_sync(&hu->write_work);
>> -
>>                 if (hdev) {
>>                         if (test_bit(HCI_UART_REGISTERED, &hu->flags))
>>                                 hci_unregister_dev(hdev);
>> --
>> 2.34.1
>>
>>
>
>
>-- 
>Luiz Augusto von Dentz

^ permalink raw reply

* [PATCH] tty: serial: samsung: Remove redundant port lock acquisition in rx helpers
From: Tudor Ambarus @ 2026-05-15 12:41 UTC (permalink / raw)
  To: Krzysztof Kozlowski, Alim Akhtar, Greg Kroah-Hartman, Jiri Slaby,
	Ben Dooks
  Cc: linux-arm-kernel, linux-samsung-soc, linux-kernel, linux-serial,
	john.ogness, peter.griffin, andre.draszik, jyescas, kernel-team,
	stable, John Ogness, Tudor Ambarus

Sashiko identified a deadlock when the console flow is engaged [1].

When console flow control is enabled (UPF_CONS_FLOW),
s3c24xx_serial_stop_tx() calls s3c24xx_serial_rx_enable() and
s3c24xx_serial_start_tx() calls s3c24xx_serial_rx_disable().

The serial core framework invokes the .stop_tx() and .start_tx()
callbacks with the port->lock spinlock already held. Furthermore, all
internal driver paths that invoke stop_tx (such as the DMA TX
completion handler s3c24xx_serial_tx_dma_complete() or the PIO TX IRQ
handler s3c24xx_serial_tx_irq()) also acquire port->lock prior to
calling it. (Note that s3c24xx_serial_start_tx() is only invoked by the
serial core).

However, s3c24xx_serial_rx_enable() and s3c24xx_serial_rx_disable()
unconditionally attempt to acquire port->lock again using
uart_port_lock_irqsave(). Since spinlocks are not recursive, this
causes a deadlock on the same CPU when console flow control is engaged.

Remove the redundant lock acquisition from both rx helper functions.

Cc: stable@vger.kernel.org
Fixes: b497549a035c ("[ARM] S3C24XX: Split serial driver into core and per-cpu drivers")
Reported-by: John Ogness <john.ogness@linutronix.de>
Closes: https://sashiko.dev/#/patchset/20260506121606.5805-1-john.ogness%40linutronix.de [1]
Signed-off-by: Tudor Ambarus <tudor.ambarus@linaro.org>
---
 drivers/tty/serial/samsung_tty.c | 8 --------
 1 file changed, 8 deletions(-)

diff --git a/drivers/tty/serial/samsung_tty.c b/drivers/tty/serial/samsung_tty.c
index 2f94fc798cff..63d0232dffc2 100644
--- a/drivers/tty/serial/samsung_tty.c
+++ b/drivers/tty/serial/samsung_tty.c
@@ -245,12 +245,9 @@ static bool s3c24xx_serial_txempty_nofifo(const struct uart_port *port)
 static void s3c24xx_serial_rx_enable(struct uart_port *port)
 {
 	struct s3c24xx_uart_port *ourport = to_ourport(port);
-	unsigned long flags;
 	int count = 10000;
 	u32 ucon, ufcon;
 
-	uart_port_lock_irqsave(port, &flags);
-
 	while (--count && !s3c24xx_serial_txempty_nofifo(port))
 		udelay(100);
 
@@ -263,23 +260,18 @@ static void s3c24xx_serial_rx_enable(struct uart_port *port)
 	wr_regl(port, S3C2410_UCON, ucon);
 
 	ourport->rx_enabled = 1;
-	uart_port_unlock_irqrestore(port, flags);
 }
 
 static void s3c24xx_serial_rx_disable(struct uart_port *port)
 {
 	struct s3c24xx_uart_port *ourport = to_ourport(port);
-	unsigned long flags;
 	u32 ucon;
 
-	uart_port_lock_irqsave(port, &flags);
-
 	ucon = rd_regl(port, S3C2410_UCON);
 	ucon &= ~S3C2410_UCON_RXIRQMODE;
 	wr_regl(port, S3C2410_UCON, ucon);
 
 	ourport->rx_enabled = 0;
-	uart_port_unlock_irqrestore(port, flags);
 }
 
 static void s3c24xx_serial_stop_tx(struct uart_port *port)

---
base-commit: 16e95bfb79b5d9d01dc7651d98caf3c2ace331cd
change-id: 20260515-samsung-tty-flow-control-deadlock-1d426171bf41

Best regards,
-- 
Tudor Ambarus <tudor.ambarus@linaro.org>


^ permalink raw reply related

* Re: [PATCH] Bluetooth: hci_uart: fix UAF in hci_uart_tty_close()
From: Luiz Augusto von Dentz @ 2026-05-15 12:37 UTC (permalink / raw)
  To: w15303746062
  Cc: marcel, linux-bluetooth, linux-serial, linux-kernel, Mingyu Wang
In-Reply-To: <20260513064547.352601-1-w15303746062@163.com>

Hi,

On Wed, May 13, 2026 at 2:46 AM <w15303746062@163.com> wrote:
>
> From: Mingyu Wang <25181214217@stu.xidian.edu.cn>
>
> A Use-After-Free (UAF) vulnerability and a subsequent General Protection
> Fault (GPF) were observed in h5_recv() due to a race condition between
> the initialization of the HCI UART line discipline and concurrent TTY
> hangup via TIOCVHANGUP.
>
> The issue arises because the workqueues (init_ready and write_work) are
> only cancelled if the HCI_UART_PROTO_READY flag is set. However, during
> the protocol initialization phase (HCI_UART_PROTO_INIT), the underlying
> protocol (e.g., H5) may schedule work (such as sending sync/config
> packets). If a hangup occurs before the setup completes and the READY
> flag is set, hci_uart_tty_close() skips the cancel_work_sync() calls
> and proceeds to free the `hu` struct.
>
> When the delayed workqueue finally executes, it blindly dereferences
> the freed `hu` struct, causing ODEBUG warnings and kernel panics.
>
> Fix this by moving the cancel_work_sync() calls outside the
> HCI_UART_PROTO_READY check, ensuring that any pending works are
> unconditionally cancelled before the hci_uart structure is freed.
>
> Signed-off-by: Mingyu Wang <25181214217@stu.xidian.edu.cn>
> ---
>  drivers/bluetooth/hci_ldisc.c | 10 +++++++---
>  1 file changed, 7 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/bluetooth/hci_ldisc.c b/drivers/bluetooth/hci_ldisc.c
> index 275ea865bc29..566e1c525ee2 100644
> --- a/drivers/bluetooth/hci_ldisc.c
> +++ b/drivers/bluetooth/hci_ldisc.c
> @@ -544,14 +544,18 @@ static void hci_uart_tty_close(struct tty_struct *tty)
>         if (hdev)
>                 hci_uart_close(hdev);
>
> +       /*
> +        * Always cancel workqueues unconditionally before freeing the hu
> +        * struct, as they might be active during the PROTO_INIT phase.
> +        */
> +       cancel_work_sync(&hu->init_ready);
> +       cancel_work_sync(&hu->write_work);

Can't we use disable_work_sync? If it frees up at the end, it's
probably best to disable it so it doesn't allow new submissions.

>         if (test_bit(HCI_UART_PROTO_READY, &hu->flags)) {
>                 percpu_down_write(&hu->proto_lock);
>                 clear_bit(HCI_UART_PROTO_READY, &hu->flags);
>                 percpu_up_write(&hu->proto_lock);
>
> -               cancel_work_sync(&hu->init_ready);
> -               cancel_work_sync(&hu->write_work);
> -
>                 if (hdev) {
>                         if (test_bit(HCI_UART_REGISTERED, &hu->flags))
>                                 hci_unregister_dev(hdev);
> --
> 2.34.1
>
>


-- 
Luiz Augusto von Dentz

^ permalink raw reply

* Re: [PATCH 0/2] serial: 8250_dw: clock-notifier cleanup
From: Andy Shevchenko @ 2026-05-15 10:55 UTC (permalink / raw)
  To: Stepan Ionichev
  Cc: ilpo.jarvinen, gregkh, jirislaby, linux-serial, linux-kernel,
	stable
In-Reply-To: <20260514143746.23671-1-sozdayvek@gmail.com>

On Thu, May 14, 2026 at 07:37:44PM +0500, Stepan Ionichev wrote:
> Two-patch series addressing Andy's review of the leak-fix on v1.
> 
> Patch 1 keeps the same single-line leak fix as v1, but with:
> - the correct "serial: 8250_dw:" prefix (underscore),
> - a Fixes: tag pointing at the original clk_notifier introduction,
> - Cc: stable@ so the fix gets picked up by stable branches that
>   still carry the notifier code.
> 
> Patch 2 drops the clock-notifier infrastructure entirely from
> mainline, as suggested by Andy. The notifier was introduced for the
> Baikal-T1 SoC (shared baudclk between UART ports) and has no other
> in-tree user; Baikal-T1 support has been removed from the kernel.
> 
> If a future platform needs the cross-device baudclk-rate notification
> pattern again, it can be reintroduced in a more general form.

Seems legit, especially the second patch.
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>


-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply

* Re: [PATCH tty v4 2/6] serial: Replace driver usage of UPF_CONS_FLOW
From: Tudor Ambarus @ 2026-05-15 10:21 UTC (permalink / raw)
  To: John Ogness, Krzysztof Kozlowski, Greg Kroah-Hartman, Jiri Slaby,
	Alim Akhtar
  Cc: Andy Shevchenko, linux-kernel, David S. Miller,
	Ilpo Järvinen, Andy Shevchenko, Thomas Fourier, Kees Cook,
	linux-serial, linux-arm-kernel, linux-samsung-soc, sparclinux,
	Peter Griffin, André Draszik, Alexey Klimov, Juan Yescas
In-Reply-To: <87wlx56rcc.fsf@jogness.linutronix.de>



On 5/15/26 10:53 AM, John Ogness wrote:
> On 2026-05-13, Krzysztof Kozlowski <krzk@kernel.org> wrote:
>>> (This email is particularly directed at the Samsung folks.)
>>>
>>> Responding to Sashiko:
>>>
>>> https://sashiko.dev/#/patchset/20260506121606.5805-1-john.ogness%40linutronix.de
>>>
>>> On 2026-05-06, Sashiko wrote:
>>>>> diff --git a/drivers/tty/serial/samsung_tty.c b/drivers/tty/serial/samsung_tty.c
>>>>> index e27806bf2cf3e..2f94fc798cffb 100644
>>>>> --- a/drivers/tty/serial/samsung_tty.c
>>>>> +++ b/drivers/tty/serial/samsung_tty.c
>>>>> @@ -319,7 +319,7 @@ static void s3c24xx_serial_stop_tx(struct uart_port *port)
>>>>>  	ourport->tx_enabled = 0;
>>>>>  	ourport->tx_in_progress = 0;
>>>>>  
>>>>> -	if (port->flags & UPF_CONS_FLOW)
>>>>> +	if (uart_cons_flow_enabled(port))
>>>>>  		s3c24xx_serial_rx_enable(port);
>>>>
>>>> This isn't a new bug introduced by this patch, but does this code
>>>> result in a recursive spinlock acquisition and deadlock when console
>>>> flow control is enabled?
>>>>
>>>> The serial core framework calls port->ops->stop_tx() with the
>>>> port->lock spinlock already held. When uart_cons_flow_enabled(port)
>>>> evaluates to true, s3c24xx_serial_stop_tx() invokes
>>>> s3c24xx_serial_rx_enable().
>>>>
>>>> Looking at s3c24xx_serial_rx_enable(), it unconditionally attempts to
>>>> acquire the exact same lock again:
>>>>
>>>> static void s3c24xx_serial_rx_enable(struct uart_port *port)
>>>> {
>>>> 	struct s3c24xx_uart_port *ourport = to_ourport(port);
>>>> 	unsigned long flags;
>>>> 	...
>>>> 	uart_port_lock_irqsave(port, &flags);
>>>> 	...
>>>> }
>>>>
>>>> Because spinlocks are not recursive in the kernel, it appears this
>>>> sequence would cause an immediate deadlock on the same CPU.
>>>
>>> Although out of scope for my series, the reported issue is real. Perhaps
>>> something for Krzysztof or Alim to look into.
>>>
>>>>> @@ -493,7 +493,7 @@ static void s3c24xx_serial_start_tx(struct uart_port *port)
>>>>>  	struct tty_port *tport = &port->state->port;
>>>>>  
>>>>>  	if (!ourport->tx_enabled) {
>>>>> -		if (port->flags & UPF_CONS_FLOW)
>>>>> +		if (uart_cons_flow_enabled(port))
>>>>>  			s3c24xx_serial_rx_disable(port);
>>>>
>>>> Similarly here, s3c24xx_serial_start_tx() is also called with
>>>> port->lock held, and s3c24xx_serial_rx_disable() will attempt to
>>>> acquire port->lock again:
>>>>
>>>> static void s3c24xx_serial_rx_disable(struct uart_port *port)
>>>> {
>>>> 	struct s3c24xx_uart_port *ourport = to_ourport(port);
>>>> 	unsigned long flags;
>>>> 	...
>>>> 	uart_port_lock_irqsave(port, &flags);
>>>> 	...
>>>> }
>>>>
>>>> Could this pre-existing locking issue in the samsung_tty driver be
>>>> addressed so that the rx enable/disable helpers do not try to take the
>>>> port lock when it is already held by the caller?
>>>
>>> Also legitimate. But out of scope for my series.
>>
>>
>> Thanks for letting us know. Deadlock did not happen so far, so something
>> is missing in Sashiko's report. :)
> 
> Nothing is missing. I am guessing you never use console flow
> control. The deadlock is clearly visible:
> 
> ->stop_tx() (always called with the port locked)
>   s3c24xx_serial_stop_tx()
>     s3c24xx_serial_rx_enable()
>       uart_port_lock_irqsave() (DEADLOCK!)
> 

Right.

The lock acquisitions in the rx helper functions are redundant and shall be
removed.

The serial core framework invokes the .stop_tx() and .start_tx() callbacks
with the port->lock spinlock already held. Furthermore, all internal driver
paths that invoke stop_tx/start_tx also acquire port->lock prior to calling
them.
    
However, s3c24xx_serial_rx_enable() and s3c24xx_serial_rx_disable()
unconditionally attempt to acquire port->lock again using
uart_port_lock_irqsave(). Since kernel spinlocks are not recursive, this
causes a deadlock on the same CPU when console flow control is engaged.

Just removing the redundant lock acquisitions shall fix it. I'll prepare
a patch.

diff --git a/drivers/tty/serial/samsung_tty.c b/drivers/tty/serial/samsung_tty.c
index e27806bf2cf3..17cd5bb100b1 100644
--- a/drivers/tty/serial/samsung_tty.c
+++ b/drivers/tty/serial/samsung_tty.c
+++ b/drivers/tty/serial/samsung_tty.c
@@ -245,12 +245,9 @@ static bool s3c24xx_serial_txempty_nofifo(const struct uart_port *port)
 static void s3c24xx_serial_rx_enable(struct uart_port *port)
 {
        struct s3c24xx_uart_port *ourport = to_ourport(port);
-       unsigned long flags;
        int count = 10000;
        u32 ucon, ufcon;
 
-       uart_port_lock_irqsave(port, &flags);
-
        while (--count && !s3c24xx_serial_txempty_nofifo(port))
                udelay(100);
 
@@ -263,23 +260,18 @@ static void s3c24xx_serial_rx_enable(struct uart_port *port)
        wr_regl(port, S3C2410_UCON, ucon);
 
        ourport->rx_enabled = 1;
-       uart_port_unlock_irqrestore(port, flags);
 }
 
 static void s3c24xx_serial_rx_disable(struct uart_port *port)
 {
        struct s3c24xx_uart_port *ourport = to_ourport(port);
-       unsigned long flags;
        u32 ucon;
 
-       uart_port_lock_irqsave(port, &flags);
-
        ucon = rd_regl(port, S3C2410_UCON);
        ucon &= ~S3C2410_UCON_RXIRQMODE;
        wr_regl(port, S3C2410_UCON, ucon);
 
        ourport->rx_enabled = 0;
-       uart_port_unlock_irqrestore(port, flags);
 }
 
 static void s3c24xx_serial_stop_tx(struct uart_port *port)

^ permalink raw reply

* Re: [PATCH] serial: max310x: fix compile errors if CONFIG_SPI_MASTER is disabled
From: kernel test robot @ 2026-05-15 10:05 UTC (permalink / raw)
  To: Hugo Villeneuve, Greg Kroah-Hartman, Jiri Slaby, Hugo Villeneuve
  Cc: oe-kbuild-all, hugo, linux-serial, kernel test robot,
	linux-kernel
In-Reply-To: <20260512152749.1767622-1-hugo@hugovil.com>

Hi Hugo,

kernel test robot noticed the following build warnings:

[auto build test WARNING on 16e95bfb79b5d9d01dc7651d98caf3c2ace331cd]

url:    https://github.com/intel-lab-lkp/linux/commits/Hugo-Villeneuve/serial-max310x-fix-compile-errors-if-CONFIG_SPI_MASTER-is-disabled/20260515-014130
base:   16e95bfb79b5d9d01dc7651d98caf3c2ace331cd
patch link:    https://lore.kernel.org/r/20260512152749.1767622-1-hugo%40hugovil.com
patch subject: [PATCH] serial: max310x: fix compile errors if CONFIG_SPI_MASTER is disabled
config: alpha-randconfig-r063-20260515 (https://download.01.org/0day-ci/archive/20260515/202605151854.fLunCPPN-lkp@intel.com/config)
compiler: alpha-linux-gcc (GCC) 14.3.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260515/202605151854.fLunCPPN-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202605151854.fLunCPPN-lkp@intel.com/

All warnings (new ones prefixed by >>):

   drivers/tty/serial/max310x.c:1510:20: warning: 'max310x_regmap_name' defined but not used [-Wunused-function]
    1510 | static const char *max310x_regmap_name(u8 port_id)
         |                    ^~~~~~~~~~~~~~~~~~~
   drivers/tty/serial/max310x.c:1482:13: warning: 'max310x_remove' defined but not used [-Wunused-function]
    1482 | static void max310x_remove(struct device *dev)
         |             ^~~~~~~~~~~~~~
   drivers/tty/serial/max310x.c:1298:12: warning: 'max310x_probe' defined but not used [-Wunused-function]
    1298 | static int max310x_probe(struct device *dev, const struct max310x_devtype *devtype,
         |            ^~~~~~~~~~~~~
>> drivers/tty/serial/max310x.c:515:13: warning: 'max310x_reg_noinc' defined but not used [-Wunused-function]
     515 | static bool max310x_reg_noinc(struct device *dev, unsigned int reg)
         |             ^~~~~~~~~~~~~~~~~
>> drivers/tty/serial/max310x.c:502:13: warning: 'max310x_reg_precious' defined but not used [-Wunused-function]
     502 | static bool max310x_reg_precious(struct device *dev, unsigned int reg)
         |             ^~~~~~~~~~~~~~~~~~~~
>> drivers/tty/serial/max310x.c:482:13: warning: 'max310x_reg_volatile' defined but not used [-Wunused-function]
     482 | static bool max310x_reg_volatile(struct device *dev, unsigned int reg)
         |             ^~~~~~~~~~~~~~~~~~~~
>> drivers/tty/serial/max310x.c:467:13: warning: 'max310x_reg_writeable' defined but not used [-Wunused-function]
     467 | static bool max310x_reg_writeable(struct device *dev, unsigned int reg)
         |             ^~~~~~~~~~~~~~~~~~~~~


vim +/max310x_reg_noinc +515 drivers/tty/serial/max310x.c

003236d9ac4d027 Alexander Shiyan 2013-06-29  466  
10d8b34a421716d Alexander Shiyan 2013-06-29 @467  static bool max310x_reg_writeable(struct device *dev, unsigned int reg)
f65444187a66bf5 Alexander Shiyan 2012-08-06  468  {
6ef281daf020592 Cosmin Tanislav  2022-06-05  469  	switch (reg) {
f65444187a66bf5 Alexander Shiyan 2012-08-06  470  	case MAX310X_IRQSTS_REG:
f65444187a66bf5 Alexander Shiyan 2012-08-06  471  	case MAX310X_LSR_IRQSTS_REG:
f65444187a66bf5 Alexander Shiyan 2012-08-06  472  	case MAX310X_SPCHR_IRQSTS_REG:
f65444187a66bf5 Alexander Shiyan 2012-08-06  473  	case MAX310X_STS_IRQSTS_REG:
f65444187a66bf5 Alexander Shiyan 2012-08-06  474  	case MAX310X_TXFIFOLVL_REG:
f65444187a66bf5 Alexander Shiyan 2012-08-06  475  	case MAX310X_RXFIFOLVL_REG:
f65444187a66bf5 Alexander Shiyan 2012-08-06  476  		return false;
f65444187a66bf5 Alexander Shiyan 2012-08-06  477  	default:
f65444187a66bf5 Alexander Shiyan 2012-08-06  478  		return true;
f65444187a66bf5 Alexander Shiyan 2012-08-06  479  	}
d5dd265cda8083c Hugo Villeneuve  2024-01-18  480  }
f65444187a66bf5 Alexander Shiyan 2012-08-06  481  
f65444187a66bf5 Alexander Shiyan 2012-08-06 @482  static bool max310x_reg_volatile(struct device *dev, unsigned int reg)
f65444187a66bf5 Alexander Shiyan 2012-08-06  483  {
6ef281daf020592 Cosmin Tanislav  2022-06-05  484  	switch (reg) {
f65444187a66bf5 Alexander Shiyan 2012-08-06  485  	case MAX310X_RHR_REG:
f65444187a66bf5 Alexander Shiyan 2012-08-06  486  	case MAX310X_IRQSTS_REG:
f65444187a66bf5 Alexander Shiyan 2012-08-06  487  	case MAX310X_LSR_IRQSTS_REG:
f65444187a66bf5 Alexander Shiyan 2012-08-06  488  	case MAX310X_SPCHR_IRQSTS_REG:
f65444187a66bf5 Alexander Shiyan 2012-08-06  489  	case MAX310X_STS_IRQSTS_REG:
f65444187a66bf5 Alexander Shiyan 2012-08-06  490  	case MAX310X_TXFIFOLVL_REG:
f65444187a66bf5 Alexander Shiyan 2012-08-06  491  	case MAX310X_RXFIFOLVL_REG:
f65444187a66bf5 Alexander Shiyan 2012-08-06  492  	case MAX310X_GPIODATA_REG:
10d8b34a421716d Alexander Shiyan 2013-06-29  493  	case MAX310X_BRGDIVLSB_REG:
10d8b34a421716d Alexander Shiyan 2013-06-29  494  	case MAX310X_REG_05:
10d8b34a421716d Alexander Shiyan 2013-06-29  495  	case MAX310X_REG_1F:
f65444187a66bf5 Alexander Shiyan 2012-08-06  496  		return true;
f65444187a66bf5 Alexander Shiyan 2012-08-06  497  	default:
f65444187a66bf5 Alexander Shiyan 2012-08-06  498  		return false;
f65444187a66bf5 Alexander Shiyan 2012-08-06  499  	}
d5dd265cda8083c Hugo Villeneuve  2024-01-18  500  }
f65444187a66bf5 Alexander Shiyan 2012-08-06  501  
f65444187a66bf5 Alexander Shiyan 2012-08-06 @502  static bool max310x_reg_precious(struct device *dev, unsigned int reg)
f65444187a66bf5 Alexander Shiyan 2012-08-06  503  {
6ef281daf020592 Cosmin Tanislav  2022-06-05  504  	switch (reg) {
f65444187a66bf5 Alexander Shiyan 2012-08-06  505  	case MAX310X_RHR_REG:
f65444187a66bf5 Alexander Shiyan 2012-08-06  506  	case MAX310X_IRQSTS_REG:
f65444187a66bf5 Alexander Shiyan 2012-08-06  507  	case MAX310X_SPCHR_IRQSTS_REG:
f65444187a66bf5 Alexander Shiyan 2012-08-06  508  	case MAX310X_STS_IRQSTS_REG:
f65444187a66bf5 Alexander Shiyan 2012-08-06  509  		return true;
f65444187a66bf5 Alexander Shiyan 2012-08-06  510  	default:
f65444187a66bf5 Alexander Shiyan 2012-08-06  511  		return false;
f65444187a66bf5 Alexander Shiyan 2012-08-06  512  	}
d5dd265cda8083c Hugo Villeneuve  2024-01-18  513  }
f65444187a66bf5 Alexander Shiyan 2012-08-06  514  
3f42b142ea11719 Jan Kundrát      2023-04-05 @515  static bool max310x_reg_noinc(struct device *dev, unsigned int reg)
3f42b142ea11719 Jan Kundrát      2023-04-05  516  {
3f42b142ea11719 Jan Kundrát      2023-04-05  517  	return reg == MAX310X_RHR_REG;
3f42b142ea11719 Jan Kundrát      2023-04-05  518  }
3f42b142ea11719 Jan Kundrát      2023-04-05  519  

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply

* Re: [PATCH tty v4 2/6] serial: Replace driver usage of UPF_CONS_FLOW
From: Tudor Ambarus @ 2026-05-15  9:29 UTC (permalink / raw)
  To: Krzysztof Kozlowski, John Ogness, Greg Kroah-Hartman, Jiri Slaby,
	Alim Akhtar
  Cc: Andy Shevchenko, linux-kernel, David S. Miller,
	Ilpo Järvinen, Andy Shevchenko, Thomas Fourier, Kees Cook,
	linux-serial, linux-arm-kernel, linux-samsung-soc, sparclinux,
	Peter Griffin, André Draszik, Alexey Klimov
In-Reply-To: <1a5abd2e-e9ab-4a48-94c2-5e082f57adde@kernel.org>



On 5/13/26 10:50 PM, Krzysztof Kozlowski wrote:
> Cc-ing also a few Linaro folks
> which are using this platform and might be able to help us here.

I think that will be me as I worked with the serial.
I'm a little bit busy right now, but I'm adding this to my todo
list in case no else is taking care about it before I get to it.

Cheers,
ta

^ permalink raw reply

* Re: [PATCH tty v4 2/6] serial: Replace driver usage of UPF_CONS_FLOW
From: John Ogness @ 2026-05-15  7:53 UTC (permalink / raw)
  To: Krzysztof Kozlowski, Greg Kroah-Hartman, Jiri Slaby, Alim Akhtar
  Cc: Andy Shevchenko, linux-kernel, David S. Miller,
	Ilpo Järvinen, Andy Shevchenko, Thomas Fourier, Kees Cook,
	linux-serial, linux-arm-kernel, linux-samsung-soc, sparclinux,
	Peter Griffin, Tudor Ambarus, André Draszik, Alexey Klimov
In-Reply-To: <1a5abd2e-e9ab-4a48-94c2-5e082f57adde@kernel.org>

On 2026-05-13, Krzysztof Kozlowski <krzk@kernel.org> wrote:
>> (This email is particularly directed at the Samsung folks.)
>> 
>> Responding to Sashiko:
>> 
>> https://sashiko.dev/#/patchset/20260506121606.5805-1-john.ogness%40linutronix.de
>> 
>> On 2026-05-06, Sashiko wrote:
>>>> diff --git a/drivers/tty/serial/samsung_tty.c b/drivers/tty/serial/samsung_tty.c
>>>> index e27806bf2cf3e..2f94fc798cffb 100644
>>>> --- a/drivers/tty/serial/samsung_tty.c
>>>> +++ b/drivers/tty/serial/samsung_tty.c
>>>> @@ -319,7 +319,7 @@ static void s3c24xx_serial_stop_tx(struct uart_port *port)
>>>>  	ourport->tx_enabled = 0;
>>>>  	ourport->tx_in_progress = 0;
>>>>  
>>>> -	if (port->flags & UPF_CONS_FLOW)
>>>> +	if (uart_cons_flow_enabled(port))
>>>>  		s3c24xx_serial_rx_enable(port);
>>>
>>> This isn't a new bug introduced by this patch, but does this code
>>> result in a recursive spinlock acquisition and deadlock when console
>>> flow control is enabled?
>>>
>>> The serial core framework calls port->ops->stop_tx() with the
>>> port->lock spinlock already held. When uart_cons_flow_enabled(port)
>>> evaluates to true, s3c24xx_serial_stop_tx() invokes
>>> s3c24xx_serial_rx_enable().
>>>
>>> Looking at s3c24xx_serial_rx_enable(), it unconditionally attempts to
>>> acquire the exact same lock again:
>>>
>>> static void s3c24xx_serial_rx_enable(struct uart_port *port)
>>> {
>>> 	struct s3c24xx_uart_port *ourport = to_ourport(port);
>>> 	unsigned long flags;
>>> 	...
>>> 	uart_port_lock_irqsave(port, &flags);
>>> 	...
>>> }
>>>
>>> Because spinlocks are not recursive in the kernel, it appears this
>>> sequence would cause an immediate deadlock on the same CPU.
>> 
>> Although out of scope for my series, the reported issue is real. Perhaps
>> something for Krzysztof or Alim to look into.
>> 
>>>> @@ -493,7 +493,7 @@ static void s3c24xx_serial_start_tx(struct uart_port *port)
>>>>  	struct tty_port *tport = &port->state->port;
>>>>  
>>>>  	if (!ourport->tx_enabled) {
>>>> -		if (port->flags & UPF_CONS_FLOW)
>>>> +		if (uart_cons_flow_enabled(port))
>>>>  			s3c24xx_serial_rx_disable(port);
>>>
>>> Similarly here, s3c24xx_serial_start_tx() is also called with
>>> port->lock held, and s3c24xx_serial_rx_disable() will attempt to
>>> acquire port->lock again:
>>>
>>> static void s3c24xx_serial_rx_disable(struct uart_port *port)
>>> {
>>> 	struct s3c24xx_uart_port *ourport = to_ourport(port);
>>> 	unsigned long flags;
>>> 	...
>>> 	uart_port_lock_irqsave(port, &flags);
>>> 	...
>>> }
>>>
>>> Could this pre-existing locking issue in the samsung_tty driver be
>>> addressed so that the rx enable/disable helpers do not try to take the
>>> port lock when it is already held by the caller?
>> 
>> Also legitimate. But out of scope for my series.
>
>
> Thanks for letting us know. Deadlock did not happen so far, so something
> is missing in Sashiko's report. :)

Nothing is missing. I am guessing you never use console flow
control. The deadlock is clearly visible:

->stop_tx() (always called with the port locked)
  s3c24xx_serial_stop_tx()
    s3c24xx_serial_rx_enable()
      uart_port_lock_irqsave() (DEADLOCK!)

John Ogness

^ permalink raw reply

* Re: [PATCH v2] serial: 8250: Clear CON_PRINTBUFFER on port re-registration
From: Fushuai Wang @ 2026-05-15  7:44 UTC (permalink / raw)
  To: gregkh
  Cc: andy.shevchenko, fushuai.wang, ilpo.jarvinen, jirislaby, kees,
	linux-kernel, linux-serial, osama.abdelkader, wangfushuai
In-Reply-To: <2026051135-kung-badass-8eba@gregkh>

>> From: Fushuai Wang <wangfushuai@baidu.com>
>> 
>> When two PnP devices map to the same physical port, the serial8250 driver
>> removes and re-registers the console structure for the same port.
>> 
>> During re-registration, the console structure still has CON_PRINTBUFFER set
>> from the initial registration, which causes console_init_seq() to set
>> console->seq to syslog_seq. This results in re-printing the entire
>> system log buffer, which may lead to RCU stall on slow serial consoles.
>> 
>> Clear CON_PRINTBUFFER when re-registering a port to prevent duplicate
>> log printing.
>> 
>> Fixes: 835d844d1a28 ("8250_pnp: do pnp probe before legacy probe")
>> Signed-off-by: Fushuai Wang <wangfushuai@baidu.com>
>> ---
>> V1->V2: Add Fixes tag
>> previous discussion: https://lore.kernel.org/all/20260416092917.27301-1-fushuai.wang@linux.dev/T/#u
>> 
>> Please ignore previous email if you received it before. There is something wrong with my email client.
>> 
>>  drivers/tty/serial/8250/8250_core.c | 9 ++++++++-
>>  1 file changed, 8 insertions(+), 1 deletion(-)
>> 
>> diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c
>> index a428e88938eb..01b14392d9f7 100644
>> --- a/drivers/tty/serial/8250/8250_core.c
>> +++ b/drivers/tty/serial/8250/8250_core.c
>> @@ -694,6 +694,7 @@ int serial8250_register_8250_port(const struct uart_8250_port *up)
>>  {
>>  	struct uart_8250_port *uart;
>>  	int ret;
>> +	bool was_removed = false;
>>  
>>  	if (up->port.uartclk == 0)
>>  		return -EINVAL;
>> @@ -716,8 +717,10 @@ int serial8250_register_8250_port(const struct uart_8250_port *up)
>>  	if (uart->port.type == PORT_8250_CIR)
>>  		return -ENODEV;
>>  
>> -	if (uart->port.dev)
>> +	if (uart->port.dev) {
>>  		uart_remove_one_port(&serial8250_reg, &uart->port);
>> +		was_removed = true;
>> +	}
>>  
>>  	uart->port.ctrl_id	= up->port.ctrl_id;
>>  	uart->port.port_id	= up->port.port_id;
>> @@ -819,6 +822,10 @@ int serial8250_register_8250_port(const struct uart_8250_port *up)
>>  					&uart->capabilities);
>>  
>>  		serial8250_apply_quirks(uart);
>> +
>> +		if (was_removed && uart_console(&uart->port))
>> +			uart->port.cons->flags &= ~CON_PRINTBUFFER;
> 
> Why not set the flag up above when you remove the port?  Why down here?
> 
> thanks,
> 
> greg k-h

Hi, Greg

I just felt it's cleaner to clear it only when re-registration happens.
Do you think there is any problem with doing it right after the removal?

-- 
Regards,
WANG

^ permalink raw reply

* [PATCH v3] Bluetooth: hci_uart: fix UAF in hci_uart_tty_close()
From: w15303746062 @ 2026-05-15  6:50 UTC (permalink / raw)
  To: pmenzel, marcel, luiz.dentz, linux-bluetooth
  Cc: linux-serial, linux-kernel, greg, stable, Mingyu Wang
In-Reply-To: <505b56bd-e5fd-4feb-a6e3-1d8269609277@molgen.mpg.de>

From: Mingyu Wang <25181214217@stu.xidian.edu.cn>

A Use-After-Free (UAF) vulnerability and a subsequent kernel panic were
observed in hci_uart_write_work() due to a race condition between the
initialization of the HCI UART line discipline and concurrent TTY hangup.

This issue was triggered by our custom device emulation and fuzzing
framework (DevGen) on the v6.18 kernel. Due to the highly timing-dependent
nature of this race condition (requiring a precise interleaving of
TIOCVHANGUP and protocol setup), Syzkaller failed to extract a reliable
standalone C reproducer (reproducer is too unreliable: 0.00).

The crash trace is as follows:
  ODEBUG: free active (active state 0) object: ffff88804024e870 object type: work_struct hint: hci_uart_write_work+0x0/0x940
  WARNING: CPU: 0 PID: 338273 at lib/debugobjects.c:612 debug_print_object+0x1a2/0x2b0
  ...
  Call Trace:
   <TASK>
   debug_check_no_obj_freed+0x3ec/0x520
   kfree+0x3f0/0x6c0
   hci_uart_tty_close+0x127/0x2a0
   tty_ldisc_close+0x113/0x1a0
   tty_ldisc_kill+0x8e/0x150
   tty_ldisc_hangup+0x3c1/0x730
   __tty_hangup.part.0+0x3fd/0x8a0
   tty_ioctl+0x120f/0x1690
   __x64_sys_ioctl+0x18f/0x210
   do_syscall_64+0xcb/0xfa0
   entry_SYSCALL_64_after_hwframe+0x77/0x7f
   </TASK>

The issue arises because the workqueues (init_ready and write_work) are
only cancelled if the HCI_UART_PROTO_READY flag is set. However, during
the protocol initialization phase (HCI_UART_PROTO_INIT), the underlying
protocol may schedule work. If a hangup occurs before the setup completes
and the READY flag is set, hci_uart_tty_close() skips the cancel_work_sync()
calls and proceeds to free the `hu` struct. When the delayed workqueue
executes, it blindly dereferences the freed `hu` struct.

Fix this by moving the cancel_work_sync() calls outside the
HCI_UART_PROTO_READY check, ensuring that any pending works are
unconditionally cancelled before the hci_uart structure is freed.
Note that hu->init_ready and hu->write_work are initialized in
hci_uart_tty_open(), so it is always safe to call cancel_work_sync()
on them in hci_uart_tty_close(), even if the protocol was never
fully attached.

Fixes: 3b799254cf6f ("Bluetooth: hci_uart: Cancel init work before unregistering")
Cc: stable@vger.kernel.org
Signed-off-by: Mingyu Wang <25181214217@stu.xidian.edu.cn>
---
Changes in v3:
- Added 'Cc: stable' tag as requested by the stable bot.

Changes in v2:
- Added KASAN/ODEBUG crash trace.

 drivers/bluetooth/hci_ldisc.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/drivers/bluetooth/hci_ldisc.c b/drivers/bluetooth/hci_ldisc.c
index 275ea865bc29..566e1c525ee2 100644
--- a/drivers/bluetooth/hci_ldisc.c
+++ b/drivers/bluetooth/hci_ldisc.c
@@ -544,14 +544,18 @@ static void hci_uart_tty_close(struct tty_struct *tty)
 	if (hdev)
 		hci_uart_close(hdev);
 
+	/*
+	 * Always cancel workqueues unconditionally before freeing the hu
+	 * struct, as they might be active during the PROTO_INIT phase.
+	 */
+	cancel_work_sync(&hu->init_ready);
+	cancel_work_sync(&hu->write_work);
+
 	if (test_bit(HCI_UART_PROTO_READY, &hu->flags)) {
 		percpu_down_write(&hu->proto_lock);
 		clear_bit(HCI_UART_PROTO_READY, &hu->flags);
 		percpu_up_write(&hu->proto_lock);
 
-		cancel_work_sync(&hu->init_ready);
-		cancel_work_sync(&hu->write_work);
-
 		if (hdev) {
 			if (test_bit(HCI_UART_REGISTERED, &hu->flags))
 				hci_unregister_dev(hdev);
-- 
2.34.1


^ permalink raw reply related

* Re: [PATCH v2] Bluetooth: hci_uart: fix UAF in hci_uart_tty_close()
From: Greg KH @ 2026-05-15  6:10 UTC (permalink / raw)
  To: w15303746062
  Cc: pmenzel, marcel, luiz.dentz, linux-bluetooth, linux-serial,
	linux-kernel, Mingyu Wang
In-Reply-To: <20260514151722.382161-1-w15303746062@163.com>

On Thu, May 14, 2026 at 11:17:22PM +0800, w15303746062@163.com wrote:
> From: Mingyu Wang <25181214217@stu.xidian.edu.cn>
> 
> A Use-After-Free (UAF) vulnerability and a subsequent kernel panic were
> observed in hci_uart_write_work() due to a race condition between the
> initialization of the HCI UART line discipline and concurrent TTY hangup.
> 
> This issue was triggered by our custom device emulation and fuzzing
> framework (DevGen) on the v6.18 kernel. Due to the highly timing-dependent
> nature of this race condition (requiring a precise interleaving of
> TIOCVHANGUP and protocol setup), Syzkaller failed to extract a reliable
> standalone C reproducer (reproducer is too unreliable: 0.00).
> 
> The crash trace is as follows:
>   ODEBUG: free active (active state 0) object: ffff88804024e870 object type: work_struct hint: hci_uart_write_work+0x0/0x940
>   WARNING: CPU: 0 PID: 338273 at lib/debugobjects.c:612 debug_print_object+0x1a2/0x2b0
>   ...
>   Call Trace:
>    <TASK>
>    debug_check_no_obj_freed+0x3ec/0x520
>    kfree+0x3f0/0x6c0
>    hci_uart_tty_close+0x127/0x2a0
>    tty_ldisc_close+0x113/0x1a0
>    tty_ldisc_kill+0x8e/0x150
>    tty_ldisc_hangup+0x3c1/0x730
>    __tty_hangup.part.0+0x3fd/0x8a0
>    tty_ioctl+0x120f/0x1690
>    __x64_sys_ioctl+0x18f/0x210
>    do_syscall_64+0xcb/0xfa0
>    entry_SYSCALL_64_after_hwframe+0x77/0x7f
>    </TASK>
> 
> The issue arises because the workqueues (init_ready and write_work) are
> only cancelled if the HCI_UART_PROTO_READY flag is set. However, during
> the protocol initialization phase (HCI_UART_PROTO_INIT), the underlying
> protocol may schedule work. If a hangup occurs before the setup completes
> and the READY flag is set, hci_uart_tty_close() skips the cancel_work_sync()
> calls and proceeds to free the `hu` struct. When the delayed workqueue
> executes, it blindly dereferences the freed `hu` struct.
> 
> Fix this by moving the cancel_work_sync() calls outside the
> HCI_UART_PROTO_READY check, ensuring that any pending works are
> unconditionally cancelled before the hci_uart structure is freed.
> Note that hu->init_ready and hu->write_work are initialized in
> hci_uart_tty_open(), so it is always safe to call cancel_work_sync()
> on them in hci_uart_tty_close(), even if the protocol was never
> fully attached.
> 
> Fixes: 3b799254cf6f ("Bluetooth: hci_uart: Cancel init work before unregistering")
> 
> Signed-off-by: Mingyu Wang <25181214217@stu.xidian.edu.cn>
> ---
> Changes in v2:
> - Added KASAN/ODEBUG crash trace.
> - Added explanation for the absence of a standalone reproducer (highly timing-dependent race condition).
> - Added Fixes tag pointing to commit 3b799254cf6f.
> 
>  drivers/bluetooth/hci_ldisc.c | 10 +++++++---
>  1 file changed, 7 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/bluetooth/hci_ldisc.c b/drivers/bluetooth/hci_ldisc.c
> index 275ea865bc29..566e1c525ee2 100644
> --- a/drivers/bluetooth/hci_ldisc.c
> +++ b/drivers/bluetooth/hci_ldisc.c
> @@ -544,14 +544,18 @@ static void hci_uart_tty_close(struct tty_struct *tty)
>  	if (hdev)
>  		hci_uart_close(hdev);
>  
> +	/*
> +	 * Always cancel workqueues unconditionally before freeing the hu
> +	 * struct, as they might be active during the PROTO_INIT phase.
> +	 */
> +	cancel_work_sync(&hu->init_ready);
> +	cancel_work_sync(&hu->write_work);
> +
>  	if (test_bit(HCI_UART_PROTO_READY, &hu->flags)) {
>  		percpu_down_write(&hu->proto_lock);
>  		clear_bit(HCI_UART_PROTO_READY, &hu->flags);
>  		percpu_up_write(&hu->proto_lock);
>  
> -		cancel_work_sync(&hu->init_ready);
> -		cancel_work_sync(&hu->write_work);
> -
>  		if (hdev) {
>  			if (test_bit(HCI_UART_REGISTERED, &hu->flags))
>  				hci_unregister_dev(hdev);
> -- 
> 2.34.1
> 
> 

Hi,

This is the friendly patch-bot of Greg Kroah-Hartman.  You have sent him
a patch that has triggered this response.  He used to manually respond
to these common problems, but in order to save his sanity (he kept
writing the same thing over and over, yet to different people), I was
created.  Hopefully you will not take offence and will fix the problem
in your patch and resubmit it so that it can be accepted into the Linux
kernel tree.

You are receiving this message because of the following common error(s)
as indicated below:

- You have marked a patch with a "Fixes:" tag for a commit that is in an
  older released kernel, yet you do not have a cc: stable line in the
  signed-off-by area at all, which means that the patch will not be
  applied to any older kernel releases.  To properly fix this, please
  follow the documented rules in the
  Documentation/process/stable-kernel-rules.rst file for how to resolve
  this.

If you wish to discuss this problem further, or you have questions about
how to resolve this issue, please feel free to respond to this email and
Greg will reply once he has dug out from the pending patches received
from other developers.

thanks,

greg k-h's patch email bot

^ permalink raw reply

* [PATCH] vt: merge ucs_is_zero_width()/ucs_is_double_width() into ucs_get_width()
From: Nicolas Pitre @ 2026-05-15  3:48 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Jiri Slaby; +Cc: linux-kernel, linux-serial

The hot path in vc_process_ucs() asks two independent questions about the
same code point -- "is it double-width?" and "is it zero-width?" -- and
was answering each with its own bsearch over its own table. For anything
past the leading bounds check that meant two scans of the BMP width
tables back to back for what is logically a single lookup.

Replace both with one ucs_get_width(cp) returning 0, 1, or 2 in a single
bsearch, while keeping the total table footprint at the same 2384 B as
before.

To do so, merge the zero-width and double-width ranges per region into
one sorted-by-`first` table. BMP entries stay 4 bytes; per-entry width
is hosted in spare bits of the non-BMP table's `last` field. Non-BMP
code points use only 20 of 32 bits, so each u32 has 12 unused high bits.
Store first/last shifted left by 12 and use the low 12 bits of `last`
for metadata: bit 11 is this entry's own width flag, bits 0..7 host an
8-bit chunk of the BMP double-width bitmap. Because the metadata bits
sit strictly below the lowest cp-scale bit, the bsearch comparator
remains a plain u32 compare on shifted keys with no masking.

In vc_process_ucs() the overwhelmingly common single-width path now
collapses to a single predicted branch:

	if (likely(w == 1))
		return 1;

Note: scripts/checkpatch.pl complains about "Macros with complex values
      should be enclosed in parentheses" for the BMP_*WIDTH and
      RANGE_*WIDTH macros. They are deliberately defined to expand to a
      comma-separated (first, last) pair so they can populate the two
      adjacent fields of a struct initializer; wrapping them in
      parentheses would turn that into a comma-expression and defeat
      the whole construction. Please ignore.

Signed-off-by: Nicolas Pitre <nico@fluxnic.net>
---
 drivers/tty/vt/gen_ucs_width_table.py    | 117 ++-
 drivers/tty/vt/ucs.c                     | 148 ++--
 drivers/tty/vt/ucs_width_table.h_shipped | 917 ++++++++++++-----------
 drivers/tty/vt/vt.c                      |  11 +-
 include/linux/consolemap.h               |  12 +-
 5 files changed, 663 insertions(+), 542 deletions(-)

diff --git a/drivers/tty/vt/gen_ucs_width_table.py b/drivers/tty/vt/gen_ucs_width_table.py
index 76e80ebeff13..4d2476842750 100755
--- a/drivers/tty/vt/gen_ucs_width_table.py
+++ b/drivers/tty/vt/gen_ucs_width_table.py
@@ -190,12 +190,23 @@ def write_tables(zero_width_ranges, double_width_ranges, out_file=DEFAULT_OUT_FI
     """
     Write the generated tables to C header file.
 
+    The output uses a single sorted-by-`first` table per region (BMP and
+    non-BMP), with zero-width and double-width ranges merged together. The
+    non-BMP table also hosts the BMP double-width bitmap in spare bits of
+    `last`. See the encoding comment at the top of ucs.c for the layout.
+
     Args:
         zero_width_ranges: List of (start, end) ranges for zero-width characters
         double_width_ranges: List of (start, end) ranges for double-width characters
         out_file: Output file name (default: DEFAULT_OUT_FILE)
     """
 
+    # Bits per BMP-bitmap chunk hosted in one non-BMP entry's `last` field.
+    # 8 bits makes `idx / BITS_PER_CHUNK` / `idx % BITS_PER_CHUNK` compile to
+    # a cheap shift+mask in the lookup. The chunk size is also emitted as
+    # UCS_NONBMP_BMP_BITS in the generated header so ucs.c stays in sync.
+    BITS_PER_CHUNK = 8
+
     # Function to split ranges into BMP (16-bit) and non-BMP (above 16-bit)
     def split_ranges_by_size(ranges):
         bmp_ranges = []
@@ -217,6 +228,40 @@ def write_tables(zero_width_ranges, double_width_ranges, out_file=DEFAULT_OUT_FI
     zero_width_bmp, zero_width_non_bmp = split_ranges_by_size(zero_width_ranges)
     double_width_bmp, double_width_non_bmp = split_ranges_by_size(double_width_ranges)
 
+    # Merge zero- and double-width ranges per region, tagging each with its
+    # width, then sort by `first` so binary search works on the union.
+    bmp_entries = sorted(
+        [(s, e, 0) for s, e in zero_width_bmp] +
+        [(s, e, 2) for s, e in double_width_bmp],
+        key=lambda t: t[0])
+    nonbmp_entries = sorted(
+        [(s, e, 0) for s, e in zero_width_non_bmp] +
+        [(s, e, 2) for s, e in double_width_non_bmp],
+        key=lambda t: t[0])
+
+    # Build the BMP double-width bitmap: one bit per BMP entry (in sort
+    # order), set iff that entry is double-width. Pack into BITS_PER_CHUNK-
+    # wide chunks, with bit j of the chunk corresponding to entry
+    # (chunk_index * BITS_PER_CHUNK + j).
+    bmp_w2_bits = [1 if w == 2 else 0 for _, _, w in bmp_entries]
+    n_chunks = (len(bmp_w2_bits) + BITS_PER_CHUNK - 1) // BITS_PER_CHUNK
+
+    if n_chunks > len(nonbmp_entries):
+        raise RuntimeError(
+            f"BMP bitmap needs {n_chunks} host entries, "
+            f"but only {len(nonbmp_entries)} non-BMP entries are available")
+
+    chunks = []  # list of (base_index, end_index, packed_value)
+    for c in range(n_chunks):
+        base = c * BITS_PER_CHUNK
+        end_idx = min(base + BITS_PER_CHUNK - 1, len(bmp_w2_bits) - 1)
+        value = 0
+        for j in range(BITS_PER_CHUNK):
+            k = base + j
+            if k < len(bmp_w2_bits) and bmp_w2_bits[k]:
+                value |= 1 << j
+        chunks.append((base, end_idx, value))
+
     # Function to generate code point description comments
     def get_code_point_comment(start, end):
         try:
@@ -242,48 +287,47 @@ def write_tables(zero_width_ranges, double_width_ranges, out_file=DEFAULT_OUT_FI
  * Auto-generated by {this_file}
  *
  * Unicode Version: {unicodedata.unidata_version}
+ *
+ * Zero-width and double-width ranges are merged into one sorted-by-`first`
+ * table per region. The non-BMP table additionally hosts the BMP
+ * double-width bitmap in the low {BITS_PER_CHUNK} bits of `last` of its
+ * first {n_chunks} entries (covering {len(bmp_w2_bits)} BMP entries).
+ * See ucs.c for the encoding details and the lookup code.
  */
 
-/* Zero-width character ranges (BMP - Basic Multilingual Plane, U+0000 to U+FFFF) */
-static const struct ucs_interval16 ucs_zero_width_bmp_ranges[] = {{
-""")
-
-        for start, end in zero_width_bmp:
-            comment = get_code_point_comment(start, end)
-            f.write(f"\t{{ 0x{start:04X}, 0x{end:04X} }}, {comment}\n")
-
-        f.write("""\
-};
+/* Bits per BMP-bitmap chunk hosted in one non-BMP entry's `last` field. */
+#define UCS_NONBMP_BMP_BITS {BITS_PER_CHUNK}
 
-/* Zero-width character ranges (non-BMP, U+10000 and above) */
-static const struct ucs_interval32 ucs_zero_width_non_bmp_ranges[] = {
+/* Combined zero- and double-width ranges
+ * (BMP - Basic Multilingual Plane, U+0000 to U+FFFF). */
+static const struct ucs_width16 ucs_bmp_ranges[] = {{
 """)
 
-        for start, end in zero_width_non_bmp:
-            comment = get_code_point_comment(start, end)
-            f.write(f"\t{{ 0x{start:05X}, 0x{end:05X} }}, {comment}\n")
+        for s, e, w in bmp_entries:
+            macro = "BMP_0WIDTH" if w == 0 else "BMP_2WIDTH"
+            comment = get_code_point_comment(s, e)
+            f.write(f"\t{{ {macro}(0x{s:04X}, 0x{e:04X}) }}, {comment}\n")
 
-        f.write("""\
-};
-
-/* Double-width character ranges (BMP - Basic Multilingual Plane, U+0000 to U+FFFF) */
-static const struct ucs_interval16 ucs_double_width_bmp_ranges[] = {
-""")
-
-        for start, end in double_width_bmp:
-            comment = get_code_point_comment(start, end)
-            f.write(f"\t{{ 0x{start:04X}, 0x{end:04X} }}, {comment}\n")
-
-        f.write("""\
-};
+        f.write(f"""\
+}};
 
-/* Double-width character ranges (non-BMP, U+10000 and above) */
-static const struct ucs_interval32 ucs_double_width_non_bmp_ranges[] = {
+/* Combined zero- and double-width ranges (non-BMP, U+10000 and above).
+ * The first {n_chunks} entries host the BMP double-width bitmap in the low
+ * {BITS_PER_CHUNK} bits of `last`. */
+static const struct ucs_width32 ucs_nonbmp_ranges[] = {{
 """)
 
-        for start, end in double_width_non_bmp:
-            comment = get_code_point_comment(start, end)
-            f.write(f"\t{{ 0x{start:05X}, 0x{end:05X} }}, {comment}\n")
+        for i, (s, e, w) in enumerate(nonbmp_entries):
+            macro = "RANGE_0WIDTH" if w == 0 else "RANGE_2WIDTH"
+            comment = get_code_point_comment(s, e)
+            if i < len(chunks):
+                base, end_idx, value = chunks[i]
+                f.write(
+                    f"\t{{ {macro}(0x{s:05X}, 0x{e:05X})   {comment}\n"
+                    f"\t  | BMP_2W_BITS(0b{value:0{BITS_PER_CHUNK}b}) }},"
+                    f" /* BMP entries [{base:>3}..{end_idx:>3}] */\n")
+            else:
+                f.write(f"\t{{ {macro}(0x{s:05X}, 0x{e:05X}) }}, {comment}\n")
 
         f.write("};\n")
 
@@ -301,7 +345,10 @@ if __name__ == "__main__":
     # Print summary
     zero_width_count = sum(end - start + 1 for start, end in zero_width_ranges)
     double_width_count = sum(end - start + 1 for start, end in double_width_ranges)
+    n_zero = len(zero_width_ranges)
+    n_double = len(double_width_ranges)
     print(f"Generated {args.output_file} with:")
-    print(f"- {len(zero_width_ranges)} zero-width ranges covering ~{zero_width_count} code points")
-    print(f"- {len(double_width_ranges)} double-width ranges covering ~{double_width_count} code points")
+    print(f"- {n_zero} zero-width ranges covering ~{zero_width_count} code points")
+    print(f"- {n_double} double-width ranges covering ~{double_width_count} code points")
+    print(f"- {n_zero + n_double} merged ranges total")
     print(f"- Unicode Version: {unicodedata.unidata_version}")
diff --git a/drivers/tty/vt/ucs.c b/drivers/tty/vt/ucs.c
index 03877485dfb7..fc41c0bb5d7b 100644
--- a/drivers/tty/vt/ucs.c
+++ b/drivers/tty/vt/ucs.c
@@ -4,26 +4,74 @@
  */
 
 #include <linux/array_size.h>
+#include <linux/build_bug.h>
 #include <linux/bsearch.h>
 #include <linux/consolemap.h>
-#include <linux/minmax.h>
+#include <linux/math.h>
 
-struct ucs_interval16 {
+struct ucs_width16 {
 	u16 first;
 	u16 last;
 };
 
-struct ucs_interval32 {
+struct ucs_width32 {
 	u32 first;
 	u32 last;
 };
 
+/*
+ * Width table encoding (consumed by ucs_width_table.h):
+ *
+ * Zero- and double-width ranges are merged into one sorted-by-`first` table
+ * per region (BMP / non-BMP). The BMP table stores plain (first, last)
+ * pairs; per-entry width lives in a packed bitmap *hosted by the non-BMP
+ * table*.
+ *
+ * That hosting is the whole point of the encoding. Non-BMP code points use
+ * only 20 bits, so each u32 has 12 spare high bits sitting around doing
+ * nothing — we'd rather use them than spend a separate parallel array for
+ * width and BMP-bitmap bits. So we move the cp value up by UCS_CP_SHIFT
+ * and stash metadata in the now-free low bits of `last`:
+ *   - bit UCS_NONBMP_W2_FLAG_BIT: this entry's own width (0=zero, 1=double),
+ *   - bits 0..UCS_NONBMP_BMP_BITS-1: a chunk of the BMP double-width
+ *     bitmap. Bit `j` of the chunk in non-BMP entry `c` is set iff BMP
+ *     entry (c * UCS_NONBMP_BMP_BITS + j) is double-width. The first
+ *     ceil(N_BMP / UCS_NONBMP_BMP_BITS) non-BMP entries carry the bitmap;
+ *     the rest leave these bits zero.
+ *
+ * Because the metadata bits sit strictly below the lowest cp-scale bit,
+ * the bsearch comparator does plain u32 comparison on the shifted key and
+ * stored values without masking — ordering between distinct code points is
+ * undisturbed.
+ */
+#define UCS_CP_SHIFT           12
+#define UCS_NONBMP_W2_FLAG_BIT 11
+#define UCS_NONBMP_W2_FLAG     (1u << UCS_NONBMP_W2_FLAG_BIT)
+
+#define BMP_0WIDTH(first, last)   first, last
+#define BMP_2WIDTH(first, last)   first, last
+#define RANGE_0WIDTH(first, last) \
+	(u32)(first) << UCS_CP_SHIFT,  (u32)(last) << UCS_CP_SHIFT
+#define RANGE_2WIDTH(first, last) \
+	(u32)(first) << UCS_CP_SHIFT, ((u32)(last) << UCS_CP_SHIFT) | UCS_NONBMP_W2_FLAG
+#define BMP_2W_BITS(b)            (b)
+
 #include "ucs_width_table.h"
 
-static int interval16_cmp(const void *key, const void *element)
+static_assert(UCS_NONBMP_BMP_BITS <= UCS_NONBMP_W2_FLAG_BIT,
+	      "BMP bitmap chunk would overlap the per-entry width flag");
+static_assert(UCS_NONBMP_W2_FLAG_BIT < UCS_CP_SHIFT,
+	      "Metadata bits collide with the shifted cp value");
+static_assert(DIV_ROUND_UP(ARRAY_SIZE(ucs_bmp_ranges), UCS_NONBMP_BMP_BITS)
+	      <= ARRAY_SIZE(ucs_nonbmp_ranges),
+	      "Not enough non-BMP entries to host the BMP width bitmap");
+
+#define UCS_IS_BMP(cp)	((cp) <= 0xffff)
+
+static int width16_cmp(const void *key, const void *element)
 {
 	u16 cp = *(u16 *)key;
-	const struct ucs_interval16 *entry = element;
+	const struct ucs_width16 *entry = element;
 
 	if (cp < entry->first)
 		return -1;
@@ -32,68 +80,62 @@ static int interval16_cmp(const void *key, const void *element)
 	return 0;
 }
 
-static int interval32_cmp(const void *key, const void *element)
+static int width32_cmp(const void *key, const void *element)
 {
-	u32 cp = *(u32 *)key;
-	const struct ucs_interval32 *entry = element;
+	u32 k = *(u32 *)key;
+	const struct ucs_width32 *entry = element;
 
-	if (cp < entry->first)
+	if (k < entry->first)
 		return -1;
-	if (cp > entry->last)
+	if (k > entry->last)
 		return 1;
 	return 0;
 }
 
-static bool cp_in_range16(u16 cp, const struct ucs_interval16 *ranges, size_t size)
+/**
+ * ucs_get_width() - Get the display width of a Unicode code point.
+ * @cp: Unicode code point (UCS-4)
+ *
+ * Return: 2 for double-width (East Asian Wide/Fullwidth, emoji, ...),
+ *         0 for zero-width (combining marks, format characters, ...),
+ *         1 for everything else (the common case).
+ */
+unsigned int ucs_get_width(u32 cp)
 {
-	if (cp < ranges[0].first || cp > ranges[size - 1].last)
-		return false;
+	const struct ucs_width16 *e16;
+	const struct ucs_width32 *e32;
+	unsigned int idx;
+	u32 k;
 
-	return __inline_bsearch(&cp, ranges, size, sizeof(*ranges),
-				interval16_cmp) != NULL;
-}
+	if (UCS_IS_BMP(cp)) {
+		u16 bmp = cp;
 
-static bool cp_in_range32(u32 cp, const struct ucs_interval32 *ranges, size_t size)
-{
-	if (cp < ranges[0].first || cp > ranges[size - 1].last)
-		return false;
+		if (bmp < ucs_bmp_ranges[0].first ||
+		    bmp > ucs_bmp_ranges[ARRAY_SIZE(ucs_bmp_ranges) - 1].last)
+			return 1;
 
-	return __inline_bsearch(&cp, ranges, size, sizeof(*ranges),
-				interval32_cmp) != NULL;
-}
+		e16 = __inline_bsearch(&bmp, ucs_bmp_ranges,
+				       ARRAY_SIZE(ucs_bmp_ranges),
+				       sizeof(*ucs_bmp_ranges), width16_cmp);
+		if (!e16)
+			return 1;
 
-#define UCS_IS_BMP(cp)	((cp) <= 0xffff)
+		idx = e16 - ucs_bmp_ranges;
+		return (ucs_nonbmp_ranges[idx / UCS_NONBMP_BMP_BITS].last
+			>> (idx % UCS_NONBMP_BMP_BITS)) & 1 ? 2 : 0;
+	}
 
-/**
- * ucs_is_zero_width() - Determine if a Unicode code point is zero-width.
- * @cp: Unicode code point (UCS-4)
- *
- * Return: true if the character is zero-width, false otherwise
- */
-bool ucs_is_zero_width(u32 cp)
-{
-	if (UCS_IS_BMP(cp))
-		return cp_in_range16(cp, ucs_zero_width_bmp_ranges,
-				     ARRAY_SIZE(ucs_zero_width_bmp_ranges));
-	else
-		return cp_in_range32(cp, ucs_zero_width_non_bmp_ranges,
-				     ARRAY_SIZE(ucs_zero_width_non_bmp_ranges));
-}
+	k = cp << UCS_CP_SHIFT;
+	if (k < ucs_nonbmp_ranges[0].first ||
+	    k > ucs_nonbmp_ranges[ARRAY_SIZE(ucs_nonbmp_ranges) - 1].last)
+		return 1;
 
-/**
- * ucs_is_double_width() - Determine if a Unicode code point is double-width.
- * @cp: Unicode code point (UCS-4)
- *
- * Return: true if the character is double-width, false otherwise
- */
-bool ucs_is_double_width(u32 cp)
-{
-	if (UCS_IS_BMP(cp))
-		return cp_in_range16(cp, ucs_double_width_bmp_ranges,
-				     ARRAY_SIZE(ucs_double_width_bmp_ranges));
-	else
-		return cp_in_range32(cp, ucs_double_width_non_bmp_ranges,
-				     ARRAY_SIZE(ucs_double_width_non_bmp_ranges));
+	e32 = __inline_bsearch(&k, ucs_nonbmp_ranges,
+			       ARRAY_SIZE(ucs_nonbmp_ranges),
+			       sizeof(*ucs_nonbmp_ranges), width32_cmp);
+	if (!e32)
+		return 1;
+	return (e32->last & UCS_NONBMP_W2_FLAG) ? 2 : 0;
 }
 
 /*
diff --git a/drivers/tty/vt/ucs_width_table.h_shipped b/drivers/tty/vt/ucs_width_table.h_shipped
index 6fcb8f1d577d..5cd6434bf329 100644
--- a/drivers/tty/vt/ucs_width_table.h_shipped
+++ b/drivers/tty/vt/ucs_width_table.h_shipped
@@ -5,449 +5,486 @@
  * Auto-generated by gen_ucs_width_table.py
  *
  * Unicode Version: 16.0.0
+ *
+ * Zero-width and double-width ranges are merged into one sorted-by-`first`
+ * table per region. The non-BMP table additionally hosts the BMP
+ * double-width bitmap in the low 8 bits of `last` of its
+ * first 33 entries (covering 262 BMP entries).
+ * See ucs.c for the encoding details and the lookup code.
  */
 
-/* Zero-width character ranges (BMP - Basic Multilingual Plane, U+0000 to U+FFFF) */
-static const struct ucs_interval16 ucs_zero_width_bmp_ranges[] = {
-	{ 0x00AD, 0x00AD }, /* SOFT HYPHEN */
-	{ 0x0300, 0x036F }, /* COMBINING GRAVE ACCENT - COMBINING LATIN SMALL LETTER X */
-	{ 0x0483, 0x0489 }, /* COMBINING CYRILLIC TITLO - COMBINING CYRILLIC MILLIONS SIGN */
-	{ 0x0591, 0x05BD }, /* HEBREW ACCENT ETNAHTA - HEBREW POINT METEG */
-	{ 0x05BF, 0x05BF }, /* HEBREW POINT RAFE */
-	{ 0x05C1, 0x05C2 }, /* HEBREW POINT SHIN DOT - HEBREW POINT SIN DOT */
-	{ 0x05C4, 0x05C5 }, /* HEBREW MARK UPPER DOT - HEBREW MARK LOWER DOT */
-	{ 0x05C7, 0x05C7 }, /* HEBREW POINT QAMATS QATAN */
-	{ 0x0600, 0x0605 }, /* ARABIC NUMBER SIGN - ARABIC NUMBER MARK ABOVE */
-	{ 0x0610, 0x061A }, /* ARABIC SIGN SALLALLAHOU ALAYHE WASSALLAM - ARABIC SMALL KASRA */
-	{ 0x061C, 0x061C }, /* ARABIC LETTER MARK */
-	{ 0x064B, 0x065F }, /* ARABIC FATHATAN - ARABIC WAVY HAMZA BELOW */
-	{ 0x0670, 0x0670 }, /* ARABIC LETTER SUPERSCRIPT ALEF */
-	{ 0x06D6, 0x06DD }, /* ARABIC SMALL HIGH LIGATURE SAD WITH LAM WITH ALEF MAKSURA - ARABIC END OF AYAH */
-	{ 0x06DF, 0x06E4 }, /* ARABIC SMALL HIGH ROUNDED ZERO - ARABIC SMALL HIGH MADDA */
-	{ 0x06E7, 0x06E8 }, /* ARABIC SMALL HIGH YEH - ARABIC SMALL HIGH NOON */
-	{ 0x06EA, 0x06ED }, /* ARABIC EMPTY CENTRE LOW STOP - ARABIC SMALL LOW MEEM */
-	{ 0x070F, 0x070F }, /* SYRIAC ABBREVIATION MARK */
-	{ 0x0711, 0x0711 }, /* SYRIAC LETTER SUPERSCRIPT ALAPH */
-	{ 0x0730, 0x074A }, /* SYRIAC PTHAHA ABOVE - SYRIAC BARREKH */
-	{ 0x07A6, 0x07B0 }, /* THAANA ABAFILI - THAANA SUKUN */
-	{ 0x07EB, 0x07F3 }, /* NKO COMBINING SHORT HIGH TONE - NKO COMBINING DOUBLE DOT ABOVE */
-	{ 0x07FD, 0x07FD }, /* NKO DANTAYALAN */
-	{ 0x0816, 0x0819 }, /* SAMARITAN MARK IN - SAMARITAN MARK DAGESH */
-	{ 0x081B, 0x0823 }, /* SAMARITAN MARK EPENTHETIC YUT - SAMARITAN VOWEL SIGN A */
-	{ 0x0825, 0x0827 }, /* SAMARITAN VOWEL SIGN SHORT A - SAMARITAN VOWEL SIGN U */
-	{ 0x0829, 0x082D }, /* SAMARITAN VOWEL SIGN LONG I - SAMARITAN MARK NEQUDAA */
-	{ 0x0859, 0x085B }, /* MANDAIC AFFRICATION MARK - MANDAIC GEMINATION MARK */
-	{ 0x0890, 0x0891 }, /* ARABIC POUND MARK ABOVE - ARABIC PIASTRE MARK ABOVE */
-	{ 0x0897, 0x089F }, /* ARABIC PEPET - ARABIC HALF MADDA OVER MADDA */
-	{ 0x08CA, 0x0903 }, /* ARABIC SMALL HIGH FARSI YEH - DEVANAGARI SIGN VISARGA */
-	{ 0x093A, 0x093C }, /* DEVANAGARI VOWEL SIGN OE - DEVANAGARI SIGN NUKTA */
-	{ 0x093E, 0x094F }, /* DEVANAGARI VOWEL SIGN AA - DEVANAGARI VOWEL SIGN AW */
-	{ 0x0951, 0x0957 }, /* DEVANAGARI STRESS SIGN UDATTA - DEVANAGARI VOWEL SIGN UUE */
-	{ 0x0962, 0x0963 }, /* DEVANAGARI VOWEL SIGN VOCALIC L - DEVANAGARI VOWEL SIGN VOCALIC LL */
-	{ 0x0981, 0x0983 }, /* BENGALI SIGN CANDRABINDU - BENGALI SIGN VISARGA */
-	{ 0x09BC, 0x09BC }, /* BENGALI SIGN NUKTA */
-	{ 0x09BE, 0x09C4 }, /* BENGALI VOWEL SIGN AA - BENGALI VOWEL SIGN VOCALIC RR */
-	{ 0x09C7, 0x09C8 }, /* BENGALI VOWEL SIGN E - BENGALI VOWEL SIGN AI */
-	{ 0x09CB, 0x09CD }, /* BENGALI VOWEL SIGN O - BENGALI SIGN VIRAMA */
-	{ 0x09D7, 0x09D7 }, /* BENGALI AU LENGTH MARK */
-	{ 0x09E2, 0x09E3 }, /* BENGALI VOWEL SIGN VOCALIC L - BENGALI VOWEL SIGN VOCALIC LL */
-	{ 0x09FE, 0x09FE }, /* BENGALI SANDHI MARK */
-	{ 0x0A01, 0x0A03 }, /* GURMUKHI SIGN ADAK BINDI - GURMUKHI SIGN VISARGA */
-	{ 0x0A3C, 0x0A3C }, /* GURMUKHI SIGN NUKTA */
-	{ 0x0A3E, 0x0A42 }, /* GURMUKHI VOWEL SIGN AA - GURMUKHI VOWEL SIGN UU */
-	{ 0x0A47, 0x0A48 }, /* GURMUKHI VOWEL SIGN EE - GURMUKHI VOWEL SIGN AI */
-	{ 0x0A4B, 0x0A4D }, /* GURMUKHI VOWEL SIGN OO - GURMUKHI SIGN VIRAMA */
-	{ 0x0A51, 0x0A51 }, /* GURMUKHI SIGN UDAAT */
-	{ 0x0A70, 0x0A71 }, /* GURMUKHI TIPPI - GURMUKHI ADDAK */
-	{ 0x0A75, 0x0A75 }, /* GURMUKHI SIGN YAKASH */
-	{ 0x0A81, 0x0A83 }, /* GUJARATI SIGN CANDRABINDU - GUJARATI SIGN VISARGA */
-	{ 0x0ABC, 0x0ABC }, /* GUJARATI SIGN NUKTA */
-	{ 0x0ABE, 0x0AC5 }, /* GUJARATI VOWEL SIGN AA - GUJARATI VOWEL SIGN CANDRA E */
-	{ 0x0AC7, 0x0AC9 }, /* GUJARATI VOWEL SIGN E - GUJARATI VOWEL SIGN CANDRA O */
-	{ 0x0ACB, 0x0ACD }, /* GUJARATI VOWEL SIGN O - GUJARATI SIGN VIRAMA */
-	{ 0x0AE2, 0x0AE3 }, /* GUJARATI VOWEL SIGN VOCALIC L - GUJARATI VOWEL SIGN VOCALIC LL */
-	{ 0x0AFA, 0x0AFF }, /* GUJARATI SIGN SUKUN - GUJARATI SIGN TWO-CIRCLE NUKTA ABOVE */
-	{ 0x0B01, 0x0B03 }, /* ORIYA SIGN CANDRABINDU - ORIYA SIGN VISARGA */
-	{ 0x0B3C, 0x0B3C }, /* ORIYA SIGN NUKTA */
-	{ 0x0B3E, 0x0B44 }, /* ORIYA VOWEL SIGN AA - ORIYA VOWEL SIGN VOCALIC RR */
-	{ 0x0B47, 0x0B48 }, /* ORIYA VOWEL SIGN E - ORIYA VOWEL SIGN AI */
-	{ 0x0B4B, 0x0B4D }, /* ORIYA VOWEL SIGN O - ORIYA SIGN VIRAMA */
-	{ 0x0B55, 0x0B57 }, /* ORIYA SIGN OVERLINE - ORIYA AU LENGTH MARK */
-	{ 0x0B62, 0x0B63 }, /* ORIYA VOWEL SIGN VOCALIC L - ORIYA VOWEL SIGN VOCALIC LL */
-	{ 0x0B82, 0x0B82 }, /* TAMIL SIGN ANUSVARA */
-	{ 0x0BBE, 0x0BC2 }, /* TAMIL VOWEL SIGN AA - TAMIL VOWEL SIGN UU */
-	{ 0x0BC6, 0x0BC8 }, /* TAMIL VOWEL SIGN E - TAMIL VOWEL SIGN AI */
-	{ 0x0BCA, 0x0BCD }, /* TAMIL VOWEL SIGN O - TAMIL SIGN VIRAMA */
-	{ 0x0BD7, 0x0BD7 }, /* TAMIL AU LENGTH MARK */
-	{ 0x0C00, 0x0C04 }, /* TELUGU SIGN COMBINING CANDRABINDU ABOVE - TELUGU SIGN COMBINING ANUSVARA ABOVE */
-	{ 0x0C3C, 0x0C3C }, /* TELUGU SIGN NUKTA */
-	{ 0x0C3E, 0x0C44 }, /* TELUGU VOWEL SIGN AA - TELUGU VOWEL SIGN VOCALIC RR */
-	{ 0x0C46, 0x0C48 }, /* TELUGU VOWEL SIGN E - TELUGU VOWEL SIGN AI */
-	{ 0x0C4A, 0x0C4D }, /* TELUGU VOWEL SIGN O - TELUGU SIGN VIRAMA */
-	{ 0x0C55, 0x0C56 }, /* TELUGU LENGTH MARK - TELUGU AI LENGTH MARK */
-	{ 0x0C62, 0x0C63 }, /* TELUGU VOWEL SIGN VOCALIC L - TELUGU VOWEL SIGN VOCALIC LL */
-	{ 0x0C81, 0x0C83 }, /* KANNADA SIGN CANDRABINDU - KANNADA SIGN VISARGA */
-	{ 0x0CBC, 0x0CBC }, /* KANNADA SIGN NUKTA */
-	{ 0x0CBE, 0x0CC4 }, /* KANNADA VOWEL SIGN AA - KANNADA VOWEL SIGN VOCALIC RR */
-	{ 0x0CC6, 0x0CC8 }, /* KANNADA VOWEL SIGN E - KANNADA VOWEL SIGN AI */
-	{ 0x0CCA, 0x0CCD }, /* KANNADA VOWEL SIGN O - KANNADA SIGN VIRAMA */
-	{ 0x0CD5, 0x0CD6 }, /* KANNADA LENGTH MARK - KANNADA AI LENGTH MARK */
-	{ 0x0CE2, 0x0CE3 }, /* KANNADA VOWEL SIGN VOCALIC L - KANNADA VOWEL SIGN VOCALIC LL */
-	{ 0x0CF3, 0x0CF3 }, /* KANNADA SIGN COMBINING ANUSVARA ABOVE RIGHT */
-	{ 0x0D00, 0x0D03 }, /* MALAYALAM SIGN COMBINING ANUSVARA ABOVE - MALAYALAM SIGN VISARGA */
-	{ 0x0D3B, 0x0D3C }, /* MALAYALAM SIGN VERTICAL BAR VIRAMA - MALAYALAM SIGN CIRCULAR VIRAMA */
-	{ 0x0D3E, 0x0D44 }, /* MALAYALAM VOWEL SIGN AA - MALAYALAM VOWEL SIGN VOCALIC RR */
-	{ 0x0D46, 0x0D48 }, /* MALAYALAM VOWEL SIGN E - MALAYALAM VOWEL SIGN AI */
-	{ 0x0D4A, 0x0D4D }, /* MALAYALAM VOWEL SIGN O - MALAYALAM SIGN VIRAMA */
-	{ 0x0D57, 0x0D57 }, /* MALAYALAM AU LENGTH MARK */
-	{ 0x0D62, 0x0D63 }, /* MALAYALAM VOWEL SIGN VOCALIC L - MALAYALAM VOWEL SIGN VOCALIC LL */
-	{ 0x0D81, 0x0D83 }, /* SINHALA SIGN CANDRABINDU - SINHALA SIGN VISARGAYA */
-	{ 0x0DCA, 0x0DCA }, /* SINHALA SIGN AL-LAKUNA */
-	{ 0x0DCF, 0x0DD4 }, /* SINHALA VOWEL SIGN AELA-PILLA - SINHALA VOWEL SIGN KETTI PAA-PILLA */
-	{ 0x0DD6, 0x0DD6 }, /* SINHALA VOWEL SIGN DIGA PAA-PILLA */
-	{ 0x0DD8, 0x0DDF }, /* SINHALA VOWEL SIGN GAETTA-PILLA - SINHALA VOWEL SIGN GAYANUKITTA */
-	{ 0x0DF2, 0x0DF3 }, /* SINHALA VOWEL SIGN DIGA GAETTA-PILLA - SINHALA VOWEL SIGN DIGA GAYANUKITTA */
-	{ 0x0E31, 0x0E31 }, /* THAI CHARACTER MAI HAN-AKAT */
-	{ 0x0E34, 0x0E3A }, /* THAI CHARACTER SARA I - THAI CHARACTER PHINTHU */
-	{ 0x0E47, 0x0E4E }, /* THAI CHARACTER MAITAIKHU - THAI CHARACTER YAMAKKAN */
-	{ 0x0EB1, 0x0EB1 }, /* LAO VOWEL SIGN MAI KAN */
-	{ 0x0EB4, 0x0EBC }, /* LAO VOWEL SIGN I - LAO SEMIVOWEL SIGN LO */
-	{ 0x0EC8, 0x0ECE }, /* LAO TONE MAI EK - LAO YAMAKKAN */
-	{ 0x0F18, 0x0F19 }, /* TIBETAN ASTROLOGICAL SIGN -KHYUD PA - TIBETAN ASTROLOGICAL SIGN SDONG TSHUGS */
-	{ 0x0F35, 0x0F35 }, /* TIBETAN MARK NGAS BZUNG NYI ZLA */
-	{ 0x0F37, 0x0F37 }, /* TIBETAN MARK NGAS BZUNG SGOR RTAGS */
-	{ 0x0F39, 0x0F39 }, /* TIBETAN MARK TSA -PHRU */
-	{ 0x0F3E, 0x0F3F }, /* TIBETAN SIGN YAR TSHES - TIBETAN SIGN MAR TSHES */
-	{ 0x0F71, 0x0F84 }, /* TIBETAN VOWEL SIGN AA - TIBETAN MARK HALANTA */
-	{ 0x0F86, 0x0F87 }, /* TIBETAN SIGN LCI RTAGS - TIBETAN SIGN YANG RTAGS */
-	{ 0x0F8D, 0x0F97 }, /* TIBETAN SUBJOINED SIGN LCE TSA CAN - TIBETAN SUBJOINED LETTER JA */
-	{ 0x0F99, 0x0FBC }, /* TIBETAN SUBJOINED LETTER NYA - TIBETAN SUBJOINED LETTER FIXED-FORM RA */
-	{ 0x0FC6, 0x0FC6 }, /* TIBETAN SYMBOL PADMA GDAN */
-	{ 0x102B, 0x103E }, /* MYANMAR VOWEL SIGN TALL AA - MYANMAR CONSONANT SIGN MEDIAL HA */
-	{ 0x1056, 0x1059 }, /* MYANMAR VOWEL SIGN VOCALIC R - MYANMAR VOWEL SIGN VOCALIC LL */
-	{ 0x105E, 0x1060 }, /* MYANMAR CONSONANT SIGN MON MEDIAL NA - MYANMAR CONSONANT SIGN MON MEDIAL LA */
-	{ 0x1062, 0x1064 }, /* MYANMAR VOWEL SIGN SGAW KAREN EU - MYANMAR TONE MARK SGAW KAREN KE PHO */
-	{ 0x1067, 0x106D }, /* MYANMAR VOWEL SIGN WESTERN PWO KAREN EU - MYANMAR SIGN WESTERN PWO KAREN TONE-5 */
-	{ 0x1071, 0x1074 }, /* MYANMAR VOWEL SIGN GEBA KAREN I - MYANMAR VOWEL SIGN KAYAH EE */
-	{ 0x1082, 0x108D }, /* MYANMAR CONSONANT SIGN SHAN MEDIAL WA - MYANMAR SIGN SHAN COUNCIL EMPHATIC TONE */
-	{ 0x108F, 0x108F }, /* MYANMAR SIGN RUMAI PALAUNG TONE-5 */
-	{ 0x109A, 0x109D }, /* MYANMAR SIGN KHAMTI TONE-1 - MYANMAR VOWEL SIGN AITON AI */
-	{ 0x135D, 0x135F }, /* ETHIOPIC COMBINING GEMINATION AND VOWEL LENGTH MARK - ETHIOPIC COMBINING GEMINATION MARK */
-	{ 0x1712, 0x1715 }, /* TAGALOG VOWEL SIGN I - TAGALOG SIGN PAMUDPOD */
-	{ 0x1732, 0x1734 }, /* HANUNOO VOWEL SIGN I - HANUNOO SIGN PAMUDPOD */
-	{ 0x1752, 0x1753 }, /* BUHID VOWEL SIGN I - BUHID VOWEL SIGN U */
-	{ 0x1772, 0x1773 }, /* TAGBANWA VOWEL SIGN I - TAGBANWA VOWEL SIGN U */
-	{ 0x17B4, 0x17D3 }, /* KHMER VOWEL INHERENT AQ - KHMER SIGN BATHAMASAT */
-	{ 0x17DD, 0x17DD }, /* KHMER SIGN ATTHACAN */
-	{ 0x180B, 0x180F }, /* MONGOLIAN FREE VARIATION SELECTOR ONE - MONGOLIAN FREE VARIATION SELECTOR FOUR */
-	{ 0x1885, 0x1886 }, /* MONGOLIAN LETTER ALI GALI BALUDA - MONGOLIAN LETTER ALI GALI THREE BALUDA */
-	{ 0x18A9, 0x18A9 }, /* MONGOLIAN LETTER ALI GALI DAGALGA */
-	{ 0x1920, 0x192B }, /* LIMBU VOWEL SIGN A - LIMBU SUBJOINED LETTER WA */
-	{ 0x1930, 0x193B }, /* LIMBU SMALL LETTER KA - LIMBU SIGN SA-I */
-	{ 0x1A17, 0x1A1B }, /* BUGINESE VOWEL SIGN I - BUGINESE VOWEL SIGN AE */
-	{ 0x1A55, 0x1A5E }, /* TAI THAM CONSONANT SIGN MEDIAL RA - TAI THAM CONSONANT SIGN SA */
-	{ 0x1A60, 0x1A7C }, /* TAI THAM SIGN SAKOT - TAI THAM SIGN KHUEN-LUE KARAN */
-	{ 0x1A7F, 0x1A7F }, /* TAI THAM COMBINING CRYPTOGRAMMIC DOT */
-	{ 0x1AB0, 0x1ACE }, /* COMBINING DOUBLED CIRCUMFLEX ACCENT - COMBINING LATIN SMALL LETTER INSULAR T */
-	{ 0x1B00, 0x1B04 }, /* BALINESE SIGN ULU RICEM - BALINESE SIGN BISAH */
-	{ 0x1B34, 0x1B44 }, /* BALINESE SIGN REREKAN - BALINESE ADEG ADEG */
-	{ 0x1B6B, 0x1B73 }, /* BALINESE MUSICAL SYMBOL COMBINING TEGEH - BALINESE MUSICAL SYMBOL COMBINING GONG */
-	{ 0x1B80, 0x1B82 }, /* SUNDANESE SIGN PANYECEK - SUNDANESE SIGN PANGWISAD */
-	{ 0x1BA1, 0x1BAD }, /* SUNDANESE CONSONANT SIGN PAMINGKAL - SUNDANESE CONSONANT SIGN PASANGAN WA */
-	{ 0x1BE6, 0x1BF3 }, /* BATAK SIGN TOMPI - BATAK PANONGONAN */
-	{ 0x1C24, 0x1C37 }, /* LEPCHA SUBJOINED LETTER YA - LEPCHA SIGN NUKTA */
-	{ 0x1CD0, 0x1CD2 }, /* VEDIC TONE KARSHANA - VEDIC TONE PRENKHA */
-	{ 0x1CD4, 0x1CE8 }, /* VEDIC SIGN YAJURVEDIC MIDLINE SVARITA - VEDIC SIGN VISARGA ANUDATTA WITH TAIL */
-	{ 0x1CED, 0x1CED }, /* VEDIC SIGN TIRYAK */
-	{ 0x1CF4, 0x1CF4 }, /* VEDIC TONE CANDRA ABOVE */
-	{ 0x1CF7, 0x1CF9 }, /* VEDIC SIGN ATIKRAMA - VEDIC TONE DOUBLE RING ABOVE */
-	{ 0x1DC0, 0x1DFF }, /* COMBINING DOTTED GRAVE ACCENT - COMBINING RIGHT ARROWHEAD AND DOWN ARROWHEAD BELOW */
-	{ 0x200B, 0x200F }, /* ZERO WIDTH SPACE - RIGHT-TO-LEFT MARK */
-	{ 0x202A, 0x202E }, /* LEFT-TO-RIGHT EMBEDDING - RIGHT-TO-LEFT OVERRIDE */
-	{ 0x2060, 0x2064 }, /* WORD JOINER - INVISIBLE PLUS */
-	{ 0x2066, 0x206F }, /* LEFT-TO-RIGHT ISOLATE - NOMINAL DIGIT SHAPES */
-	{ 0x20D0, 0x20F0 }, /* COMBINING LEFT HARPOON ABOVE - COMBINING ASTERISK ABOVE */
-	{ 0x2640, 0x2640 }, /* FEMALE SIGN */
-	{ 0x2642, 0x2642 }, /* MALE SIGN */
-	{ 0x26A7, 0x26A7 }, /* MALE WITH STROKE AND MALE AND FEMALE SIGN */
-	{ 0x2CEF, 0x2CF1 }, /* COPTIC COMBINING NI ABOVE - COPTIC COMBINING SPIRITUS LENIS */
-	{ 0x2D7F, 0x2D7F }, /* TIFINAGH CONSONANT JOINER */
-	{ 0x2DE0, 0x2DFF }, /* COMBINING CYRILLIC LETTER BE - COMBINING CYRILLIC LETTER IOTIFIED BIG YUS */
-	{ 0x302A, 0x302F }, /* IDEOGRAPHIC LEVEL TONE MARK - HANGUL DOUBLE DOT TONE MARK */
-	{ 0x3099, 0x309A }, /* COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK - COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK */
-	{ 0xA66F, 0xA672 }, /* COMBINING CYRILLIC VZMET - COMBINING CYRILLIC THOUSAND MILLIONS SIGN */
-	{ 0xA674, 0xA67D }, /* COMBINING CYRILLIC LETTER UKRAINIAN IE - COMBINING CYRILLIC PAYEROK */
-	{ 0xA69E, 0xA69F }, /* COMBINING CYRILLIC LETTER EF - COMBINING CYRILLIC LETTER IOTIFIED E */
-	{ 0xA6F0, 0xA6F1 }, /* BAMUM COMBINING MARK KOQNDON - BAMUM COMBINING MARK TUKWENTIS */
-	{ 0xA802, 0xA802 }, /* SYLOTI NAGRI SIGN DVISVARA */
-	{ 0xA806, 0xA806 }, /* SYLOTI NAGRI SIGN HASANTA */
-	{ 0xA80B, 0xA80B }, /* SYLOTI NAGRI SIGN ANUSVARA */
-	{ 0xA823, 0xA827 }, /* SYLOTI NAGRI VOWEL SIGN A - SYLOTI NAGRI VOWEL SIGN OO */
-	{ 0xA82C, 0xA82C }, /* SYLOTI NAGRI SIGN ALTERNATE HASANTA */
-	{ 0xA880, 0xA881 }, /* SAURASHTRA SIGN ANUSVARA - SAURASHTRA SIGN VISARGA */
-	{ 0xA8B4, 0xA8C5 }, /* SAURASHTRA CONSONANT SIGN HAARU - SAURASHTRA SIGN CANDRABINDU */
-	{ 0xA8E0, 0xA8F1 }, /* COMBINING DEVANAGARI DIGIT ZERO - COMBINING DEVANAGARI SIGN AVAGRAHA */
-	{ 0xA8FF, 0xA8FF }, /* DEVANAGARI VOWEL SIGN AY */
-	{ 0xA926, 0xA92D }, /* KAYAH LI VOWEL UE - KAYAH LI TONE CALYA PLOPHU */
-	{ 0xA947, 0xA953 }, /* REJANG VOWEL SIGN I - REJANG VIRAMA */
-	{ 0xA980, 0xA983 }, /* JAVANESE SIGN PANYANGGA - JAVANESE SIGN WIGNYAN */
-	{ 0xA9B3, 0xA9C0 }, /* JAVANESE SIGN CECAK TELU - JAVANESE PANGKON */
-	{ 0xA9E5, 0xA9E5 }, /* MYANMAR SIGN SHAN SAW */
-	{ 0xAA29, 0xAA36 }, /* CHAM VOWEL SIGN AA - CHAM CONSONANT SIGN WA */
-	{ 0xAA43, 0xAA43 }, /* CHAM CONSONANT SIGN FINAL NG */
-	{ 0xAA4C, 0xAA4D }, /* CHAM CONSONANT SIGN FINAL M - CHAM CONSONANT SIGN FINAL H */
-	{ 0xAA7B, 0xAA7D }, /* MYANMAR SIGN PAO KAREN TONE - MYANMAR SIGN TAI LAING TONE-5 */
-	{ 0xAAB0, 0xAAB0 }, /* TAI VIET MAI KANG */
-	{ 0xAAB2, 0xAAB4 }, /* TAI VIET VOWEL I - TAI VIET VOWEL U */
-	{ 0xAAB7, 0xAAB8 }, /* TAI VIET MAI KHIT - TAI VIET VOWEL IA */
-	{ 0xAABE, 0xAABF }, /* TAI VIET VOWEL AM - TAI VIET TONE MAI EK */
-	{ 0xAAC1, 0xAAC1 }, /* TAI VIET TONE MAI THO */
-	{ 0xAAEB, 0xAAEF }, /* MEETEI MAYEK VOWEL SIGN II - MEETEI MAYEK VOWEL SIGN AAU */
-	{ 0xAAF5, 0xAAF6 }, /* MEETEI MAYEK VOWEL SIGN VISARGA - MEETEI MAYEK VIRAMA */
-	{ 0xABE3, 0xABEA }, /* MEETEI MAYEK VOWEL SIGN ONAP - MEETEI MAYEK VOWEL SIGN NUNG */
-	{ 0xABEC, 0xABED }, /* MEETEI MAYEK LUM IYEK - MEETEI MAYEK APUN IYEK */
-	{ 0xFB1E, 0xFB1E }, /* HEBREW POINT JUDEO-SPANISH VARIKA */
-	{ 0xFE00, 0xFE0F }, /* VARIATION SELECTOR-1 - VARIATION SELECTOR-16 */
-	{ 0xFE20, 0xFE2F }, /* COMBINING LIGATURE LEFT HALF - COMBINING CYRILLIC TITLO RIGHT HALF */
-	{ 0xFEFF, 0xFEFF }, /* ZERO WIDTH NO-BREAK SPACE */
-	{ 0xFFF9, 0xFFFB }, /* INTERLINEAR ANNOTATION ANCHOR - INTERLINEAR ANNOTATION TERMINATOR */
-};
-
-/* Zero-width character ranges (non-BMP, U+10000 and above) */
-static const struct ucs_interval32 ucs_zero_width_non_bmp_ranges[] = {
-	{ 0x101FD, 0x101FD }, /* PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE */
-	{ 0x102E0, 0x102E0 }, /* COPTIC EPACT THOUSANDS MARK */
-	{ 0x10376, 0x1037A }, /* COMBINING OLD PERMIC LETTER AN - COMBINING OLD PERMIC LETTER SII */
-	{ 0x10A01, 0x10A03 }, /* KHAROSHTHI VOWEL SIGN I - KHAROSHTHI VOWEL SIGN VOCALIC R */
-	{ 0x10A05, 0x10A06 }, /* KHAROSHTHI VOWEL SIGN E - KHAROSHTHI VOWEL SIGN O */
-	{ 0x10A0C, 0x10A0F }, /* KHAROSHTHI VOWEL LENGTH MARK - KHAROSHTHI SIGN VISARGA */
-	{ 0x10A38, 0x10A3A }, /* KHAROSHTHI SIGN BAR ABOVE - KHAROSHTHI SIGN DOT BELOW */
-	{ 0x10A3F, 0x10A3F }, /* KHAROSHTHI VIRAMA */
-	{ 0x10AE5, 0x10AE6 }, /* MANICHAEAN ABBREVIATION MARK ABOVE - MANICHAEAN ABBREVIATION MARK BELOW */
-	{ 0x10D24, 0x10D27 }, /* HANIFI ROHINGYA SIGN HARBAHAY - HANIFI ROHINGYA SIGN TASSI */
-	{ 0x10D69, 0x10D6D }, /* GARAY VOWEL SIGN E - GARAY CONSONANT NASALIZATION MARK */
-	{ 0x10EAB, 0x10EAC }, /* YEZIDI COMBINING HAMZA MARK - YEZIDI COMBINING MADDA MARK */
-	{ 0x10EFC, 0x10EFF }, /* ARABIC COMBINING ALEF OVERLAY - ARABIC SMALL LOW WORD MADDA */
-	{ 0x10F46, 0x10F50 }, /* SOGDIAN COMBINING DOT BELOW - SOGDIAN COMBINING STROKE BELOW */
-	{ 0x10F82, 0x10F85 }, /* OLD UYGHUR COMBINING DOT ABOVE - OLD UYGHUR COMBINING TWO DOTS BELOW */
-	{ 0x11000, 0x11002 }, /* BRAHMI SIGN CANDRABINDU - BRAHMI SIGN VISARGA */
-	{ 0x11038, 0x11046 }, /* BRAHMI VOWEL SIGN AA - BRAHMI VIRAMA */
-	{ 0x11070, 0x11070 }, /* BRAHMI SIGN OLD TAMIL VIRAMA */
-	{ 0x11073, 0x11074 }, /* BRAHMI VOWEL SIGN OLD TAMIL SHORT E - BRAHMI VOWEL SIGN OLD TAMIL SHORT O */
-	{ 0x1107F, 0x11082 }, /* BRAHMI NUMBER JOINER - KAITHI SIGN VISARGA */
-	{ 0x110B0, 0x110BA }, /* KAITHI VOWEL SIGN AA - KAITHI SIGN NUKTA */
-	{ 0x110BD, 0x110BD }, /* KAITHI NUMBER SIGN */
-	{ 0x110C2, 0x110C2 }, /* KAITHI VOWEL SIGN VOCALIC R */
-	{ 0x110CD, 0x110CD }, /* KAITHI NUMBER SIGN ABOVE */
-	{ 0x11100, 0x11102 }, /* CHAKMA SIGN CANDRABINDU - CHAKMA SIGN VISARGA */
-	{ 0x11127, 0x11134 }, /* CHAKMA VOWEL SIGN A - CHAKMA MAAYYAA */
-	{ 0x11145, 0x11146 }, /* CHAKMA VOWEL SIGN AA - CHAKMA VOWEL SIGN EI */
-	{ 0x11173, 0x11173 }, /* MAHAJANI SIGN NUKTA */
-	{ 0x11180, 0x11182 }, /* SHARADA SIGN CANDRABINDU - SHARADA SIGN VISARGA */
-	{ 0x111B3, 0x111C0 }, /* SHARADA VOWEL SIGN AA - SHARADA SIGN VIRAMA */
-	{ 0x111C9, 0x111CC }, /* SHARADA SANDHI MARK - SHARADA EXTRA SHORT VOWEL MARK */
-	{ 0x111CE, 0x111CF }, /* SHARADA VOWEL SIGN PRISHTHAMATRA E - SHARADA SIGN INVERTED CANDRABINDU */
-	{ 0x1122C, 0x11237 }, /* KHOJKI VOWEL SIGN AA - KHOJKI SIGN SHADDA */
-	{ 0x1123E, 0x1123E }, /* KHOJKI SIGN SUKUN */
-	{ 0x11241, 0x11241 }, /* KHOJKI VOWEL SIGN VOCALIC R */
-	{ 0x112DF, 0x112EA }, /* KHUDAWADI SIGN ANUSVARA - KHUDAWADI SIGN VIRAMA */
-	{ 0x11300, 0x11303 }, /* GRANTHA SIGN COMBINING ANUSVARA ABOVE - GRANTHA SIGN VISARGA */
-	{ 0x1133B, 0x1133C }, /* COMBINING BINDU BELOW - GRANTHA SIGN NUKTA */
-	{ 0x1133E, 0x11344 }, /* GRANTHA VOWEL SIGN AA - GRANTHA VOWEL SIGN VOCALIC RR */
-	{ 0x11347, 0x11348 }, /* GRANTHA VOWEL SIGN EE - GRANTHA VOWEL SIGN AI */
-	{ 0x1134B, 0x1134D }, /* GRANTHA VOWEL SIGN OO - GRANTHA SIGN VIRAMA */
-	{ 0x11357, 0x11357 }, /* GRANTHA AU LENGTH MARK */
-	{ 0x11362, 0x11363 }, /* GRANTHA VOWEL SIGN VOCALIC L - GRANTHA VOWEL SIGN VOCALIC LL */
-	{ 0x11366, 0x1136C }, /* COMBINING GRANTHA DIGIT ZERO - COMBINING GRANTHA DIGIT SIX */
-	{ 0x11370, 0x11374 }, /* COMBINING GRANTHA LETTER A - COMBINING GRANTHA LETTER PA */
-	{ 0x113B8, 0x113C0 }, /* TULU-TIGALARI VOWEL SIGN AA - TULU-TIGALARI VOWEL SIGN VOCALIC LL */
-	{ 0x113C2, 0x113C2 }, /* TULU-TIGALARI VOWEL SIGN EE */
-	{ 0x113C5, 0x113C5 }, /* TULU-TIGALARI VOWEL SIGN AI */
-	{ 0x113C7, 0x113CA }, /* TULU-TIGALARI VOWEL SIGN OO - TULU-TIGALARI SIGN CANDRA ANUNASIKA */
-	{ 0x113CC, 0x113D0 }, /* TULU-TIGALARI SIGN ANUSVARA - TULU-TIGALARI CONJOINER */
-	{ 0x113D2, 0x113D2 }, /* TULU-TIGALARI GEMINATION MARK */
-	{ 0x113E1, 0x113E2 }, /* TULU-TIGALARI VEDIC TONE SVARITA - TULU-TIGALARI VEDIC TONE ANUDATTA */
-	{ 0x11435, 0x11446 }, /* NEWA VOWEL SIGN AA - NEWA SIGN NUKTA */
-	{ 0x1145E, 0x1145E }, /* NEWA SANDHI MARK */
-	{ 0x114B0, 0x114C3 }, /* TIRHUTA VOWEL SIGN AA - TIRHUTA SIGN NUKTA */
-	{ 0x115AF, 0x115B5 }, /* SIDDHAM VOWEL SIGN AA - SIDDHAM VOWEL SIGN VOCALIC RR */
-	{ 0x115B8, 0x115C0 }, /* SIDDHAM VOWEL SIGN E - SIDDHAM SIGN NUKTA */
-	{ 0x115DC, 0x115DD }, /* SIDDHAM VOWEL SIGN ALTERNATE U - SIDDHAM VOWEL SIGN ALTERNATE UU */
-	{ 0x11630, 0x11640 }, /* MODI VOWEL SIGN AA - MODI SIGN ARDHACANDRA */
-	{ 0x116AB, 0x116B7 }, /* TAKRI SIGN ANUSVARA - TAKRI SIGN NUKTA */
-	{ 0x1171D, 0x1172B }, /* AHOM CONSONANT SIGN MEDIAL LA - AHOM SIGN KILLER */
-	{ 0x1182C, 0x1183A }, /* DOGRA VOWEL SIGN AA - DOGRA SIGN NUKTA */
-	{ 0x11930, 0x11935 }, /* DIVES AKURU VOWEL SIGN AA - DIVES AKURU VOWEL SIGN E */
-	{ 0x11937, 0x11938 }, /* DIVES AKURU VOWEL SIGN AI - DIVES AKURU VOWEL SIGN O */
-	{ 0x1193B, 0x1193E }, /* DIVES AKURU SIGN ANUSVARA - DIVES AKURU VIRAMA */
-	{ 0x11940, 0x11940 }, /* DIVES AKURU MEDIAL YA */
-	{ 0x11942, 0x11943 }, /* DIVES AKURU MEDIAL RA - DIVES AKURU SIGN NUKTA */
-	{ 0x119D1, 0x119D7 }, /* NANDINAGARI VOWEL SIGN AA - NANDINAGARI VOWEL SIGN VOCALIC RR */
-	{ 0x119DA, 0x119E0 }, /* NANDINAGARI VOWEL SIGN E - NANDINAGARI SIGN VIRAMA */
-	{ 0x119E4, 0x119E4 }, /* NANDINAGARI VOWEL SIGN PRISHTHAMATRA E */
-	{ 0x11A01, 0x11A0A }, /* ZANABAZAR SQUARE VOWEL SIGN I - ZANABAZAR SQUARE VOWEL LENGTH MARK */
-	{ 0x11A33, 0x11A39 }, /* ZANABAZAR SQUARE FINAL CONSONANT MARK - ZANABAZAR SQUARE SIGN VISARGA */
-	{ 0x11A3B, 0x11A3E }, /* ZANABAZAR SQUARE CLUSTER-FINAL LETTER YA - ZANABAZAR SQUARE CLUSTER-FINAL LETTER VA */
-	{ 0x11A47, 0x11A47 }, /* ZANABAZAR SQUARE SUBJOINER */
-	{ 0x11A51, 0x11A5B }, /* SOYOMBO VOWEL SIGN I - SOYOMBO VOWEL LENGTH MARK */
-	{ 0x11A8A, 0x11A99 }, /* SOYOMBO FINAL CONSONANT SIGN G - SOYOMBO SUBJOINER */
-	{ 0x11C2F, 0x11C36 }, /* BHAIKSUKI VOWEL SIGN AA - BHAIKSUKI VOWEL SIGN VOCALIC L */
-	{ 0x11C38, 0x11C3F }, /* BHAIKSUKI VOWEL SIGN E - BHAIKSUKI SIGN VIRAMA */
-	{ 0x11C92, 0x11CA7 }, /* MARCHEN SUBJOINED LETTER KA - MARCHEN SUBJOINED LETTER ZA */
-	{ 0x11CA9, 0x11CB6 }, /* MARCHEN SUBJOINED LETTER YA - MARCHEN SIGN CANDRABINDU */
-	{ 0x11D31, 0x11D36 }, /* MASARAM GONDI VOWEL SIGN AA - MASARAM GONDI VOWEL SIGN VOCALIC R */
-	{ 0x11D3A, 0x11D3A }, /* MASARAM GONDI VOWEL SIGN E */
-	{ 0x11D3C, 0x11D3D }, /* MASARAM GONDI VOWEL SIGN AI - MASARAM GONDI VOWEL SIGN O */
-	{ 0x11D3F, 0x11D45 }, /* MASARAM GONDI VOWEL SIGN AU - MASARAM GONDI VIRAMA */
-	{ 0x11D47, 0x11D47 }, /* MASARAM GONDI RA-KARA */
-	{ 0x11D8A, 0x11D8E }, /* GUNJALA GONDI VOWEL SIGN AA - GUNJALA GONDI VOWEL SIGN UU */
-	{ 0x11D90, 0x11D91 }, /* GUNJALA GONDI VOWEL SIGN EE - GUNJALA GONDI VOWEL SIGN AI */
-	{ 0x11D93, 0x11D97 }, /* GUNJALA GONDI VOWEL SIGN OO - GUNJALA GONDI VIRAMA */
-	{ 0x11EF3, 0x11EF6 }, /* MAKASAR VOWEL SIGN I - MAKASAR VOWEL SIGN O */
-	{ 0x11F00, 0x11F01 }, /* KAWI SIGN CANDRABINDU - KAWI SIGN ANUSVARA */
-	{ 0x11F03, 0x11F03 }, /* KAWI SIGN VISARGA */
-	{ 0x11F34, 0x11F3A }, /* KAWI VOWEL SIGN AA - KAWI VOWEL SIGN VOCALIC R */
-	{ 0x11F3E, 0x11F42 }, /* KAWI VOWEL SIGN E - KAWI CONJOINER */
-	{ 0x11F5A, 0x11F5A }, /* KAWI SIGN NUKTA */
-	{ 0x13430, 0x13440 }, /* EGYPTIAN HIEROGLYPH VERTICAL JOINER - EGYPTIAN HIEROGLYPH MIRROR HORIZONTALLY */
-	{ 0x13447, 0x13455 }, /* EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT TOP START - EGYPTIAN HIEROGLYPH MODIFIER DAMAGED */
-	{ 0x1611E, 0x1612F }, /* GURUNG KHEMA VOWEL SIGN AA - GURUNG KHEMA SIGN THOLHOMA */
-	{ 0x16AF0, 0x16AF4 }, /* BASSA VAH COMBINING HIGH TONE - BASSA VAH COMBINING HIGH-LOW TONE */
-	{ 0x16B30, 0x16B36 }, /* PAHAWH HMONG MARK CIM TUB - PAHAWH HMONG MARK CIM TAUM */
-	{ 0x16F4F, 0x16F4F }, /* MIAO SIGN CONSONANT MODIFIER BAR */
-	{ 0x16F51, 0x16F87 }, /* MIAO SIGN ASPIRATION - MIAO VOWEL SIGN UI */
-	{ 0x16F8F, 0x16F92 }, /* MIAO TONE RIGHT - MIAO TONE BELOW */
-	{ 0x16FE4, 0x16FE4 }, /* KHITAN SMALL SCRIPT FILLER */
-	{ 0x16FF0, 0x16FF1 }, /* VIETNAMESE ALTERNATE READING MARK CA - VIETNAMESE ALTERNATE READING MARK NHAY */
-	{ 0x1BC9D, 0x1BC9E }, /* DUPLOYAN THICK LETTER SELECTOR - DUPLOYAN DOUBLE MARK */
-	{ 0x1BCA0, 0x1BCA3 }, /* SHORTHAND FORMAT LETTER OVERLAP - SHORTHAND FORMAT UP STEP */
-	{ 0x1CF00, 0x1CF2D }, /* ZNAMENNY COMBINING MARK GORAZDO NIZKO S KRYZHEM ON LEFT - ZNAMENNY COMBINING MARK KRYZH ON LEFT */
-	{ 0x1CF30, 0x1CF46 }, /* ZNAMENNY COMBINING TONAL RANGE MARK MRACHNO - ZNAMENNY PRIZNAK MODIFIER ROG */
-	{ 0x1D165, 0x1D169 }, /* MUSICAL SYMBOL COMBINING STEM - MUSICAL SYMBOL COMBINING TREMOLO-3 */
-	{ 0x1D16D, 0x1D182 }, /* MUSICAL SYMBOL COMBINING AUGMENTATION DOT - MUSICAL SYMBOL COMBINING LOURE */
-	{ 0x1D185, 0x1D18B }, /* MUSICAL SYMBOL COMBINING DOIT - MUSICAL SYMBOL COMBINING TRIPLE TONGUE */
-	{ 0x1D1AA, 0x1D1AD }, /* MUSICAL SYMBOL COMBINING DOWN BOW - MUSICAL SYMBOL COMBINING SNAP PIZZICATO */
-	{ 0x1D242, 0x1D244 }, /* COMBINING GREEK MUSICAL TRISEME - COMBINING GREEK MUSICAL PENTASEME */
-	{ 0x1DA00, 0x1DA36 }, /* SIGNWRITING HEAD RIM - SIGNWRITING AIR SUCKING IN */
-	{ 0x1DA3B, 0x1DA6C }, /* SIGNWRITING MOUTH CLOSED NEUTRAL - SIGNWRITING EXCITEMENT */
-	{ 0x1DA75, 0x1DA75 }, /* SIGNWRITING UPPER BODY TILTING FROM HIP JOINTS */
-	{ 0x1DA84, 0x1DA84 }, /* SIGNWRITING LOCATION HEAD NECK */
-	{ 0x1DA9B, 0x1DA9F }, /* SIGNWRITING FILL MODIFIER-2 - SIGNWRITING FILL MODIFIER-6 */
-	{ 0x1DAA1, 0x1DAAF }, /* SIGNWRITING ROTATION MODIFIER-2 - SIGNWRITING ROTATION MODIFIER-16 */
-	{ 0x1E000, 0x1E006 }, /* COMBINING GLAGOLITIC LETTER AZU - COMBINING GLAGOLITIC LETTER ZHIVETE */
-	{ 0x1E008, 0x1E018 }, /* COMBINING GLAGOLITIC LETTER ZEMLJA - COMBINING GLAGOLITIC LETTER HERU */
-	{ 0x1E01B, 0x1E021 }, /* COMBINING GLAGOLITIC LETTER SHTA - COMBINING GLAGOLITIC LETTER YATI */
-	{ 0x1E023, 0x1E024 }, /* COMBINING GLAGOLITIC LETTER YU - COMBINING GLAGOLITIC LETTER SMALL YUS */
-	{ 0x1E026, 0x1E02A }, /* COMBINING GLAGOLITIC LETTER YO - COMBINING GLAGOLITIC LETTER FITA */
-	{ 0x1E08F, 0x1E08F }, /* COMBINING CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I */
-	{ 0x1E130, 0x1E136 }, /* NYIAKENG PUACHUE HMONG TONE-B - NYIAKENG PUACHUE HMONG TONE-D */
-	{ 0x1E2AE, 0x1E2AE }, /* TOTO SIGN RISING TONE */
-	{ 0x1E2EC, 0x1E2EF }, /* WANCHO TONE TUP - WANCHO TONE KOINI */
-	{ 0x1E4EC, 0x1E4EF }, /* NAG MUNDARI SIGN MUHOR - NAG MUNDARI SIGN SUTUH */
-	{ 0x1E5EE, 0x1E5EF }, /* OL ONAL SIGN MU - OL ONAL SIGN IKIR */
-	{ 0x1E8D0, 0x1E8D6 }, /* MENDE KIKAKUI COMBINING NUMBER TEENS - MENDE KIKAKUI COMBINING NUMBER MILLIONS */
-	{ 0x1E944, 0x1E94A }, /* ADLAM ALIF LENGTHENER - ADLAM NUKTA */
-	{ 0x1F3FB, 0x1F3FF }, /* EMOJI MODIFIER FITZPATRICK TYPE-1-2 - EMOJI MODIFIER FITZPATRICK TYPE-6 */
-	{ 0x1F9B0, 0x1F9B3 }, /* EMOJI COMPONENT RED HAIR - EMOJI COMPONENT WHITE HAIR */
-	{ 0xE0001, 0xE0001 }, /* LANGUAGE TAG */
-	{ 0xE0020, 0xE007F }, /* TAG SPACE - CANCEL TAG */
-	{ 0xE0100, 0xE01EF }, /* VARIATION SELECTOR-17 - VARIATION SELECTOR-256 */
-};
+/* Bits per BMP-bitmap chunk hosted in one non-BMP entry's `last` field. */
+#define UCS_NONBMP_BMP_BITS 8
 
-/* Double-width character ranges (BMP - Basic Multilingual Plane, U+0000 to U+FFFF) */
-static const struct ucs_interval16 ucs_double_width_bmp_ranges[] = {
-	{ 0x1100, 0x115F }, /* HANGUL CHOSEONG KIYEOK - HANGUL CHOSEONG FILLER */
-	{ 0x231A, 0x231B }, /* WATCH - HOURGLASS */
-	{ 0x2329, 0x232A }, /* LEFT-POINTING ANGLE BRACKET - RIGHT-POINTING ANGLE BRACKET */
-	{ 0x23E9, 0x23EC }, /* BLACK RIGHT-POINTING DOUBLE TRIANGLE - BLACK DOWN-POINTING DOUBLE TRIANGLE */
-	{ 0x23F0, 0x23F0 }, /* ALARM CLOCK */
-	{ 0x23F3, 0x23F3 }, /* HOURGLASS WITH FLOWING SAND */
-	{ 0x25FD, 0x25FE }, /* WHITE MEDIUM SMALL SQUARE - BLACK MEDIUM SMALL SQUARE */
-	{ 0x2614, 0x2615 }, /* UMBRELLA WITH RAIN DROPS - HOT BEVERAGE */
-	{ 0x2630, 0x2637 }, /* TRIGRAM FOR HEAVEN - TRIGRAM FOR EARTH */
-	{ 0x2648, 0x2653 }, /* ARIES - PISCES */
-	{ 0x267F, 0x267F }, /* WHEELCHAIR SYMBOL */
-	{ 0x268A, 0x268F }, /* MONOGRAM FOR YANG - DIGRAM FOR GREATER YIN */
-	{ 0x2693, 0x2693 }, /* ANCHOR */
-	{ 0x26A1, 0x26A1 }, /* HIGH VOLTAGE SIGN */
-	{ 0x26AA, 0x26AB }, /* MEDIUM WHITE CIRCLE - MEDIUM BLACK CIRCLE */
-	{ 0x26BD, 0x26BE }, /* SOCCER BALL - BASEBALL */
-	{ 0x26C4, 0x26C5 }, /* SNOWMAN WITHOUT SNOW - SUN BEHIND CLOUD */
-	{ 0x26CE, 0x26CE }, /* OPHIUCHUS */
-	{ 0x26D4, 0x26D4 }, /* NO ENTRY */
-	{ 0x26EA, 0x26EA }, /* CHURCH */
-	{ 0x26F2, 0x26F3 }, /* FOUNTAIN - FLAG IN HOLE */
-	{ 0x26F5, 0x26F5 }, /* SAILBOAT */
-	{ 0x26FA, 0x26FA }, /* TENT */
-	{ 0x26FD, 0x26FD }, /* FUEL PUMP */
-	{ 0x2705, 0x2705 }, /* WHITE HEAVY CHECK MARK */
-	{ 0x270A, 0x270B }, /* RAISED FIST - RAISED HAND */
-	{ 0x2728, 0x2728 }, /* SPARKLES */
-	{ 0x274C, 0x274C }, /* CROSS MARK */
-	{ 0x274E, 0x274E }, /* NEGATIVE SQUARED CROSS MARK */
-	{ 0x2753, 0x2755 }, /* BLACK QUESTION MARK ORNAMENT - WHITE EXCLAMATION MARK ORNAMENT */
-	{ 0x2757, 0x2757 }, /* HEAVY EXCLAMATION MARK SYMBOL */
-	{ 0x2795, 0x2797 }, /* HEAVY PLUS SIGN - HEAVY DIVISION SIGN */
-	{ 0x27B0, 0x27B0 }, /* CURLY LOOP */
-	{ 0x27BF, 0x27BF }, /* DOUBLE CURLY LOOP */
-	{ 0x2B1B, 0x2B1C }, /* BLACK LARGE SQUARE - WHITE LARGE SQUARE */
-	{ 0x2B50, 0x2B50 }, /* WHITE MEDIUM STAR */
-	{ 0x2B55, 0x2B55 }, /* HEAVY LARGE CIRCLE */
-	{ 0x2E80, 0x2E99 }, /* CJK RADICAL REPEAT - CJK RADICAL RAP */
-	{ 0x2E9B, 0x2EF3 }, /* CJK RADICAL CHOKE - CJK RADICAL C-SIMPLIFIED TURTLE */
-	{ 0x2F00, 0x2FD5 }, /* KANGXI RADICAL ONE - KANGXI RADICAL FLUTE */
-	{ 0x2FF0, 0x3029 }, /* IDEOGRAPHIC DESCRIPTION CHARACTER LEFT TO RIGHT - HANGZHOU NUMERAL NINE */
-	{ 0x3030, 0x303E }, /* WAVY DASH - IDEOGRAPHIC VARIATION INDICATOR */
-	{ 0x3041, 0x3096 }, /* HIRAGANA LETTER SMALL A - HIRAGANA LETTER SMALL KE */
-	{ 0x309B, 0x30FF }, /* KATAKANA-HIRAGANA VOICED SOUND MARK - KATAKANA DIGRAPH KOTO */
-	{ 0x3105, 0x312F }, /* BOPOMOFO LETTER B - BOPOMOFO LETTER NN */
-	{ 0x3131, 0x318E }, /* HANGUL LETTER KIYEOK - HANGUL LETTER ARAEAE */
-	{ 0x3190, 0x31E5 }, /* IDEOGRAPHIC ANNOTATION LINKING MARK - CJK STROKE SZP */
-	{ 0x31EF, 0x321E }, /* IDEOGRAPHIC DESCRIPTION CHARACTER SUBTRACTION - PARENTHESIZED KOREAN CHARACTER O HU */
-	{ 0x3220, 0x3247 }, /* PARENTHESIZED IDEOGRAPH ONE - CIRCLED IDEOGRAPH KOTO */
-	{ 0x3250, 0xA48C }, /* PARTNERSHIP SIGN - YI SYLLABLE YYR */
-	{ 0xA490, 0xA4C6 }, /* YI RADICAL QOT - YI RADICAL KE */
-	{ 0xA960, 0xA97C }, /* HANGUL CHOSEONG TIKEUT-MIEUM - HANGUL CHOSEONG SSANGYEORINHIEUH */
-	{ 0xAC00, 0xD7A3 }, /* HANGUL SYLLABLE GA - HANGUL SYLLABLE HIH */
-	{ 0xF900, 0xFAFF }, /* U+F900 - U+FAFF */
-	{ 0xFE10, 0xFE19 }, /* PRESENTATION FORM FOR VERTICAL COMMA - PRESENTATION FORM FOR VERTICAL HORIZONTAL ELLIPSIS */
-	{ 0xFE30, 0xFE52 }, /* PRESENTATION FORM FOR VERTICAL TWO DOT LEADER - SMALL FULL STOP */
-	{ 0xFE54, 0xFE66 }, /* SMALL SEMICOLON - SMALL EQUALS SIGN */
-	{ 0xFE68, 0xFE6B }, /* SMALL REVERSE SOLIDUS - SMALL COMMERCIAL AT */
-	{ 0xFF01, 0xFF60 }, /* FULLWIDTH EXCLAMATION MARK - FULLWIDTH RIGHT WHITE PARENTHESIS */
-	{ 0xFFE0, 0xFFE6 }, /* FULLWIDTH CENT SIGN - FULLWIDTH WON SIGN */
+/* Combined zero- and double-width ranges
+ * (BMP - Basic Multilingual Plane, U+0000 to U+FFFF). */
+static const struct ucs_width16 ucs_bmp_ranges[] = {
+	{ BMP_0WIDTH(0x00AD, 0x00AD) }, /* SOFT HYPHEN */
+	{ BMP_0WIDTH(0x0300, 0x036F) }, /* COMBINING GRAVE ACCENT - COMBINING LATIN SMALL LETTER X */
+	{ BMP_0WIDTH(0x0483, 0x0489) }, /* COMBINING CYRILLIC TITLO - COMBINING CYRILLIC MILLIONS SIGN */
+	{ BMP_0WIDTH(0x0591, 0x05BD) }, /* HEBREW ACCENT ETNAHTA - HEBREW POINT METEG */
+	{ BMP_0WIDTH(0x05BF, 0x05BF) }, /* HEBREW POINT RAFE */
+	{ BMP_0WIDTH(0x05C1, 0x05C2) }, /* HEBREW POINT SHIN DOT - HEBREW POINT SIN DOT */
+	{ BMP_0WIDTH(0x05C4, 0x05C5) }, /* HEBREW MARK UPPER DOT - HEBREW MARK LOWER DOT */
+	{ BMP_0WIDTH(0x05C7, 0x05C7) }, /* HEBREW POINT QAMATS QATAN */
+	{ BMP_0WIDTH(0x0600, 0x0605) }, /* ARABIC NUMBER SIGN - ARABIC NUMBER MARK ABOVE */
+	{ BMP_0WIDTH(0x0610, 0x061A) }, /* ARABIC SIGN SALLALLAHOU ALAYHE WASSALLAM - ARABIC SMALL KASRA */
+	{ BMP_0WIDTH(0x061C, 0x061C) }, /* ARABIC LETTER MARK */
+	{ BMP_0WIDTH(0x064B, 0x065F) }, /* ARABIC FATHATAN - ARABIC WAVY HAMZA BELOW */
+	{ BMP_0WIDTH(0x0670, 0x0670) }, /* ARABIC LETTER SUPERSCRIPT ALEF */
+	{ BMP_0WIDTH(0x06D6, 0x06DD) }, /* ARABIC SMALL HIGH LIGATURE SAD WITH LAM WITH ALEF MAKSURA - ARABIC END OF AYAH */
+	{ BMP_0WIDTH(0x06DF, 0x06E4) }, /* ARABIC SMALL HIGH ROUNDED ZERO - ARABIC SMALL HIGH MADDA */
+	{ BMP_0WIDTH(0x06E7, 0x06E8) }, /* ARABIC SMALL HIGH YEH - ARABIC SMALL HIGH NOON */
+	{ BMP_0WIDTH(0x06EA, 0x06ED) }, /* ARABIC EMPTY CENTRE LOW STOP - ARABIC SMALL LOW MEEM */
+	{ BMP_0WIDTH(0x070F, 0x070F) }, /* SYRIAC ABBREVIATION MARK */
+	{ BMP_0WIDTH(0x0711, 0x0711) }, /* SYRIAC LETTER SUPERSCRIPT ALAPH */
+	{ BMP_0WIDTH(0x0730, 0x074A) }, /* SYRIAC PTHAHA ABOVE - SYRIAC BARREKH */
+	{ BMP_0WIDTH(0x07A6, 0x07B0) }, /* THAANA ABAFILI - THAANA SUKUN */
+	{ BMP_0WIDTH(0x07EB, 0x07F3) }, /* NKO COMBINING SHORT HIGH TONE - NKO COMBINING DOUBLE DOT ABOVE */
+	{ BMP_0WIDTH(0x07FD, 0x07FD) }, /* NKO DANTAYALAN */
+	{ BMP_0WIDTH(0x0816, 0x0819) }, /* SAMARITAN MARK IN - SAMARITAN MARK DAGESH */
+	{ BMP_0WIDTH(0x081B, 0x0823) }, /* SAMARITAN MARK EPENTHETIC YUT - SAMARITAN VOWEL SIGN A */
+	{ BMP_0WIDTH(0x0825, 0x0827) }, /* SAMARITAN VOWEL SIGN SHORT A - SAMARITAN VOWEL SIGN U */
+	{ BMP_0WIDTH(0x0829, 0x082D) }, /* SAMARITAN VOWEL SIGN LONG I - SAMARITAN MARK NEQUDAA */
+	{ BMP_0WIDTH(0x0859, 0x085B) }, /* MANDAIC AFFRICATION MARK - MANDAIC GEMINATION MARK */
+	{ BMP_0WIDTH(0x0890, 0x0891) }, /* ARABIC POUND MARK ABOVE - ARABIC PIASTRE MARK ABOVE */
+	{ BMP_0WIDTH(0x0897, 0x089F) }, /* ARABIC PEPET - ARABIC HALF MADDA OVER MADDA */
+	{ BMP_0WIDTH(0x08CA, 0x0903) }, /* ARABIC SMALL HIGH FARSI YEH - DEVANAGARI SIGN VISARGA */
+	{ BMP_0WIDTH(0x093A, 0x093C) }, /* DEVANAGARI VOWEL SIGN OE - DEVANAGARI SIGN NUKTA */
+	{ BMP_0WIDTH(0x093E, 0x094F) }, /* DEVANAGARI VOWEL SIGN AA - DEVANAGARI VOWEL SIGN AW */
+	{ BMP_0WIDTH(0x0951, 0x0957) }, /* DEVANAGARI STRESS SIGN UDATTA - DEVANAGARI VOWEL SIGN UUE */
+	{ BMP_0WIDTH(0x0962, 0x0963) }, /* DEVANAGARI VOWEL SIGN VOCALIC L - DEVANAGARI VOWEL SIGN VOCALIC LL */
+	{ BMP_0WIDTH(0x0981, 0x0983) }, /* BENGALI SIGN CANDRABINDU - BENGALI SIGN VISARGA */
+	{ BMP_0WIDTH(0x09BC, 0x09BC) }, /* BENGALI SIGN NUKTA */
+	{ BMP_0WIDTH(0x09BE, 0x09C4) }, /* BENGALI VOWEL SIGN AA - BENGALI VOWEL SIGN VOCALIC RR */
+	{ BMP_0WIDTH(0x09C7, 0x09C8) }, /* BENGALI VOWEL SIGN E - BENGALI VOWEL SIGN AI */
+	{ BMP_0WIDTH(0x09CB, 0x09CD) }, /* BENGALI VOWEL SIGN O - BENGALI SIGN VIRAMA */
+	{ BMP_0WIDTH(0x09D7, 0x09D7) }, /* BENGALI AU LENGTH MARK */
+	{ BMP_0WIDTH(0x09E2, 0x09E3) }, /* BENGALI VOWEL SIGN VOCALIC L - BENGALI VOWEL SIGN VOCALIC LL */
+	{ BMP_0WIDTH(0x09FE, 0x09FE) }, /* BENGALI SANDHI MARK */
+	{ BMP_0WIDTH(0x0A01, 0x0A03) }, /* GURMUKHI SIGN ADAK BINDI - GURMUKHI SIGN VISARGA */
+	{ BMP_0WIDTH(0x0A3C, 0x0A3C) }, /* GURMUKHI SIGN NUKTA */
+	{ BMP_0WIDTH(0x0A3E, 0x0A42) }, /* GURMUKHI VOWEL SIGN AA - GURMUKHI VOWEL SIGN UU */
+	{ BMP_0WIDTH(0x0A47, 0x0A48) }, /* GURMUKHI VOWEL SIGN EE - GURMUKHI VOWEL SIGN AI */
+	{ BMP_0WIDTH(0x0A4B, 0x0A4D) }, /* GURMUKHI VOWEL SIGN OO - GURMUKHI SIGN VIRAMA */
+	{ BMP_0WIDTH(0x0A51, 0x0A51) }, /* GURMUKHI SIGN UDAAT */
+	{ BMP_0WIDTH(0x0A70, 0x0A71) }, /* GURMUKHI TIPPI - GURMUKHI ADDAK */
+	{ BMP_0WIDTH(0x0A75, 0x0A75) }, /* GURMUKHI SIGN YAKASH */
+	{ BMP_0WIDTH(0x0A81, 0x0A83) }, /* GUJARATI SIGN CANDRABINDU - GUJARATI SIGN VISARGA */
+	{ BMP_0WIDTH(0x0ABC, 0x0ABC) }, /* GUJARATI SIGN NUKTA */
+	{ BMP_0WIDTH(0x0ABE, 0x0AC5) }, /* GUJARATI VOWEL SIGN AA - GUJARATI VOWEL SIGN CANDRA E */
+	{ BMP_0WIDTH(0x0AC7, 0x0AC9) }, /* GUJARATI VOWEL SIGN E - GUJARATI VOWEL SIGN CANDRA O */
+	{ BMP_0WIDTH(0x0ACB, 0x0ACD) }, /* GUJARATI VOWEL SIGN O - GUJARATI SIGN VIRAMA */
+	{ BMP_0WIDTH(0x0AE2, 0x0AE3) }, /* GUJARATI VOWEL SIGN VOCALIC L - GUJARATI VOWEL SIGN VOCALIC LL */
+	{ BMP_0WIDTH(0x0AFA, 0x0AFF) }, /* GUJARATI SIGN SUKUN - GUJARATI SIGN TWO-CIRCLE NUKTA ABOVE */
+	{ BMP_0WIDTH(0x0B01, 0x0B03) }, /* ORIYA SIGN CANDRABINDU - ORIYA SIGN VISARGA */
+	{ BMP_0WIDTH(0x0B3C, 0x0B3C) }, /* ORIYA SIGN NUKTA */
+	{ BMP_0WIDTH(0x0B3E, 0x0B44) }, /* ORIYA VOWEL SIGN AA - ORIYA VOWEL SIGN VOCALIC RR */
+	{ BMP_0WIDTH(0x0B47, 0x0B48) }, /* ORIYA VOWEL SIGN E - ORIYA VOWEL SIGN AI */
+	{ BMP_0WIDTH(0x0B4B, 0x0B4D) }, /* ORIYA VOWEL SIGN O - ORIYA SIGN VIRAMA */
+	{ BMP_0WIDTH(0x0B55, 0x0B57) }, /* ORIYA SIGN OVERLINE - ORIYA AU LENGTH MARK */
+	{ BMP_0WIDTH(0x0B62, 0x0B63) }, /* ORIYA VOWEL SIGN VOCALIC L - ORIYA VOWEL SIGN VOCALIC LL */
+	{ BMP_0WIDTH(0x0B82, 0x0B82) }, /* TAMIL SIGN ANUSVARA */
+	{ BMP_0WIDTH(0x0BBE, 0x0BC2) }, /* TAMIL VOWEL SIGN AA - TAMIL VOWEL SIGN UU */
+	{ BMP_0WIDTH(0x0BC6, 0x0BC8) }, /* TAMIL VOWEL SIGN E - TAMIL VOWEL SIGN AI */
+	{ BMP_0WIDTH(0x0BCA, 0x0BCD) }, /* TAMIL VOWEL SIGN O - TAMIL SIGN VIRAMA */
+	{ BMP_0WIDTH(0x0BD7, 0x0BD7) }, /* TAMIL AU LENGTH MARK */
+	{ BMP_0WIDTH(0x0C00, 0x0C04) }, /* TELUGU SIGN COMBINING CANDRABINDU ABOVE - TELUGU SIGN COMBINING ANUSVARA ABOVE */
+	{ BMP_0WIDTH(0x0C3C, 0x0C3C) }, /* TELUGU SIGN NUKTA */
+	{ BMP_0WIDTH(0x0C3E, 0x0C44) }, /* TELUGU VOWEL SIGN AA - TELUGU VOWEL SIGN VOCALIC RR */
+	{ BMP_0WIDTH(0x0C46, 0x0C48) }, /* TELUGU VOWEL SIGN E - TELUGU VOWEL SIGN AI */
+	{ BMP_0WIDTH(0x0C4A, 0x0C4D) }, /* TELUGU VOWEL SIGN O - TELUGU SIGN VIRAMA */
+	{ BMP_0WIDTH(0x0C55, 0x0C56) }, /* TELUGU LENGTH MARK - TELUGU AI LENGTH MARK */
+	{ BMP_0WIDTH(0x0C62, 0x0C63) }, /* TELUGU VOWEL SIGN VOCALIC L - TELUGU VOWEL SIGN VOCALIC LL */
+	{ BMP_0WIDTH(0x0C81, 0x0C83) }, /* KANNADA SIGN CANDRABINDU - KANNADA SIGN VISARGA */
+	{ BMP_0WIDTH(0x0CBC, 0x0CBC) }, /* KANNADA SIGN NUKTA */
+	{ BMP_0WIDTH(0x0CBE, 0x0CC4) }, /* KANNADA VOWEL SIGN AA - KANNADA VOWEL SIGN VOCALIC RR */
+	{ BMP_0WIDTH(0x0CC6, 0x0CC8) }, /* KANNADA VOWEL SIGN E - KANNADA VOWEL SIGN AI */
+	{ BMP_0WIDTH(0x0CCA, 0x0CCD) }, /* KANNADA VOWEL SIGN O - KANNADA SIGN VIRAMA */
+	{ BMP_0WIDTH(0x0CD5, 0x0CD6) }, /* KANNADA LENGTH MARK - KANNADA AI LENGTH MARK */
+	{ BMP_0WIDTH(0x0CE2, 0x0CE3) }, /* KANNADA VOWEL SIGN VOCALIC L - KANNADA VOWEL SIGN VOCALIC LL */
+	{ BMP_0WIDTH(0x0CF3, 0x0CF3) }, /* KANNADA SIGN COMBINING ANUSVARA ABOVE RIGHT */
+	{ BMP_0WIDTH(0x0D00, 0x0D03) }, /* MALAYALAM SIGN COMBINING ANUSVARA ABOVE - MALAYALAM SIGN VISARGA */
+	{ BMP_0WIDTH(0x0D3B, 0x0D3C) }, /* MALAYALAM SIGN VERTICAL BAR VIRAMA - MALAYALAM SIGN CIRCULAR VIRAMA */
+	{ BMP_0WIDTH(0x0D3E, 0x0D44) }, /* MALAYALAM VOWEL SIGN AA - MALAYALAM VOWEL SIGN VOCALIC RR */
+	{ BMP_0WIDTH(0x0D46, 0x0D48) }, /* MALAYALAM VOWEL SIGN E - MALAYALAM VOWEL SIGN AI */
+	{ BMP_0WIDTH(0x0D4A, 0x0D4D) }, /* MALAYALAM VOWEL SIGN O - MALAYALAM SIGN VIRAMA */
+	{ BMP_0WIDTH(0x0D57, 0x0D57) }, /* MALAYALAM AU LENGTH MARK */
+	{ BMP_0WIDTH(0x0D62, 0x0D63) }, /* MALAYALAM VOWEL SIGN VOCALIC L - MALAYALAM VOWEL SIGN VOCALIC LL */
+	{ BMP_0WIDTH(0x0D81, 0x0D83) }, /* SINHALA SIGN CANDRABINDU - SINHALA SIGN VISARGAYA */
+	{ BMP_0WIDTH(0x0DCA, 0x0DCA) }, /* SINHALA SIGN AL-LAKUNA */
+	{ BMP_0WIDTH(0x0DCF, 0x0DD4) }, /* SINHALA VOWEL SIGN AELA-PILLA - SINHALA VOWEL SIGN KETTI PAA-PILLA */
+	{ BMP_0WIDTH(0x0DD6, 0x0DD6) }, /* SINHALA VOWEL SIGN DIGA PAA-PILLA */
+	{ BMP_0WIDTH(0x0DD8, 0x0DDF) }, /* SINHALA VOWEL SIGN GAETTA-PILLA - SINHALA VOWEL SIGN GAYANUKITTA */
+	{ BMP_0WIDTH(0x0DF2, 0x0DF3) }, /* SINHALA VOWEL SIGN DIGA GAETTA-PILLA - SINHALA VOWEL SIGN DIGA GAYANUKITTA */
+	{ BMP_0WIDTH(0x0E31, 0x0E31) }, /* THAI CHARACTER MAI HAN-AKAT */
+	{ BMP_0WIDTH(0x0E34, 0x0E3A) }, /* THAI CHARACTER SARA I - THAI CHARACTER PHINTHU */
+	{ BMP_0WIDTH(0x0E47, 0x0E4E) }, /* THAI CHARACTER MAITAIKHU - THAI CHARACTER YAMAKKAN */
+	{ BMP_0WIDTH(0x0EB1, 0x0EB1) }, /* LAO VOWEL SIGN MAI KAN */
+	{ BMP_0WIDTH(0x0EB4, 0x0EBC) }, /* LAO VOWEL SIGN I - LAO SEMIVOWEL SIGN LO */
+	{ BMP_0WIDTH(0x0EC8, 0x0ECE) }, /* LAO TONE MAI EK - LAO YAMAKKAN */
+	{ BMP_0WIDTH(0x0F18, 0x0F19) }, /* TIBETAN ASTROLOGICAL SIGN -KHYUD PA - TIBETAN ASTROLOGICAL SIGN SDONG TSHUGS */
+	{ BMP_0WIDTH(0x0F35, 0x0F35) }, /* TIBETAN MARK NGAS BZUNG NYI ZLA */
+	{ BMP_0WIDTH(0x0F37, 0x0F37) }, /* TIBETAN MARK NGAS BZUNG SGOR RTAGS */
+	{ BMP_0WIDTH(0x0F39, 0x0F39) }, /* TIBETAN MARK TSA -PHRU */
+	{ BMP_0WIDTH(0x0F3E, 0x0F3F) }, /* TIBETAN SIGN YAR TSHES - TIBETAN SIGN MAR TSHES */
+	{ BMP_0WIDTH(0x0F71, 0x0F84) }, /* TIBETAN VOWEL SIGN AA - TIBETAN MARK HALANTA */
+	{ BMP_0WIDTH(0x0F86, 0x0F87) }, /* TIBETAN SIGN LCI RTAGS - TIBETAN SIGN YANG RTAGS */
+	{ BMP_0WIDTH(0x0F8D, 0x0F97) }, /* TIBETAN SUBJOINED SIGN LCE TSA CAN - TIBETAN SUBJOINED LETTER JA */
+	{ BMP_0WIDTH(0x0F99, 0x0FBC) }, /* TIBETAN SUBJOINED LETTER NYA - TIBETAN SUBJOINED LETTER FIXED-FORM RA */
+	{ BMP_0WIDTH(0x0FC6, 0x0FC6) }, /* TIBETAN SYMBOL PADMA GDAN */
+	{ BMP_0WIDTH(0x102B, 0x103E) }, /* MYANMAR VOWEL SIGN TALL AA - MYANMAR CONSONANT SIGN MEDIAL HA */
+	{ BMP_0WIDTH(0x1056, 0x1059) }, /* MYANMAR VOWEL SIGN VOCALIC R - MYANMAR VOWEL SIGN VOCALIC LL */
+	{ BMP_0WIDTH(0x105E, 0x1060) }, /* MYANMAR CONSONANT SIGN MON MEDIAL NA - MYANMAR CONSONANT SIGN MON MEDIAL LA */
+	{ BMP_0WIDTH(0x1062, 0x1064) }, /* MYANMAR VOWEL SIGN SGAW KAREN EU - MYANMAR TONE MARK SGAW KAREN KE PHO */
+	{ BMP_0WIDTH(0x1067, 0x106D) }, /* MYANMAR VOWEL SIGN WESTERN PWO KAREN EU - MYANMAR SIGN WESTERN PWO KAREN TONE-5 */
+	{ BMP_0WIDTH(0x1071, 0x1074) }, /* MYANMAR VOWEL SIGN GEBA KAREN I - MYANMAR VOWEL SIGN KAYAH EE */
+	{ BMP_0WIDTH(0x1082, 0x108D) }, /* MYANMAR CONSONANT SIGN SHAN MEDIAL WA - MYANMAR SIGN SHAN COUNCIL EMPHATIC TONE */
+	{ BMP_0WIDTH(0x108F, 0x108F) }, /* MYANMAR SIGN RUMAI PALAUNG TONE-5 */
+	{ BMP_0WIDTH(0x109A, 0x109D) }, /* MYANMAR SIGN KHAMTI TONE-1 - MYANMAR VOWEL SIGN AITON AI */
+	{ BMP_2WIDTH(0x1100, 0x115F) }, /* HANGUL CHOSEONG KIYEOK - HANGUL CHOSEONG FILLER */
+	{ BMP_0WIDTH(0x135D, 0x135F) }, /* ETHIOPIC COMBINING GEMINATION AND VOWEL LENGTH MARK - ETHIOPIC COMBINING GEMINATION MARK */
+	{ BMP_0WIDTH(0x1712, 0x1715) }, /* TAGALOG VOWEL SIGN I - TAGALOG SIGN PAMUDPOD */
+	{ BMP_0WIDTH(0x1732, 0x1734) }, /* HANUNOO VOWEL SIGN I - HANUNOO SIGN PAMUDPOD */
+	{ BMP_0WIDTH(0x1752, 0x1753) }, /* BUHID VOWEL SIGN I - BUHID VOWEL SIGN U */
+	{ BMP_0WIDTH(0x1772, 0x1773) }, /* TAGBANWA VOWEL SIGN I - TAGBANWA VOWEL SIGN U */
+	{ BMP_0WIDTH(0x17B4, 0x17D3) }, /* KHMER VOWEL INHERENT AQ - KHMER SIGN BATHAMASAT */
+	{ BMP_0WIDTH(0x17DD, 0x17DD) }, /* KHMER SIGN ATTHACAN */
+	{ BMP_0WIDTH(0x180B, 0x180F) }, /* MONGOLIAN FREE VARIATION SELECTOR ONE - MONGOLIAN FREE VARIATION SELECTOR FOUR */
+	{ BMP_0WIDTH(0x1885, 0x1886) }, /* MONGOLIAN LETTER ALI GALI BALUDA - MONGOLIAN LETTER ALI GALI THREE BALUDA */
+	{ BMP_0WIDTH(0x18A9, 0x18A9) }, /* MONGOLIAN LETTER ALI GALI DAGALGA */
+	{ BMP_0WIDTH(0x1920, 0x192B) }, /* LIMBU VOWEL SIGN A - LIMBU SUBJOINED LETTER WA */
+	{ BMP_0WIDTH(0x1930, 0x193B) }, /* LIMBU SMALL LETTER KA - LIMBU SIGN SA-I */
+	{ BMP_0WIDTH(0x1A17, 0x1A1B) }, /* BUGINESE VOWEL SIGN I - BUGINESE VOWEL SIGN AE */
+	{ BMP_0WIDTH(0x1A55, 0x1A5E) }, /* TAI THAM CONSONANT SIGN MEDIAL RA - TAI THAM CONSONANT SIGN SA */
+	{ BMP_0WIDTH(0x1A60, 0x1A7C) }, /* TAI THAM SIGN SAKOT - TAI THAM SIGN KHUEN-LUE KARAN */
+	{ BMP_0WIDTH(0x1A7F, 0x1A7F) }, /* TAI THAM COMBINING CRYPTOGRAMMIC DOT */
+	{ BMP_0WIDTH(0x1AB0, 0x1ACE) }, /* COMBINING DOUBLED CIRCUMFLEX ACCENT - COMBINING LATIN SMALL LETTER INSULAR T */
+	{ BMP_0WIDTH(0x1B00, 0x1B04) }, /* BALINESE SIGN ULU RICEM - BALINESE SIGN BISAH */
+	{ BMP_0WIDTH(0x1B34, 0x1B44) }, /* BALINESE SIGN REREKAN - BALINESE ADEG ADEG */
+	{ BMP_0WIDTH(0x1B6B, 0x1B73) }, /* BALINESE MUSICAL SYMBOL COMBINING TEGEH - BALINESE MUSICAL SYMBOL COMBINING GONG */
+	{ BMP_0WIDTH(0x1B80, 0x1B82) }, /* SUNDANESE SIGN PANYECEK - SUNDANESE SIGN PANGWISAD */
+	{ BMP_0WIDTH(0x1BA1, 0x1BAD) }, /* SUNDANESE CONSONANT SIGN PAMINGKAL - SUNDANESE CONSONANT SIGN PASANGAN WA */
+	{ BMP_0WIDTH(0x1BE6, 0x1BF3) }, /* BATAK SIGN TOMPI - BATAK PANONGONAN */
+	{ BMP_0WIDTH(0x1C24, 0x1C37) }, /* LEPCHA SUBJOINED LETTER YA - LEPCHA SIGN NUKTA */
+	{ BMP_0WIDTH(0x1CD0, 0x1CD2) }, /* VEDIC TONE KARSHANA - VEDIC TONE PRENKHA */
+	{ BMP_0WIDTH(0x1CD4, 0x1CE8) }, /* VEDIC SIGN YAJURVEDIC MIDLINE SVARITA - VEDIC SIGN VISARGA ANUDATTA WITH TAIL */
+	{ BMP_0WIDTH(0x1CED, 0x1CED) }, /* VEDIC SIGN TIRYAK */
+	{ BMP_0WIDTH(0x1CF4, 0x1CF4) }, /* VEDIC TONE CANDRA ABOVE */
+	{ BMP_0WIDTH(0x1CF7, 0x1CF9) }, /* VEDIC SIGN ATIKRAMA - VEDIC TONE DOUBLE RING ABOVE */
+	{ BMP_0WIDTH(0x1DC0, 0x1DFF) }, /* COMBINING DOTTED GRAVE ACCENT - COMBINING RIGHT ARROWHEAD AND DOWN ARROWHEAD BELOW */
+	{ BMP_0WIDTH(0x200B, 0x200F) }, /* ZERO WIDTH SPACE - RIGHT-TO-LEFT MARK */
+	{ BMP_0WIDTH(0x202A, 0x202E) }, /* LEFT-TO-RIGHT EMBEDDING - RIGHT-TO-LEFT OVERRIDE */
+	{ BMP_0WIDTH(0x2060, 0x2064) }, /* WORD JOINER - INVISIBLE PLUS */
+	{ BMP_0WIDTH(0x2066, 0x206F) }, /* LEFT-TO-RIGHT ISOLATE - NOMINAL DIGIT SHAPES */
+	{ BMP_0WIDTH(0x20D0, 0x20F0) }, /* COMBINING LEFT HARPOON ABOVE - COMBINING ASTERISK ABOVE */
+	{ BMP_2WIDTH(0x231A, 0x231B) }, /* WATCH - HOURGLASS */
+	{ BMP_2WIDTH(0x2329, 0x232A) }, /* LEFT-POINTING ANGLE BRACKET - RIGHT-POINTING ANGLE BRACKET */
+	{ BMP_2WIDTH(0x23E9, 0x23EC) }, /* BLACK RIGHT-POINTING DOUBLE TRIANGLE - BLACK DOWN-POINTING DOUBLE TRIANGLE */
+	{ BMP_2WIDTH(0x23F0, 0x23F0) }, /* ALARM CLOCK */
+	{ BMP_2WIDTH(0x23F3, 0x23F3) }, /* HOURGLASS WITH FLOWING SAND */
+	{ BMP_2WIDTH(0x25FD, 0x25FE) }, /* WHITE MEDIUM SMALL SQUARE - BLACK MEDIUM SMALL SQUARE */
+	{ BMP_2WIDTH(0x2614, 0x2615) }, /* UMBRELLA WITH RAIN DROPS - HOT BEVERAGE */
+	{ BMP_2WIDTH(0x2630, 0x2637) }, /* TRIGRAM FOR HEAVEN - TRIGRAM FOR EARTH */
+	{ BMP_0WIDTH(0x2640, 0x2640) }, /* FEMALE SIGN */
+	{ BMP_0WIDTH(0x2642, 0x2642) }, /* MALE SIGN */
+	{ BMP_2WIDTH(0x2648, 0x2653) }, /* ARIES - PISCES */
+	{ BMP_2WIDTH(0x267F, 0x267F) }, /* WHEELCHAIR SYMBOL */
+	{ BMP_2WIDTH(0x268A, 0x268F) }, /* MONOGRAM FOR YANG - DIGRAM FOR GREATER YIN */
+	{ BMP_2WIDTH(0x2693, 0x2693) }, /* ANCHOR */
+	{ BMP_2WIDTH(0x26A1, 0x26A1) }, /* HIGH VOLTAGE SIGN */
+	{ BMP_0WIDTH(0x26A7, 0x26A7) }, /* MALE WITH STROKE AND MALE AND FEMALE SIGN */
+	{ BMP_2WIDTH(0x26AA, 0x26AB) }, /* MEDIUM WHITE CIRCLE - MEDIUM BLACK CIRCLE */
+	{ BMP_2WIDTH(0x26BD, 0x26BE) }, /* SOCCER BALL - BASEBALL */
+	{ BMP_2WIDTH(0x26C4, 0x26C5) }, /* SNOWMAN WITHOUT SNOW - SUN BEHIND CLOUD */
+	{ BMP_2WIDTH(0x26CE, 0x26CE) }, /* OPHIUCHUS */
+	{ BMP_2WIDTH(0x26D4, 0x26D4) }, /* NO ENTRY */
+	{ BMP_2WIDTH(0x26EA, 0x26EA) }, /* CHURCH */
+	{ BMP_2WIDTH(0x26F2, 0x26F3) }, /* FOUNTAIN - FLAG IN HOLE */
+	{ BMP_2WIDTH(0x26F5, 0x26F5) }, /* SAILBOAT */
+	{ BMP_2WIDTH(0x26FA, 0x26FA) }, /* TENT */
+	{ BMP_2WIDTH(0x26FD, 0x26FD) }, /* FUEL PUMP */
+	{ BMP_2WIDTH(0x2705, 0x2705) }, /* WHITE HEAVY CHECK MARK */
+	{ BMP_2WIDTH(0x270A, 0x270B) }, /* RAISED FIST - RAISED HAND */
+	{ BMP_2WIDTH(0x2728, 0x2728) }, /* SPARKLES */
+	{ BMP_2WIDTH(0x274C, 0x274C) }, /* CROSS MARK */
+	{ BMP_2WIDTH(0x274E, 0x274E) }, /* NEGATIVE SQUARED CROSS MARK */
+	{ BMP_2WIDTH(0x2753, 0x2755) }, /* BLACK QUESTION MARK ORNAMENT - WHITE EXCLAMATION MARK ORNAMENT */
+	{ BMP_2WIDTH(0x2757, 0x2757) }, /* HEAVY EXCLAMATION MARK SYMBOL */
+	{ BMP_2WIDTH(0x2795, 0x2797) }, /* HEAVY PLUS SIGN - HEAVY DIVISION SIGN */
+	{ BMP_2WIDTH(0x27B0, 0x27B0) }, /* CURLY LOOP */
+	{ BMP_2WIDTH(0x27BF, 0x27BF) }, /* DOUBLE CURLY LOOP */
+	{ BMP_2WIDTH(0x2B1B, 0x2B1C) }, /* BLACK LARGE SQUARE - WHITE LARGE SQUARE */
+	{ BMP_2WIDTH(0x2B50, 0x2B50) }, /* WHITE MEDIUM STAR */
+	{ BMP_2WIDTH(0x2B55, 0x2B55) }, /* HEAVY LARGE CIRCLE */
+	{ BMP_0WIDTH(0x2CEF, 0x2CF1) }, /* COPTIC COMBINING NI ABOVE - COPTIC COMBINING SPIRITUS LENIS */
+	{ BMP_0WIDTH(0x2D7F, 0x2D7F) }, /* TIFINAGH CONSONANT JOINER */
+	{ BMP_0WIDTH(0x2DE0, 0x2DFF) }, /* COMBINING CYRILLIC LETTER BE - COMBINING CYRILLIC LETTER IOTIFIED BIG YUS */
+	{ BMP_2WIDTH(0x2E80, 0x2E99) }, /* CJK RADICAL REPEAT - CJK RADICAL RAP */
+	{ BMP_2WIDTH(0x2E9B, 0x2EF3) }, /* CJK RADICAL CHOKE - CJK RADICAL C-SIMPLIFIED TURTLE */
+	{ BMP_2WIDTH(0x2F00, 0x2FD5) }, /* KANGXI RADICAL ONE - KANGXI RADICAL FLUTE */
+	{ BMP_2WIDTH(0x2FF0, 0x3029) }, /* IDEOGRAPHIC DESCRIPTION CHARACTER LEFT TO RIGHT - HANGZHOU NUMERAL NINE */
+	{ BMP_0WIDTH(0x302A, 0x302F) }, /* IDEOGRAPHIC LEVEL TONE MARK - HANGUL DOUBLE DOT TONE MARK */
+	{ BMP_2WIDTH(0x3030, 0x303E) }, /* WAVY DASH - IDEOGRAPHIC VARIATION INDICATOR */
+	{ BMP_2WIDTH(0x3041, 0x3096) }, /* HIRAGANA LETTER SMALL A - HIRAGANA LETTER SMALL KE */
+	{ BMP_0WIDTH(0x3099, 0x309A) }, /* COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK - COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK */
+	{ BMP_2WIDTH(0x309B, 0x30FF) }, /* KATAKANA-HIRAGANA VOICED SOUND MARK - KATAKANA DIGRAPH KOTO */
+	{ BMP_2WIDTH(0x3105, 0x312F) }, /* BOPOMOFO LETTER B - BOPOMOFO LETTER NN */
+	{ BMP_2WIDTH(0x3131, 0x318E) }, /* HANGUL LETTER KIYEOK - HANGUL LETTER ARAEAE */
+	{ BMP_2WIDTH(0x3190, 0x31E5) }, /* IDEOGRAPHIC ANNOTATION LINKING MARK - CJK STROKE SZP */
+	{ BMP_2WIDTH(0x31EF, 0x321E) }, /* IDEOGRAPHIC DESCRIPTION CHARACTER SUBTRACTION - PARENTHESIZED KOREAN CHARACTER O HU */
+	{ BMP_2WIDTH(0x3220, 0x3247) }, /* PARENTHESIZED IDEOGRAPH ONE - CIRCLED IDEOGRAPH KOTO */
+	{ BMP_2WIDTH(0x3250, 0xA48C) }, /* PARTNERSHIP SIGN - YI SYLLABLE YYR */
+	{ BMP_2WIDTH(0xA490, 0xA4C6) }, /* YI RADICAL QOT - YI RADICAL KE */
+	{ BMP_0WIDTH(0xA66F, 0xA672) }, /* COMBINING CYRILLIC VZMET - COMBINING CYRILLIC THOUSAND MILLIONS SIGN */
+	{ BMP_0WIDTH(0xA674, 0xA67D) }, /* COMBINING CYRILLIC LETTER UKRAINIAN IE - COMBINING CYRILLIC PAYEROK */
+	{ BMP_0WIDTH(0xA69E, 0xA69F) }, /* COMBINING CYRILLIC LETTER EF - COMBINING CYRILLIC LETTER IOTIFIED E */
+	{ BMP_0WIDTH(0xA6F0, 0xA6F1) }, /* BAMUM COMBINING MARK KOQNDON - BAMUM COMBINING MARK TUKWENTIS */
+	{ BMP_0WIDTH(0xA802, 0xA802) }, /* SYLOTI NAGRI SIGN DVISVARA */
+	{ BMP_0WIDTH(0xA806, 0xA806) }, /* SYLOTI NAGRI SIGN HASANTA */
+	{ BMP_0WIDTH(0xA80B, 0xA80B) }, /* SYLOTI NAGRI SIGN ANUSVARA */
+	{ BMP_0WIDTH(0xA823, 0xA827) }, /* SYLOTI NAGRI VOWEL SIGN A - SYLOTI NAGRI VOWEL SIGN OO */
+	{ BMP_0WIDTH(0xA82C, 0xA82C) }, /* SYLOTI NAGRI SIGN ALTERNATE HASANTA */
+	{ BMP_0WIDTH(0xA880, 0xA881) }, /* SAURASHTRA SIGN ANUSVARA - SAURASHTRA SIGN VISARGA */
+	{ BMP_0WIDTH(0xA8B4, 0xA8C5) }, /* SAURASHTRA CONSONANT SIGN HAARU - SAURASHTRA SIGN CANDRABINDU */
+	{ BMP_0WIDTH(0xA8E0, 0xA8F1) }, /* COMBINING DEVANAGARI DIGIT ZERO - COMBINING DEVANAGARI SIGN AVAGRAHA */
+	{ BMP_0WIDTH(0xA8FF, 0xA8FF) }, /* DEVANAGARI VOWEL SIGN AY */
+	{ BMP_0WIDTH(0xA926, 0xA92D) }, /* KAYAH LI VOWEL UE - KAYAH LI TONE CALYA PLOPHU */
+	{ BMP_0WIDTH(0xA947, 0xA953) }, /* REJANG VOWEL SIGN I - REJANG VIRAMA */
+	{ BMP_2WIDTH(0xA960, 0xA97C) }, /* HANGUL CHOSEONG TIKEUT-MIEUM - HANGUL CHOSEONG SSANGYEORINHIEUH */
+	{ BMP_0WIDTH(0xA980, 0xA983) }, /* JAVANESE SIGN PANYANGGA - JAVANESE SIGN WIGNYAN */
+	{ BMP_0WIDTH(0xA9B3, 0xA9C0) }, /* JAVANESE SIGN CECAK TELU - JAVANESE PANGKON */
+	{ BMP_0WIDTH(0xA9E5, 0xA9E5) }, /* MYANMAR SIGN SHAN SAW */
+	{ BMP_0WIDTH(0xAA29, 0xAA36) }, /* CHAM VOWEL SIGN AA - CHAM CONSONANT SIGN WA */
+	{ BMP_0WIDTH(0xAA43, 0xAA43) }, /* CHAM CONSONANT SIGN FINAL NG */
+	{ BMP_0WIDTH(0xAA4C, 0xAA4D) }, /* CHAM CONSONANT SIGN FINAL M - CHAM CONSONANT SIGN FINAL H */
+	{ BMP_0WIDTH(0xAA7B, 0xAA7D) }, /* MYANMAR SIGN PAO KAREN TONE - MYANMAR SIGN TAI LAING TONE-5 */
+	{ BMP_0WIDTH(0xAAB0, 0xAAB0) }, /* TAI VIET MAI KANG */
+	{ BMP_0WIDTH(0xAAB2, 0xAAB4) }, /* TAI VIET VOWEL I - TAI VIET VOWEL U */
+	{ BMP_0WIDTH(0xAAB7, 0xAAB8) }, /* TAI VIET MAI KHIT - TAI VIET VOWEL IA */
+	{ BMP_0WIDTH(0xAABE, 0xAABF) }, /* TAI VIET VOWEL AM - TAI VIET TONE MAI EK */
+	{ BMP_0WIDTH(0xAAC1, 0xAAC1) }, /* TAI VIET TONE MAI THO */
+	{ BMP_0WIDTH(0xAAEB, 0xAAEF) }, /* MEETEI MAYEK VOWEL SIGN II - MEETEI MAYEK VOWEL SIGN AAU */
+	{ BMP_0WIDTH(0xAAF5, 0xAAF6) }, /* MEETEI MAYEK VOWEL SIGN VISARGA - MEETEI MAYEK VIRAMA */
+	{ BMP_0WIDTH(0xABE3, 0xABEA) }, /* MEETEI MAYEK VOWEL SIGN ONAP - MEETEI MAYEK VOWEL SIGN NUNG */
+	{ BMP_0WIDTH(0xABEC, 0xABED) }, /* MEETEI MAYEK LUM IYEK - MEETEI MAYEK APUN IYEK */
+	{ BMP_2WIDTH(0xAC00, 0xD7A3) }, /* HANGUL SYLLABLE GA - HANGUL SYLLABLE HIH */
+	{ BMP_2WIDTH(0xF900, 0xFAFF) }, /* U+F900 - U+FAFF */
+	{ BMP_0WIDTH(0xFB1E, 0xFB1E) }, /* HEBREW POINT JUDEO-SPANISH VARIKA */
+	{ BMP_0WIDTH(0xFE00, 0xFE0F) }, /* VARIATION SELECTOR-1 - VARIATION SELECTOR-16 */
+	{ BMP_2WIDTH(0xFE10, 0xFE19) }, /* PRESENTATION FORM FOR VERTICAL COMMA - PRESENTATION FORM FOR VERTICAL HORIZONTAL ELLIPSIS */
+	{ BMP_0WIDTH(0xFE20, 0xFE2F) }, /* COMBINING LIGATURE LEFT HALF - COMBINING CYRILLIC TITLO RIGHT HALF */
+	{ BMP_2WIDTH(0xFE30, 0xFE52) }, /* PRESENTATION FORM FOR VERTICAL TWO DOT LEADER - SMALL FULL STOP */
+	{ BMP_2WIDTH(0xFE54, 0xFE66) }, /* SMALL SEMICOLON - SMALL EQUALS SIGN */
+	{ BMP_2WIDTH(0xFE68, 0xFE6B) }, /* SMALL REVERSE SOLIDUS - SMALL COMMERCIAL AT */
+	{ BMP_0WIDTH(0xFEFF, 0xFEFF) }, /* ZERO WIDTH NO-BREAK SPACE */
+	{ BMP_2WIDTH(0xFF01, 0xFF60) }, /* FULLWIDTH EXCLAMATION MARK - FULLWIDTH RIGHT WHITE PARENTHESIS */
+	{ BMP_2WIDTH(0xFFE0, 0xFFE6) }, /* FULLWIDTH CENT SIGN - FULLWIDTH WON SIGN */
+	{ BMP_0WIDTH(0xFFF9, 0xFFFB) }, /* INTERLINEAR ANNOTATION ANCHOR - INTERLINEAR ANNOTATION TERMINATOR */
 };
 
-/* Double-width character ranges (non-BMP, U+10000 and above) */
-static const struct ucs_interval32 ucs_double_width_non_bmp_ranges[] = {
-	{ 0x16FE0, 0x16FE3 }, /* TANGUT ITERATION MARK - OLD CHINESE ITERATION MARK */
-	{ 0x17000, 0x187F7 }, /* U+17000 - U+187F7 */
-	{ 0x18800, 0x18CD5 }, /* TANGUT COMPONENT-001 - KHITAN SMALL SCRIPT CHARACTER-18CD5 */
-	{ 0x18CFF, 0x18D08 }, /* U+18CFF - U+18D08 */
-	{ 0x1AFF0, 0x1AFF3 }, /* KATAKANA LETTER MINNAN TONE-2 - KATAKANA LETTER MINNAN TONE-5 */
-	{ 0x1AFF5, 0x1AFFB }, /* KATAKANA LETTER MINNAN TONE-7 - KATAKANA LETTER MINNAN NASALIZED TONE-5 */
-	{ 0x1AFFD, 0x1AFFE }, /* KATAKANA LETTER MINNAN NASALIZED TONE-7 - KATAKANA LETTER MINNAN NASALIZED TONE-8 */
-	{ 0x1B000, 0x1B122 }, /* KATAKANA LETTER ARCHAIC E - KATAKANA LETTER ARCHAIC WU */
-	{ 0x1B132, 0x1B132 }, /* HIRAGANA LETTER SMALL KO */
-	{ 0x1B150, 0x1B152 }, /* HIRAGANA LETTER SMALL WI - HIRAGANA LETTER SMALL WO */
-	{ 0x1B155, 0x1B155 }, /* KATAKANA LETTER SMALL KO */
-	{ 0x1B164, 0x1B167 }, /* KATAKANA LETTER SMALL WI - KATAKANA LETTER SMALL N */
-	{ 0x1B170, 0x1B2FB }, /* NUSHU CHARACTER-1B170 - NUSHU CHARACTER-1B2FB */
-	{ 0x1D300, 0x1D356 }, /* MONOGRAM FOR EARTH - TETRAGRAM FOR FOSTERING */
-	{ 0x1D360, 0x1D376 }, /* COUNTING ROD UNIT DIGIT ONE - IDEOGRAPHIC TALLY MARK FIVE */
-	{ 0x1F000, 0x1F02F }, /* U+1F000 - U+1F02F */
-	{ 0x1F0A0, 0x1F0FF }, /* U+1F0A0 - U+1F0FF */
-	{ 0x1F18E, 0x1F18E }, /* NEGATIVE SQUARED AB */
-	{ 0x1F191, 0x1F19A }, /* SQUARED CL - SQUARED VS */
-	{ 0x1F200, 0x1F202 }, /* SQUARE HIRAGANA HOKA - SQUARED KATAKANA SA */
-	{ 0x1F210, 0x1F23B }, /* SQUARED CJK UNIFIED IDEOGRAPH-624B - SQUARED CJK UNIFIED IDEOGRAPH-914D */
-	{ 0x1F240, 0x1F248 }, /* TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-672C - TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-6557 */
-	{ 0x1F250, 0x1F251 }, /* CIRCLED IDEOGRAPH ADVANTAGE - CIRCLED IDEOGRAPH ACCEPT */
-	{ 0x1F260, 0x1F265 }, /* ROUNDED SYMBOL FOR FU - ROUNDED SYMBOL FOR CAI */
-	{ 0x1F300, 0x1F3FA }, /* CYCLONE - AMPHORA */
-	{ 0x1F400, 0x1F64F }, /* RAT - PERSON WITH FOLDED HANDS */
-	{ 0x1F680, 0x1F9AF }, /* ROCKET - PROBING CANE */
-	{ 0x1F9B4, 0x1FAFF }, /* U+1F9B4 - U+1FAFF */
-	{ 0x20000, 0x2FFFD }, /* U+20000 - U+2FFFD */
-	{ 0x30000, 0x3FFFD }, /* U+30000 - U+3FFFD */
+/* Combined zero- and double-width ranges (non-BMP, U+10000 and above).
+ * The first 33 entries host the BMP double-width bitmap in the low
+ * 8 bits of `last`. */
+static const struct ucs_width32 ucs_nonbmp_ranges[] = {
+	{ RANGE_0WIDTH(0x101FD, 0x101FD)   /* PHAISTOS DISC SIGN COMBINING OBLIQUE STROKE */
+	  | BMP_2W_BITS(0b00000000) }, /* BMP entries [  0..  7] */
+	{ RANGE_0WIDTH(0x102E0, 0x102E0)   /* COPTIC EPACT THOUSANDS MARK */
+	  | BMP_2W_BITS(0b00000000) }, /* BMP entries [  8.. 15] */
+	{ RANGE_0WIDTH(0x10376, 0x1037A)   /* COMBINING OLD PERMIC LETTER AN - COMBINING OLD PERMIC LETTER SII */
+	  | BMP_2W_BITS(0b00000000) }, /* BMP entries [ 16.. 23] */
+	{ RANGE_0WIDTH(0x10A01, 0x10A03)   /* KHAROSHTHI VOWEL SIGN I - KHAROSHTHI VOWEL SIGN VOCALIC R */
+	  | BMP_2W_BITS(0b00000000) }, /* BMP entries [ 24.. 31] */
+	{ RANGE_0WIDTH(0x10A05, 0x10A06)   /* KHAROSHTHI VOWEL SIGN E - KHAROSHTHI VOWEL SIGN O */
+	  | BMP_2W_BITS(0b00000000) }, /* BMP entries [ 32.. 39] */
+	{ RANGE_0WIDTH(0x10A0C, 0x10A0F)   /* KHAROSHTHI VOWEL LENGTH MARK - KHAROSHTHI SIGN VISARGA */
+	  | BMP_2W_BITS(0b00000000) }, /* BMP entries [ 40.. 47] */
+	{ RANGE_0WIDTH(0x10A38, 0x10A3A)   /* KHAROSHTHI SIGN BAR ABOVE - KHAROSHTHI SIGN DOT BELOW */
+	  | BMP_2W_BITS(0b00000000) }, /* BMP entries [ 48.. 55] */
+	{ RANGE_0WIDTH(0x10A3F, 0x10A3F)   /* KHAROSHTHI VIRAMA */
+	  | BMP_2W_BITS(0b00000000) }, /* BMP entries [ 56.. 63] */
+	{ RANGE_0WIDTH(0x10AE5, 0x10AE6)   /* MANICHAEAN ABBREVIATION MARK ABOVE - MANICHAEAN ABBREVIATION MARK BELOW */
+	  | BMP_2W_BITS(0b00000000) }, /* BMP entries [ 64.. 71] */
+	{ RANGE_0WIDTH(0x10D24, 0x10D27)   /* HANIFI ROHINGYA SIGN HARBAHAY - HANIFI ROHINGYA SIGN TASSI */
+	  | BMP_2W_BITS(0b00000000) }, /* BMP entries [ 72.. 79] */
+	{ RANGE_0WIDTH(0x10D69, 0x10D6D)   /* GARAY VOWEL SIGN E - GARAY CONSONANT NASALIZATION MARK */
+	  | BMP_2W_BITS(0b00000000) }, /* BMP entries [ 80.. 87] */
+	{ RANGE_0WIDTH(0x10EAB, 0x10EAC)   /* YEZIDI COMBINING HAMZA MARK - YEZIDI COMBINING MADDA MARK */
+	  | BMP_2W_BITS(0b00000000) }, /* BMP entries [ 88.. 95] */
+	{ RANGE_0WIDTH(0x10EFC, 0x10EFF)   /* ARABIC COMBINING ALEF OVERLAY - ARABIC SMALL LOW WORD MADDA */
+	  | BMP_2W_BITS(0b00000000) }, /* BMP entries [ 96..103] */
+	{ RANGE_0WIDTH(0x10F46, 0x10F50)   /* SOGDIAN COMBINING DOT BELOW - SOGDIAN COMBINING STROKE BELOW */
+	  | BMP_2W_BITS(0b00000000) }, /* BMP entries [104..111] */
+	{ RANGE_0WIDTH(0x10F82, 0x10F85)   /* OLD UYGHUR COMBINING DOT ABOVE - OLD UYGHUR COMBINING TWO DOTS BELOW */
+	  | BMP_2W_BITS(0b00000000) }, /* BMP entries [112..119] */
+	{ RANGE_0WIDTH(0x11000, 0x11002)   /* BRAHMI SIGN CANDRABINDU - BRAHMI SIGN VISARGA */
+	  | BMP_2W_BITS(0b00001000) }, /* BMP entries [120..127] */
+	{ RANGE_0WIDTH(0x11038, 0x11046)   /* BRAHMI VOWEL SIGN AA - BRAHMI VIRAMA */
+	  | BMP_2W_BITS(0b00000000) }, /* BMP entries [128..135] */
+	{ RANGE_0WIDTH(0x11070, 0x11070)   /* BRAHMI SIGN OLD TAMIL VIRAMA */
+	  | BMP_2W_BITS(0b00000000) }, /* BMP entries [136..143] */
+	{ RANGE_0WIDTH(0x11073, 0x11074)   /* BRAHMI VOWEL SIGN OLD TAMIL SHORT E - BRAHMI VOWEL SIGN OLD TAMIL SHORT O */
+	  | BMP_2W_BITS(0b00000000) }, /* BMP entries [144..151] */
+	{ RANGE_0WIDTH(0x1107F, 0x11082)   /* BRAHMI NUMBER JOINER - KAITHI SIGN VISARGA */
+	  | BMP_2W_BITS(0b10000000) }, /* BMP entries [152..159] */
+	{ RANGE_0WIDTH(0x110B0, 0x110BA)   /* KAITHI VOWEL SIGN AA - KAITHI SIGN NUKTA */
+	  | BMP_2W_BITS(0b01111111) }, /* BMP entries [160..167] */
+	{ RANGE_0WIDTH(0x110BD, 0x110BD)   /* KAITHI NUMBER SIGN */
+	  | BMP_2W_BITS(0b10111110) }, /* BMP entries [168..175] */
+	{ RANGE_0WIDTH(0x110C2, 0x110C2)   /* KAITHI VOWEL SIGN VOCALIC R */
+	  | BMP_2W_BITS(0b11111111) }, /* BMP entries [176..183] */
+	{ RANGE_0WIDTH(0x110CD, 0x110CD)   /* KAITHI NUMBER SIGN ABOVE */
+	  | BMP_2W_BITS(0b11111111) }, /* BMP entries [184..191] */
+	{ RANGE_0WIDTH(0x11100, 0x11102)   /* CHAKMA SIGN CANDRABINDU - CHAKMA SIGN VISARGA */
+	  | BMP_2W_BITS(0b00111111) }, /* BMP entries [192..199] */
+	{ RANGE_0WIDTH(0x11127, 0x11134)   /* CHAKMA VOWEL SIGN A - CHAKMA MAAYYAA */
+	  | BMP_2W_BITS(0b11011110) }, /* BMP entries [200..207] */
+	{ RANGE_0WIDTH(0x11145, 0x11146)   /* CHAKMA VOWEL SIGN AA - CHAKMA VOWEL SIGN EI */
+	  | BMP_2W_BITS(0b11111110) }, /* BMP entries [208..215] */
+	{ RANGE_0WIDTH(0x11173, 0x11173)   /* MAHAJANI SIGN NUKTA */
+	  | BMP_2W_BITS(0b00000001) }, /* BMP entries [216..223] */
+	{ RANGE_0WIDTH(0x11180, 0x11182)   /* SHARADA SIGN CANDRABINDU - SHARADA SIGN VISARGA */
+	  | BMP_2W_BITS(0b00000000) }, /* BMP entries [224..231] */
+	{ RANGE_0WIDTH(0x111B3, 0x111C0)   /* SHARADA VOWEL SIGN AA - SHARADA SIGN VIRAMA */
+	  | BMP_2W_BITS(0b00000001) }, /* BMP entries [232..239] */
+	{ RANGE_0WIDTH(0x111C9, 0x111CC)   /* SHARADA SANDHI MARK - SHARADA EXTRA SHORT VOWEL MARK */
+	  | BMP_2W_BITS(0b00000000) }, /* BMP entries [240..247] */
+	{ RANGE_0WIDTH(0x111CE, 0x111CF)   /* SHARADA VOWEL SIGN PRISHTHAMATRA E - SHARADA SIGN INVERTED CANDRABINDU */
+	  | BMP_2W_BITS(0b10100110) }, /* BMP entries [248..255] */
+	{ RANGE_0WIDTH(0x1122C, 0x11237)   /* KHOJKI VOWEL SIGN AA - KHOJKI SIGN SHADDA */
+	  | BMP_2W_BITS(0b00011011) }, /* BMP entries [256..261] */
+	{ RANGE_0WIDTH(0x1123E, 0x1123E) }, /* KHOJKI SIGN SUKUN */
+	{ RANGE_0WIDTH(0x11241, 0x11241) }, /* KHOJKI VOWEL SIGN VOCALIC R */
+	{ RANGE_0WIDTH(0x112DF, 0x112EA) }, /* KHUDAWADI SIGN ANUSVARA - KHUDAWADI SIGN VIRAMA */
+	{ RANGE_0WIDTH(0x11300, 0x11303) }, /* GRANTHA SIGN COMBINING ANUSVARA ABOVE - GRANTHA SIGN VISARGA */
+	{ RANGE_0WIDTH(0x1133B, 0x1133C) }, /* COMBINING BINDU BELOW - GRANTHA SIGN NUKTA */
+	{ RANGE_0WIDTH(0x1133E, 0x11344) }, /* GRANTHA VOWEL SIGN AA - GRANTHA VOWEL SIGN VOCALIC RR */
+	{ RANGE_0WIDTH(0x11347, 0x11348) }, /* GRANTHA VOWEL SIGN EE - GRANTHA VOWEL SIGN AI */
+	{ RANGE_0WIDTH(0x1134B, 0x1134D) }, /* GRANTHA VOWEL SIGN OO - GRANTHA SIGN VIRAMA */
+	{ RANGE_0WIDTH(0x11357, 0x11357) }, /* GRANTHA AU LENGTH MARK */
+	{ RANGE_0WIDTH(0x11362, 0x11363) }, /* GRANTHA VOWEL SIGN VOCALIC L - GRANTHA VOWEL SIGN VOCALIC LL */
+	{ RANGE_0WIDTH(0x11366, 0x1136C) }, /* COMBINING GRANTHA DIGIT ZERO - COMBINING GRANTHA DIGIT SIX */
+	{ RANGE_0WIDTH(0x11370, 0x11374) }, /* COMBINING GRANTHA LETTER A - COMBINING GRANTHA LETTER PA */
+	{ RANGE_0WIDTH(0x113B8, 0x113C0) }, /* TULU-TIGALARI VOWEL SIGN AA - TULU-TIGALARI VOWEL SIGN VOCALIC LL */
+	{ RANGE_0WIDTH(0x113C2, 0x113C2) }, /* TULU-TIGALARI VOWEL SIGN EE */
+	{ RANGE_0WIDTH(0x113C5, 0x113C5) }, /* TULU-TIGALARI VOWEL SIGN AI */
+	{ RANGE_0WIDTH(0x113C7, 0x113CA) }, /* TULU-TIGALARI VOWEL SIGN OO - TULU-TIGALARI SIGN CANDRA ANUNASIKA */
+	{ RANGE_0WIDTH(0x113CC, 0x113D0) }, /* TULU-TIGALARI SIGN ANUSVARA - TULU-TIGALARI CONJOINER */
+	{ RANGE_0WIDTH(0x113D2, 0x113D2) }, /* TULU-TIGALARI GEMINATION MARK */
+	{ RANGE_0WIDTH(0x113E1, 0x113E2) }, /* TULU-TIGALARI VEDIC TONE SVARITA - TULU-TIGALARI VEDIC TONE ANUDATTA */
+	{ RANGE_0WIDTH(0x11435, 0x11446) }, /* NEWA VOWEL SIGN AA - NEWA SIGN NUKTA */
+	{ RANGE_0WIDTH(0x1145E, 0x1145E) }, /* NEWA SANDHI MARK */
+	{ RANGE_0WIDTH(0x114B0, 0x114C3) }, /* TIRHUTA VOWEL SIGN AA - TIRHUTA SIGN NUKTA */
+	{ RANGE_0WIDTH(0x115AF, 0x115B5) }, /* SIDDHAM VOWEL SIGN AA - SIDDHAM VOWEL SIGN VOCALIC RR */
+	{ RANGE_0WIDTH(0x115B8, 0x115C0) }, /* SIDDHAM VOWEL SIGN E - SIDDHAM SIGN NUKTA */
+	{ RANGE_0WIDTH(0x115DC, 0x115DD) }, /* SIDDHAM VOWEL SIGN ALTERNATE U - SIDDHAM VOWEL SIGN ALTERNATE UU */
+	{ RANGE_0WIDTH(0x11630, 0x11640) }, /* MODI VOWEL SIGN AA - MODI SIGN ARDHACANDRA */
+	{ RANGE_0WIDTH(0x116AB, 0x116B7) }, /* TAKRI SIGN ANUSVARA - TAKRI SIGN NUKTA */
+	{ RANGE_0WIDTH(0x1171D, 0x1172B) }, /* AHOM CONSONANT SIGN MEDIAL LA - AHOM SIGN KILLER */
+	{ RANGE_0WIDTH(0x1182C, 0x1183A) }, /* DOGRA VOWEL SIGN AA - DOGRA SIGN NUKTA */
+	{ RANGE_0WIDTH(0x11930, 0x11935) }, /* DIVES AKURU VOWEL SIGN AA - DIVES AKURU VOWEL SIGN E */
+	{ RANGE_0WIDTH(0x11937, 0x11938) }, /* DIVES AKURU VOWEL SIGN AI - DIVES AKURU VOWEL SIGN O */
+	{ RANGE_0WIDTH(0x1193B, 0x1193E) }, /* DIVES AKURU SIGN ANUSVARA - DIVES AKURU VIRAMA */
+	{ RANGE_0WIDTH(0x11940, 0x11940) }, /* DIVES AKURU MEDIAL YA */
+	{ RANGE_0WIDTH(0x11942, 0x11943) }, /* DIVES AKURU MEDIAL RA - DIVES AKURU SIGN NUKTA */
+	{ RANGE_0WIDTH(0x119D1, 0x119D7) }, /* NANDINAGARI VOWEL SIGN AA - NANDINAGARI VOWEL SIGN VOCALIC RR */
+	{ RANGE_0WIDTH(0x119DA, 0x119E0) }, /* NANDINAGARI VOWEL SIGN E - NANDINAGARI SIGN VIRAMA */
+	{ RANGE_0WIDTH(0x119E4, 0x119E4) }, /* NANDINAGARI VOWEL SIGN PRISHTHAMATRA E */
+	{ RANGE_0WIDTH(0x11A01, 0x11A0A) }, /* ZANABAZAR SQUARE VOWEL SIGN I - ZANABAZAR SQUARE VOWEL LENGTH MARK */
+	{ RANGE_0WIDTH(0x11A33, 0x11A39) }, /* ZANABAZAR SQUARE FINAL CONSONANT MARK - ZANABAZAR SQUARE SIGN VISARGA */
+	{ RANGE_0WIDTH(0x11A3B, 0x11A3E) }, /* ZANABAZAR SQUARE CLUSTER-FINAL LETTER YA - ZANABAZAR SQUARE CLUSTER-FINAL LETTER VA */
+	{ RANGE_0WIDTH(0x11A47, 0x11A47) }, /* ZANABAZAR SQUARE SUBJOINER */
+	{ RANGE_0WIDTH(0x11A51, 0x11A5B) }, /* SOYOMBO VOWEL SIGN I - SOYOMBO VOWEL LENGTH MARK */
+	{ RANGE_0WIDTH(0x11A8A, 0x11A99) }, /* SOYOMBO FINAL CONSONANT SIGN G - SOYOMBO SUBJOINER */
+	{ RANGE_0WIDTH(0x11C2F, 0x11C36) }, /* BHAIKSUKI VOWEL SIGN AA - BHAIKSUKI VOWEL SIGN VOCALIC L */
+	{ RANGE_0WIDTH(0x11C38, 0x11C3F) }, /* BHAIKSUKI VOWEL SIGN E - BHAIKSUKI SIGN VIRAMA */
+	{ RANGE_0WIDTH(0x11C92, 0x11CA7) }, /* MARCHEN SUBJOINED LETTER KA - MARCHEN SUBJOINED LETTER ZA */
+	{ RANGE_0WIDTH(0x11CA9, 0x11CB6) }, /* MARCHEN SUBJOINED LETTER YA - MARCHEN SIGN CANDRABINDU */
+	{ RANGE_0WIDTH(0x11D31, 0x11D36) }, /* MASARAM GONDI VOWEL SIGN AA - MASARAM GONDI VOWEL SIGN VOCALIC R */
+	{ RANGE_0WIDTH(0x11D3A, 0x11D3A) }, /* MASARAM GONDI VOWEL SIGN E */
+	{ RANGE_0WIDTH(0x11D3C, 0x11D3D) }, /* MASARAM GONDI VOWEL SIGN AI - MASARAM GONDI VOWEL SIGN O */
+	{ RANGE_0WIDTH(0x11D3F, 0x11D45) }, /* MASARAM GONDI VOWEL SIGN AU - MASARAM GONDI VIRAMA */
+	{ RANGE_0WIDTH(0x11D47, 0x11D47) }, /* MASARAM GONDI RA-KARA */
+	{ RANGE_0WIDTH(0x11D8A, 0x11D8E) }, /* GUNJALA GONDI VOWEL SIGN AA - GUNJALA GONDI VOWEL SIGN UU */
+	{ RANGE_0WIDTH(0x11D90, 0x11D91) }, /* GUNJALA GONDI VOWEL SIGN EE - GUNJALA GONDI VOWEL SIGN AI */
+	{ RANGE_0WIDTH(0x11D93, 0x11D97) }, /* GUNJALA GONDI VOWEL SIGN OO - GUNJALA GONDI VIRAMA */
+	{ RANGE_0WIDTH(0x11EF3, 0x11EF6) }, /* MAKASAR VOWEL SIGN I - MAKASAR VOWEL SIGN O */
+	{ RANGE_0WIDTH(0x11F00, 0x11F01) }, /* KAWI SIGN CANDRABINDU - KAWI SIGN ANUSVARA */
+	{ RANGE_0WIDTH(0x11F03, 0x11F03) }, /* KAWI SIGN VISARGA */
+	{ RANGE_0WIDTH(0x11F34, 0x11F3A) }, /* KAWI VOWEL SIGN AA - KAWI VOWEL SIGN VOCALIC R */
+	{ RANGE_0WIDTH(0x11F3E, 0x11F42) }, /* KAWI VOWEL SIGN E - KAWI CONJOINER */
+	{ RANGE_0WIDTH(0x11F5A, 0x11F5A) }, /* KAWI SIGN NUKTA */
+	{ RANGE_0WIDTH(0x13430, 0x13440) }, /* EGYPTIAN HIEROGLYPH VERTICAL JOINER - EGYPTIAN HIEROGLYPH MIRROR HORIZONTALLY */
+	{ RANGE_0WIDTH(0x13447, 0x13455) }, /* EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT TOP START - EGYPTIAN HIEROGLYPH MODIFIER DAMAGED */
+	{ RANGE_0WIDTH(0x1611E, 0x1612F) }, /* GURUNG KHEMA VOWEL SIGN AA - GURUNG KHEMA SIGN THOLHOMA */
+	{ RANGE_0WIDTH(0x16AF0, 0x16AF4) }, /* BASSA VAH COMBINING HIGH TONE - BASSA VAH COMBINING HIGH-LOW TONE */
+	{ RANGE_0WIDTH(0x16B30, 0x16B36) }, /* PAHAWH HMONG MARK CIM TUB - PAHAWH HMONG MARK CIM TAUM */
+	{ RANGE_0WIDTH(0x16F4F, 0x16F4F) }, /* MIAO SIGN CONSONANT MODIFIER BAR */
+	{ RANGE_0WIDTH(0x16F51, 0x16F87) }, /* MIAO SIGN ASPIRATION - MIAO VOWEL SIGN UI */
+	{ RANGE_0WIDTH(0x16F8F, 0x16F92) }, /* MIAO TONE RIGHT - MIAO TONE BELOW */
+	{ RANGE_2WIDTH(0x16FE0, 0x16FE3) }, /* TANGUT ITERATION MARK - OLD CHINESE ITERATION MARK */
+	{ RANGE_0WIDTH(0x16FE4, 0x16FE4) }, /* KHITAN SMALL SCRIPT FILLER */
+	{ RANGE_0WIDTH(0x16FF0, 0x16FF1) }, /* VIETNAMESE ALTERNATE READING MARK CA - VIETNAMESE ALTERNATE READING MARK NHAY */
+	{ RANGE_2WIDTH(0x17000, 0x187F7) }, /* U+17000 - U+187F7 */
+	{ RANGE_2WIDTH(0x18800, 0x18CD5) }, /* TANGUT COMPONENT-001 - KHITAN SMALL SCRIPT CHARACTER-18CD5 */
+	{ RANGE_2WIDTH(0x18CFF, 0x18D08) }, /* U+18CFF - U+18D08 */
+	{ RANGE_2WIDTH(0x1AFF0, 0x1AFF3) }, /* KATAKANA LETTER MINNAN TONE-2 - KATAKANA LETTER MINNAN TONE-5 */
+	{ RANGE_2WIDTH(0x1AFF5, 0x1AFFB) }, /* KATAKANA LETTER MINNAN TONE-7 - KATAKANA LETTER MINNAN NASALIZED TONE-5 */
+	{ RANGE_2WIDTH(0x1AFFD, 0x1AFFE) }, /* KATAKANA LETTER MINNAN NASALIZED TONE-7 - KATAKANA LETTER MINNAN NASALIZED TONE-8 */
+	{ RANGE_2WIDTH(0x1B000, 0x1B122) }, /* KATAKANA LETTER ARCHAIC E - KATAKANA LETTER ARCHAIC WU */
+	{ RANGE_2WIDTH(0x1B132, 0x1B132) }, /* HIRAGANA LETTER SMALL KO */
+	{ RANGE_2WIDTH(0x1B150, 0x1B152) }, /* HIRAGANA LETTER SMALL WI - HIRAGANA LETTER SMALL WO */
+	{ RANGE_2WIDTH(0x1B155, 0x1B155) }, /* KATAKANA LETTER SMALL KO */
+	{ RANGE_2WIDTH(0x1B164, 0x1B167) }, /* KATAKANA LETTER SMALL WI - KATAKANA LETTER SMALL N */
+	{ RANGE_2WIDTH(0x1B170, 0x1B2FB) }, /* NUSHU CHARACTER-1B170 - NUSHU CHARACTER-1B2FB */
+	{ RANGE_0WIDTH(0x1BC9D, 0x1BC9E) }, /* DUPLOYAN THICK LETTER SELECTOR - DUPLOYAN DOUBLE MARK */
+	{ RANGE_0WIDTH(0x1BCA0, 0x1BCA3) }, /* SHORTHAND FORMAT LETTER OVERLAP - SHORTHAND FORMAT UP STEP */
+	{ RANGE_0WIDTH(0x1CF00, 0x1CF2D) }, /* ZNAMENNY COMBINING MARK GORAZDO NIZKO S KRYZHEM ON LEFT - ZNAMENNY COMBINING MARK KRYZH ON LEFT */
+	{ RANGE_0WIDTH(0x1CF30, 0x1CF46) }, /* ZNAMENNY COMBINING TONAL RANGE MARK MRACHNO - ZNAMENNY PRIZNAK MODIFIER ROG */
+	{ RANGE_0WIDTH(0x1D165, 0x1D169) }, /* MUSICAL SYMBOL COMBINING STEM - MUSICAL SYMBOL COMBINING TREMOLO-3 */
+	{ RANGE_0WIDTH(0x1D16D, 0x1D182) }, /* MUSICAL SYMBOL COMBINING AUGMENTATION DOT - MUSICAL SYMBOL COMBINING LOURE */
+	{ RANGE_0WIDTH(0x1D185, 0x1D18B) }, /* MUSICAL SYMBOL COMBINING DOIT - MUSICAL SYMBOL COMBINING TRIPLE TONGUE */
+	{ RANGE_0WIDTH(0x1D1AA, 0x1D1AD) }, /* MUSICAL SYMBOL COMBINING DOWN BOW - MUSICAL SYMBOL COMBINING SNAP PIZZICATO */
+	{ RANGE_0WIDTH(0x1D242, 0x1D244) }, /* COMBINING GREEK MUSICAL TRISEME - COMBINING GREEK MUSICAL PENTASEME */
+	{ RANGE_2WIDTH(0x1D300, 0x1D356) }, /* MONOGRAM FOR EARTH - TETRAGRAM FOR FOSTERING */
+	{ RANGE_2WIDTH(0x1D360, 0x1D376) }, /* COUNTING ROD UNIT DIGIT ONE - IDEOGRAPHIC TALLY MARK FIVE */
+	{ RANGE_0WIDTH(0x1DA00, 0x1DA36) }, /* SIGNWRITING HEAD RIM - SIGNWRITING AIR SUCKING IN */
+	{ RANGE_0WIDTH(0x1DA3B, 0x1DA6C) }, /* SIGNWRITING MOUTH CLOSED NEUTRAL - SIGNWRITING EXCITEMENT */
+	{ RANGE_0WIDTH(0x1DA75, 0x1DA75) }, /* SIGNWRITING UPPER BODY TILTING FROM HIP JOINTS */
+	{ RANGE_0WIDTH(0x1DA84, 0x1DA84) }, /* SIGNWRITING LOCATION HEAD NECK */
+	{ RANGE_0WIDTH(0x1DA9B, 0x1DA9F) }, /* SIGNWRITING FILL MODIFIER-2 - SIGNWRITING FILL MODIFIER-6 */
+	{ RANGE_0WIDTH(0x1DAA1, 0x1DAAF) }, /* SIGNWRITING ROTATION MODIFIER-2 - SIGNWRITING ROTATION MODIFIER-16 */
+	{ RANGE_0WIDTH(0x1E000, 0x1E006) }, /* COMBINING GLAGOLITIC LETTER AZU - COMBINING GLAGOLITIC LETTER ZHIVETE */
+	{ RANGE_0WIDTH(0x1E008, 0x1E018) }, /* COMBINING GLAGOLITIC LETTER ZEMLJA - COMBINING GLAGOLITIC LETTER HERU */
+	{ RANGE_0WIDTH(0x1E01B, 0x1E021) }, /* COMBINING GLAGOLITIC LETTER SHTA - COMBINING GLAGOLITIC LETTER YATI */
+	{ RANGE_0WIDTH(0x1E023, 0x1E024) }, /* COMBINING GLAGOLITIC LETTER YU - COMBINING GLAGOLITIC LETTER SMALL YUS */
+	{ RANGE_0WIDTH(0x1E026, 0x1E02A) }, /* COMBINING GLAGOLITIC LETTER YO - COMBINING GLAGOLITIC LETTER FITA */
+	{ RANGE_0WIDTH(0x1E08F, 0x1E08F) }, /* COMBINING CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I */
+	{ RANGE_0WIDTH(0x1E130, 0x1E136) }, /* NYIAKENG PUACHUE HMONG TONE-B - NYIAKENG PUACHUE HMONG TONE-D */
+	{ RANGE_0WIDTH(0x1E2AE, 0x1E2AE) }, /* TOTO SIGN RISING TONE */
+	{ RANGE_0WIDTH(0x1E2EC, 0x1E2EF) }, /* WANCHO TONE TUP - WANCHO TONE KOINI */
+	{ RANGE_0WIDTH(0x1E4EC, 0x1E4EF) }, /* NAG MUNDARI SIGN MUHOR - NAG MUNDARI SIGN SUTUH */
+	{ RANGE_0WIDTH(0x1E5EE, 0x1E5EF) }, /* OL ONAL SIGN MU - OL ONAL SIGN IKIR */
+	{ RANGE_0WIDTH(0x1E8D0, 0x1E8D6) }, /* MENDE KIKAKUI COMBINING NUMBER TEENS - MENDE KIKAKUI COMBINING NUMBER MILLIONS */
+	{ RANGE_0WIDTH(0x1E944, 0x1E94A) }, /* ADLAM ALIF LENGTHENER - ADLAM NUKTA */
+	{ RANGE_2WIDTH(0x1F000, 0x1F02F) }, /* U+1F000 - U+1F02F */
+	{ RANGE_2WIDTH(0x1F0A0, 0x1F0FF) }, /* U+1F0A0 - U+1F0FF */
+	{ RANGE_2WIDTH(0x1F18E, 0x1F18E) }, /* NEGATIVE SQUARED AB */
+	{ RANGE_2WIDTH(0x1F191, 0x1F19A) }, /* SQUARED CL - SQUARED VS */
+	{ RANGE_2WIDTH(0x1F200, 0x1F202) }, /* SQUARE HIRAGANA HOKA - SQUARED KATAKANA SA */
+	{ RANGE_2WIDTH(0x1F210, 0x1F23B) }, /* SQUARED CJK UNIFIED IDEOGRAPH-624B - SQUARED CJK UNIFIED IDEOGRAPH-914D */
+	{ RANGE_2WIDTH(0x1F240, 0x1F248) }, /* TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-672C - TORTOISE SHELL BRACKETED CJK UNIFIED IDEOGRAPH-6557 */
+	{ RANGE_2WIDTH(0x1F250, 0x1F251) }, /* CIRCLED IDEOGRAPH ADVANTAGE - CIRCLED IDEOGRAPH ACCEPT */
+	{ RANGE_2WIDTH(0x1F260, 0x1F265) }, /* ROUNDED SYMBOL FOR FU - ROUNDED SYMBOL FOR CAI */
+	{ RANGE_2WIDTH(0x1F300, 0x1F3FA) }, /* CYCLONE - AMPHORA */
+	{ RANGE_0WIDTH(0x1F3FB, 0x1F3FF) }, /* EMOJI MODIFIER FITZPATRICK TYPE-1-2 - EMOJI MODIFIER FITZPATRICK TYPE-6 */
+	{ RANGE_2WIDTH(0x1F400, 0x1F64F) }, /* RAT - PERSON WITH FOLDED HANDS */
+	{ RANGE_2WIDTH(0x1F680, 0x1F9AF) }, /* ROCKET - PROBING CANE */
+	{ RANGE_0WIDTH(0x1F9B0, 0x1F9B3) }, /* EMOJI COMPONENT RED HAIR - EMOJI COMPONENT WHITE HAIR */
+	{ RANGE_2WIDTH(0x1F9B4, 0x1FAFF) }, /* U+1F9B4 - U+1FAFF */
+	{ RANGE_2WIDTH(0x20000, 0x2FFFD) }, /* U+20000 - U+2FFFD */
+	{ RANGE_2WIDTH(0x30000, 0x3FFFD) }, /* U+30000 - U+3FFFD */
+	{ RANGE_0WIDTH(0xE0001, 0xE0001) }, /* LANGUAGE TAG */
+	{ RANGE_0WIDTH(0xE0020, 0xE007F) }, /* TAG SPACE - CANCEL TAG */
+	{ RANGE_0WIDTH(0xE0100, 0xE01EF) }, /* VARIATION SELECTOR-17 - VARIATION SELECTOR-256 */
 };
diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
index e99636ab9db5..cf26fb8f6545 100644
--- a/drivers/tty/vt/vt.c
+++ b/drivers/tty/vt/vt.c
@@ -3094,8 +3094,12 @@ static void vc_con_rewind(struct vc_data *vc)
 static int vc_process_ucs(struct vc_data *vc, int *c, int *tc)
 {
 	u32 prev_c, curr_c = *c;
+	unsigned int w = ucs_get_width(curr_c);
 
-	if (ucs_is_double_width(curr_c)) {
+	if (likely(w == 1))
+		return 1;
+
+	if (w == 2) {
 		/*
 		 * The Unicode screen memory is allocated only when
 		 * required. This is one such case as we need to remember
@@ -3105,12 +3109,9 @@ static int vc_process_ucs(struct vc_data *vc, int *c, int *tc)
 		return 2;
 	}
 
-	if (!ucs_is_zero_width(curr_c))
-		return 1;
-
 	/* From here curr_c is known to be zero-width. */
 
-	if (ucs_is_double_width(vc_uniscr_getc(vc, -2))) {
+	if (ucs_get_width(vc_uniscr_getc(vc, -2)) == 2) {
 		/*
 		 * Let's merge this zero-width code point with the preceding
 		 * double-width code point by replacing the existing
diff --git a/include/linux/consolemap.h b/include/linux/consolemap.h
index 6180b803795c..539d488fdc03 100644
--- a/include/linux/consolemap.h
+++ b/include/linux/consolemap.h
@@ -28,8 +28,7 @@ int conv_uni_to_pc(struct vc_data *conp, long ucs);
 u32 conv_8bit_to_uni(unsigned char c);
 int conv_uni_to_8bit(u32 uni);
 void console_map_init(void);
-bool ucs_is_double_width(uint32_t cp);
-bool ucs_is_zero_width(uint32_t cp);
+unsigned int ucs_get_width(uint32_t cp);
 u32 ucs_recompose(u32 base, u32 mark);
 u32 ucs_get_fallback(u32 cp);
 #else
@@ -62,14 +61,9 @@ static inline int conv_uni_to_8bit(u32 uni)
 
 static inline void console_map_init(void) { }
 
-static inline bool ucs_is_double_width(uint32_t cp)
+static inline unsigned int ucs_get_width(uint32_t cp)
 {
-	return false;
-}
-
-static inline bool ucs_is_zero_width(uint32_t cp)
-{
-	return false;
+	return 1;
 }
 
 static inline u32 ucs_recompose(u32 base, u32 mark)
-- 
2.54.0


^ permalink raw reply related

* [tty:tty-next 28/31] drivers/tty/serial/max310x.c:1730:1: warning: unused label 'err_spi_register'
From: kernel test robot @ 2026-05-14 23:35 UTC (permalink / raw)
  To: Hugo Villeneuve; +Cc: oe-kbuild-all, linux-serial, Greg Kroah-Hartman

Hi Hugo,

First bad commit (maybe != root cause):

tree:   https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/tty.git tty-next
head:   16e95bfb79b5d9d01dc7651d98caf3c2ace331cd
commit: 20ffe4b3330a8bde9e933e9ba2323d5e9386caa5 [28/31] serial: max310x: allow driver to be built with SPI or I2C
config: i386-randconfig-r121-20260515 (https://download.01.org/0day-ci/archive/20260515/202605150707.I4LYaT6N-lkp@intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
sparse: v0.6.5-rc1
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260515/202605150707.I4LYaT6N-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202605150707.I4LYaT6N-lkp@intel.com/

All warnings (new ones prefixed by >>):

>> drivers/tty/serial/max310x.c:1730:1: warning: unused label 'err_spi_register' [-Wunused-label]
    1730 | err_spi_register:
         | ^~~~~~~~~~~~~~~~~
   drivers/tty/serial/max310x.c:1280:12: warning: unused function 'max310x_probe' [-Wunused-function]
    1280 | static int max310x_probe(struct device *dev, const struct max310x_devtype *devtype,
         |            ^~~~~~~~~~~~~
   drivers/tty/serial/max310x.c:1464:13: warning: unused function 'max310x_remove' [-Wunused-function]
    1464 | static void max310x_remove(struct device *dev)
         |             ^~~~~~~~~~~~~~
>> drivers/tty/serial/max310x.c:1492:29: warning: unused variable 'regcfg' [-Wunused-variable]
    1492 | static struct regmap_config regcfg = {
         |                             ^~~~~~
   drivers/tty/serial/max310x.c:1507:20: warning: unused function 'max310x_regmap_name' [-Wunused-function]
    1507 | static const char *max310x_regmap_name(u8 port_id)
         |                    ^~~~~~~~~~~~~~~~~~~
   5 warnings generated.


vim +/err_spi_register +1730 drivers/tty/serial/max310x.c

2e1f2d9a9bdbe12 Cosmin Tanislav  2022-06-05  1729  
2e1f2d9a9bdbe12 Cosmin Tanislav  2022-06-05 @1730  err_spi_register:
2e1f2d9a9bdbe12 Cosmin Tanislav  2022-06-05  1731  	uart_unregister_driver(&max310x_uart);
2e1f2d9a9bdbe12 Cosmin Tanislav  2022-06-05  1732  
51f689cc1133394 Kangjie Lu       2018-12-25  1733  	return ret;
6286767ad3afc88 Alexander Shiyan 2016-06-07  1734  }
6286767ad3afc88 Alexander Shiyan 2016-06-07  1735  module_init(max310x_uart_init);
6286767ad3afc88 Alexander Shiyan 2016-06-07  1736  

:::::: The code at line 1730 was first introduced by commit
:::::: 2e1f2d9a9bdbe12ee475c82a45ac46a278e8049a serial: max310x: implement I2C support

:::::: TO: Cosmin Tanislav <cosmin.tanislav@analog.com>
:::::: CC: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply

* [PATCH v2] Bluetooth: hci_uart: fix UAF in hci_uart_tty_close()
From: w15303746062 @ 2026-05-14 15:17 UTC (permalink / raw)
  To: pmenzel, marcel, luiz.dentz, linux-bluetooth
  Cc: linux-serial, linux-kernel, Mingyu Wang
In-Reply-To: <505b56bd-e5fd-4feb-a6e3-1d8269609277@molgen.mpg.de>

From: Mingyu Wang <25181214217@stu.xidian.edu.cn>

A Use-After-Free (UAF) vulnerability and a subsequent kernel panic were
observed in hci_uart_write_work() due to a race condition between the
initialization of the HCI UART line discipline and concurrent TTY hangup.

This issue was triggered by our custom device emulation and fuzzing
framework (DevGen) on the v6.18 kernel. Due to the highly timing-dependent
nature of this race condition (requiring a precise interleaving of
TIOCVHANGUP and protocol setup), Syzkaller failed to extract a reliable
standalone C reproducer (reproducer is too unreliable: 0.00).

The crash trace is as follows:
  ODEBUG: free active (active state 0) object: ffff88804024e870 object type: work_struct hint: hci_uart_write_work+0x0/0x940
  WARNING: CPU: 0 PID: 338273 at lib/debugobjects.c:612 debug_print_object+0x1a2/0x2b0
  ...
  Call Trace:
   <TASK>
   debug_check_no_obj_freed+0x3ec/0x520
   kfree+0x3f0/0x6c0
   hci_uart_tty_close+0x127/0x2a0
   tty_ldisc_close+0x113/0x1a0
   tty_ldisc_kill+0x8e/0x150
   tty_ldisc_hangup+0x3c1/0x730
   __tty_hangup.part.0+0x3fd/0x8a0
   tty_ioctl+0x120f/0x1690
   __x64_sys_ioctl+0x18f/0x210
   do_syscall_64+0xcb/0xfa0
   entry_SYSCALL_64_after_hwframe+0x77/0x7f
   </TASK>

The issue arises because the workqueues (init_ready and write_work) are
only cancelled if the HCI_UART_PROTO_READY flag is set. However, during
the protocol initialization phase (HCI_UART_PROTO_INIT), the underlying
protocol may schedule work. If a hangup occurs before the setup completes
and the READY flag is set, hci_uart_tty_close() skips the cancel_work_sync()
calls and proceeds to free the `hu` struct. When the delayed workqueue
executes, it blindly dereferences the freed `hu` struct.

Fix this by moving the cancel_work_sync() calls outside the
HCI_UART_PROTO_READY check, ensuring that any pending works are
unconditionally cancelled before the hci_uart structure is freed.
Note that hu->init_ready and hu->write_work are initialized in
hci_uart_tty_open(), so it is always safe to call cancel_work_sync()
on them in hci_uart_tty_close(), even if the protocol was never
fully attached.

Fixes: 3b799254cf6f ("Bluetooth: hci_uart: Cancel init work before unregistering")

Signed-off-by: Mingyu Wang <25181214217@stu.xidian.edu.cn>
---
Changes in v2:
- Added KASAN/ODEBUG crash trace.
- Added explanation for the absence of a standalone reproducer (highly timing-dependent race condition).
- Added Fixes tag pointing to commit 3b799254cf6f.

 drivers/bluetooth/hci_ldisc.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/drivers/bluetooth/hci_ldisc.c b/drivers/bluetooth/hci_ldisc.c
index 275ea865bc29..566e1c525ee2 100644
--- a/drivers/bluetooth/hci_ldisc.c
+++ b/drivers/bluetooth/hci_ldisc.c
@@ -544,14 +544,18 @@ static void hci_uart_tty_close(struct tty_struct *tty)
 	if (hdev)
 		hci_uart_close(hdev);
 
+	/*
+	 * Always cancel workqueues unconditionally before freeing the hu
+	 * struct, as they might be active during the PROTO_INIT phase.
+	 */
+	cancel_work_sync(&hu->init_ready);
+	cancel_work_sync(&hu->write_work);
+
 	if (test_bit(HCI_UART_PROTO_READY, &hu->flags)) {
 		percpu_down_write(&hu->proto_lock);
 		clear_bit(HCI_UART_PROTO_READY, &hu->flags);
 		percpu_up_write(&hu->proto_lock);
 
-		cancel_work_sync(&hu->init_ready);
-		cancel_work_sync(&hu->write_work);
-
 		if (hdev) {
 			if (test_bit(HCI_UART_REGISTERED, &hu->flags))
 				hci_unregister_dev(hdev);
-- 
2.34.1


^ permalink raw reply related

* [PATCH 2/2] serial: 8250_dw: remove clock-notifier infrastructure
From: Stepan Ionichev @ 2026-05-14 14:37 UTC (permalink / raw)
  To: ilpo.jarvinen
  Cc: andriy.shevchenko, gregkh, jirislaby, linux-serial, linux-kernel,
	stable, sozdayvek
In-Reply-To: <20260514143746.23671-1-sozdayvek@gmail.com>

The clock notifier and matching work_struct in dw8250_data were added
in 2020 for the Baikal-T1 SoC, whose multiple UART ports share a
single reference clock and need to be informed when another consumer
re-rates that clock.

Baikal SoC support has since been removed from the kernel (see e.g.
commit 5d6c477687ae ("clk: baikal-t1: Remove not-going-to-be-supported
code for Baikal SoC") and the matching removals across bus/, mtd/,
PCI/, hwmon/, memory/). No remaining in-tree user needs the
cross-device baudclk rate-change notification path: the only
configuration that wired up the notifier was Baikal-T1's shared
reference clock topology.

Drop the now-unused clock-notifier and its deferred-update worker:

  - struct dw8250_data fields clk_notifier and clk_work,
  - the clk_to_dw8250_data() and work_to_dw8250_data() helpers,
  - the dw8250_clk_work_cb() and dw8250_clk_notifier_cb() callbacks,
  - the INIT_WORK / notifier_call setup in dw8250_probe(),
  - the clk_notifier_register() / queue_work() in dw8250_probe(),
  - the matching clk_notifier_unregister() / flush_work() in
    dw8250_remove(),
  - the stale comment in dw8250_set_termios() about the worker
    blocking,
  - the linux/notifier.h and linux/workqueue.h includes that are
    no longer used.

dw8250_set_termios() keeps calling clk_set_rate() directly, which is
all the remaining single-UART configurations require.

Signed-off-by: Stepan Ionichev <sozdayvek@gmail.com>
---
 drivers/tty/serial/8250/8250_dw.c | 81 -------------------------------
 1 file changed, 81 deletions(-)

diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
index 7dbd79a91..41c5abccd 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -19,13 +19,11 @@
 #include <linux/lockdep.h>
 #include <linux/mod_devicetable.h>
 #include <linux/module.h>
-#include <linux/notifier.h>
 #include <linux/platform_device.h>
 #include <linux/pm_runtime.h>
 #include <linux/property.h>
 #include <linux/reset.h>
 #include <linux/slab.h>
-#include <linux/workqueue.h>
 
 #include <asm/byteorder.h>
 
@@ -86,8 +84,6 @@ struct dw8250_data {
 	u32			msr_mask_off;
 	struct clk		*clk;
 	struct clk		*pclk;
-	struct notifier_block	clk_notifier;
-	struct work_struct	clk_work;
 	struct reset_control	*rst;
 
 	unsigned int		skip_autocfg:1;
@@ -102,16 +98,6 @@ static inline struct dw8250_data *to_dw8250_data(struct dw8250_port_data *data)
 	return container_of(data, struct dw8250_data, data);
 }
 
-static inline struct dw8250_data *clk_to_dw8250_data(struct notifier_block *nb)
-{
-	return container_of(nb, struct dw8250_data, clk_notifier);
-}
-
-static inline struct dw8250_data *work_to_dw8250_data(struct work_struct *work)
-{
-	return container_of(work, struct dw8250_data, clk_work);
-}
-
 static inline u32 dw8250_modify_msr(struct uart_port *p, unsigned int offset, u32 value)
 {
 	struct dw8250_data *d = to_dw8250_data(p->private_data);
@@ -484,46 +470,6 @@ static int dw8250_handle_irq(struct uart_port *p)
 	return 1;
 }
 
-static void dw8250_clk_work_cb(struct work_struct *work)
-{
-	struct dw8250_data *d = work_to_dw8250_data(work);
-	struct uart_8250_port *up;
-	unsigned long rate;
-
-	rate = clk_get_rate(d->clk);
-	if (rate <= 0)
-		return;
-
-	up = serial8250_get_port(d->data.line);
-
-	serial8250_update_uartclk(&up->port, rate);
-}
-
-static int dw8250_clk_notifier_cb(struct notifier_block *nb,
-				  unsigned long event, void *data)
-{
-	struct dw8250_data *d = clk_to_dw8250_data(nb);
-
-	/*
-	 * We have no choice but to defer the uartclk update due to two
-	 * deadlocks. First one is caused by a recursive mutex lock which
-	 * happens when clk_set_rate() is called from dw8250_set_termios().
-	 * Second deadlock is more tricky and is caused by an inverted order of
-	 * the clk and tty-port mutexes lock. It happens if clock rate change
-	 * is requested asynchronously while set_termios() is executed between
-	 * tty-port mutex lock and clk_set_rate() function invocation and
-	 * vise-versa. Anyway if we didn't have the reference clock alteration
-	 * in the dw8250_set_termios() method we wouldn't have needed this
-	 * deferred event handling complication.
-	 */
-	if (event == POST_RATE_CHANGE) {
-		queue_work(system_dfl_wq, &d->clk_work);
-		return NOTIFY_OK;
-	}
-
-	return NOTIFY_DONE;
-}
-
 static void
 dw8250_do_pm(struct uart_port *port, unsigned int state, unsigned int old)
 {
@@ -547,10 +493,6 @@ static void dw8250_set_termios(struct uart_port *p, struct ktermios *termios,
 	clk_disable_unprepare(d->clk);
 	rate = clk_round_rate(d->clk, newrate);
 	if (rate > 0) {
-		/*
-		 * Note that any clock-notifier worker will block in
-		 * serial8250_update_uartclk() until we are done.
-		 */
 		ret = clk_set_rate(d->clk, newrate);
 		if (!ret)
 			p->uartclk = rate;
@@ -783,9 +725,6 @@ static int dw8250_probe(struct platform_device *pdev)
 		return dev_err_probe(dev, PTR_ERR(data->clk),
 				     "failed to get baudclk\n");
 
-	INIT_WORK(&data->clk_work, dw8250_clk_work_cb);
-	data->clk_notifier.notifier_call = dw8250_clk_notifier_cb;
-
 	if (data->clk)
 		p->uartclk = clk_get_rate(data->clk);
 
@@ -843,20 +782,6 @@ static int dw8250_probe(struct platform_device *pdev)
 	if (data->data.line < 0)
 		return data->data.line;
 
-	/*
-	 * Some platforms may provide a reference clock shared between several
-	 * devices. In this case any clock state change must be known to the
-	 * UART port at least post factum.
-	 */
-	if (data->clk) {
-		err = clk_notifier_register(data->clk, &data->clk_notifier);
-		if (err) {
-			serial8250_unregister_port(data->data.line);
-			return dev_err_probe(dev, err, "Failed to set the clock notifier\n");
-		}
-		queue_work(system_dfl_wq, &data->clk_work);
-	}
-
 	platform_set_drvdata(pdev, data);
 
 	pm_runtime_enable(dev);
@@ -871,12 +796,6 @@ static void dw8250_remove(struct platform_device *pdev)
 
 	pm_runtime_get_sync(dev);
 
-	if (data->clk) {
-		clk_notifier_unregister(data->clk, &data->clk_notifier);
-
-		flush_work(&data->clk_work);
-	}
-
 	serial8250_unregister_port(data->data.line);
 
 	pm_runtime_disable(dev);
-- 
2.43.0


^ permalink raw reply related

* [PATCH 1/2] serial: 8250_dw: unregister 8250 port if clk_notifier_register() fails
From: Stepan Ionichev @ 2026-05-14 14:37 UTC (permalink / raw)
  To: ilpo.jarvinen
  Cc: andriy.shevchenko, gregkh, jirislaby, linux-serial, linux-kernel,
	stable, sozdayvek
In-Reply-To: <20260514143746.23671-1-sozdayvek@gmail.com>

dw8250_probe() registers the 8250 port via serial8250_register_8250_port()
and then, if the device has a clock, registers a clock notifier. If
clk_notifier_register() fails, probe returns the error but leaves the
8250 port registered. The matching serial8250_unregister_port() lives
in dw8250_remove(), which is not called when probe fails, so the port
slot stays occupied until the device is rebound or the system is
rebooted. The devm-allocated driver data is freed while the port still
references it (via the saved private_data and serial_in/serial_out
callbacks), so any access to that port slot before a rebind is a
use-after-free hazard.

Unregister the port on the clk_notifier_register() error path.

Fixes: cc816969d7b5 ("serial: 8250_dw: Fix common clocks usage race condition")
Cc: stable@vger.kernel.org
Signed-off-by: Stepan Ionichev <sozdayvek@gmail.com>
---
 drivers/tty/serial/8250/8250_dw.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
index 94beadb40..7dbd79a91 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -850,8 +850,10 @@ static int dw8250_probe(struct platform_device *pdev)
 	 */
 	if (data->clk) {
 		err = clk_notifier_register(data->clk, &data->clk_notifier);
-		if (err)
+		if (err) {
+			serial8250_unregister_port(data->data.line);
 			return dev_err_probe(dev, err, "Failed to set the clock notifier\n");
+		}
 		queue_work(system_dfl_wq, &data->clk_work);
 	}
 
-- 
2.43.0


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox