* [PATCH RFC 01/12] dt-bindings: vendor-prefixes: Add Bouffalo Lab
2026-10-29 5:05 [PATCH RFC 00/12] Add BL808 UART3 support with a Rust serial-core polling console Kwanghoon Son
@ 2026-10-29 5:05 ` Kwanghoon Son
2026-10-29 5:10 ` sashiko-bot
2026-10-29 5:05 ` [PATCH RFC 02/12] dt-bindings: interrupt-controller: Add BL808 PLIC Kwanghoon Son
` (10 subsequent siblings)
11 siblings, 1 reply; 24+ messages in thread
From: Kwanghoon Son @ 2026-10-29 5:05 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Daniel Almeida, Tamir Duberstein, Alexandre Courbot,
Onur Özkan, Thomas Gleixner, Radu Rendec, Paul Walmsley,
Samuel Holland, Palmer Dabbelt, Daniel Lezcano, Anup Patel,
Kwanghoon Son, Albert Ou, Alexandre Ghiti, Greg Kroah-Hartman,
Jiri Slaby
Cc: devicetree, linux-kernel, rust-for-linux, Paul Walmsley,
linux-riscv, linux-serial
Register bflb for Bouffalo Lab Technology (Nanjing) Co., Ltd. Reuse the
prefix already deployed by Zephyr, including its bflb,uart binding.
Link: https://docs.zephyrproject.org/latest/build/dts/api/bindings/serial/bflb,uart.html
Signed-off-by: Kwanghoon Son <asdfkwang@gmail.com>
---
Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index 5471a06f93f1012f08e26f7f105ad846ab3629ac..24c670ff75b6f482ede1bda3b59c70ed9116042f 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -249,6 +249,8 @@ patternProperties:
description: Shanghai Belling Co., Ltd.
"^bestar,.*":
description: Shenzhen Bestar Electronic Technology Co., Ltd.
+ "^bflb,.*":
+ description: Bouffalo Lab Technology (Nanjing) Co., Ltd.
"^bhf,.*":
description: Beckhoff Automation GmbH & Co. KG
"^bigtreetech,.*":
--
2.47.3
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH RFC 02/12] dt-bindings: interrupt-controller: Add BL808 PLIC
2026-10-29 5:05 [PATCH RFC 00/12] Add BL808 UART3 support with a Rust serial-core polling console Kwanghoon Son
2026-10-29 5:05 ` [PATCH RFC 01/12] dt-bindings: vendor-prefixes: Add Bouffalo Lab Kwanghoon Son
@ 2026-10-29 5:05 ` Kwanghoon Son
2026-10-29 5:11 ` sashiko-bot
2026-10-29 5:05 ` [PATCH RFC 03/12] dt-bindings: timer: Add BL808 CLINT Kwanghoon Son
` (9 subsequent siblings)
11 siblings, 1 reply; 24+ messages in thread
From: Kwanghoon Son @ 2026-10-29 5:05 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Daniel Almeida, Tamir Duberstein, Alexandre Courbot,
Onur Özkan, Thomas Gleixner, Radu Rendec, Paul Walmsley,
Samuel Holland, Palmer Dabbelt, Daniel Lezcano, Anup Patel,
Kwanghoon Son, Albert Ou, Alexandre Ghiti, Greg Kroah-Hartman,
Jiri Slaby
Cc: devicetree, linux-kernel, rust-for-linux, Paul Walmsley,
linux-riscv, linux-serial
Describe the BL808 D0 PLIC as a T-HEAD C900 implementation. Its
interrupt specifier includes the source number and trigger type.
Signed-off-by: Kwanghoon Son <asdfkwang@gmail.com>
---
.../devicetree/bindings/interrupt-controller/sifive,plic-1.0.0.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/interrupt-controller/sifive,plic-1.0.0.yaml b/Documentation/devicetree/bindings/interrupt-controller/sifive,plic-1.0.0.yaml
index cde4bb6918978b40037774269cee85212d39d4aa..9c625dff1968ae6267b0ab65ed8d4e771c70c3ec 100644
--- a/Documentation/devicetree/bindings/interrupt-controller/sifive,plic-1.0.0.yaml
+++ b/Documentation/devicetree/bindings/interrupt-controller/sifive,plic-1.0.0.yaml
@@ -72,6 +72,7 @@ properties:
- items:
- enum:
- allwinner,sun20i-d1-plic
+ - bflb,bl808-plic
- canaan,k230-plic
- sophgo,cv1800b-plic
- sophgo,cv1812h-plic
--
2.47.3
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH RFC 03/12] dt-bindings: timer: Add BL808 CLINT
2026-10-29 5:05 [PATCH RFC 00/12] Add BL808 UART3 support with a Rust serial-core polling console Kwanghoon Son
2026-10-29 5:05 ` [PATCH RFC 01/12] dt-bindings: vendor-prefixes: Add Bouffalo Lab Kwanghoon Son
2026-10-29 5:05 ` [PATCH RFC 02/12] dt-bindings: interrupt-controller: Add BL808 PLIC Kwanghoon Son
@ 2026-10-29 5:05 ` Kwanghoon Son
2026-10-29 5:12 ` sashiko-bot
2026-10-29 5:05 ` [PATCH RFC 04/12] dt-bindings: riscv: Add Bouffalo Lab BL808 Kwanghoon Son
` (8 subsequent siblings)
11 siblings, 1 reply; 24+ messages in thread
From: Kwanghoon Son @ 2026-10-29 5:05 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Daniel Almeida, Tamir Duberstein, Alexandre Courbot,
Onur Özkan, Thomas Gleixner, Radu Rendec, Paul Walmsley,
Samuel Holland, Palmer Dabbelt, Daniel Lezcano, Anup Patel,
Kwanghoon Son, Albert Ou, Alexandre Ghiti, Greg Kroah-Hartman,
Jiri Slaby
Cc: devicetree, linux-kernel, rust-for-linux, Paul Walmsley,
linux-riscv, linux-serial
The BL808 D0 processor uses the T-HEAD C900 local interruptor. Add its
SoC-specific compatible with the existing thead,c900-clint fallback,
which accounts for the lack of a memory-mapped MTIME register.
Signed-off-by: Kwanghoon Son <asdfkwang@gmail.com>
---
Documentation/devicetree/bindings/timer/sifive,clint.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/timer/sifive,clint.yaml b/Documentation/devicetree/bindings/timer/sifive,clint.yaml
index d53f52e80d887b40c0d20049d86867c8a5b6fae2..3934c0842751e02174c1be6074548e3e1e8f11fd 100644
--- a/Documentation/devicetree/bindings/timer/sifive,clint.yaml
+++ b/Documentation/devicetree/bindings/timer/sifive,clint.yaml
@@ -49,6 +49,7 @@ properties:
- items:
- enum:
- allwinner,sun20i-d1-clint
+ - bflb,bl808-clint
- canaan,k230-clint
- sophgo,cv1800b-clint
- sophgo,cv1812h-clint
--
2.47.3
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH RFC 04/12] dt-bindings: riscv: Add Bouffalo Lab BL808
2026-10-29 5:05 [PATCH RFC 00/12] Add BL808 UART3 support with a Rust serial-core polling console Kwanghoon Son
` (2 preceding siblings ...)
2026-10-29 5:05 ` [PATCH RFC 03/12] dt-bindings: timer: Add BL808 CLINT Kwanghoon Son
@ 2026-10-29 5:05 ` Kwanghoon Son
2026-10-29 5:11 ` sashiko-bot
2026-10-29 5:05 ` [PATCH RFC 05/12] dt-bindings: serial: Add BL808 UART Kwanghoon Son
` (7 subsequent siblings)
11 siblings, 1 reply; 24+ messages in thread
From: Kwanghoon Son @ 2026-10-29 5:05 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Daniel Almeida, Tamir Duberstein, Alexandre Courbot,
Onur Özkan, Thomas Gleixner, Radu Rendec, Paul Walmsley,
Samuel Holland, Palmer Dabbelt, Daniel Lezcano, Anup Patel,
Kwanghoon Son, Albert Ou, Alexandre Ghiti, Greg Kroah-Hartman,
Jiri Slaby
Cc: devicetree, linux-kernel, rust-for-linux, Paul Walmsley,
linux-riscv, linux-serial
Describe the Sipeed M1s Dock carrier, M1s system-on-module and BL808 SoC
compatible chain. Initial Linux support targets the 64-bit D0 processor.
Use the bflb vendor prefix shared with Zephyr.
Signed-off-by: Kwanghoon Son <asdfkwang@gmail.com>
---
Documentation/devicetree/bindings/riscv/bflb.yaml | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/Documentation/devicetree/bindings/riscv/bflb.yaml b/Documentation/devicetree/bindings/riscv/bflb.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..a3c14fcc6c2e9fd6297cc2556c1525f4bf8e90c4
--- /dev/null
+++ b/Documentation/devicetree/bindings/riscv/bflb.yaml
@@ -0,0 +1,21 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/riscv/bflb.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Bouffalo Lab RISC-V boards
+
+maintainers:
+ - Kwanghoon Son <asdfkwang@gmail.com>
+
+properties:
+ $nodename:
+ const: '/'
+ compatible:
+ items:
+ - const: sipeed,m1s-dock
+ - const: sipeed,m1s
+ - const: bflb,bl808
+
+additionalProperties: true
--
2.47.3
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH RFC 05/12] dt-bindings: serial: Add BL808 UART
2026-10-29 5:05 [PATCH RFC 00/12] Add BL808 UART3 support with a Rust serial-core polling console Kwanghoon Son
` (3 preceding siblings ...)
2026-10-29 5:05 ` [PATCH RFC 04/12] dt-bindings: riscv: Add Bouffalo Lab BL808 Kwanghoon Son
@ 2026-10-29 5:05 ` Kwanghoon Son
2026-10-29 5:15 ` sashiko-bot
2026-10-29 5:05 ` [PATCH RFC 06/12] rust: serial: Add minimal serial-core polling console support Kwanghoon Son
` (6 subsequent siblings)
11 siblings, 1 reply; 24+ messages in thread
From: Kwanghoon Son @ 2026-10-29 5:05 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Daniel Almeida, Tamir Duberstein, Alexandre Courbot,
Onur Özkan, Thomas Gleixner, Radu Rendec, Paul Walmsley,
Samuel Holland, Palmer Dabbelt, Daniel Lezcano, Anup Patel,
Kwanghoon Son, Albert Ou, Alexandre Ghiti, Greg Kroah-Hartman,
Jiri Slaby
Cc: devicetree, linux-kernel, rust-for-linux, Paul Walmsley,
linux-riscv, linux-serial
Describe the UART register range, interrupt and baud clock input.
Initial Linux support targets the BL808 32-byte FIFO and reuses
Zephyr's bflb,uart compatible. UART3 is D0 PLIC source 20.
Link: https://docs.zephyrproject.org/latest/build/dts/api/bindings/serial/bflb,uart.html
Signed-off-by: Kwanghoon Son <asdfkwang@gmail.com>
---
.../devicetree/bindings/serial/bflb,uart.yaml | 57 ++++++++++++++++++++++
1 file changed, 57 insertions(+)
diff --git a/Documentation/devicetree/bindings/serial/bflb,uart.yaml b/Documentation/devicetree/bindings/serial/bflb,uart.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..df29d258f0a1c6d7bb283cf19c4157436991b64b
--- /dev/null
+++ b/Documentation/devicetree/bindings/serial/bflb,uart.yaml
@@ -0,0 +1,57 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/serial/bflb,uart.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Bouffalo Lab UART Controller
+
+description: |
+ UART IP used by Bouffalo Lab SoCs. Initial Linux support covers the
+ BL808 controller with a 32-byte FIFO. The compatible follows Zephyr.
+
+maintainers:
+ - Kwanghoon Son <asdfkwang@gmail.com>
+
+allOf:
+ - $ref: serial.yaml#
+
+properties:
+ compatible:
+ const: bflb,uart
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ clocks:
+ items:
+ - description: Baud rate generator clock
+
+ clock-names:
+ items:
+ - const: baud
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+ - clock-names
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ uart3: serial@30002000 {
+ compatible = "bflb,uart";
+ reg = <0x30002000 0x1000>;
+ interrupts = <20 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&uart_baud>;
+ clock-names = "baud";
+ };
+...
--
2.47.3
^ permalink raw reply related [flat|nested] 24+ messages in thread* Re: [PATCH RFC 05/12] dt-bindings: serial: Add BL808 UART
2026-10-29 5:05 ` [PATCH RFC 05/12] dt-bindings: serial: Add BL808 UART Kwanghoon Son
@ 2026-10-29 5:15 ` sashiko-bot
0 siblings, 0 replies; 24+ messages in thread
From: sashiko-bot @ 2026-10-29 5:15 UTC (permalink / raw)
To: Kwanghoon Son; +Cc: devicetree, linux-serial, robh, conor+dt, ojeda, gary
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] dt-bindings: serial: missing SoC-specific compatible in bflb,uart.yaml
- [Low] dt-bindings: serial: unused label in bflb,uart.yaml example
--
commit 1bdd5b51639067c3a28b1a40beeb8fbfbbc65a65
Author: Kwanghoon Son <asdfkwang@gmail.com>
dt-bindings: serial: Add BL808 UART
This commit adds the Device Tree binding schema for the Bouffalo Lab UART
controller, describing its register range, interrupt, and baud clock input.
Link: https://docs.zephyrproject.org/latest/build/dts/api/bindings/serial/bflb,uart.html
> diff --git a/Documentation/devicetree/bindings/serial/bflb,uart.yaml b/Documentation/devicetree/bindings/serial/bflb,uart.yaml
> new file mode 100644
> index 0000000000000..df29d258f0a1c
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/serial/bflb,uart.yaml
> @@ -0,0 +1,57 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/serial/bflb,uart.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Bouffalo Lab UART Controller
> +
> +description: |
> + UART IP used by Bouffalo Lab SoCs. Initial Linux support covers the
> + BL808 controller with a 32-byte FIFO. The compatible follows Zephyr.
> +
> +maintainers:
> + - Kwanghoon Son <asdfkwang@gmail.com>
> +
> +allOf:
> + - $ref: serial.yaml#
> +
> +properties:
> + compatible:
> + const: bflb,uart
[Severity: Medium]
Should the compatible property in Documentation/devicetree/bindings/serial/bflb,uart.yaml
include a SoC-specific compatible string such as bflb,bl808-uart (either
alone or with bflb,uart as a fallback), and should the description section
describe the hardware rather than what Linux currently supports?
Documentation/devicetree/bindings/writing-bindings.rst requires SoC-specific
compatible strings for SoC devices rather than generic vendor-wide names, and
notes that bindings should describe the hardware rather than what an OS or
driver currently supports.
In the accompanying driver (drivers/tty/serial/bflb_uart.rs), tx_empty()
relies on a hardcoded 32-byte FIFO depth to check whether all TX FIFO slots
are free:
const FIFO_DEPTH: u32 = 32;
...
fn tx_empty(&self) -> bool {
self.read::<FIFO_CONFIG1>() & TX_FIFO_CNT_MASK == FIFO_DEPTH
}
Without a SoC-specific compatible string like bflb,bl808-uart, Device Trees
using only bflb,uart cannot distinguish the BL808 UART from other Bouffalo Lab
SoCs with different FIFO depths or hardware capabilities without breaking the
DT ABI.
[ ... ]
> +examples:
> + - |
> + #include <dt-bindings/interrupt-controller/irq.h>
> +
> + uart3: serial@30002000 {
> + compatible = "bflb,uart";
[Severity: Low]
Can the unused uart3 label be dropped from the serial@30002000 node in the
examples section of Documentation/devicetree/bindings/serial/bflb,uart.yaml?
Because uart3 is never referenced by a phandle in the example, running
scripts/dtc/dt-check-style --mode=strict reports an unused-labels warning in
check_unused_labels().
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029-rust-bl808-console-v1-0-b736ee920078@gmail.com?part=5
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH RFC 06/12] rust: serial: Add minimal serial-core polling console support
2026-10-29 5:05 [PATCH RFC 00/12] Add BL808 UART3 support with a Rust serial-core polling console Kwanghoon Son
` (4 preceding siblings ...)
2026-10-29 5:05 ` [PATCH RFC 05/12] dt-bindings: serial: Add BL808 UART Kwanghoon Son
@ 2026-10-29 5:05 ` Kwanghoon Son
2026-10-29 5:37 ` sashiko-bot
2026-10-29 5:05 ` [PATCH RFC 07/12] serial: bflb: Add Rust BL808 polling console Kwanghoon Son
` (5 subsequent siblings)
11 siblings, 1 reply; 24+ messages in thread
From: Kwanghoon Son @ 2026-10-29 5:05 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Daniel Almeida, Tamir Duberstein, Alexandre Courbot,
Onur Özkan, Thomas Gleixner, Radu Rendec, Paul Walmsley,
Samuel Holland, Palmer Dabbelt, Daniel Lezcano, Anup Patel,
Kwanghoon Son, Albert Ou, Alexandre Ghiti, Greg Kroah-Hartman,
Jiri Slaby
Cc: devicetree, linux-kernel, rust-for-linux, Paul Walmsley,
linux-riscv, linux-serial
Add the serial-core abstractions needed to write polling console drivers
in Rust, including Driver, SerialOps, Port, LockedPort and Termios.
Use owned, device-managed port registrations to keep hardware data and
the driver alive until serial-core users and console readers are drained.
Callback-scoped Port and LockedPort views provide access to the parent
device and UART lock context.
The adapters acquire the UART lock for termios changes and normal console
writes. Emergency output may proceed after a failed trylock. TTY device
registration remains in C serial core; userspace TTY I/O, RX, interrupts
and DMA are not implemented.
Signed-off-by: Kwanghoon Son <asdfkwang@gmail.com>
---
drivers/tty/serial/Kconfig | 8 +
rust/bindings/bindings_helper.h | 2 +
rust/helpers/helpers.c | 3 +
rust/helpers/serial.c | 33 +++
rust/kernel/lib.rs | 2 +
rust/kernel/serial.rs | 505 ++++++++++++++++++++++++++++++++++++++++
rust/kernel/serial/console.rs | 107 +++++++++
rust/kernel/serial/termios.rs | 104 +++++++++
8 files changed, 764 insertions(+)
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index 3864cef711a667b294f3205c220b3ab34aa3d2c9..9b17d451f19df6575bc8a4a08245065f9b8e3715 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -18,6 +18,14 @@ source "drivers/tty/serial/8250/Kconfig"
comment "Non-8250 serial port support"
+config RUST_SERIAL_ABSTRACTIONS
+ bool
+ depends on RUST
+ select SERIAL_CORE
+ select SERIAL_CORE_CONSOLE
+ help
+ This enables Rust abstractions for the serial core API.
+
config SERIAL_AMBA_PL010
tristate "ARM AMBA PL010 serial port support"
depends on ARM_AMBA || COMPILE_TEST
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 3791af0aadc73e9919e8536039b402a8f38b0378..65eb55b79b80d7e036c0b8eac58074871bce0fb6 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -88,6 +88,8 @@
#include <linux/sched.h>
#include <linux/security.h>
#include <linux/serdev.h>
+#include <linux/serial_core.h>
+#include <linux/console.h>
#include <linux/slab.h>
#include <linux/sys_soc.h>
#include <linux/task_work.h>
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index d62c7be1ca7bd9cb805c8e2c46975d077b2918f9..b3074f580c2b6273499a0f46570e4f00082017db 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -95,6 +95,9 @@
#include "scatterlist.c"
#include "security.c"
#include "serdev.c"
+#ifdef CONFIG_RUST_SERIAL_ABSTRACTIONS
+#include "serial.c"
+#endif
#include "signal.c"
#include "slab.c"
#include "spinlock.c"
diff --git a/rust/helpers/serial.c b/rust/helpers/serial.c
new file mode 100644
index 0000000000000000000000000000000000000000..3d303dfd56207942e3579b1c81ad489521374db8
--- /dev/null
+++ b/rust/helpers/serial.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/serial_core.h>
+#include <linux/console.h>
+
+__rust_helper
+void rust_helper_uart_port_lock_irqsave(struct uart_port *port, unsigned long *flags)
+{
+ uart_port_lock_irqsave(port, flags);
+}
+
+__rust_helper
+void rust_helper_uart_port_unlock_irqrestore(struct uart_port *port, unsigned long flags)
+{
+ uart_port_unlock_irqrestore(port, flags);
+}
+
+__rust_helper
+void rust_helper_uart_port_set_boot_autoconf(struct uart_port *port)
+{
+ port->flags = UPF_BOOT_AUTOCONF | UPF_FIXED_PORT | UPF_FIXED_TYPE;
+}
+
+#ifdef CONFIG_SERIAL_CORE_CONSOLE
+__rust_helper
+bool rust_helper_uart_port_console_lock(struct uart_port *port, unsigned long *flags)
+{
+ if (oops_in_progress)
+ return uart_port_trylock_irqsave(port, flags);
+ uart_port_lock_irqsave(port, flags);
+ return true;
+}
+#endif
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 1ab41129d557a97c09b4fb1bfc6aadb4250156a7..ef1c67826e6b131618eacb53901dd2e8c213e76c 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -132,6 +132,8 @@
pub mod seq_file;
#[cfg(CONFIG_RUST_SERIAL_DEV_BUS_ABSTRACTIONS)]
pub mod serdev;
+#[cfg(CONFIG_RUST_SERIAL_ABSTRACTIONS)]
+pub mod serial;
pub mod sizes;
#[cfg(CONFIG_SOC_BUS)]
pub mod soc;
diff --git a/rust/kernel/serial.rs b/rust/kernel/serial.rs
new file mode 100644
index 0000000000000000000000000000000000000000..f24c685d8592fe91e927a52c116aba9820378ac6
--- /dev/null
+++ b/rust/kernel/serial.rs
@@ -0,0 +1,505 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Serial-core support for memory-mapped, two-wire polling consoles.
+//!
+//! C header: [`include/linux/serial_core.h`](srctree/include/linux/serial_core.h).
+//!
+//! [`Driver`] owns the serial driver and its legacy console. Each device-managed
+//! port owns its hardware data and retains the driver until removal completes.
+//! Registration failures release unpublished data, or remove a published port if
+//! devres registration fails. Removal drains serial-core users and console SRCU
+//! readers before releasing hardware data and the driver reference.
+//! Device-managed resources can be revoked before port removal; callbacks must
+//! use checked resource access rather than assume the parent is still bound.
+//!
+//! The adapters acquire the UART lock for [`SerialOps::set_termios`] and normal
+//! console writes. Oops output may proceed without the lock after a failed
+//! trylock; it never receives a [`LockedPort`]. Callbacks must not sleep.
+//! TTY registration stays in C serial core; TTY startup returns `EOPNOTSUPP`.
+//! IRQ-driven I/O, reception and DMA are outside this polling interface.
+
+use crate::{
+ bindings,
+ device::{
+ Bound,
+ Device, //
+ },
+ devres,
+ error::to_result,
+ prelude::*,
+ sync::{
+ aref::ARef,
+ Arc, //
+ },
+ types::Opaque,
+ ThisModule, //
+};
+use core::marker::PhantomData;
+
+mod console;
+mod termios;
+
+use self::console::ConsoleAdapter;
+pub use self::termios::Termios;
+
+/// A registered serial driver. Ports retain a reference until removal completes.
+#[pin_data(PinnedDrop)]
+pub struct Driver<T: SerialOps> {
+ _type: PhantomData<T>,
+ config: DriverConfig,
+ #[pin]
+ console: Opaque<bindings::console>,
+ #[pin]
+ inner: Opaque<bindings::uart_driver>,
+}
+
+// SAFETY: The C objects are pinned. uart_driver state is allocated before publication;
+// port_mutex serializes port changes. Each port retains an Arc until removal finishes.
+unsafe impl<T: SerialOps> Send for Driver<T> {}
+// SAFETY: C fields have interior mutability through Opaque. Console-list updates use
+// console_list_lock and console_srcu; active console callbacks finish before removal.
+// Driver names and callback pointers are immutable after initialization.
+unsafe impl<T: SerialOps> Sync for Driver<T> {}
+
+impl<T: SerialOps> Driver<T> {
+ /// Registers a two-wire polling console with dynamic TTY device numbers.
+ ///
+ /// `config.default_baud` supplies the initial default console rate.
+ pub fn new(config: DriverConfig, module: &'static ThisModule) -> impl PinInit<Self, Error> {
+ pin_init::pin_init_scope(move || {
+ config.validate()?;
+ Ok(try_pin_init!(&this in Self {
+ _type: PhantomData,
+ config,
+ console <- Opaque::ffi_init(|ptr: *mut bindings::console| {
+ let mut console = bindings::console {
+ write: Some(ConsoleAdapter::<T>::console_write),
+ setup: Some(ConsoleAdapter::<T>::console_setup),
+ device: Some(bindings::uart_console_device),
+ flags: bindings::cons_flags_CON_PRINTBUFFER as i16,
+ index: -1,
+ ispeed: config.default_baud,
+ ospeed: config.default_baud,
+ ..Default::default()
+ };
+ // SAFETY: The validated name fits with its trailing NUL, supplied by
+ // the zeroed array. The source and destination do not overlap, and the
+ // initializer exclusively owns the pinned destination slot.
+ unsafe {
+ core::ptr::copy_nonoverlapping(
+ config.device_name.as_char_ptr(),
+ console.name.as_mut_ptr(),
+ config.device_name.to_bytes().len(),
+ );
+ ptr.write(console);
+ }
+ }),
+ inner <- Opaque::try_ffi_init(move |ptr: *mut bindings::uart_driver| {
+ // SAFETY: The preceding field initializer initialized the pinned console.
+ let console = unsafe { (*this.as_ptr()).console.get() };
+ let driver = bindings::uart_driver {
+ owner: module.as_ptr(),
+ driver_name: config.name.as_char_ptr(),
+ dev_name: config.device_name.as_char_ptr(),
+ nr: config.ports as i32,
+ cons: console,
+ ..Default::default()
+ };
+ // SAFETY: Both objects are private and pinned. No callback can use these
+ // reciprocal pointers before registration below publishes the driver.
+ unsafe {
+ ptr.write(driver);
+ (*console).data = ptr.cast();
+ }
+ // SAFETY: All C fields and callbacks are initialized. Names and module
+ // outlive registration; ports retain the driver until removal completes.
+ to_result(unsafe { bindings::uart_register_driver(ptr) })
+ }),
+ }))
+ })
+ }
+}
+
+/// Configuration for a serial driver and its polling console.
+#[derive(Clone, Copy)]
+pub struct DriverConfig {
+ /// Driver name used for serial-core reporting.
+ pub name: &'static CStr,
+ /// TTY and console name prefix.
+ pub device_name: &'static CStr,
+ /// Number of serial-core lines owned by this driver.
+ pub ports: u32,
+ /// Initial baud rate when console options do not supply one.
+ pub default_baud: u32,
+}
+
+impl DriverConfig {
+ fn validate(&self) -> Result {
+ // struct console uses name[16] and a signed short index. Its setup
+ // callback passes the baud rate to uart_set_options() as a signed int.
+ const CONSOLE_NAME_LEN: usize = 16;
+ if self.ports == 0
+ || self.ports > i16::MAX as u32
+ || self.default_baud == 0
+ || self.default_baud > i32::MAX as u32
+ || self.device_name.to_bytes().len() >= CONSOLE_NAME_LEN
+ {
+ return Err(EINVAL);
+ }
+ Ok(())
+ }
+}
+
+#[pinned_drop]
+impl<T: SerialOps> PinnedDrop for Driver<T> {
+ fn drop(self: Pin<&mut Self>) {
+ // SAFETY: Registration succeeded and every port holds an Arc to this driver.
+ unsafe { bindings::uart_unregister_driver(self.inner.get()) };
+ }
+}
+
+/// Serial hardware callbacks currently needed by a two-wire polling console.
+///
+/// This is a subset of serial-core operations, not a full UART interface.
+/// Reception, interrupt-driven transmission and DMA are not supported.
+///
+/// Callbacks must not sleep or allocate. The adapters lock the UART and save local
+/// IRQ state for `set_termios` and normal `console_write` calls. During an oops,
+/// console output may run without the lock. `tx_empty` acquires no lock and may
+/// be called with the UART lock already held.
+///
+/// The parent device allocation remains live, but its managed resources may
+/// already have been revoked. Callbacks must check access to those resources.
+pub trait SerialOps: Send + Sync + Sized + 'static {
+ /// Whether both the transmit FIFO and shift register are empty.
+ fn tx_empty(&self, port: &Port<'_>) -> bool;
+ /// Applies console framing and baud rate, reflecting unsupported modes in `new`.
+ ///
+ /// The UART lock is held and local IRQs are disabled throughout this call.
+ fn set_termios(&self, port: &LockedPort<'_>, new: &mut Termios, old: Option<&Termios>);
+ /// Writes a console record using bounded polling, including during an oops.
+ fn console_write(&self, port: &Port<'_>, bytes: &[u8]);
+}
+
+/// Configuration supplied before registering a memory-mapped UART.
+pub struct PortConfig {
+ /// Serial-core line index.
+ pub line: u32,
+ /// Physical register base, for serial-core reporting.
+ pub mapbase: bindings::resource_size_t,
+ /// Baud generator frequency in Hz.
+ pub clock: u32,
+ /// Hardware FIFO capacity in bytes.
+ pub fifo_size: u32,
+}
+
+/// Registers an owned port until the device is unbound.
+///
+/// All state is owned and `'static`; no callback can retain a borrow into probe's
+/// stack. The registration is transferred to devres rather than returned to the
+/// caller. Forgetting a caller-owned object cannot leave borrowed data registered.
+/// On devres allocation failure, the registration is removed before returning.
+pub fn register_port<T: SerialOps>(
+ driver: Arc<Driver<T>>,
+ device: &Device<Bound>,
+ config: PortConfig,
+ data: impl PinInit<T, Error>,
+) -> Result {
+ if config.line >= driver.config.ports {
+ return Err(EINVAL);
+ }
+ let port = KBox::pin_init(
+ PortData::new(driver, device.into(), config, data),
+ GFP_KERNEL,
+ )?;
+ // SAFETY: All callback state is initialized and pinned. The device borrow keeps
+ // it bound until devres takes ownership. C retains nothing on registration failure.
+ unsafe {
+ to_result(bindings::uart_add_one_port(
+ port.driver.inner.get(),
+ port.inner.get(),
+ ))?;
+ }
+ devres::register(device, Registration { port }, GFP_KERNEL)
+}
+
+#[pin_data]
+struct PortData<T: SerialOps> {
+ #[pin]
+ inner: Opaque<bindings::uart_port>,
+ #[pin]
+ data: T,
+ driver: Arc<Driver<T>>,
+ device: ARef<Device>,
+}
+
+// SAFETY: uart_port's mutable C fields live in Opaque and use serial-core locking.
+// Data is Send + Sync; device/driver references keep their allocations alive.
+unsafe impl<T: SerialOps> Send for PortData<T> {}
+// SAFETY: Rust-owned fields are immutable after publication. C state changes use
+// port_mutex, tty_port.mutex, the UART lock and console-list/SRCU synchronization.
+unsafe impl<T: SerialOps> Sync for PortData<T> {}
+
+impl<T: SerialOps> PortData<T> {
+ fn new(
+ driver: Arc<Driver<T>>,
+ device: ARef<Device>,
+ config: PortConfig,
+ data: impl PinInit<T, Error>,
+ ) -> impl PinInit<Self, Error> {
+ try_pin_init!(&this in Self {
+ data <- data,
+ driver,
+ device,
+ inner <- Opaque::ffi_init(|ptr: *mut bindings::uart_port| {
+ let port = bindings::uart_port {
+ dev: device.as_raw(),
+ line: config.line,
+ mapbase: config.mapbase,
+ uartclk: config.clock,
+ fifosize: config.fifo_size,
+ iotype: bindings::uart_iotype_UPIO_MEM,
+ type_: bindings::PORT_GENERIC as u32,
+ ops: &SerialAdapter::<T>::OPS,
+ private_data: this.as_ptr().cast(),
+ ..Default::default()
+ };
+ // SAFETY: The slot is private and exclusively owned until registration.
+ unsafe {
+ ptr.write(port);
+ bindings::uart_port_set_boot_autoconf(ptr);
+ }
+ }),
+ })
+ }
+
+ /// # Safety
+ /// `ptr` must be a PortData<T> port in a serial-core callback. Registration
+ /// owns its allocation until uart_remove_one_port returns; removal waits for
+ /// console_srcu readers and serial-core users before this port is freed.
+ /// Other device-managed resources may already have been revoked.
+ unsafe fn with_port<R>(
+ ptr: *mut bindings::uart_port,
+ f: impl for<'a> FnOnce(&'a Self, &Port<'a>) -> R,
+ ) -> R {
+ // SAFETY: The private_data type is fixed by Driver<T>/register_port<T>.
+ let state = unsafe { &*(*ptr).private_data.cast::<Self>() };
+ f(
+ state,
+ &Port {
+ raw: ptr,
+ device: &state.device,
+ },
+ )
+ }
+}
+
+/// Converts serial-core callbacks to the hardware operations and lock contexts.
+struct SerialAdapter<T: SerialOps>(PhantomData<T>);
+
+impl<T: SerialOps> SerialAdapter<T> {
+ const OPS: bindings::uart_ops = bindings::uart_ops {
+ tx_empty: Some(Self::tx_empty),
+ set_mctrl: Some(Self::set_mctrl),
+ get_mctrl: Some(Self::get_mctrl),
+ start_tx: Some(Self::inactive),
+ stop_tx: Some(Self::inactive),
+ stop_rx: Some(Self::inactive),
+ startup: Some(Self::startup),
+ shutdown: Some(Self::inactive),
+ set_termios: Some(Self::set_termios),
+ type_: Some(Self::port_type),
+ config_port: Some(Self::config_port),
+ verify_port: Some(Self::verify_port),
+ ..pin_init::zeroed()
+ };
+
+ /// # Safety
+ /// Must be called by serial core with a live port under the uart_ops contract.
+ unsafe extern "C" fn tx_empty(ptr: *mut bindings::uart_port) -> u32 {
+ // SAFETY: Serial core retains the registered port throughout this callback.
+ unsafe {
+ PortData::<T>::with_port(ptr, |state, port| {
+ if state.data.tx_empty(port) {
+ bindings::TIOCSER_TEMT
+ } else {
+ 0
+ }
+ })
+ }
+ }
+
+ // Serial core requires these modem callbacks. This two-wire interface has no
+ // modem outputs and reports carrier/DSR/CTS asserted without hardware access.
+ extern "C" fn set_mctrl(_ptr: *mut bindings::uart_port, _value: u32) {}
+
+ extern "C" fn get_mctrl(_ptr: *mut bindings::uart_port) -> u32 {
+ bindings::TIOCM_CAR | bindings::TIOCM_DSR | bindings::TIOCM_CTS
+ }
+
+ // Required TTY operations remain inactive for this output-only console.
+ extern "C" fn inactive(_ptr: *mut bindings::uart_port) {}
+
+ extern "C" fn startup(_ptr: *mut bindings::uart_port) -> i32 {
+ EOPNOTSUPP.to_errno()
+ }
+
+ /// # Safety
+ /// Must be called by serial core with a live port under the uart_ops contract.
+ unsafe extern "C" fn set_termios(
+ ptr: *mut bindings::uart_port,
+ new: *mut bindings::ktermios,
+ old: *const bindings::ktermios,
+ ) {
+ // SAFETY: Serial core supplies live termios pointers and holds the port
+ // registration through this callback. No view or termios reference escapes.
+ unsafe {
+ PortData::<T>::with_port(ptr, |state, port| {
+ port.with_lock(|locked| {
+ state.data.set_termios(
+ locked,
+ Termios::from_raw_mut(new),
+ old.as_ref().map(|termios| Termios::from_raw(termios)),
+ );
+ });
+ });
+ }
+ }
+
+ /// # Safety
+ /// Must be called by serial core with a live port under the uart_ops contract.
+ unsafe extern "C" fn port_type(ptr: *mut bindings::uart_port) -> *const crate::ffi::c_char {
+ // SAFETY: The registered port owns its hardware data and driver throughout
+ // the callback. The returned name is an immutable static C string.
+ unsafe { PortData::<T>::with_port(ptr, |state, _| state.driver.config.name.as_char_ptr()) }
+ }
+
+ /// # Safety
+ /// Must be called by serial core with a live port under the uart_ops contract.
+ unsafe extern "C" fn config_port(ptr: *mut bindings::uart_port, flags: i32) {
+ if flags & bindings::UART_CONFIG_TYPE as i32 != 0 {
+ // SAFETY: Serial core serializes port configuration.
+ unsafe { (*ptr).type_ = bindings::PORT_GENERIC as u32 };
+ }
+ }
+
+ /// # Safety
+ /// Must be called by serial core with a live port under the uart_ops contract.
+ unsafe extern "C" fn verify_port(
+ _ptr: *mut bindings::uart_port,
+ serial: *mut bindings::serial_struct,
+ ) -> i32 {
+ // SAFETY: Serial core supplies a valid serial_struct for the callback.
+ let kind = unsafe { (*serial).type_ };
+ if kind == bindings::PORT_UNKNOWN as i32 || kind == bindings::PORT_GENERIC {
+ 0
+ } else {
+ EINVAL.to_errno()
+ }
+ }
+}
+
+struct Registration<T: SerialOps> {
+ port: Pin<KBox<PortData<T>>>,
+}
+
+impl<T: SerialOps> Drop for Registration<T> {
+ fn drop(&mut self) {
+ // SAFETY: Only successful registration constructs this guard. Serial core
+ // hangs up TTY users, unregisters the console, waits for console_srcu readers
+ // and drains uart_state references before returning. The allocation, hardware
+ // data and Driver Arc are released only after this synchronization.
+ unsafe {
+ bindings::uart_remove_one_port(self.port.driver.inner.get(), self.port.inner.get())
+ };
+ }
+}
+
+/// A callback-scoped view of a registered port.
+///
+/// Instances are only created by the FFI adapters. The registration and its
+/// device allocation remain live throughout the callback. Managed hardware
+/// resources need their own revocation-checked access guard.
+/// This view exposes the callback context independently of the hardware data type.
+///
+/// # Invariants
+///
+/// `raw` points to a registered port belonging to `device`. The FFI adapter keeps
+/// the port and its device allocation live for the entire borrow.
+pub struct Port<'a> {
+ raw: *mut bindings::uart_port,
+ device: &'a Device,
+}
+
+impl Port<'_> {
+ /// The parent device. Managed resources require revocation-checked access.
+ pub fn device(&self) -> &Device {
+ self.device
+ }
+
+ /// Runs a closure with the serial-core UART lock held and local IRQs saved.
+ fn with_lock<R>(&self, f: impl FnOnce(&LockedPort<'_>) -> R) -> R {
+ let guard = PortGuard::lock_irqsave(self);
+ let locked = LockedPort {
+ raw: self.raw,
+ device: self.device(),
+ };
+ let result = f(&locked);
+ drop(guard);
+ result
+ }
+}
+
+struct PortGuard<'a> {
+ port: &'a Port<'a>,
+ flags: crate::ffi::c_ulong,
+ unlock: bool,
+}
+
+impl<'a> PortGuard<'a> {
+ fn lock_irqsave(port: &'a Port<'_>) -> Self {
+ let mut flags = 0;
+ // SAFETY: The port remains live for this borrow. The caller does not
+ // already hold its UART lock.
+ unsafe { bindings::uart_port_lock_irqsave(port.raw, &mut flags) };
+ Self {
+ port,
+ flags,
+ unlock: true,
+ }
+ }
+
+ fn console_lock(port: &'a Port<'_>) -> Self {
+ let mut flags = 0;
+ // SAFETY: The port remains live for this borrow. The C helper permits
+ // emergency output without the lock if its trylock fails.
+ let unlock = unsafe { bindings::uart_port_console_lock(port.raw, &mut flags) };
+ Self {
+ port,
+ flags,
+ unlock,
+ }
+ }
+}
+
+impl Drop for PortGuard<'_> {
+ fn drop(&mut self) {
+ if self.unlock {
+ // SAFETY: The port is live and this guard acquired its UART lock.
+ unsafe { bindings::uart_port_unlock_irqrestore(self.port.raw, self.flags) };
+ }
+ }
+}
+
+/// A UART borrowed while its serial-core lock is held. Cannot escape the borrow.
+pub struct LockedPort<'a> {
+ raw: *mut bindings::uart_port,
+ device: &'a Device,
+}
+
+impl LockedPort<'_> {
+ /// The parent device. The UART lock does not prevent resource revocation.
+ pub fn device(&self) -> &Device {
+ self.device
+ }
+}
diff --git a/rust/kernel/serial/console.rs b/rust/kernel/serial/console.rs
new file mode 100644
index 0000000000000000000000000000000000000000..3562191eb7a937ca1b8d78be413c45f9de1d9b10
--- /dev/null
+++ b/rust/kernel/serial/console.rs
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Legacy polling console callbacks and emergency lock handling.
+
+use super::{
+ Port,
+ PortData,
+ PortGuard,
+ SerialOps, //
+};
+use crate::{
+ bindings,
+ prelude::*, //
+};
+use core::marker::PhantomData;
+
+/// Converts legacy console callbacks without owning a separate registration.
+pub(super) struct ConsoleAdapter<T: SerialOps>(PhantomData<T>);
+
+impl<T: SerialOps> ConsoleAdapter<T> {
+ /// # Safety
+ ///
+ /// `console` must belong to this adapter's live Driver<T>. The selected port
+ /// must remain live and its state must be serialized against removal. Console
+ /// core provides this through console_list_lock or console_srcu for writes.
+ unsafe fn console_port(console: *mut bindings::console) -> *mut bindings::uart_port {
+ // SAFETY: Serial core invokes callbacks only while this pinned console and driver
+ // are live. Port removal unregisters the console before releasing its port state.
+ unsafe {
+ let driver = (*console).data.cast::<bindings::uart_driver>();
+ if driver.is_null() || (*driver).state.is_null() {
+ return core::ptr::null_mut();
+ }
+ let index = (*console).index;
+ if index < 0 || i32::from(index) >= (*driver).nr {
+ return core::ptr::null_mut();
+ }
+ (*(*driver).state.add(index as usize)).uart_port
+ }
+ }
+
+ /// # Safety
+ ///
+ /// Console core must hold console_list_lock and supply this driver's live
+ /// console. `options` must be null or a valid NUL-terminated option string.
+ pub(super) unsafe extern "C" fn console_setup(
+ console: *mut bindings::console,
+ options: *mut crate::ffi::c_char,
+ ) -> i32 {
+ // SAFETY: Console core holds console_list_lock; serial core has published the port
+ // before registering this console. uart_set_options calls our termios adapter.
+ unsafe {
+ let port = Self::console_port(console);
+ if port.is_null() {
+ return ENODEV.to_errno();
+ }
+ let mut baud = (*console).ispeed as i32;
+ let (mut parity, mut bits, mut flow) = (i32::from(b'n'), 8, i32::from(b'n'));
+ if !options.is_null() {
+ bindings::uart_parse_options(options, &mut baud, &mut parity, &mut bits, &mut flow);
+ }
+ bindings::uart_set_options(port, console, baud, parity, bits, flow)
+ }
+ }
+
+ /// # Safety
+ ///
+ /// The caller must keep this driver's console and port live, normally with
+ /// console_srcu. For nonzero `count`, `text` must be valid for that many
+ /// initialized bytes within one allocation for the duration of this call.
+ pub(super) unsafe extern "C" fn console_write(
+ console: *mut bindings::console,
+ text: *const crate::ffi::c_char,
+ count: u32,
+ ) {
+ if count == 0 {
+ return;
+ }
+ // SAFETY: Legacy console writes hold console_srcu. unregister_console() waits
+ // for these readers before Registration releases the pinned port and data.
+ // Driver<T> only registers PortData<T>, so private_data has the matching type.
+ unsafe {
+ let port = Self::console_port(console);
+ if !port.is_null() {
+ PortData::<T>::with_port(port, |state, port| {
+ let bytes = core::slice::from_raw_parts(text.cast(), count as usize);
+ port.with_console_access(|| state.data.console_write(port, bytes));
+ });
+ }
+ }
+ }
+}
+
+impl Port<'_> {
+ /// Runs bounded, non-sleeping console I/O, including during emergency output.
+ ///
+ /// Normally the UART lock is held with local IRQs saved. During an oops a
+ /// failed trylock runs `f` without that lock: MMIO may race and output is
+ /// best effort. No LockedPort is provided, so locked serial-core state must
+ /// not be accessed here. Rust data shared by callbacks must remain Sync.
+ pub(super) fn with_console_access<R>(&self, f: impl FnOnce() -> R) -> R {
+ let guard = PortGuard::console_lock(self);
+ let result = f();
+ drop(guard);
+ result
+ }
+}
diff --git a/rust/kernel/serial/termios.rs b/rust/kernel/serial/termios.rs
new file mode 100644
index 0000000000000000000000000000000000000000..449662d9b6b5e634aacdd1e3b9016b0776a5afc3
--- /dev/null
+++ b/rust/kernel/serial/termios.rs
@@ -0,0 +1,104 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Termios views and serial-core configuration under the UART lock.
+
+use super::LockedPort;
+use crate::bindings;
+use core::num::NonZeroU32;
+
+/// Termios borrowed for a serial-core set_termios callback.
+#[repr(transparent)]
+pub struct Termios(bindings::ktermios);
+
+impl Termios {
+ /// Borrows a termios supplied by serial core.
+ ///
+ /// # Safety
+ ///
+ /// `ptr` must point to an initialized `ktermios` that remains valid and is
+ /// not mutated for the returned borrow's lifetime.
+ pub(super) unsafe fn from_raw<'a>(ptr: *const bindings::ktermios) -> &'a Self {
+ // SAFETY: The caller guarantees a valid shared borrow. Self is transparent.
+ unsafe { &*ptr.cast() }
+ }
+
+ /// Exclusively borrows a termios supplied by serial core.
+ ///
+ /// # Safety
+ ///
+ /// `ptr` must point to an initialized `ktermios` that remains valid and is
+ /// exclusively accessible for the returned borrow's lifetime.
+ pub(super) unsafe fn from_raw_mut<'a>(ptr: *mut bindings::ktermios) -> &'a mut Self {
+ // SAFETY: The caller guarantees an exclusive borrow. Self is transparent.
+ unsafe { &mut *ptr.cast() }
+ }
+
+ /// Whether parity generation and checking are enabled.
+ pub fn parity_enabled(&self) -> bool {
+ self.0.c_cflag & bindings::PARENB != 0
+ }
+
+ /// Whether odd parity, or mark parity in mark/space mode, is selected.
+ pub fn odd_parity(&self) -> bool {
+ self.0.c_cflag & bindings::PARODD != 0
+ }
+
+ /// Whether two stop bits are requested.
+ pub fn two_stop_bits(&self) -> bool {
+ self.0.c_cflag & bindings::CSTOPB != 0
+ }
+
+ /// Disables mark/space parity without changing parity enable or odd/even selection.
+ pub fn disable_mark_space_parity(&mut self) {
+ self.0.c_cflag &= !bindings::CMSPAR;
+ }
+
+ /// Disables RTS/CTS hardware flow control.
+ pub fn disable_hardware_flow_control(&mut self) {
+ self.0.c_cflag &= !bindings::CRTSCTS;
+ }
+
+ /// Data bits per character.
+ pub fn char_size(&self) -> u32 {
+ // SAFETY: c_cflag is a valid termios control flag value.
+ unsafe { bindings::tty_get_char_size(self.0.c_cflag).into() }
+ }
+}
+
+impl LockedPort<'_> {
+ /// Selects a supported baud rate using serial-core termios rules.
+ ///
+ /// Returns `None` if serial core fails to select a rate. Termios changes
+ /// made by serial core are preserved, including on failure.
+ pub fn baud_rate(
+ &self,
+ new: &mut Termios,
+ old: Option<&Termios>,
+ min: u32,
+ max: u32,
+ ) -> Option<NonZeroU32> {
+ // SAFETY: The port is locked, and the termios references are valid for this callback.
+ let baud = unsafe {
+ bindings::uart_get_baud_rate(
+ self.raw,
+ &mut new.0,
+ old.map_or(core::ptr::null(), |t| &t.0),
+ min,
+ max,
+ )
+ };
+ NonZeroU32::new(baud)
+ }
+
+ /// Updates serial-core timeout and the actual console baud rate.
+ pub fn configure(&self, new: &mut Termios, baud: NonZeroU32) {
+ let baud = baud.get();
+ // SAFETY: All port fields are accessed under the UART lock. new is exclusively borrowed.
+ unsafe {
+ bindings::uart_update_timeout(self.raw, new.0.c_cflag, baud);
+ if bindings::tty_termios_baud_rate(&new.0) != 0 {
+ bindings::tty_termios_encode_baud_rate(&mut new.0, baud, baud);
+ }
+ }
+ }
+}
--
2.47.3
^ permalink raw reply related [flat|nested] 24+ messages in thread* Re: [PATCH RFC 06/12] rust: serial: Add minimal serial-core polling console support
2026-10-29 5:05 ` [PATCH RFC 06/12] rust: serial: Add minimal serial-core polling console support Kwanghoon Son
@ 2026-10-29 5:37 ` sashiko-bot
0 siblings, 0 replies; 24+ messages in thread
From: sashiko-bot @ 2026-10-29 5:37 UTC (permalink / raw)
To: Kwanghoon Son; +Cc: devicetree, linux-serial, robh, conor+dt, ojeda, gary
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [Critical] rust: serial: use-after-free of uart_state on Driver::drop() with open tty
- [Medium] rust: serial: xmit_buf memory leak on TTY open when startup() fails
- [Medium] rust: serial: console_setup() and configure() corrupt console->ispeed
- [Low] rust: serial: missing INVARIANT comment in PortData::with_port()
- [Low] rust: serial: missing #[inline] annotations in serial abstractions
--
commit b586ab9682dee028e1eb2b244ed7a9b0fb3bdd94
Author: Kwanghoon Son <asdfkwang@gmail.com>
rust: serial: Add minimal serial-core polling console support
This commit adds serial-core abstractions in Rust for memory-mapped, two-wire
polling console drivers, including Driver, SerialOps, Port, LockedPort, and
Termios. It manages port lifetime via devres and provides callback adapters
for console output and termios configuration.
> diff --git a/rust/kernel/serial.rs b/rust/kernel/serial.rs
> new file mode 100644
> index 0000000000000..f24c685d8592f
> --- /dev/null
> +++ b/rust/kernel/serial.rs
> @@ -0,0 +1,505 @@
[ ... ]
> +#[pinned_drop]
> +impl<T: SerialOps> PinnedDrop for Driver<T> {
> + fn drop(self: Pin<&mut Self>) {
> + // SAFETY: Registration succeeded and every port holds an Arc to this driver.
> + unsafe { bindings::uart_unregister_driver(self.inner.get()) };
> + }
> +}
[Severity: Critical]
Can dropping Driver<T> after port removal cause a use-after-free of
drv->state when a TTY file descriptor is still open?
Driver::new() registers a struct tty_driver via uart_register_driver() and
sets console.device to uart_console_device, while register_port() publishes
the TTY device via uart_add_one_port(). Although SerialAdapter::startup()
returns -EOPNOTSUPP, uart_port_startup() in drivers/tty/serial/serial_core.c
turns a negative return value into 1 when capable(CAP_SYS_ADMIN) is true:
drivers/tty/serial/serial_core.c:uart_port_startup() {
...
retval = uport->ops->startup(uport);
if (retval == 0) {
...
}
if (retval && capable(CAP_SYS_ADMIN))
return 1;
return retval;
}
uart_open() -> tty_port_open() -> uart_port_activate() -> uart_startup()
then converts that positive return value into 0, allowing open() to succeed
with TTY_IO_ERROR set. When console_on_rootfs() opens /dev/console at boot,
or when a privileged userspace process opens /dev/console or the TTY device
node, it obtains an open struct tty_struct with tty->driver_data =
&drv->state[line] and tty->port = &drv->state[line].port. Even for
unprivileged callers, tty_init_dev() attaches tty->port before uart_open()
fails and tty_release() runs.
Because uart_register_driver() leaves tty_driver->owner set to serial_core's
THIS_MODULE rather than drv->owner, open TTY file descriptors do not pin the
driver module or Arc<Driver<T>>. When a device unbinds and Registration::drop()
calls uart_remove_one_port(), serial_core only calls tty_port_tty_vhangup()
(which does not free open tty_struct instances and skips /dev/console
redirected files) and waits on state->refcount (which only tracks transient
uart_port_ref() sections).
When the last Arc<Driver<T>> is then dropped, Driver::drop() calls
uart_unregister_driver(), which immediately frees drv->state:
drivers/tty/serial/serial_core.c:uart_unregister_driver() {
...
for (i = 0; i < drv->nr; i++)
tty_port_destroy(&drv->state[i].port);
kfree(drv->state);
}
When the open /dev/console or TTY file descriptor is later used or closed in
tty_release() -> uart_close() and release_tty() (which sets
tty->port->itty = NULL and calls tty_buffer_cancel_work(tty->port)), won't
the TTY layer dereference and mutate the freed drv->state memory?
[ ... ]
> + /// # Safety
> + /// `ptr` must be a PortData<T> port in a serial-core callback. Registration
> + /// owns its allocation until uart_remove_one_port returns; removal waits for
> + /// console_srcu readers and serial-core users before this port is freed.
> + /// Other device-managed resources may already have been revoked.
> + unsafe fn with_port<R>(
> + ptr: *mut bindings::uart_port,
> + f: impl for<'a> FnOnce(&'a Self, &Port<'a>) -> R,
> + ) -> R {
> + // SAFETY: The private_data type is fixed by Driver<T>/register_port<T>.
> + let state = unsafe { &*(*ptr).private_data.cast::<Self>() };
> + f(
> + state,
> + &Port {
> + raw: ptr,
> + device: &state.device,
> + },
> + )
> + }
> +}
[Severity: Low]
Since Port<'a> declares a type invariants section in its documentation,
should this Port construction in PortData::with_port() have an // INVARIANT:
comment explaining why raw and device satisfy the invariants?
[ ... ]
> + // Required TTY operations remain inactive for this output-only console.
> + extern "C" fn inactive(_ptr: *mut bindings::uart_port) {}
> +
> + extern "C" fn startup(_ptr: *mut bindings::uart_port) -> i32 {
> + EOPNOTSUPP.to_errno()
> + }
[Severity: Medium]
Does returning -EOPNOTSUPP from SerialAdapter::startup() leak
state->port.xmit_buf whenever the TTY or /dev/console is opened?
When a process opens /dev/console (including console_on_rootfs() during boot)
or the registered TTY device node, uart_open() -> tty_port_open() ->
uart_port_activate() -> uart_startup() -> uart_port_startup() allocates a
page for state->port.xmit_buf before invoking uport->ops->startup(uport):
drivers/tty/serial/serial_core.c:uart_port_startup() {
...
retval = uart_alloc_xmit_buf(&state->port);
if (retval)
return retval;
retval = uport->ops->startup(uport);
...
}
Because SerialAdapter::startup() returns -EOPNOTSUPP, uart_port_startup()
returns a non-zero value without freeing state->port.xmit_buf. In
tty_port_open(), a non-zero return from port->ops->activate() causes an early
return before tty_port_set_initialized(port, true) is called:
drivers/tty/tty_port.c:tty_port_open() {
...
if (!tty_port_initialized(port)) {
clear_bit(TTY_IO_ERROR, &tty->flags);
retval = port->ops->activate(port, tty);
if (retval) {
mutex_unlock(&port->mutex);
return retval;
}
tty_port_set_initialized(port, true);
}
...
}
Because tty_port_initialized(port) remains false, neither
tty_port_shutdown_locked() on close nor serial_core_remove_one_port() on port
removal calls uart_shutdown() to free state->port.xmit_buf:
drivers/tty/serial/serial_core.c:serial_core_remove_one_port() {
...
scoped_guard(mutex, &port->mutex) {
port->console = false;
if (tty_port_initialized(port))
uart_shutdown(NULL, state);
}
...
}
And when Driver::drop() later calls uart_unregister_driver(), drv->state is
freed without freeing state->port.xmit_buf.
> diff --git a/rust/kernel/serial/console.rs b/rust/kernel/serial/console.rs
> new file mode 100644
> index 0000000000000..3562191eb7a93
> --- /dev/null
> +++ b/rust/kernel/serial/console.rs
> @@ -0,0 +1,107 @@
[ ... ]
> + /// # Safety
> + ///
> + /// Console core must hold console_list_lock and supply this driver's live
> + /// console. `options` must be null or a valid NUL-terminated option string.
> + pub(super) unsafe extern "C" fn console_setup(
> + console: *mut bindings::console,
> + options: *mut crate::ffi::c_char,
> + ) -> i32 {
> + // SAFETY: Console core holds console_list_lock; serial core has published the port
> + // before registering this console. uart_set_options calls our termios adapter.
> + unsafe {
> + let port = Self::console_port(console);
> + if port.is_null() {
> + return ENODEV.to_errno();
> + }
> + let mut baud = (*console).ispeed as i32;
> + let (mut parity, mut bits, mut flow) = (i32::from(b'n'), 8, i32::from(b'n'));
> + if !options.is_null() {
> + bindings::uart_parse_options(options, &mut baud, &mut parity, &mut bits, &mut flow);
> + }
> + bindings::uart_set_options(port, console, baud, parity, bits, flow)
> + }
> + }
[Severity: Medium]
Can ConsoleAdapter::console_setup() and LockedPort::configure() lose
config.default_baud and overwrite console->ispeed and console->ospeed with 0?
First, if non-null console options do not start with a positive baud rate,
uart_parse_options() calls simple_strtoul() and sets baud to 0, overwriting
the initial value read from (*console).ispeed rather than preserving
config.default_baud.
Second, uart_set_options() in drivers/tty/serial/serial_core.c constructs a
zeroed dummy ktermios and calls port->ops->set_termios(port, &termios, &dummy):
drivers/tty/serial/serial_core.c:uart_set_options() {
struct ktermios termios, dummy;
...
memset(&termios, 0, sizeof(struct ktermios));
memset(&dummy, 0, sizeof(struct ktermios));
...
port->ops->set_termios(port, &termios, &dummy);
...
co->cflag = termios.c_cflag;
co->ispeed = termios.c_ispeed;
co->ospeed = termios.c_ospeed;
return 0;
}
When a driver's set_termios callback calls LockedPort::baud_rate() with an
out-of-range baud rate (or baud == 0 from uart_parse_options()),
uart_get_baud_rate() falls back to the old termios (&dummy, which has baud
rate 0), encodes 0 into new.0, and clamps its return value to at least 9600.
When the driver then passes that non-zero baud rate to
LockedPort::configure(), the check:
if bindings::tty_termios_baud_rate(&new.0) != 0 {
bindings::tty_termios_encode_baud_rate(&mut new.0, baud, baud);
}
sees tty_termios_baud_rate(&new.0) == 0 and skips encoding the actual baud
rate into new.0. uart_set_options() then copies termios.c_ispeed (0) and
termios.c_ospeed (0) into co->ispeed and co->ospeed, so any subsequent
console_setup() or port resume reads (*console).ispeed == 0 instead of
config.default_baud.
> diff --git a/rust/kernel/serial/termios.rs b/rust/kernel/serial/termios.rs
> new file mode 100644
> index 0000000000000..449662d9b6b5e
> --- /dev/null
> +++ b/rust/kernel/serial/termios.rs
> @@ -0,0 +1,104 @@
[ ... ]
> + /// Whether parity generation and checking are enabled.
> + pub fn parity_enabled(&self) -> bool {
> + self.0.c_cflag & bindings::PARENB != 0
> + }
> +
> + /// Whether odd parity, or mark parity in mark/space mode, is selected.
> + pub fn odd_parity(&self) -> bool {
> + self.0.c_cflag & bindings::PARODD != 0
> + }
> +
> + /// Whether two stop bits are requested.
> + pub fn two_stop_bits(&self) -> bool {
> + self.0.c_cflag & bindings::CSTOPB != 0
> + }
> +
> + /// Disables mark/space parity without changing parity enable or odd/even selection.
> + pub fn disable_mark_space_parity(&mut self) {
> + self.0.c_cflag &= !bindings::CMSPAR;
> + }
> +
> + /// Disables RTS/CTS hardware flow control.
> + pub fn disable_hardware_flow_control(&mut self) {
> + self.0.c_cflag &= !bindings::CRTSCTS;
> + }
> +
> + /// Data bits per character.
> + pub fn char_size(&self) -> u32 {
> + // SAFETY: c_cflag is a valid termios control flag value.
> + unsafe { bindings::tty_get_char_size(self.0.c_cflag).into() }
> + }
> +}
> +
> +impl LockedPort<'_> {
> + /// Selects a supported baud rate using serial-core termios rules.
> + ///
> + /// Returns `None` if serial core fails to select a rate. Termios changes
> + /// made by serial core are preserved, including on failure.
> + pub fn baud_rate(
> + &self,
> + new: &mut Termios,
> + old: Option<&Termios>,
> + min: u32,
> + max: u32,
> + ) -> Option<NonZeroU32> {
> + // SAFETY: The port is locked, and the termios references are valid for this callback.
> + let baud = unsafe {
> + bindings::uart_get_baud_rate(
> + self.raw,
> + &mut new.0,
> + old.map_or(core::ptr::null(), |t| &t.0),
> + min,
> + max,
> + )
> + };
> + NonZeroU32::new(baud)
> + }
> +
> + /// Updates serial-core timeout and the actual console baud rate.
> + pub fn configure(&self, new: &mut Termios, baud: NonZeroU32) {
> + let baud = baud.get();
> + // SAFETY: All port fields are accessed under the UART lock. new is exclusively borrowed.
> + unsafe {
> + bindings::uart_update_timeout(self.raw, new.0.c_cflag, baud);
> + if bindings::tty_termios_baud_rate(&new.0) != 0 {
> + bindings::tty_termios_encode_baud_rate(&mut new.0, baud, baud);
> + }
> + }
> + }
> +}
[Severity: Low]
This isn't a bug, but should the small accessor methods and C binding
wrappers in the kernel crate (Termios::from_raw, Termios::from_raw_mut,
Termios::parity_enabled, Termios::odd_parity, Termios::two_stop_bits,
Termios::disable_mark_space_parity, Termios::disable_hardware_flow_control,
Termios::char_size, LockedPort::baud_rate, and LockedPort::configure here, as
well as Port::device and LockedPort::device in rust/kernel/serial.rs) be
annotated with #[inline]?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261029-rust-bl808-console-v1-0-b736ee920078@gmail.com?part=6
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH RFC 07/12] serial: bflb: Add Rust BL808 polling console
2026-10-29 5:05 [PATCH RFC 00/12] Add BL808 UART3 support with a Rust serial-core polling console Kwanghoon Son
` (5 preceding siblings ...)
2026-10-29 5:05 ` [PATCH RFC 06/12] rust: serial: Add minimal serial-core polling console support Kwanghoon Son
@ 2026-10-29 5:05 ` Kwanghoon Son
2026-10-29 5:16 ` sashiko-bot
2026-10-29 5:05 ` [PATCH RFC 08/12] riscv: Add Bouffalo Lab SoC selection Kwanghoon Son
` (4 subsequent siblings)
11 siblings, 1 reply; 24+ messages in thread
From: Kwanghoon Son @ 2026-10-29 5:05 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Daniel Almeida, Tamir Duberstein, Alexandre Courbot,
Onur Özkan, Thomas Gleixner, Radu Rendec, Paul Walmsley,
Samuel Holland, Palmer Dabbelt, Daniel Lezcano, Anup Patel,
Kwanghoon Son, Albert Ou, Alexandre Ghiti, Greg Kroah-Hartman,
Jiri Slaby
Cc: devicetree, linux-kernel, rust-for-linux, Paul Walmsley,
linux-riscv, linux-serial
Add a Rust driver for the Bouffalo Lab BL808 UART controller with polling
console support. Register UART3 as ttyBFLB0 and provide bounded polling
and newline conversion.
The driver uses an exclusive MMIO mapping and a baud clock supplied by
the device tree. Firmware configures clock routing, gates, reset and
pinmux. Userspace TTY I/O, RX, interrupts and DMA are not implemented.
Signed-off-by: Kwanghoon Son <asdfkwang@gmail.com>
---
drivers/tty/serial/Kconfig | 8 +
drivers/tty/serial/Makefile | 2 +
drivers/tty/serial/bflb_uart.rs | 334 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 344 insertions(+)
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index 9b17d451f19df6575bc8a4a08245065f9b8e3715..9f549c5033fc077e921f5b6f3e72bad2144f83b7 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -26,6 +26,14 @@ config RUST_SERIAL_ABSTRACTIONS
help
This enables Rust abstractions for the serial core API.
+config SERIAL_BFLB_RUST
+ bool "Bouffalo Lab polling serial console in Rust"
+ depends on RUST && OF && COMMON_CLK && HAS_IOMEM
+ select RUST_SERIAL_ABSTRACTIONS
+ help
+ Say Y here to enable kernel console support for the Bouffalo Lab
+ BL808 UART controller.
+
config SERIAL_AMBA_PL010
tristate "ARM AMBA PL010 serial port support"
depends on ARM_AMBA || COMPILE_TEST
diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index d63b126f40caf55b44c9ac2e6a484a0c13ca1b57..36364fe714c85df5029f2ac95a027a3787f331c7 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -99,3 +99,5 @@ obj-$(CONFIG_SERIAL_MCTRL_GPIO) += serial_mctrl_gpio.o
obj-$(CONFIG_KGDB_SERIAL_CONSOLE) += kgdboc.o
obj-$(CONFIG_SERIAL_NUVOTON_MA35D1) += ma35d1_serial.o
+
+obj-$(CONFIG_SERIAL_BFLB_RUST) += bflb_uart.o
diff --git a/drivers/tty/serial/bflb_uart.rs b/drivers/tty/serial/bflb_uart.rs
new file mode 100644
index 0000000000000000000000000000000000000000..4e34aa9a95cab665e18230453af5706b0061049a
--- /dev/null
+++ b/drivers/tty/serial/bflb_uart.rs
@@ -0,0 +1,334 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+//! Bouffalo Lab UART3 polling console.
+
+use core::num::NonZeroU32;
+
+use kernel::{
+ clk::Clk,
+ device::{
+ Bound,
+ Core,
+ Device, //
+ },
+ devres::DevresGuard,
+ driver,
+ io::{
+ mem::{
+ DevresExclusiveIoMem,
+ ExclusiveIoMem, //
+ },
+ poll::read_poll_timeout_atomic,
+ Io,
+ IoBase, //
+ },
+ of,
+ platform,
+ prelude::*,
+ serial::{
+ self,
+ DriverConfig,
+ LockedPort,
+ Port,
+ Termios, //
+ },
+ sync::Arc,
+ time::Delta, //
+};
+
+const REG_SIZE: usize = 0x90;
+const FIFO_DEPTH: u32 = 32;
+const PORTS: u32 = 1;
+const DEFAULT_BAUD: u32 = 115200;
+const POLL_RETRIES: usize = 100_000;
+const MAX_BIT_PERIOD: u32 = 65536;
+const MAX_BAUD: u32 = 4000000;
+
+const UTX_CONFIG: usize = 0x00;
+const URX_CONFIG: usize = 0x04;
+const BIT_PRD: usize = 0x08;
+const DATA_CONFIG: usize = 0x0c;
+const SW_MODE: usize = 0x1c;
+const INT_MASK: usize = 0x24;
+const INT_EN: usize = 0x2c;
+const STATUS: usize = 0x30;
+const FIFO_CONFIG0: usize = 0x80;
+const FIFO_CONFIG1: usize = 0x84;
+const FIFO_WDATA: usize = 0x88;
+
+// BL808 UART register fields, named after the reference manual.
+const UTX_EN: u32 = 1 << 0;
+const UTX_FRM_EN: u32 = 1 << 2;
+const UTX_PRT_EN: u32 = 1 << 4;
+const UTX_PRT_SEL: u32 = 1 << 5;
+const UTX_BIT_CNT_D_SHIFT: u32 = 8;
+const UTX_BIT_CNT_P_SHIFT: u32 = 11;
+const UTX_STOP_ONE: u32 = 1 << UTX_BIT_CNT_P_SHIFT;
+const UTX_STOP_TWO: u32 = 3 << UTX_BIT_CNT_P_SHIFT;
+const UTX_BUS_BUSY: u32 = 1 << 0;
+const TX_FIFO_CNT_MASK: u32 = 0x3f;
+const URX_BIT_PRD_SHIFT: u32 = 16;
+
+kernel::sync::global_lock! {
+ // SAFETY: Initialized once in module init, before registering the platform driver.
+ unsafe(uninit) static SERIAL: Mutex<Option<Arc<serial::Driver<Hardware>>>> = None;
+}
+
+struct SerialOwner;
+
+impl SerialOwner {
+ fn new(module: &'static ThisModule) -> Result<Self> {
+ // SAFETY: Module init runs once per load, before any access to SERIAL.
+ unsafe { SERIAL.init() };
+ *SERIAL.lock() = Some(Arc::pin_init(
+ serial::Driver::new(
+ DriverConfig {
+ name: c"bflb_uart",
+ device_name: c"ttyBFLB",
+ ports: PORTS,
+ default_baud: DEFAULT_BAUD,
+ },
+ module,
+ ),
+ GFP_KERNEL,
+ )?);
+ Ok(Self)
+ }
+}
+
+impl Drop for SerialOwner {
+ fn drop(&mut self) {
+ SERIAL.lock().take();
+ }
+}
+
+#[pin_data]
+struct UartModule {
+ // Fields drop in declaration order: remove platform devices and drain their
+ // serial callbacks before releasing the module's serial-driver reference.
+ #[pin]
+ _platform: driver::Registration<platform::Adapter<Uart>>,
+ _serial: SerialOwner,
+}
+
+impl kernel::InPlaceModule for UartModule {
+ fn init(module: &'static ThisModule) -> impl PinInit<Self, Error> {
+ try_pin_init!(Self {
+ // Publish the serial driver before platform registration can probe.
+ _serial: SerialOwner::new(module)?,
+ _platform <- driver::Registration::new(c"bflb_uart", module),
+ })
+ }
+}
+
+module! {
+ type: UartModule,
+ name: "bflb_uart",
+ authors: ["Kwanghoon Son"],
+ description: "Bouffalo Lab polling serial console",
+ license: "GPL",
+}
+
+struct Uart;
+
+kernel::of_device_table!(OF_TABLE, (), [(of::DeviceId::new(c"bflb,uart"), ())]);
+
+impl Uart {
+ fn register(pdev: &platform::Device<Core<'_>>) -> Result {
+ let dev: &Device<Bound> = pdev.as_ref();
+ let mapbase = pdev.resource_by_index(0).ok_or(ENODEV)?.start();
+ let io = pdev
+ .io_request_by_index(0)
+ .ok_or(ENODEV)?
+ .iomap_exclusive_sized::<REG_SIZE>()?
+ .into_devres()?;
+ let baud = EnabledClock::get(dev, c"baud")?;
+ let clock = u32::try_from(baud.0.rate().as_hz())?;
+ if clock == 0 {
+ return Err(EINVAL);
+ }
+ let serial = SERIAL.lock().as_ref().ok_or(ENODEV)?.clone();
+ serial::register_port(
+ serial,
+ dev,
+ serial::PortConfig {
+ line: 0,
+ mapbase,
+ clock,
+ fifo_size: FIFO_DEPTH,
+ },
+ Ok(Hardware {
+ io,
+ clock,
+ _baud: baud,
+ }),
+ )?;
+ Ok(())
+ }
+}
+
+impl platform::Driver for Uart {
+ type IdInfo = ();
+ type Data<'bound> = ();
+ const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = Some(&OF_TABLE);
+
+ fn probe<'bound>(
+ pdev: &'bound platform::Device<Core<'_>>,
+ _info: Option<&'bound ()>,
+ ) -> impl PinInit<Self::Data<'bound>, Error> + 'bound {
+ Self::register(pdev)
+ }
+}
+
+struct EnabledClock(Clk);
+
+impl EnabledClock {
+ fn get(dev: &Device, name: &CStr) -> Result<Self> {
+ let clock = Clk::get(dev, Some(name))?;
+ clock.prepare_enable()?;
+ Ok(Self(clock))
+ }
+}
+
+impl Drop for EnabledClock {
+ fn drop(&mut self) {
+ self.0.disable_unprepare();
+ }
+}
+
+struct Hardware {
+ io: DevresExclusiveIoMem<REG_SIZE>,
+ clock: u32,
+ _baud: EnabledClock,
+}
+
+impl Hardware {
+ fn registers(&self, dev: &Device) -> Option<Registers<'_>> {
+ if !core::ptr::eq(self.io.device(), dev) {
+ return None;
+ }
+ Some(Registers {
+ io: self.io.try_access()?,
+ })
+ }
+}
+
+/// Keeps the mapping alive for the entire non-sleeping register operation.
+/// Unbind can revoke other resources before the serial port itself is removed.
+struct Registers<'a> {
+ io: DevresGuard<'a, ExclusiveIoMem<'static, REG_SIZE>>,
+}
+
+impl Registers<'_> {
+ fn read<const REG: usize>(&self) -> u32 {
+ (&*self.io).as_view().read32(REG)
+ }
+
+ fn write<const REG: usize>(&self, value: u32) {
+ (&*self.io).as_view().write32(value, REG);
+ }
+
+ fn wait_for(&self, ready: impl Fn() -> bool) -> Result {
+ read_poll_timeout_atomic(|| Ok(ready()), |ready| *ready, Delta::ZERO, POLL_RETRIES)?;
+ Ok(())
+ }
+
+ fn write_byte(&self, ch: u8) -> Result {
+ self.wait_for(|| self.read::<FIFO_CONFIG1>() & TX_FIFO_CNT_MASK != 0)?;
+ (&*self.io).as_view().write8(ch, FIFO_WDATA);
+ Ok(())
+ }
+
+ fn tx_empty(&self) -> bool {
+ self.read::<FIFO_CONFIG1>() & TX_FIFO_CNT_MASK == FIFO_DEPTH
+ && self.read::<STATUS>() & UTX_BUS_BUSY == 0
+ }
+
+ fn write_record(&self, bytes: &[u8]) -> Result {
+ for &ch in bytes {
+ if ch == b'\n' {
+ self.write_byte(b'\r')?;
+ }
+ self.write_byte(ch)?;
+ }
+ self.wait_for(|| self.tx_empty())
+ }
+
+ fn mask_interrupts(&self) -> u32 {
+ let saved = self.read::<INT_MASK>();
+ self.write::<INT_MASK>(u32::MAX);
+ saved
+ }
+
+ fn enable_tx(&self) {
+ self.write::<UTX_CONFIG>(self.read::<UTX_CONFIG>() | UTX_EN);
+ }
+
+ fn console_write(&self, bytes: &[u8]) {
+ let saved_mask = self.mask_interrupts();
+ self.enable_tx();
+ let _ = self.write_record(bytes);
+ self.write::<INT_MASK>(saved_mask);
+ }
+}
+
+impl serial::SerialOps for Hardware {
+ fn tx_empty(&self, port: &Port<'_>) -> bool {
+ let Some(registers) = self.registers(port.device()) else {
+ return false;
+ };
+ registers.tx_empty()
+ }
+
+ fn set_termios(&self, port: &LockedPort<'_>, new: &mut Termios, old: Option<&Termios>) {
+ let Some(registers) = self.registers(port.device()) else {
+ return;
+ };
+ new.disable_mark_space_parity();
+ new.disable_hardware_flow_control();
+ let mut tx = (new.char_size() - 1) << UTX_BIT_CNT_D_SHIFT;
+ if new.parity_enabled() {
+ tx |= UTX_PRT_EN;
+ if new.odd_parity() {
+ tx |= UTX_PRT_SEL;
+ }
+ }
+ tx |= if new.two_stop_bits() {
+ UTX_STOP_TWO
+ } else {
+ UTX_STOP_ONE
+ };
+ tx |= UTX_EN | UTX_FRM_EN;
+ let Some(baud) = port.baud_rate(
+ new,
+ old,
+ self.clock.div_ceil(MAX_BIT_PERIOD),
+ self.clock.min(MAX_BAUD),
+ ) else {
+ return;
+ };
+ let baud = baud.get();
+ let divisor = ((u64::from(self.clock) + u64::from(baud) / 2) / u64::from(baud))
+ .clamp(1, u64::from(MAX_BIT_PERIOD)) as u32;
+ let Some(actual_baud) = NonZeroU32::new(self.clock / divisor) else {
+ return;
+ };
+ let _ = registers.wait_for(|| registers.tx_empty());
+ registers.write::<INT_MASK>(u32::MAX);
+ registers.write::<INT_EN>(0);
+ registers.write::<FIFO_CONFIG0>(0);
+ registers.write::<URX_CONFIG>(0);
+ registers.write::<DATA_CONFIG>(0);
+ registers.write::<SW_MODE>(0);
+ registers.write::<BIT_PRD>((divisor - 1) | ((divisor - 1) << URX_BIT_PRD_SHIFT));
+ registers.write::<UTX_CONFIG>(tx);
+ port.configure(new, actual_baud);
+ }
+
+ fn console_write(&self, port: &Port<'_>, bytes: &[u8]) {
+ let Some(registers) = self.registers(port.device()) else {
+ return;
+ };
+ registers.console_write(bytes);
+ }
+}
--
2.47.3
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH RFC 08/12] riscv: Add Bouffalo Lab SoC selection
2026-10-29 5:05 [PATCH RFC 00/12] Add BL808 UART3 support with a Rust serial-core polling console Kwanghoon Son
` (6 preceding siblings ...)
2026-10-29 5:05 ` [PATCH RFC 07/12] serial: bflb: Add Rust BL808 polling console Kwanghoon Son
@ 2026-10-29 5:05 ` Kwanghoon Son
2026-10-29 5:14 ` sashiko-bot
2026-10-29 5:05 ` [PATCH RFC 09/12] riscv: dts: bouffalolab: Add BL808 SoC Kwanghoon Son
` (3 subsequent siblings)
11 siblings, 1 reply; 24+ messages in thread
From: Kwanghoon Son @ 2026-10-29 5:05 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Daniel Almeida, Tamir Duberstein, Alexandre Courbot,
Onur Özkan, Thomas Gleixner, Radu Rendec, Paul Walmsley,
Samuel Holland, Palmer Dabbelt, Daniel Lezcano, Anup Patel,
Kwanghoon Son, Albert Ou, Alexandre Ghiti, Greg Kroah-Hartman,
Jiri Slaby
Cc: devicetree, linux-kernel, rust-for-linux, Paul Walmsley,
linux-riscv, linux-serial
Add a platform selection for the BL808 64-bit D0 processor, including
the T-HEAD errata and common clock framework used by its UART.
Signed-off-by: Kwanghoon Son <asdfkwang@gmail.com>
---
arch/riscv/Kconfig.socs | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/arch/riscv/Kconfig.socs b/arch/riscv/Kconfig.socs
index cecbce0c498384d3283ed23cbf2514391eee361d..ea4667da7716a9a8d2ae156c422b277064b42530 100644
--- a/arch/riscv/Kconfig.socs
+++ b/arch/riscv/Kconfig.socs
@@ -12,6 +12,14 @@ config ARCH_ANLOGIC
help
This enables support for Anlogic SoC platform hardware.
+config ARCH_BOUFFALOLAB
+ bool "Bouffalo Lab BL808 SoC"
+ depends on 64BIT && MMU
+ select ERRATA_THEAD
+ select COMMON_CLK
+ help
+ This enables support for Bouffalo Lab SoC platform hardware.
+
config ARCH_ESWIN
bool "ESWIN SoCs"
help
--
2.47.3
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH RFC 09/12] riscv: dts: bouffalolab: Add BL808 SoC
2026-10-29 5:05 [PATCH RFC 00/12] Add BL808 UART3 support with a Rust serial-core polling console Kwanghoon Son
` (7 preceding siblings ...)
2026-10-29 5:05 ` [PATCH RFC 08/12] riscv: Add Bouffalo Lab SoC selection Kwanghoon Son
@ 2026-10-29 5:05 ` Kwanghoon Son
2026-10-29 5:12 ` sashiko-bot
2026-10-29 5:05 ` [PATCH RFC 10/12] riscv: dts: bouffalolab: Add Sipeed M1s Dock Kwanghoon Son
` (2 subsequent siblings)
11 siblings, 1 reply; 24+ messages in thread
From: Kwanghoon Son @ 2026-10-29 5:05 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Daniel Almeida, Tamir Duberstein, Alexandre Courbot,
Onur Özkan, Thomas Gleixner, Radu Rendec, Paul Walmsley,
Samuel Holland, Palmer Dabbelt, Daniel Lezcano, Anup Patel,
Kwanghoon Son, Albert Ou, Alexandre Ghiti, Greg Kroah-Hartman,
Jiri Slaby
Cc: devicetree, linux-kernel, rust-for-linux, Paul Walmsley,
linux-riscv, linux-serial
Add the BL808 D0 CPU, PLIC, CLINT and UART3. Leave UART3 disabled for
boards to enable. UART3 connects directly to PLIC source 20; firmware
sets up UART clocks and pins.
Signed-off-by: Kwanghoon Son <asdfkwang@gmail.com>
---
arch/riscv/boot/dts/bouffalolab/bl808.dtsi | 70 ++++++++++++++++++++++++++++++
1 file changed, 70 insertions(+)
diff --git a/arch/riscv/boot/dts/bouffalolab/bl808.dtsi b/arch/riscv/boot/dts/bouffalolab/bl808.dtsi
new file mode 100644
index 0000000000000000000000000000000000000000..c739c77960c4578b87fcf2bc096ef7c8cda089bd
--- /dev/null
+++ b/arch/riscv/boot/dts/bouffalolab/bl808.dtsi
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+
+#include <dt-bindings/interrupt-controller/irq.h>
+
+/ {
+ compatible = "bflb,bl808";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ cpus {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ cpu0: cpu@0 {
+ compatible = "thead,c906", "riscv";
+ device_type = "cpu";
+ reg = <0>;
+ mmu-type = "riscv,sv39";
+ riscv,isa = "rv64imafdc";
+ riscv,isa-base = "rv64i";
+ riscv,isa-extensions = "i", "m", "a", "f", "d", "c",
+ "zicsr", "zifencei", "zicntr", "zihpm";
+
+ cpu0_intc: interrupt-controller {
+ compatible = "riscv,cpu-intc";
+ interrupt-controller;
+ #interrupt-cells = <1>;
+ };
+ };
+ };
+
+ xtal: clock-xtal {
+ compatible = "fixed-clock";
+ #clock-cells = <0>;
+ };
+
+ soc {
+ compatible = "simple-bus";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ ranges;
+ interrupt-parent = <&plic>;
+ dma-noncoherent;
+
+ uart3: serial@30002000 {
+ compatible = "bflb,uart";
+ reg = <0x30002000 0x1000>;
+ interrupts = <20 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&xtal>;
+ clock-names = "baud";
+ status = "disabled";
+ };
+
+ plic: interrupt-controller@e0000000 {
+ compatible = "bflb,bl808-plic", "thead,c900-plic";
+ reg = <0xe0000000 0x04000000>;
+ interrupts-extended = <&cpu0_intc 11>, <&cpu0_intc 9>;
+ interrupt-controller;
+ #address-cells = <0>;
+ #interrupt-cells = <2>;
+ riscv,ndev = <82>;
+ };
+
+ timer@e4000000 {
+ compatible = "bflb,bl808-clint", "thead,c900-clint";
+ reg = <0xe4000000 0xc000>;
+ interrupts-extended = <&cpu0_intc 3>, <&cpu0_intc 7>;
+ };
+ };
+};
--
2.47.3
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH RFC 10/12] riscv: dts: bouffalolab: Add Sipeed M1s Dock
2026-10-29 5:05 [PATCH RFC 00/12] Add BL808 UART3 support with a Rust serial-core polling console Kwanghoon Son
` (8 preceding siblings ...)
2026-10-29 5:05 ` [PATCH RFC 09/12] riscv: dts: bouffalolab: Add BL808 SoC Kwanghoon Son
@ 2026-10-29 5:05 ` Kwanghoon Son
2026-10-29 5:10 ` sashiko-bot
2026-10-29 5:05 ` [PATCH RFC 11/12] riscv: defconfig: Enable Bouffalo Lab SoCs Kwanghoon Son
2026-10-29 5:05 ` [PATCH RFC 12/12] MAINTAINERS: Add Bouffalo Lab BL808 support Kwanghoon Son
11 siblings, 1 reply; 24+ messages in thread
From: Kwanghoon Son @ 2026-10-29 5:05 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Daniel Almeida, Tamir Duberstein, Alexandre Courbot,
Onur Özkan, Thomas Gleixner, Radu Rendec, Paul Walmsley,
Samuel Holland, Palmer Dabbelt, Daniel Lezcano, Anup Patel,
Kwanghoon Son, Albert Ou, Alexandre Ghiti, Greg Kroah-Hartman,
Jiri Slaby
Cc: devicetree, linux-kernel, rust-for-linux, Paul Walmsley,
linux-riscv, linux-serial
Add the M1s SoM memory and 40 MHz crystal, and enable UART3 on the Dock
with a direct stdout path and 115200 8N1 settings.
Boot firmware initializes PSRAM, UART pads and shared PLLs. The tested
firmware configures D0 TIME at 320 MHz / 380, represented as 842105 Hz.
Firmware using another timer configuration must fix up timebase-
frequency. The declared 64 MiB PSRAM range must retain firmware
reservations or be narrowed by firmware when memory is assigned to
another core.
The stdout path selects the native polling printk console; userspace TTY
I/O is deferred.
Signed-off-by: Kwanghoon Son <asdfkwang@gmail.com>
---
arch/riscv/boot/dts/Makefile | 1 +
arch/riscv/boot/dts/bouffalolab/Makefile | 2 ++
.../boot/dts/bouffalolab/bl808-sipeed-m1s-dock.dts | 18 ++++++++++++++++++
arch/riscv/boot/dts/bouffalolab/bl808-sipeed-m1s.dtsi | 19 +++++++++++++++++++
4 files changed, 40 insertions(+)
diff --git a/arch/riscv/boot/dts/Makefile b/arch/riscv/boot/dts/Makefile
index 69d8751fb17c803f0efb8003e111ee24144c222b..294f9c119f8f1d608c878ca1f95891843301f07e 100644
--- a/arch/riscv/boot/dts/Makefile
+++ b/arch/riscv/boot/dts/Makefile
@@ -2,6 +2,7 @@
subdir-y += allwinner
subdir-y += andes
subdir-y += anlogic
+subdir-y += bouffalolab
subdir-y += canaan
subdir-y += eswin
subdir-y += microchip
diff --git a/arch/riscv/boot/dts/bouffalolab/Makefile b/arch/riscv/boot/dts/bouffalolab/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..4d63365edc095fbe9df31c4b12e29c4b624cbd1f
--- /dev/null
+++ b/arch/riscv/boot/dts/bouffalolab/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0
+dtb-$(CONFIG_ARCH_BOUFFALOLAB) += bl808-sipeed-m1s-dock.dtb
diff --git a/arch/riscv/boot/dts/bouffalolab/bl808-sipeed-m1s-dock.dts b/arch/riscv/boot/dts/bouffalolab/bl808-sipeed-m1s-dock.dts
new file mode 100644
index 0000000000000000000000000000000000000000..c719a53c6e2de45f3718b48ba2116e99c70e559b
--- /dev/null
+++ b/arch/riscv/boot/dts/bouffalolab/bl808-sipeed-m1s-dock.dts
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+
+/dts-v1/;
+
+#include "bl808-sipeed-m1s.dtsi"
+
+/ {
+ model = "Sipeed M1s Dock";
+ compatible = "sipeed,m1s-dock", "sipeed,m1s", "bflb,bl808";
+
+ chosen {
+ stdout-path = "/soc/serial@30002000:115200n8";
+ };
+};
+
+&uart3 {
+ status = "okay";
+};
diff --git a/arch/riscv/boot/dts/bouffalolab/bl808-sipeed-m1s.dtsi b/arch/riscv/boot/dts/bouffalolab/bl808-sipeed-m1s.dtsi
new file mode 100644
index 0000000000000000000000000000000000000000..ae54e6e301ed2c5a1448d52c530c5074515ee38c
--- /dev/null
+++ b/arch/riscv/boot/dts/bouffalolab/bl808-sipeed-m1s.dtsi
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+
+#include "bl808.dtsi"
+
+/ {
+ compatible = "sipeed,m1s", "bflb,bl808";
+ memory@50000000 {
+ device_type = "memory";
+ reg = <0x50000000 0x04000000>;
+ };
+
+ cpus {
+ timebase-frequency = <842105>;
+ };
+};
+
+&xtal {
+ clock-frequency = <40000000>;
+};
--
2.47.3
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH RFC 11/12] riscv: defconfig: Enable Bouffalo Lab SoCs
2026-10-29 5:05 [PATCH RFC 00/12] Add BL808 UART3 support with a Rust serial-core polling console Kwanghoon Son
` (9 preceding siblings ...)
2026-10-29 5:05 ` [PATCH RFC 10/12] riscv: dts: bouffalolab: Add Sipeed M1s Dock Kwanghoon Son
@ 2026-10-29 5:05 ` Kwanghoon Son
2026-10-29 5:13 ` sashiko-bot
2026-10-29 5:05 ` [PATCH RFC 12/12] MAINTAINERS: Add Bouffalo Lab BL808 support Kwanghoon Son
11 siblings, 1 reply; 24+ messages in thread
From: Kwanghoon Son @ 2026-10-29 5:05 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Daniel Almeida, Tamir Duberstein, Alexandre Courbot,
Onur Özkan, Thomas Gleixner, Radu Rendec, Paul Walmsley,
Samuel Holland, Palmer Dabbelt, Daniel Lezcano, Anup Patel,
Kwanghoon Son, Albert Ou, Alexandre Ghiti, Greg Kroah-Hartman,
Jiri Slaby
Cc: devicetree, linux-kernel, rust-for-linux, Paul Walmsley,
linux-riscv, linux-serial
Enable support for Bouffalo Lab SoCs in the default configuration and
build the BL808 device trees. The Rust UART driver remains an explicit
configuration choice.
Signed-off-by: Kwanghoon Son <asdfkwang@gmail.com>
---
arch/riscv/configs/defconfig | 1 +
1 file changed, 1 insertion(+)
diff --git a/arch/riscv/configs/defconfig b/arch/riscv/configs/defconfig
index 04ae305d5511bb0760247f50d4b69b1e3a00527b..78a5580021d8cd654c33df931149a2224465285a 100644
--- a/arch/riscv/configs/defconfig
+++ b/arch/riscv/configs/defconfig
@@ -24,6 +24,7 @@ CONFIG_BLK_DEV_INITRD=y
CONFIG_PROFILING=y
CONFIG_ARCH_ANDES=y
CONFIG_ARCH_ANLOGIC=y
+CONFIG_ARCH_BOUFFALOLAB=y
CONFIG_ARCH_ESWIN=y
CONFIG_ARCH_MICROCHIP=y
CONFIG_ARCH_SIFIVE=y
--
2.47.3
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH RFC 12/12] MAINTAINERS: Add Bouffalo Lab BL808 support
2026-10-29 5:05 [PATCH RFC 00/12] Add BL808 UART3 support with a Rust serial-core polling console Kwanghoon Son
` (10 preceding siblings ...)
2026-10-29 5:05 ` [PATCH RFC 11/12] riscv: defconfig: Enable Bouffalo Lab SoCs Kwanghoon Son
@ 2026-10-29 5:05 ` Kwanghoon Son
11 siblings, 0 replies; 24+ messages in thread
From: Kwanghoon Son @ 2026-10-29 5:05 UTC (permalink / raw)
To: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Daniel Almeida, Tamir Duberstein, Alexandre Courbot,
Onur Özkan, Thomas Gleixner, Radu Rendec, Paul Walmsley,
Samuel Holland, Palmer Dabbelt, Daniel Lezcano, Anup Patel,
Kwanghoon Son, Albert Ou, Alexandre Ghiti, Greg Kroah-Hartman,
Jiri Slaby
Cc: devicetree, linux-kernel, rust-for-linux, Paul Walmsley,
linux-riscv, linux-serial
Add a MAINTAINERS entry for the Bouffalo Lab BL808 platform, device trees
and Rust serial driver and abstractions.
Signed-off-by: Kwanghoon Son <asdfkwang@gmail.com>
---
MAINTAINERS | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 801f964899ba790a8f71fe61f9b3777a494dabd5..b76d3e168468bf6139c958a81b626c73b422c0d4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4926,6 +4926,19 @@ S: Maintained
F: Documentation/devicetree/bindings/iio/imu/bosch,bmi323.yaml
F: drivers/iio/imu/bmi323/
+BOUFFALO LAB BL808 SUPPORT
+M: Kwanghoon Son <asdfkwang@gmail.com>
+L: linux-riscv@lists.infradead.org
+L: linux-serial@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/riscv/bflb.yaml
+F: Documentation/devicetree/bindings/serial/bflb,uart.yaml
+F: arch/riscv/boot/dts/bouffalolab/
+F: drivers/tty/serial/bflb_uart.rs
+F: rust/helpers/serial.c
+F: rust/kernel/serial.rs
+F: rust/kernel/serial/
+
BPF JIT for ARC
M: Shahab Vahedi <list+bpf@vahedi.org>
L: bpf@vger.kernel.org
--
2.47.3
^ permalink raw reply related [flat|nested] 24+ messages in thread