* Re: [PATCH v2 0/2] Add ZTE LRX UART driver
From: Greg KH @ 2026-05-13 10:01 UTC (permalink / raw)
To: liu.qingtao2
Cc: krzk, jirislaby, robh, krzk+dt, conor+dt, marex, pjw, palmer, aou,
alex, rdunlap, geert+renesas, quic_zongjian, arturs.artamonovs,
robert.marko, hvilleneuve, thierry.bultel.yh, julianbraha, flavra,
prabhakar.mahadev-lad.rj, linux-serial, linux-kernel, devicetree,
linux-riscv, liu.wenhong35, liu.fei16, dai.hualiang, deng.weixian,
jia.yunxiang, he.yilin, bai.lu5, yang.susheng, shen.lin1,
zuo.jiang, hu.shengming, gao.rui, tan.hu
In-Reply-To: <202605130852.64D8qMgL084414@mse-fl1.zte.com.cn>
On Wed, May 13, 2026 at 04:46:42PM +0800, liu.qingtao2@zte.com.cn wrote:
> >From 9eba3be2e9b4d5c77956258e3c5db95049c3a895 Mon Sep 17 00:00:00 2001
> From: Wenhong Liu
> Date: Mon, 12 May 2026 10:15:55 +0800
> Subject: [PATCH v2 0/2] Add ZTE LRX UART driver
Again, why is this here?
Just use 'git send-email' to send patches out please. Your patches here
are not threaded together at all and our tooling can not pick them up
:(
thanks,
greg k-h
^ permalink raw reply
* Re: [PATCH v2 1/2] Add ZTE LRX UART driver
From: Greg KH @ 2026-05-13 8:59 UTC (permalink / raw)
To: liu.qingtao2
Cc: krzk, jirislaby, robh, krzk+dt, conor+dt, marex, pjw, palmer, aou,
alex, rdunlap, geert+renesas, quic_zongjian, arturs.artamonovs,
robert.marko, hvilleneuve, thierry.bultel.yh, julianbraha, flavra,
prabhakar.mahadev-lad.rj, linux-serial, linux-kernel, devicetree,
linux-riscv, liu.wenhong35, liu.fei16, dai.hualiang, deng.weixian,
jia.yunxiang, he.yilin, bai.lu5, yang.susheng, shen.lin1,
zuo.jiang, hu.shengming, gao.rui, tan.hu
In-Reply-To: <202605130851.64D8prqc083672@mse-fl1.zte.com.cn>
On Wed, May 13, 2026 at 04:46:51PM +0800, liu.qingtao2@zte.com.cn wrote:
> >From 08610be731b6fc3919d5eebfd2ff9d67f38a094c Mon Sep 17 00:00:00 2001
> From: Wenhong Liu <liu.wenhong35@zte.com.cn>
> Date: Tue, 28 Apr 2026 22:30:31 +0800
> Subject: [PATCH v2 1/2] dt-bindings: serial: Add zte,lrx-uart
Um, something went wrong with your email client, this should not be in
the body of the email :(
thanks,
greg k-h
^ permalink raw reply
* Re: [PATCH v2 6.12.y 00/10] serial: 8250_dw: backport BUSY deassert series
From: Ilpo Järvinen @ 2026-05-13 10:16 UTC (permalink / raw)
To: Ionut Nechita (Wind River)
Cc: Greg Kroah-Hartman, stable, Andy Shevchenko, wander,
chris.friesen, linux-serial
In-Reply-To: <20260513065028.112468-1-ionut.nechita@windriver.com>
[-- Attachment #1: Type: text/plain, Size: 7606 bytes --]
On Wed, 13 May 2026, Ionut Nechita (Wind River) wrote:
> From: Ionut Nechita <ionut.nechita@windriver.com>
>
> Hi Greg, Ilpo,
>
> This is v2 of the 8250_dw BUSY deassert backport to 6.12.y,
> addressing Ilpo's review feedback on v1.
FYI, this came up yesterday related to guard()s vs unlock variants:
https://lore.kernel.org/linux-serial/cover.1778592805.git.jnilo@free.fr/
> v1 thread:
> https://lore.kernel.org/linux-serial/20260510134011.618215-1-ionut.nechita@windriver.com/
>
> v1 review (Ilpo, 2026-05-11):
> https://lore.kernel.org/linux-serial/d4b4e4db-505f-4400-c1b9-fe589786456e@linux.intel.com/
>
> Changes in v2:
>
> - Keep guard()/scoped_guard() from upstream verbatim in patches
> 5, 7, 8 and 9 (no more explicit uart_port_lock_irqsave/
> unlock_irqrestore + goto-out adaptations), per Ilpo's review:
>
> "By doing this you had to add some gotos which makes code
> control flow more complicated than it was in the original
> changes. From reviewer's point of view, the code is more
> complicated without guard() than with them."
>
> To make that work on 6.12.y, two small additional prerequisites
> are now part of the series (see new patches 3/10 and 4/10
> below).
>
> - Stop replacing guard()s with explicit lock/unlock pairs; v2
> diff against upstream is now significantly smaller.
>
> Series structure (10 patches):
>
> Prerequisites (per Ilpo's original guidance, plus one implicit
> guard-defs prereq):
> 1/10 serial: 8250: use serial_port_in/out() helpers [dbd26a886e94]
> 2/10 serial: 8250_dw: Comment possible corner cases ... [bd8cad85561b]
> 3/10 serial: introduce uart_port_lock() guard()s [0fd60b689b0d] (NEW in v2)
> 4/10 serial: 8250: convert serial8250_do_shutdown()
> to scoped_guard() [partial b339809edda1] (NEW in v2)
>
> BUSY deassert series:
> 5/10 serial: 8250: Protect LCR write in shutdown [59a33d83bbe6]
> 6/10 serial: 8250_dw: Avoid unnecessary LCR writes [8002d6d6d0d8]
> 7/10 serial: 8250: Add serial8250_handle_irq_locked() [8324a54f604d]
> 8/10 serial: 8250_dw: Rework dw8250_handle_irq() ... [883c5a2bc934]
> 9/10 serial: 8250_dw: Rework IIR_NO_INT handling ... [73a4ed8f9efa]
> 10/10 serial: 8250_dw: Ensure BUSY is deasserted [a7b9ce39fbe4]
>
> Notes on the new prerequisites (3/10 and 4/10):
>
> - Patch 3/10 cherry-picks commit 0fd60b689b0d ("serial: introduce
> uart_port_lock() guard()s") unmodified (13 additive lines in
> include/linux/serial_core.h). It introduces DEFINE_GUARD() /
> DEFINE_LOCK_GUARD_1() invocations for uart_port_lock,
> uart_port_lock_irq and uart_port_lock_irqsave, which are
> required so that scoped_guard(uart_port_lock_irqsave, port) and
> guard(uart_port_lock_irqsave)(port) compile in 6.12.y. The
> underlying cleanup.h infrastructure (DEFINE_GUARD,
> DEFINE_LOCK_GUARD_1) already exists in 6.12.y; only this small
> serial-specific wiring is missing.
>
> - Patch 4/10 is a *partial* backport of commit b339809edda1
> ("serial: 8250: use guard()s"), per Ilpo's original request:
>
> "b339809edda1 ('serial: 8250: use guard()s')
> (8250_port.c shutdown hunks)"
>
> The commit is authored by Jiri Slaby (SUSE) and carries the
> upstream Link/SoB chain, plus an explicit [Ionut: partial
> backport - only the serial8250_do_shutdown() hunks from
> b339809edda1] note. The remaining (much larger) refactor of
> 8250_port.c and 8250_core.c is not needed for the BUSY deassert
> series and is intentionally not backported.
>
> Notes on the BUSY series itself:
>
> - Patch 6/7 of the original mainline BUSY series,
> commit e0a368ae7953 ("serial: 8250: Add late synchronize_irq()
> to shutdown to handle DW UART BUSY"), is *already* in 6.12.y as
> commit 0bae1c670aa8 (Ilpo, 2026-02-03), so it is not re-sent
> here. Functionally this means patches 5-10 above land on top of
> the existing late-synchronize_irq() fix.
>
> - Ilpo's other suggested-but-not-required prerequisites remain
> *not* included:
> * commit fc9ceb501e38 ("serial: 8250: sanitize
> uart_port::serial_{in,out}() types") only causes a trivial
> conflict in patch 10 in code that the BUSY change removes
> anyway (per Ilpo's note);
> * commit c213375e3283 ("serial: 8250_dw: Call dw8250_quirks()
> conditionally") only causes a conflict in patch 9 around
> the dw8250_setup_dma_filter() helper and the conditional
> p->handle_irq assignment, neither of which exist in 6.12.y
> and neither of which is needed for the BUSY fix.
>
> Both of those conflicts are resolved trivially in patches 9 and
> 10 with explicit [Ionut: adapt to 6.12.y - ...] notes.
>
> - Namespace export syntax: in 6.12.y both EXPORT_SYMBOL_NS_GPL()
> and MODULE_IMPORT_NS() apply __stringify(ns) to the namespace
> argument, so it must be a bare identifier. Mainline (where the
> upstream patches were written) accepts a string literal. Patches
> 7 and 10 here use the bare-identifier form (SERIAL_8250) instead
> of the upstream string form ("SERIAL_8250"); without this fix
> the .vmlinux.export.c link step fails with "expected ':' or ')'
> before 'SERIAL_8250'". This is noted in the [Ionut: ...] block
> of the affected patches.
>
> Only patches 4, 7, 9 and 10 carry "[Ionut: adapt to 6.12.y - ...]"
> notes; the rest are clean cherry-picks of the upstream commits
> using the standard "commit <hash> upstream." stable convention.
>
> Build:
> - HEAD of the series builds to a complete vmlinux on 6.12.87 with
> CONFIG_SERIAL_8250=y, CONFIG_SERIAL_8250_DW=m on x86_64. The
> .vmlinux.export.c link step (which v1 originally tripped on
> during development) passes cleanly.
>
> Testing plan:
> - Same as v1: test on Intel platforms with DW APB UART
> (snps,dw-apb-uart) running 6.12.x-rt (PREEMPT_RT) to confirm
> the original symptom (LCR writes silently ignored under Rx
> load -> baud / framing mismatch after set_termios) is gone.
> Will report Tested-by once cycles complete.
>
> Based on: linux-6.12.y at v6.12.87 (8bf2f55ef536).
>
>
> Andy Shevchenko (1):
> serial: 8250_dw: Comment possible corner cases in serial_out()
> implementation
>
> Ilpo Järvinen (6):
> serial: 8250: Protect LCR write in shutdown
> serial: 8250_dw: Avoid unnecessary LCR writes
> serial: 8250: Add serial8250_handle_irq_locked()
> serial: 8250_dw: Rework dw8250_handle_irq() locking and IIR handling
> serial: 8250_dw: Rework IIR_NO_INT handling to stop interrupt storm
> serial: 8250_dw: Ensure BUSY is deasserted
>
> Jiri Slaby (SUSE) (3):
> serial: 8250: use serial_port_in/out() helpers
> serial: introduce uart_port_lock() guard()s
> serial: 8250: convert serial8250_do_shutdown() to scoped_guard()
>
> drivers/tty/serial/8250/8250.h | 25 +++
> drivers/tty/serial/8250/8250_dw.c | 296 +++++++++++++++++++++++-----
> drivers/tty/serial/8250/8250_fsl.c | 8 +-
> drivers/tty/serial/8250/8250_omap.c | 2 +-
> drivers/tty/serial/8250/8250_port.c | 90 +++++----
> include/linux/serial_8250.h | 1 +
> include/linux/serial_core.h | 13 ++
> 7 files changed, 338 insertions(+), 97 deletions(-)
>
>
--
i.
^ permalink raw reply
* Re: [PATCH] Bluetooth: hci_uart: fix UAF in hci_uart_tty_close()
From: Paul Menzel @ 2026-05-13 9:04 UTC (permalink / raw)
To: Mingyu Wang, Mingyu Wang
Cc: marcel, luiz.dentz, linux-bluetooth, linux-serial, linux-kernel
In-Reply-To: <20260513064547.352601-1-w15303746062@163.com>
Dear Mingyu,
Thank you for the patch, and your work on the Linux kernel.
Am 13.05.26 um 08:45 schrieb w15303746062@163.com:
> 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.
Please elaborate, in what setup it was observed, and please add an
excerpt of the trace.
> 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.
Please add a Fixes: tag, so it gets backported.
Also, please add a Link: tag with a URL to the test case, or include it
in the commit message.
> 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);
> +
> 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);
Kind regards,
Paul
PS: If you resend, and don’t know yet (you have commits in the Linux
kernel already), please add v2 to the tag. (`git format-patch -2 …` or
an equivalent option to your tooling.
PPS: sashiko.dev did not pick this patch up yet [1].
[1]: https://sashiko.dev/#/?list=org.kernel.vger.linux-bluetooth
^ permalink raw reply
* Re: [PATCH v1 1/2] LRW UART: dt-bindings: Add binding for LRW UART
From: liu.qingtao2 @ 2026-05-13 8:43 UTC (permalink / raw)
To: krzk
Cc: gregkh, jirislaby, robh, krzk+dt, conor+dt, marex, pjw, palmer,
aou, alex, rdunlap, geert+renesas, quic_zongjian,
arturs.artamonovs, robert.marko, hvilleneuve, thierry.bultel.yh,
julianbraha, flavra, prabhakar.mahadev-lad.rj, linux-serial,
linux-kernel, devicetree, linux-riscv, liu.wenhong35,
dai.hualiang, deng.weixian, jia.yunxiang, bai.lu5, yang.susheng,
shen.lin1, zuo.jiang, hu.shengming, gao.rui, tan.hu, liu.qingtao2
Thanks for your kindly review. Sorry for the delay.
> On 13/02/2026 10:33, LiuQingtao wrote:
> > From: Wenhong Liu <liu.wenhong35@zte.com.cn>
> >
> > Add documentation for LRW UART devicetree bindings.
> >
> > Signed-off-by: Wenhong Liu <liu.wenhong35@zte.com.cn>
> > Signed-off-by: Qingtao Liu <liu.qingtao2@zte.com.cn>
>
> Please use subject prefixes matching the subsystem. You can get them for
> example with `git log --oneline -- DIRECTORY_OR_FILE` on the directory
> your patch is touching. For bindings, the preferred subjects are
> explained here:
> https://www.kernel.org/doc/html/latest/devicetree/bindings/submitting-patches.html#i-for-patch-submitters
>
>
> A nit, subject: drop second/last, redundant "bindings". The
> "dt-bindings" prefix is already stating that these are bindings.
> See also:
> https://elixir.bootlin.com/linux/v6.17-rc3/source/Documentation/devicetree/bindings/submitting-patches.rst#L18
Thanks for the notice. I will change the subject to "Add devicetree binding for ZTE LRX UART controller"
in the v2 patch series.
> > ---
> > .../bindings/serial/lrw,lrw-uart.yaml | 49 +++++++++++++++++++
> > .../devicetree/bindings/vendor-prefixes.yaml | 2 +
> > MAINTAINERS | 7 +++
> > 3 files changed, 58 insertions(+)
> > create mode 100644 Documentation/devicetree/bindings/serial/lrw,lrw-uart.yaml
> >
> > diff --git a/Documentation/devicetree/bindings/serial/lrw,lrw-uart.yaml b/Documentation/devicetree/bindings/> serial/lrw,lrw-uart.yaml
> > new file mode 100644
> > index 000000000000..a2d41c278c4f
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/serial/lrw,lrw-uart.yaml
> > @@ -0,0 +1,49 @@
> > +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> > +
> > +%YAML 1.2
> > +---
> > +$id: http://devicetree.org/schemas/serial/lrw-uart.yaml#
>
> Never tested, NAK. There are several other issues here, but I am not
> going through rest of review if you did not bother to even build test
> it. Please open any other recent binding and apply same style here
> (filename, descriptions etc), so you won't be repeating SAME mistakes.
>
>
Sorry, i tested on an older version kernel.
No errors or warnings ever showed up running the command
"make dt_binding_check DT_SCHEMA_FILES=Documentation/devicetree/bindings/serial/zte,lrx-uart.yaml".
Log from v2 patch:
$ make dt_binding_check DT_SCHEMA_FILES=Documentation/devicetree/bindings/serial/zte,lrx-uart.yaml
SCHEMA Documentation/devicetree/bindings/processed-schema.json
CHKDT ./Documentation/devicetree/bindings
LINT ./Documentation/devicetree/bindings
DTEX Documentation/devicetree/bindings/serial/zte,lrx-uart.example.dts
DTC [C] Documentation/devicetree/bindings/serial/zte,lrx-uart.example.dtb
> > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > +
> > +title: LRW serial UART
> > +
> > +maintainers:
> > + - Wenhong Liu <liu.wenhong35@zte.com.cn>
> > + - Qingtao Liu <liu.qingtao2@zte.com.cn>
> > +
> > +description: |
> > + Should be something similar to "lrw,<chip>-uart"
> > + for the UART as integrated on a particular chip, It supports
> > + multiple CPU architectures, currently including e.g. RISC-V and ARM.
> > +
> > +properties:
> > + compatible:
> > + const: lrw,lrw-uart
>
> No way lrw is a chip if this is a company.
Sorry for the name problem. lrw is a subsidiary of ZTE.
We will use ZTE as company name and lrx as chip name in the v2 patches.
lrx is a chip that will be comming up soon.
> > +
> > + reg:
> > + maxItems: 1
> > +
> > + interrupts:
> > + maxItems: 1
> > +
> > + clocks:
> > + maxItems: 1
> > +
> > +required:
> > + - compatible
> > + - reg
> > + - current-speed
> > + - clocks
> > +
> > +additionalProperties: false
> > +
> > +examples:
> > + - |
> > + uart0: serial@e0001800 {
> > + compatible = "lrw,lrw-uart";
> > + interrupt-parent = <&aplic0>;
> > + interrupts = <0x12 0x4>;
> > + reg = <0xe0001800 0x100>;
> > + clocks = <&bar_clk>;
> > + current-speed = <115200>;
> > + };
> > diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/> vendor-prefixes.yaml
> > index ee7fd3cfe203..ec9bf262f466 100644
> > --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
> > +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
> > @@ -961,6 +961,8 @@ patternProperties:
> > description: Loongson Technology Corporation Limited
> > "^loongmasses,.*":
> > description: Nanjing Loongmasses Ltd.
> > + "^lrw,.*":
> > + description: LRW Corp.
>
> What is the website/domain address?
>
>
> Best regards,
> Krzysztof
There won't be such problem since we use ZTE as company name.
ZTE was described in Documentation/devicetree/bindings/vendor-prefixes.yaml already.
Thanks,
Qingtao Liu
^ permalink raw reply
* Re: [PATCH v1 2/2] LRW UART: serial: add driver for the LRW UART
From: liu.qingtao2 @ 2026-05-13 8:43 UTC (permalink / raw)
To: krzk
Cc: gregkh, jirislaby, robh, krzk+dt, conor+dt, marex, pjw, palmer,
aou, alex, rdunlap, geert+renesas, quic_zongjian,
arturs.artamonovs, robert.marko, hvilleneuve, thierry.bultel.yh,
julianbraha, flavra, prabhakar.mahadev-lad.rj, linux-serial,
linux-kernel, devicetree, linux-riscv, liu.wenhong35,
dai.hualiang, deng.weixian, jia.yunxiang, bai.lu5, yang.susheng,
shen.lin1, zuo.jiang, hu.shengming, gao.rui, tan.hu, liu.qingtao2
> On 13/02/2026 10:33, LiuQingtao wrote:
> > From: Wenhong Liu <liu.wenhong35@zte.com.cn>
> >
> > This commit introduces a serial driver for the LRW UART controller
> Please do not use "This commit/patch/change", but imperative mood. See
> longer explanation here:
> https://elixir.bootlin.com/linux/v6.16/source/Documentation/process/submitting-patches.rst#L94
Thanks. I change subject to "Add support for the ZTE LRX UART controller with the following features"
in the following patches.
> >
> > Key features implemented:
> > - Support for FIFO mode (16-byte depth)
> > - Baud rate configuration
> > - Standard asynchronous communication formats:
> > * Data bits: 5, 6, 7, 8, 9 bits
> > * Parity: odd, even, fixed, none
> > * Stop bits: 1 or 2 bits
> > - Hardware flow control (RTS/CTS)
> > - Multiple interrupt reporting mechanisms
> >
> > Signed-off-by: Wenhong Liu <liu.wenhong35@zte.com.cn>
> > Signed-off-by: Qingtao Liu <liu.qingtao2@zte.com.cn>
> Mismatched DCO/SoB.
> Run checkpatch on your code.
Thanks for your notice. This patch is co-developed by Qingtao Liu.
SoB will be modified in v2 patches:
Co-developed-by: Qingtao Liu <liu.qingtao2@zte.com.cn>
Signed-off-by: Qingtao Liu <liu.qingtao2@zte.com.cn>
Signed-off-by: Wenhong Liu <liu.wenhong35@zte.com.cn>
> > ---
> > MAINTAINERS | 3 +
> > drivers/tty/serial/Kconfig | 33 +
> > drivers/tty/serial/Makefile | 1 +
> > drivers/tty/serial/lrw_uart.c | 2822 ++++++++++++++++++++++++++++++
> > include/uapi/linux/serial_core.h | 3 +
> > 5 files changed, 2862 insertions(+)
> > create mode 100644 drivers/tty/serial/lrw_uart.c
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index ad6acbe24544..a97fbd205f75 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -15041,6 +15041,9 @@ R: Qingtao Liu <liu.qingtao2@zte.com.cn>
> > L: linux-serial@vger.kernel.org
> > S: Maintained
> > F: Documentation/devicetree/bindings/serial/lrw,lrw-uart.yaml
> > +F: drivers/tty/serial/Kconfig
> > +F: drivers/tty/serial/Makefile
>
> Why do you claim you maintain these files?
>
Oh sorry, now i only added these files in v2 patch.
+F: Documentation/devicetree/bindings/serial/zte,lrx-uart.yaml
+F: drivers/tty/serial/lrx_uart.c
> > +F: drivers/tty/serial/lrw_uart.c
> >
> ....
> > +
> > +static int lrw_uart_probe(struct platform_device *pdev)
> > +{
> > + struct lrw_uart_port *sup;
> > + struct resource *r;
> > + int portnr, ret;
> > + unsigned int clk;
> > + unsigned int baudrate;
> > +
> > + /*
> > + * Check the mandatory baud rate parameter in the DT node early
> > + * so that we can easily exit with the error.
> > + */
> > + if (pdev->dev.of_node) {
> > + struct device_node *np = pdev->dev.of_node;
> > +
> > + ret = of_property_read_u32(np, "current-speed", &baudrate);
>
> Test your code/DTS - there is no such property allowed and you would see
> warnings on DTS.
>
> Best regards,
> Krzysztof
Oh, i've lost this property in dt-bindings. It will be added in the
v2 patches and no more warnings.
^ permalink raw reply
* Re: [PATCH v1 1/2] LRW UART: dt-bindings: Add binding for LRW UART
From: liu.qingtao2 @ 2026-05-13 8:44 UTC (permalink / raw)
To: krzk
Cc: gregkh, jirislaby, robh, krzk+dt, conor+dt, marex, pjw, palmer,
aou, alex, rdunlap, geert+renesas, quic_zongjian,
arturs.artamonovs, robert.marko, hvilleneuve, thierry.bultel.yh,
julianbraha, flavra, prabhakar.mahadev-lad.rj, linux-serial,
linux-kernel, devicetree, linux-riscv, liu.wenhong35,
dai.hualiang, deng.weixian, jia.yunxiang, bai.lu5, yang.susheng,
shen.lin1, zuo.jiang, hu.shengming, gao.rui, tan.hu, liu.qingtao2
> On 13/02/2026 10:33, LiuQingtao wrote:
> > From: Wenhong Liu <liu.wenhong35@zte.com.cn>
> >
> > Add documentation for LRW UART devicetree bindings.
> >
> > Signed-off-by: Wenhong Liu <liu.wenhong35@zte.com.cn>
> > Signed-off-by: Qingtao Liu <liu.qingtao2@zte.com.cn>
>
> Please use scripts/get_maintainers.pl to get a list of necessary people
> and lists to CC. It might happen, that command when run on an older
> kernel, gives you outdated entries. Therefore please be sure you base
> your patches on recent Linux kernel.
>
>
> Tools like b4 or scripts/get_maintainer.pl provide you proper list of
> people, so fix your workflow. Tools might also fail if you work on some
> ancient tree (don't, instead use mainline) or work on fork of kernel
> (don't, instead use mainline). Just use b4 and everything should be
> fine, although remember about `b4 prep --auto-to-cc` if you added new
> patches to the patchset.
>
> You missed at least devicetree list (maybe more), so this won't be
> tested by automated tooling. Performing review on untested code might be
> a waste of time.
>
> Please kindly resend and include all necessary To/Cc entries.
>
>
> Best regards,
> Krzysztof
Much thanks. I got correct lists based on most recent Linux kernel.
I'll resend v2 patches soon.
^ permalink raw reply
* Re: [PATCH v1 2/2] LRW UART: serial: add driver for the LRW UART
From: liu.qingtao2 @ 2026-05-13 8:44 UTC (permalink / raw)
To: gregkh
Cc: krzk, jirislaby, robh, krzk+dt, conor+dt, marex, pjw, palmer, aou,
alex, rdunlap, geert+renesas, quic_zongjian, arturs.artamonovs,
robert.marko, hvilleneuve, thierry.bultel.yh, julianbraha, flavra,
prabhakar.mahadev-lad.rj, linux-serial, linux-kernel, devicetree,
linux-riscv, liu.wenhong35, dai.hualiang, deng.weixian,
jia.yunxiang, bai.lu5, yang.susheng, shen.lin1, zuo.jiang,
hu.shengming, gao.rui, tan.hu, liu.qingtao2
> On Fri, Feb 13, 2026 at 05:33:34PM +0800, LiuQingtao wrote:
> > From: Wenhong Liu <liu.wenhong35@zte.com.cn>
> >
> > This commit introduces a serial driver for the LRW UART controller
> >
> > Key features implemented:
> > - Support for FIFO mode (16-byte depth)
> > - Baud rate configuration
> > - Standard asynchronous communication formats:
> > * Data bits: 5, 6, 7, 8, 9 bits
> > * Parity: odd, even, fixed, none
> > * Stop bits: 1 or 2 bits
> > - Hardware flow control (RTS/CTS)
> > - Multiple interrupt reporting mechanisms
> >
> > Signed-off-by: Wenhong Liu <liu.wenhong35@zte.com.cn>
> > Signed-off-by: Qingtao Liu <liu.qingtao2@zte.com.cn>
> > ---
> > MAINTAINERS | 3 +
> > drivers/tty/serial/Kconfig | 33 +
> > drivers/tty/serial/Makefile | 1 +
> > drivers/tty/serial/lrw_uart.c | 2822 ++++++++++++++++++++++++++++++
>
> This really is a totally new uart? No relation to any existing devices
> at all? Why would that be created?
>
> Anyway, this doesn't seem to build properly, how was it tested?
Yes, it`s a new uart for lrx SoC. The lrx SoC with its related docs will be released soon.
The uart TX & RX test environment:
1.Kernel: linux-next, commit 4cd074ae20bbcc293bbbce9163abe99d68ae6ae0(Tue May 5 14:57:23 2026 +0200),
with CONFIG_SERIAL_LRX_UART and SERIAL_LRX_UART_CONSOLE selected.
2.GCC: 14.1.0.
3.SoC: lrx SoC.
> > --- a/include/uapi/linux/serial_core.h
> > +++ b/include/uapi/linux/serial_core.h
> > @@ -231,6 +231,9 @@
> > /* Sunplus UART */
> > #define PORT_SUNPLUS 123
> >
> > +/* LRW UART */
> > +#define PORT_LRW 124
>
> Why is this id needed?
>
> thanks,
>
> greg k-h
Thanks for notice. This id is not needed actually.
According to https://lore.kernel.org/all/20231008001804.889727-1-jcmvbkbc@gmail.com/, the specific type
of the port is not important to the userspace.
^ permalink raw reply
* [PATCH v2 0/2] Add ZTE LRX UART driver
From: liu.qingtao2 @ 2026-05-13 8:46 UTC (permalink / raw)
To: krzk, gregkh, jirislaby, robh, krzk+dt, conor+dt, marex, pjw,
palmer, aou, alex, rdunlap, geert+renesas, quic_zongjian,
arturs.artamonovs, robert.marko, hvilleneuve, thierry.bultel.yh,
julianbraha, flavra, prabhakar.mahadev-lad.rj, linux-serial,
linux-kernel, devicetree, linux-riscv, liu.wenhong35, liu.fei16,
dai.hualiang, deng.weixian, jia.yunxiang, he.yilin, bai.lu5,
yang.susheng, shen.lin1, zuo.jiang, hu.shengming, gao.rui, tan.hu,
liu.qingtao2
From 9eba3be2e9b4d5c77956258e3c5db95049c3a895 Mon Sep 17 00:00:00 2001
From: Wenhong Liu
Date: Mon, 12 May 2026 10:15:55 +0800
Subject: [PATCH v2 0/2] Add ZTE LRX UART driver
This patch series adds support for the ZTE LRX UART controller.
Patch 1: Devicetree binding documentation
Patch 2: UART driver implementation
Key features:
- FIFO mode (16-byte depth)
- Baud rate configuration
- Hardware flow control (RTS/CTS)
- DMA support
- Multiple interrupt mechanisms
Wenhong Liu (2):
dt-bindings: serial: Add zte,lrx-uart
tty: serial: Add LRX UART driver
Co-developed-by: Qingtao Liu
Signed-off-by: Qingtao Liu
Signed-off-by: Wenhong Liu
../bindings/serial/zte,lrx-uart.yaml | 51 +
MAINTAINERS | 8 +
drivers/tty/serial/Kconfig | 33 +
drivers/tty/serial/Makefile | 1 +
drivers/tty/serial/lrx_uart.c | 2822 +++++++++++++++++
5 files changed, 2915 insertions(+)
create mode 100644 Documentation/devicetree/bindings/serial/zte,lrx-uart.yaml
create mode 100644 drivers/tty/serial/lrx_uart.c
--
2.27.0
^ permalink raw reply
* [PATCH v2 2/2] tty: serial: Add LRX UART driver
From: liu.qingtao2 @ 2026-05-13 8:46 UTC (permalink / raw)
To: krzk, gregkh, jirislaby, robh, krzk+dt, conor+dt, marex, pjw,
palmer, aou, alex, rdunlap, geert+renesas, quic_zongjian,
arturs.artamonovs, robert.marko, hvilleneuve, thierry.bultel.yh,
julianbraha, flavra, prabhakar.mahadev-lad.rj, linux-serial,
linux-kernel, devicetree, linux-riscv, liu.wenhong35, liu.fei16,
dai.hualiang, deng.weixian, jia.yunxiang, he.yilin, bai.lu5,
yang.susheng, shen.lin1, zuo.jiang, hu.shengming, gao.rui, tan.hu,
liu.qingtao2
From 9eba3be2e9b4d5c77956258e3c5db95049c3a895 Mon Sep 17 00:00:00 2001
From: Wenhong Liu <liu.wenhong35@zte.com.cn>
Date: Tue, 28 Apr 2026 22:30:40 +0800
Subject: [PATCH v2 2/2] tty: serial: Add LRX UART driver
Add support for the ZTE LRX UART controller with the following features:
- Support for FIFO mode (16-byte depth)
- Baud rate configuration
- Standard asynchronous communication formats:
* Data bits: 5, 6, 7, 8, 9 bits
* Parity: odd, even, fixed, none
* Stop bits: 1 or 2 bits
- Hardware flow control (RTS/CTS)
- Multiple interrupt reporting mechanisms
- DMA support for improved performance
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202602140029.NXkDToZ7-lkp@intel.com/
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202602140108.kLMOYbwS-lkp@intel.com/
Co-developed-by: Qingtao Liu <liu.qingtao2@zte.com.cn>
Signed-off-by: Qingtao Liu <liu.qingtao2@zte.com.cn>
Signed-off-by: Wenhong Liu <liu.wenhong35@zte.com.cn>
---
drivers/tty/serial/Kconfig | 33 +
drivers/tty/serial/Makefile | 1 +
drivers/tty/serial/lrx_uart.c | 2822 +++++++++++++++++++++++++++++++++
3 files changed, 2856 insertions(+)
create mode 100644 drivers/tty/serial/lrx_uart.c
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index 3e519d001e02..3e57eb85234e 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -1592,6 +1592,39 @@ config SERIAL_NUVOTON_MA35D1_CONSOLE
but you can alter that using a kernel command line option such as
"console=ttyNVTx".
+config SERIAL_LRX_UART
+ tristate "ZTE LRX UART support"
+ select SERIAL_CORE
+ help
+ This option enables support for the ZTE LRX Universal Asynchronous
+ Receiver/Transmitter (UART) serial controller.
+
+ Select this option if you are building a kernel for a device that
+ contains a ZTE LRX UART IP block.
+
+ This driver can be built as a module; if so, the module will be
+ called lrx_uart.
+
+ If you are using a system with an ZTE LRX UART controller, say Y or M here.
+ If unsure, say N.
+
+config SERIAL_LRX_UART_CONSOLE
+ bool "Console on ZTE LRX UART"
+ depends on SERIAL_LRX_UART=y
+ select SERIAL_CORE_CONSOLE
+ select SERIAL_EARLYCON
+ help
+ Say Y here if you wish to use an ZTE LRX UART as the system
+ console (the system console is the device which receives all kernel
+ messages and warnings and which allows logins in single user mode).
+
+ Even if you say Y here, the currently visible framebuffer console
+ (/dev/tty0) will still be used as the system console by default, but
+ you can alter that using a kernel command line option such as
+ "console=ttyLRX". (Try "man bootparam" or see the documentation of
+ your boot loader (lilo or loadlin) about how to pass options to the
+ kernel at boot time.)
+
endmenu
config SERIAL_MCTRL_GPIO
diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index bba7b21a4a1d..c02c4aa52398 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -46,6 +46,7 @@ obj-$(CONFIG_SERIAL_IP22_ZILOG) += ip22zilog.o
obj-$(CONFIG_SERIAL_JSM) += jsm/
obj-$(CONFIG_SERIAL_LANTIQ) += lantiq.o
obj-$(CONFIG_SERIAL_LITEUART) += liteuart.o
+obj-$(CONFIG_SERIAL_LRX_UART) += lrx_uart.o
obj-$(CONFIG_SERIAL_HS_LPC32XX) += lpc32xx_hs.o
obj-$(CONFIG_SERIAL_MAX3100) += max3100.o
obj-$(CONFIG_SERIAL_MAX310X) += max310x.o
diff --git a/drivers/tty/serial/lrx_uart.c b/drivers/tty/serial/lrx_uart.c
new file mode 100644
index 000000000000..4e9d59c9d11b
--- /dev/null
+++ b/drivers/tty/serial/lrx_uart.c
@@ -0,0 +1,2822 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Serial Port driver for ZTE LRX
+ *
+ * Copyright (c) 2025, ZTE Corporation. All rights reserved.
+ */
+
+#include <linux/module.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/console.h>
+#include <linux/platform_device.h>
+#include <linux/sysrq.h>
+#include <linux/device.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/serial_core.h>
+#include <linux/serial.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
+#include <linux/scatterlist.h>
+#include <linux/delay.h>
+#include <linux/types.h>
+#include <linux/of.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/sizes.h>
+#include <linux/io.h>
+#include <linux/acpi.h>
+
+#define UART_NR 14
+
+#define ISR_PASS_LIMIT 256
+
+#define LRX_UART_NAME "lrx-uart"
+
+#define LRX_UART_TTY_PREFIX "ttyLRX"
+
+/* LRX_UART_TX_FIFO_DEPTH: depth of the TX FIFO (in bytes) */
+#define LRX_UART_TX_FIFO_DEPTH 16
+
+/* LRX_UART_RX_FIFO_DEPTH: depth of the RX FIFO (in bytes) */
+#define LRX_UART_RX_FIFO_DEPTH 16
+
+/* ZTE LRX UART register offsets */
+#define UARTDR 0x00 /* Data register */
+#define UARTRSR 0x04 /* Receive status register */
+#define UARTECR 0x04 /* Error clear register */
+#define UARTSC 0x08 /* Special character register */
+#define UARTMDR 0x0C /* RS485 Muti-drop register */
+#define UARTTAT 0x10 /* RS485 turn-around time register */
+#define UARTFCR 0x14 /* FIFO control register */
+#define UARTFR 0x18 /* Flag register */
+#define UARTIND 0x1C /* Integer baud rate register */
+#define UARTFD 0x20 /* Fractional baud rate register */
+#define UARTBSR 0x24 /* Baud sample rate register */
+#define UARTFRCR 0x28 /* Frame control register */
+#define UARTMCFG 0x2C /* config register */
+#define UARTMCR 0x30 /* Modem control register */
+#define UARTIRCR 0x34 /* IrDA mode control register */
+#define UARTIMSC 0x38 /* Interrupt mask set/clear register */
+#define UARTRIS 0x3C /* Raw interrupt status register */
+#define UARTMIS 0x40 /* Masked interrupt states register */
+#define UARTICR 0x44 /* Interrupt clear register */
+#define UARTFCCR 0x48 /* Flow control register */
+#define UARTRVS 0x58 /* Version register */
+
+#define UARTDR_OE BIT(11)
+#define UARTDR_BE BIT(10)
+#define UARTDR_PE BIT(9)
+#define UARTDR_FE BIT(8)
+
+#define UARTRSR_OE BIT(3)
+#define UARTRSR_BE BIT(2)
+#define UARTRSR_PE BIT(1)
+#define UARTRSR_FE BIT(0)
+
+#define UARTFCR_RXFTRS GENMASK(7, 5)
+#define UARTFCR_RXFTRS_RX1_8 FIELD_PREP_CONST(UARTFCR_RXFTRS, 0)
+#define UARTFCR_RXFTRS_RX2_8 FIELD_PREP_CONST(UARTFCR_RXFTRS, 1)
+#define UARTFCR_RXFTRS_RX4_8 FIELD_PREP_CONST(UARTFCR_RXFTRS, 2)
+#define UARTFCR_RXFTRS_RX6_8 FIELD_PREP_CONST(UARTFCR_RXFTRS, 3)
+#define UARTFCR_RXFTRS_RX7_8 FIELD_PREP_CONST(UARTFCR_RXFTRS, 4)
+#define UARTFCR_TXFTRS GENMASK(4, 2)
+#define UARTFCR_TXFTRS_TX1_8 FIELD_PREP_CONST(UARTFCR_TXFTRS, 0)
+#define UARTFCR_TXFTRS_TX2_8 FIELD_PREP_CONST(UARTFCR_TXFTRS, 1)
+#define UARTFCR_TXFTRS_TX4_8 FIELD_PREP_CONST(UARTFCR_TXFTRS, 2)
+#define UARTFCR_TXFTRS_TX6_8 FIELD_PREP_CONST(UARTFCR_TXFTRS, 3)
+#define UARTFCR_TXFTRS_TX7_8 FIELD_PREP_CONST(UARTFCR_TXFTRS, 4)
+#define UARTFCR_FEN BIT(0)
+
+#define UARTFR_RI BIT(8)
+#define UARTFR_TXFE BIT(7)
+#define UARTFR_RXFF BIT(6)
+#define UARTFR_TXFF (1 << 5) /* used in ASM */
+#define UARTFR_RXFE BIT(4)
+#define UARTFR_BUSY (1 << 3) /* used in ASM */
+#define UARTFR_DCD BIT(2)
+#define UARTFR_DSR BIT(1)
+#define UARTFR_CTS BIT(0)
+#define UARTFR_TMSK (UARTFR_TXFF + UARTFR_BUSY)
+
+#define UARTFRCR_STP2 BIT(5)
+#define UARTFRCR_SPS BIT(4)
+#define UARTFRCR_EOP BIT(3)
+#define UARTFRCR_PEN BIT(2)
+#define UARTFRCR_WLEN_8 0x3
+#define UARTFRCR_WLEN_7 0x2
+#define UARTFRCR_WLEN_6 0x1
+#define UARTFRCR_WLEN_5 0x0
+
+#define UARTMCFG_LBE BIT(7) /* loopback enable */
+#define UARTMCFG_RXE BIT(3) /* receive enable */
+#define UARTMCFG_TXE BIT(2) /* transmit enable */
+#define UARTMCFG_BRK BIT(1) /* send break */
+#define UARTMCFG_UARTEN BIT(0) /* UART enable */
+
+#define UARTMCR_OUT2 BIT(3) /* OUT2 */
+#define UARTMCR_OUT1 BIT(2) /* OUT1 */
+#define UARTMCR_RTS BIT(1) /* RTS */
+#define UARTMCR_DTR BIT(0) /* DTR */
+
+#define UARTIMSC_OEIM BIT(10) /* overrun error interrupt mask */
+#define UARTIMSC_BEIM BIT(9) /* break error interrupt mask */
+#define UARTIMSC_PEIM BIT(8) /* parity error interrupt mask */
+#define UARTIMSC_FEIM BIT(7) /* framing error interrupt mask */
+#define UARTIMSC_RTIM BIT(6) /* receive timeout interrupt mask */
+#define UARTIMSC_TXIM BIT(5) /* transmit interrupt mask */
+#define UARTIMSC_RXIM BIT(4) /* receive interrupt mask */
+#define UARTIMSC_DSRMIM BIT(3) /* DSR interrupt mask */
+#define UARTIMSC_DCDMIM BIT(2) /* DCD interrupt mask */
+#define UARTIMSC_CTSMIM BIT(1) /* CTS interrupt mask */
+#define UARTIMSC_RIMIM BIT(0) /* RI interrupt mask */
+
+#define UARTICR_OEIC BIT(10) /* overrun error interrupt clear */
+#define UARTICR_BEIC BIT(9) /* break error interrupt clear */
+#define UARTICR_PEIC BIT(8) /* parity error interrupt clear */
+#define UARTICR_FEIC BIT(7) /* framing error interrupt clear */
+#define UARTICR_RTIC BIT(6) /* receive timeout interrupt clear */
+#define UARTICR_TXIC BIT(5) /* transmit interrupt clear */
+#define UARTICR_RXIC BIT(4) /* receive interrupt clear */
+#define UARTICR_DSRMIC BIT(3) /* DSR interrupt clear */
+#define UARTICR_DCDMIC BIT(2) /* DCD interrupt clear */
+#define UARTICR_CTSMIC BIT(1) /* CTS interrupt clear */
+#define UARTICR_RIMIC BIT(0) /* RI interrupt clear */
+
+#define UARTFCCR_CTSEN BIT(5) /* CTS hardware flow control */
+#define UARTFCCR_RTSEN BIT(4) /* RTS hardware flow control */
+#define UARTFCCR_DMAONERR BIT(2) /* disable dma on error */
+#define UARTFCCR_TXDMAE BIT(1) /* enable transmit dma */
+#define UARTFCCR_RXDMAE BIT(0) /* enable receive dma */
+
+#define UARTRSR_ANY (UARTRSR_OE | UARTRSR_BE | UARTRSR_PE | UARTRSR_FE)
+#define UARTFR_MODEM_ANY (UARTFR_DCD | UARTFR_DSR | UARTFR_CTS)
+
+#define UART_DR_ERROR (UARTDR_OE | UARTDR_BE | UARTDR_PE | UARTDR_FE)
+#define UART_DUMMY_DR_RX BIT(16)
+
+enum {
+ REG_DR,
+ REG_FCR,
+ REG_FR,
+ REG_IND,
+ REG_FD,
+ REG_BSR,
+ REG_FRCR,
+ REG_MCFG,
+ REG_MCR,
+ REG_IMSC,
+ REG_RIS,
+ REG_MIS,
+ REG_ICR,
+ REG_FCCR,
+
+ /* The size of the array - must be last */
+ REG_ARRAY_SIZE,
+};
+
+static u16 lrx_uart_std_offsets[REG_ARRAY_SIZE] = {
+ [REG_DR] = UARTDR,
+ [REG_FCR] = UARTFCR,
+ [REG_FR] = UARTFR,
+ [REG_IND] = UARTIND,
+ [REG_FD] = UARTFD,
+ [REG_BSR] = UARTBSR,
+ [REG_FRCR] = UARTFRCR,
+ [REG_MCFG] = UARTMCFG,
+ [REG_MCR] = UARTMCR,
+ [REG_IMSC] = UARTIMSC,
+ [REG_RIS] = UARTRIS,
+ [REG_MIS] = UARTMIS,
+ [REG_ICR] = UARTICR,
+ [REG_FCCR] = UARTFCCR,
+};
+
+/* There is by now at least one vendor with differing details, so handle it */
+struct vendor_data {
+ const u16 *reg_offset;
+ unsigned int fcr;
+ unsigned int fr_busy;
+ unsigned int fr_dsr;
+ unsigned int fr_cts;
+ unsigned int fr_ri;
+ unsigned int inv_fr;
+ bool access_32b;
+ bool oversampling;
+ bool dma_threshold;
+ bool cts_event_workaround;
+ bool always_enabled;
+ bool fixed_options;
+};
+
+static struct vendor_data vendor_lrx = {
+ .reg_offset = lrx_uart_std_offsets,
+ .fcr = UARTFCR_RXFTRS_RX4_8 | UARTFCR_TXFTRS_TX4_8 | UARTFCR_FEN,
+ .fr_busy = UARTFR_BUSY,
+ .fr_dsr = UARTFR_DSR,
+ .fr_cts = UARTFR_CTS,
+ .fr_ri = UARTFR_RI,
+ .access_32b = true,
+ .oversampling = false,
+ .dma_threshold = false,
+ .cts_event_workaround = false,
+ .always_enabled = false,
+ .fixed_options = true,
+};
+
+/* Deals with DMA transactions */
+
+struct lrx_uart_dmabuf {
+ dma_addr_t dma;
+ size_t len;
+ char *buf;
+};
+
+struct lrx_uart_dmarx_data {
+ struct dma_chan *chan;
+ struct completion complete;
+ bool use_buf_b;
+ struct lrx_uart_dmabuf dbuf_a;
+ struct lrx_uart_dmabuf dbuf_b;
+ dma_cookie_t cookie;
+ bool running;
+ struct timer_list timer;
+ unsigned int last_residue;
+ unsigned long last_jiffies;
+ bool auto_poll_rate;
+ unsigned int poll_rate;
+ unsigned int poll_timeout;
+};
+
+struct lrx_uart_dmatx_data {
+ struct dma_chan *chan;
+ dma_addr_t dma;
+ size_t len;
+ char *buf;
+ bool queued;
+};
+
+struct lrx_uart_data {
+ bool (*dma_filter)(struct dma_chan *chan, void *filter_param);
+ void *dma_rx_param;
+ void *dma_tx_param;
+ bool dma_rx_poll_enable;
+ unsigned int dma_rx_poll_rate;
+ unsigned int dma_rx_poll_timeout;
+ void (*init)(void);
+ void (*exit)(void);
+};
+
+/*
+ * We wrap our port structure around the generic uart_port.
+ */
+struct lrx_uart_port {
+ struct uart_port port;
+ const u16 *reg_offset;
+ struct clk *clk;
+ const struct vendor_data *vendor;
+ unsigned int im; /* interrupt mask */
+ unsigned int old_status;
+ unsigned int fifosize; /* vendor-specific */
+ unsigned int fixed_baud; /* vendor-set fixed baud rate */
+ char type[16];
+ bool rs485_tx_started;
+ unsigned int rs485_tx_drain_interval; /* usecs */
+#ifdef CONFIG_DMA_ENGINE
+ /* DMA stuff */
+ unsigned int dmacr; /* dma control reg */
+ bool using_tx_dma;
+ bool using_rx_dma;
+ struct lrx_uart_dmarx_data dmarx;
+ struct lrx_uart_dmatx_data dmatx;
+ bool dma_probed;
+#endif
+};
+
+static unsigned int lrx_uart_tx_empty(struct uart_port *port);
+
+static unsigned int lrx_uart_reg_to_offset(const struct lrx_uart_port *sup,
+ unsigned int reg)
+{
+ return sup->reg_offset[reg];
+}
+
+static unsigned int lrx_uart_read(const struct lrx_uart_port *sup,
+ unsigned int reg)
+{
+ void __iomem *addr = sup->port.membase + lrx_uart_reg_to_offset(sup, reg);
+
+ return (sup->port.iotype == UPIO_MEM32) ?
+ readl_relaxed(addr) : readw_relaxed(addr);
+}
+
+static void lrx_uart_write(unsigned int val, const struct lrx_uart_port *sup,
+ unsigned int reg)
+{
+ void __iomem *addr = sup->port.membase + lrx_uart_reg_to_offset(sup, reg);
+
+ if (sup->port.iotype == UPIO_MEM32)
+ writel_relaxed(val, addr);
+ else
+ writew_relaxed(val, addr);
+}
+
+/*
+ * Reads up to 256 characters from the FIFO or until it's empty and
+ * inserts them into the TTY layer. Returns the number of characters
+ * read from the FIFO.
+ */
+static int lrx_uart_fifo_to_tty(struct lrx_uart_port *sup)
+{
+ unsigned int ch, fifotaken;
+ int sysrq;
+ u16 status;
+ u8 flag;
+
+ for (fifotaken = 0; fifotaken != 256; fifotaken++) {
+ status = lrx_uart_read(sup, REG_FR);
+ if (status & UARTFR_RXFE)
+ break;
+
+ /* Take chars from the FIFO and update status */
+ ch = lrx_uart_read(sup, REG_DR) | UART_DUMMY_DR_RX;
+ flag = TTY_NORMAL;
+ sup->port.icount.rx++;
+
+ if (unlikely(ch & UART_DR_ERROR)) {
+ if (ch & UARTDR_BE) {
+ ch &= ~(UARTDR_FE | UARTDR_PE);
+ sup->port.icount.brk++;
+ if (uart_handle_break(&sup->port))
+ continue;
+ } else if (ch & UARTDR_PE) {
+ sup->port.icount.parity++;
+ } else if (ch & UARTDR_FE) {
+ sup->port.icount.frame++;
+ }
+ if (ch & UARTDR_OE)
+ sup->port.icount.overrun++;
+
+ ch &= sup->port.read_status_mask;
+
+ if (ch & UARTDR_BE)
+ flag = TTY_BREAK;
+ else if (ch & UARTDR_PE)
+ flag = TTY_PARITY;
+ else if (ch & UARTDR_FE)
+ flag = TTY_FRAME;
+ }
+
+ sysrq = uart_prepare_sysrq_char(&sup->port, ch & 255);
+ if (!sysrq)
+ uart_insert_char(&sup->port, ch, UARTDR_OE, ch, flag);
+ }
+
+ return fifotaken;
+}
+
+/*
+ * All the DMA operation mode stuff goes inside this ifdef.
+ * This assumes that you have a generic DMA device interface,
+ * no custom DMA interfaces are supported.
+ */
+#ifdef CONFIG_DMA_ENGINE
+
+#define LRX_UART_DMA_BUFFER_SIZE PAGE_SIZE
+
+static int lrx_uart_dmabuf_init(struct dma_chan *chan, struct lrx_uart_dmabuf *db,
+ enum dma_data_direction dir)
+{
+ db->buf = dma_alloc_coherent(chan->device->dev, LRX_UART_DMA_BUFFER_SIZE,
+ &db->dma, GFP_KERNEL);
+ if (!db->buf)
+ return -ENOMEM;
+ db->len = LRX_UART_DMA_BUFFER_SIZE;
+
+ return 0;
+}
+
+static void lrx_uart_dmabuf_free(struct dma_chan *chan, struct lrx_uart_dmabuf *db,
+ enum dma_data_direction dir)
+{
+ if (db->buf) {
+ dma_free_coherent(chan->device->dev,
+ LRX_UART_DMA_BUFFER_SIZE, db->buf, db->dma);
+ }
+}
+
+static void lrx_uart_dma_probe(struct lrx_uart_port *sup)
+{
+ /* DMA is the sole user of the platform data right now */
+ struct lrx_uart_data *plat = dev_get_platdata(sup->port.dev);
+ struct device *dev = sup->port.dev;
+ struct dma_slave_config tx_conf = {
+ .dst_addr = sup->port.mapbase +
+ lrx_uart_reg_to_offset(sup, REG_DR),
+ .dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE,
+ .direction = DMA_MEM_TO_DEV,
+ .dst_maxburst = sup->fifosize >> 1,
+ .device_fc = false,
+ };
+ struct dma_chan *chan;
+ dma_cap_mask_t mask;
+
+ sup->dma_probed = true;
+ chan = dma_request_chan(dev, "tx");
+ if (IS_ERR(chan)) {
+ if (PTR_ERR(chan) == -EPROBE_DEFER) {
+ sup->dma_probed = false;
+ return;
+ }
+
+ /* We need platform data */
+ if (!plat || !plat->dma_filter) {
+ dev_dbg(sup->port.dev, "no DMA platform data\n");
+ return;
+ }
+
+ /* Try to acquire a generic DMA engine slave TX channel */
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_SLAVE, mask);
+
+ chan = dma_request_channel(mask, plat->dma_filter,
+ plat->dma_tx_param);
+ if (!chan) {
+ dev_err(sup->port.dev, "no TX DMA channel!\n");
+ return;
+ }
+ }
+
+ dmaengine_slave_config(chan, &tx_conf);
+ sup->dmatx.chan = chan;
+
+ dev_info(sup->port.dev, "DMA channel TX %s\n",
+ dma_chan_name(sup->dmatx.chan));
+
+ /* Optionally make use of an RX channel as well */
+ chan = dma_request_chan(dev, "rx");
+
+ if (IS_ERR(chan) && plat && plat->dma_rx_param) {
+ chan = dma_request_channel(mask, plat->dma_filter, plat->dma_rx_param);
+
+ if (!chan) {
+ dev_err(sup->port.dev, "no RX DMA channel!\n");
+ return;
+ }
+ }
+
+ if (!IS_ERR(chan)) {
+ struct dma_slave_config rx_conf = {
+ .src_addr = sup->port.mapbase +
+ lrx_uart_reg_to_offset(sup, REG_DR),
+ .src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE,
+ .direction = DMA_DEV_TO_MEM,
+ .src_maxburst = sup->fifosize >> 2,
+ .device_fc = false,
+ };
+ struct dma_slave_caps caps;
+
+ /*
+ * Some DMA controllers provide information on their capabilities.
+ * If the controller does, check for suitable residue processing
+ * otherwise assime all is well.
+ */
+ if (dma_get_slave_caps(chan, &caps) == 0) {
+ if (caps.residue_granularity ==
+ DMA_RESIDUE_GRANULARITY_DESCRIPTOR) {
+ dma_release_channel(chan);
+ dev_info(sup->port.dev,
+ "RX DMA disabled - no residue processing\n");
+ return;
+ }
+ }
+ dmaengine_slave_config(chan, &rx_conf);
+ sup->dmarx.chan = chan;
+
+ sup->dmarx.auto_poll_rate = false;
+ if (plat && plat->dma_rx_poll_enable) {
+ /* Set poll rate if specified. */
+ if (plat->dma_rx_poll_rate) {
+ sup->dmarx.auto_poll_rate = false;
+ sup->dmarx.poll_rate = plat->dma_rx_poll_rate;
+ } else {
+ /*
+ * 100 ms defaults to poll rate if not
+ * specified. This will be adjusted with
+ * the baud rate at set_termios.
+ */
+ sup->dmarx.auto_poll_rate = true;
+ sup->dmarx.poll_rate = 100;
+ }
+ /* 3 secs defaults poll_timeout if not specified. */
+ if (plat->dma_rx_poll_timeout)
+ sup->dmarx.poll_timeout =
+ plat->dma_rx_poll_timeout;
+ else
+ sup->dmarx.poll_timeout = 3000;
+ } else if (!plat && dev->of_node) {
+ sup->dmarx.auto_poll_rate =
+ of_property_read_bool(dev->of_node, "auto-poll");
+ if (sup->dmarx.auto_poll_rate) {
+ u32 x;
+
+ if (of_property_read_u32(dev->of_node, "poll-rate-ms", &x) == 0)
+ sup->dmarx.poll_rate = x;
+ else
+ sup->dmarx.poll_rate = 100;
+ if (of_property_read_u32(dev->of_node, "poll-timeout-ms", &x) == 0)
+ sup->dmarx.poll_timeout = x;
+ else
+ sup->dmarx.poll_timeout = 3000;
+ }
+ }
+ dev_info(sup->port.dev, "DMA channel RX %s\n",
+ dma_chan_name(sup->dmarx.chan));
+ }
+}
+
+static void lrx_uart_dma_remove(struct lrx_uart_port *sup)
+{
+ if (sup->dmatx.chan)
+ dma_release_channel(sup->dmatx.chan);
+ if (sup->dmarx.chan)
+ dma_release_channel(sup->dmarx.chan);
+}
+
+/* Forward declare these for the refill routine */
+static int lrx_uart_dma_tx_refill(struct lrx_uart_port *sup);
+static void lrx_uart_start_tx_pio(struct lrx_uart_port *sup);
+
+/*
+ * The current DMA TX buffer has been sent.
+ * Try to queue up another DMA buffer.
+ */
+static void lrx_uart_dma_tx_callback(void *data)
+{
+ struct lrx_uart_port *sup = data;
+ struct tty_port *tport = &sup->port.state->port;
+ struct lrx_uart_dmatx_data *dmatx = &sup->dmatx;
+ unsigned long flags;
+ u16 dmacr;
+
+ uart_port_lock_irqsave(&sup->port, &flags);
+ if (sup->dmatx.queued)
+ dma_unmap_single(dmatx->chan->device->dev, dmatx->dma,
+ dmatx->len, DMA_TO_DEVICE);
+
+ dmacr = sup->dmacr;
+ sup->dmacr = dmacr & ~UARTFCCR_TXDMAE;
+ lrx_uart_write(sup->dmacr, sup, REG_FCCR);
+
+ /*
+ * If TX DMA was disabled, it means that we've stopped the DMA for
+ * some reason (eg, XOFF received, or we want to send an X-char.)
+ *
+ * Note: we need to be careful here of a potential race between DMA
+ * and the rest of the driver - if the driver disables TX DMA while
+ * a TX buffer completing, we must update the tx queued status to
+ * get further refills (hence we check dmacr).
+ */
+ if (!(dmacr & UARTFCCR_TXDMAE) || uart_tx_stopped(&sup->port) ||
+ kfifo_is_empty(&tport->xmit_fifo)) {
+ sup->dmatx.queued = false;
+ uart_port_unlock_irqrestore(&sup->port, flags);
+ return;
+ }
+
+ if (lrx_uart_dma_tx_refill(sup) <= 0)
+ /*
+ * We didn't queue a DMA buffer for some reason, but we
+ * have data pending to be sent. Re-enable the TX IRQ.
+ */
+ lrx_uart_start_tx_pio(sup);
+
+ uart_port_unlock_irqrestore(&sup->port, flags);
+}
+
+/*
+ * Try to refill the TX DMA buffer.
+ * Locking: called with port lock held and IRQs disabled.
+ * Returns:
+ * 1 if we queued up a TX DMA buffer.
+ * 0 if we didn't want to handle this by DMA
+ * <0 on error
+ */
+static int lrx_uart_dma_tx_refill(struct lrx_uart_port *sup)
+{
+ struct lrx_uart_dmatx_data *dmatx = &sup->dmatx;
+ struct dma_chan *chan = dmatx->chan;
+ struct dma_device *dma_dev = chan->device;
+ struct dma_async_tx_descriptor *desc;
+ struct tty_port *tport = &sup->port.state->port;
+ unsigned int count;
+
+ /*
+ * Try to avoid the overhead involved in using DMA if the
+ * transaction fits in the first half of the FIFO, by using
+ * the standard interrupt handling. This ensures that we
+ * issue a uart_write_wakeup() at the appropriate time.
+ */
+ count = kfifo_len(&tport->xmit_fifo);
+ if (count < (sup->fifosize >> 1)) {
+ sup->dmatx.queued = false;
+ return 0;
+ }
+
+ /*
+ * Bodge: don't send the last character by DMA, as this
+ * will prevent XON from notifying us to restart DMA.
+ */
+ count -= 1;
+
+ /* Else proceed to copy the TX chars to the DMA buffer and fire DMA */
+ if (count > LRX_UART_DMA_BUFFER_SIZE)
+ count = LRX_UART_DMA_BUFFER_SIZE;
+
+ count = kfifo_out_peek(&tport->xmit_fifo, dmatx->buf, count);
+ dmatx->len = count;
+ dmatx->dma = dma_map_single(dma_dev->dev, dmatx->buf, count,
+ DMA_TO_DEVICE);
+ if (dmatx->dma == DMA_MAPPING_ERROR) {
+ sup->dmatx.queued = false;
+ dev_dbg(sup->port.dev, "unable to map TX DMA\n");
+ return -EBUSY;
+ }
+
+ desc = dmaengine_prep_slave_single(chan, dmatx->dma, dmatx->len, DMA_MEM_TO_DEV,
+ DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+ if (!desc) {
+ dma_unmap_single(dma_dev->dev, dmatx->dma, dmatx->len, DMA_TO_DEVICE);
+ sup->dmatx.queued = false;
+ /*
+ * If DMA cannot be used right now, we complete this
+ * transaction via IRQ and let the TTY layer retry.
+ */
+ dev_dbg(sup->port.dev, "TX DMA busy\n");
+ return -EBUSY;
+ }
+
+ /* Some data to go along to the callback */
+ desc->callback = lrx_uart_dma_tx_callback;
+ desc->callback_param = sup;
+
+ /* All errors should happen at prepare time */
+ dmaengine_submit(desc);
+
+ /* Fire the DMA transaction */
+ dma_dev->device_issue_pending(chan);
+
+ sup->dmacr |= UARTFCCR_TXDMAE;
+ lrx_uart_write(sup->dmacr, sup, REG_FCCR);
+ sup->dmatx.queued = true;
+
+ /*
+ * Now we know that DMA will fire, so advance the ring buffer
+ * with the stuff we just dispatched.
+ */
+ uart_xmit_advance(&sup->port, count);
+
+ if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS)
+ uart_write_wakeup(&sup->port);
+
+ return 1;
+}
+
+/*
+ * We received a transmit interrupt without a pending X-char but with
+ * pending characters.
+ * Locking: called with port lock held and IRQs disabled.
+ * Returns:
+ * false if we want to use PIO to transmit
+ * true if we queued a DMA buffer
+ */
+static bool lrx_uart_dma_tx_irq(struct lrx_uart_port *sup)
+{
+ if (!sup->using_tx_dma)
+ return false;
+
+ /*
+ * If we already have a TX buffer queued, but received a
+ * TX interrupt, it will be because we've just sent an X-char.
+ * Ensure the TX DMA is enabled and the TX IRQ is disabled.
+ */
+ if (sup->dmatx.queued) {
+ sup->dmacr |= UARTFCCR_TXDMAE;
+ lrx_uart_write(sup->dmacr, sup, REG_FCCR);
+ sup->im &= ~UARTIMSC_TXIM;
+ lrx_uart_write(sup->im, sup, REG_IMSC);
+ return true;
+ }
+
+ /*
+ * We don't have a TX buffer queued, so try to queue one.
+ * If we successfully queued a buffer, mask the TX IRQ.
+ */
+ if (lrx_uart_dma_tx_refill(sup) > 0) {
+ sup->im &= ~UARTIMSC_TXIM;
+ lrx_uart_write(sup->im, sup, REG_IMSC);
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Stop the DMA transmit (eg, due to received XOFF).
+ * Locking: called with port lock held and IRQs disabled.
+ */
+static inline void lrx_uart_dma_tx_stop(struct lrx_uart_port *sup)
+{
+ if (sup->dmatx.queued) {
+ sup->dmacr &= ~UARTFCCR_TXDMAE;
+ lrx_uart_write(sup->dmacr, sup, REG_FCCR);
+ }
+}
+
+/*
+ * Try to start a DMA transmit, or in the case of an XON/OFF
+ * character queued for send, try to get that character out ASAP.
+ * Locking: called with port lock held and IRQs disabled.
+ * Returns:
+ * false if we want the TX IRQ to be enabled
+ * true if we have a buffer queued
+ */
+static inline bool lrx_uart_dma_tx_start(struct lrx_uart_port *sup)
+{
+ u16 dmacr;
+
+ if (!sup->using_tx_dma)
+ return false;
+
+ if (!sup->port.x_char) {
+ /* no X-char, try to push chars out in DMA mode */
+ bool ret = true;
+
+ if (!sup->dmatx.queued) {
+ if (lrx_uart_dma_tx_refill(sup) > 0) {
+ sup->im &= ~UARTIMSC_TXIM;
+ lrx_uart_write(sup->im, sup, REG_IMSC);
+ } else {
+ ret = false;
+ }
+ } else if (!(sup->dmacr & UARTFCCR_TXDMAE)) {
+ sup->dmacr |= UARTFCCR_TXDMAE;
+ lrx_uart_write(sup->dmacr, sup, REG_FCCR);
+ }
+ return ret;
+ }
+
+ /*
+ * We have an X-char to send. Disable DMA to prevent it loading
+ * the TX fifo, and then see if we can stuff it into the FIFO.
+ */
+ dmacr = sup->dmacr;
+ sup->dmacr &= ~UARTFCCR_TXDMAE;
+ lrx_uart_write(sup->dmacr, sup, REG_FCCR);
+
+ if (lrx_uart_read(sup, REG_FR) & UARTFR_TXFF) {
+ /*
+ * No space in the FIFO, so enable the transmit interrupt
+ * so we know when there is space. Note that once we've
+ * loaded the character, we should just re-enable DMA.
+ */
+ return false;
+ }
+
+ lrx_uart_write(sup->port.x_char, sup, REG_DR);
+ sup->port.icount.tx++;
+ sup->port.x_char = 0;
+
+ /* Success - restore the DMA state */
+ sup->dmacr = dmacr;
+ lrx_uart_write(dmacr, sup, REG_FCCR);
+
+ return true;
+}
+
+/*
+ * Flush the transmit buffer.
+ * Locking: called with port lock held and IRQs disabled.
+ */
+static void lrx_uart_dma_flush_buffer(struct uart_port *port)
+__releases(&sup->port.lock)
+__acquires(&sup->port.lock)
+{
+ struct lrx_uart_port *sup =
+ container_of(port, struct lrx_uart_port, port);
+
+ if (!sup->using_tx_dma)
+ return;
+
+ dmaengine_terminate_async(sup->dmatx.chan);
+
+ if (sup->dmatx.queued) {
+ dma_unmap_single(sup->dmatx.chan->device->dev, sup->dmatx.dma,
+ sup->dmatx.len, DMA_TO_DEVICE);
+ sup->dmatx.queued = false;
+ sup->dmacr &= ~UARTFCCR_TXDMAE;
+ lrx_uart_write(sup->dmacr, sup, REG_FCCR);
+ }
+}
+
+static void lrx_uart_dma_rx_callback(void *data);
+
+static int lrx_uart_dma_rx_trigger_dma(struct lrx_uart_port *sup)
+{
+ struct dma_chan *rxchan = sup->dmarx.chan;
+ struct lrx_uart_dmarx_data *dmarx = &sup->dmarx;
+ struct dma_async_tx_descriptor *desc;
+ struct lrx_uart_dmabuf *dbuf;
+
+ if (!rxchan)
+ return -EIO;
+
+ /* Start the RX DMA job */
+ dbuf = sup->dmarx.use_buf_b ?
+ &sup->dmarx.dbuf_b : &sup->dmarx.dbuf_a;
+ desc = dmaengine_prep_slave_single(rxchan, dbuf->dma, dbuf->len,
+ DMA_DEV_TO_MEM,
+ DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+ /*
+ * If the DMA engine is busy and cannot prepare a
+ * channel, no big deal, the driver will fall back
+ * to interrupt mode as a result of this error code.
+ */
+ if (!desc) {
+ sup->dmarx.running = false;
+ dmaengine_terminate_all(rxchan);
+ return -EBUSY;
+ }
+
+ /* Some data to go along to the callback */
+ desc->callback = lrx_uart_dma_rx_callback;
+ desc->callback_param = sup;
+ dmarx->cookie = dmaengine_submit(desc);
+ dma_async_issue_pending(rxchan);
+
+ sup->dmacr |= UARTFCCR_RXDMAE;
+ lrx_uart_write(sup->dmacr, sup, REG_FCCR);
+ sup->dmarx.running = true;
+
+ sup->im &= ~UARTIMSC_RXIM;
+ lrx_uart_write(sup->im, sup, REG_IMSC);
+
+ return 0;
+}
+
+/*
+ * This is called when either the DMA job is complete, or
+ * the FIFO timeout interrupt occurred. This must be called
+ * with the port spinlock sup->port.lock held.
+ */
+static void lrx_uart_dma_rx_chars(struct lrx_uart_port *sup,
+ u32 pending, bool use_buf_b,
+ bool readfifo)
+{
+ struct tty_port *port = &sup->port.state->port;
+ struct lrx_uart_dmabuf *dbuf = use_buf_b ?
+ &sup->dmarx.dbuf_b : &sup->dmarx.dbuf_a;
+ int dma_count = 0;
+ u32 fifotaken = 0; /* only used for vdbg() */
+
+ struct lrx_uart_dmarx_data *dmarx = &sup->dmarx;
+ int dmataken = 0;
+
+ if (sup->dmarx.poll_rate) {
+ /* The data can be taken by polling */
+ dmataken = dbuf->len - dmarx->last_residue;
+ /* Recalculate the pending size */
+ if (pending >= dmataken)
+ pending -= dmataken;
+ }
+
+ /* Pick the remain data from the DMA */
+ if (pending) {
+ /*
+ * First take all chars in the DMA pipe, then look in the FIFO.
+ * Note that tty_insert_flip_buf() tries to take as many chars
+ * as it can.
+ */
+ dma_count = tty_insert_flip_string(port, dbuf->buf + dmataken, pending);
+
+ sup->port.icount.rx += dma_count;
+ if (dma_count < pending)
+ dev_warn(sup->port.dev,
+ "couldn't insert all characters (TTY is full?)\n");
+ }
+
+ /* Reset the last_residue for Rx DMA poll */
+ if (sup->dmarx.poll_rate)
+ dmarx->last_residue = dbuf->len;
+
+ /*
+ * Only continue with trying to read the FIFO if all DMA chars have
+ * been taken first.
+ */
+ if (dma_count == pending && readfifo) {
+ /* Clear any error flags */
+ lrx_uart_write(UARTICR_OEIC | UARTICR_BEIC | UARTICR_PEIC |
+ UARTICR_FEIC, sup, REG_ICR);
+
+ /*
+ * If we read all the DMA'd characters, and we had an
+ * incomplete buffer, that could be due to an rx error, or
+ * maybe we just timed out. Read any pending chars and check
+ * the error status.
+ *
+ * Error conditions will only occur in the FIFO, these will
+ * trigger an immediate interrupt and stop the DMA job, so we
+ * will always find the error in the FIFO, never in the DMA
+ * buffer.
+ */
+ fifotaken = lrx_uart_fifo_to_tty(sup);
+ }
+
+ dev_vdbg(sup->port.dev,
+ "Took %d chars from DMA buffer and %d chars from the FIFO\n",
+ dma_count, fifotaken);
+ tty_flip_buffer_push(port);
+}
+
+static void lrx_uart_dma_rx_irq(struct lrx_uart_port *sup)
+{
+ struct lrx_uart_dmarx_data *dmarx = &sup->dmarx;
+ struct dma_chan *rxchan = dmarx->chan;
+ struct lrx_uart_dmabuf *dbuf = dmarx->use_buf_b ?
+ &dmarx->dbuf_b : &dmarx->dbuf_a;
+ size_t pending;
+ struct dma_tx_state state;
+ enum dma_status dmastat;
+
+ /*
+ * Pause the transfer so we can trust the current counter,
+ * do this before we pause the ZTE LRX UART block, else we may
+ * overflow the FIFO.
+ */
+ if (dmaengine_pause(rxchan))
+ dev_err(sup->port.dev, "unable to pause DMA transfer\n");
+ dmastat = rxchan->device->device_tx_status(rxchan,
+ dmarx->cookie, &state);
+ if (dmastat != DMA_PAUSED)
+ dev_err(sup->port.dev, "unable to pause DMA transfer\n");
+
+ /* Disable RX DMA - incoming data will wait in the FIFO */
+ sup->dmacr &= ~UARTFCCR_RXDMAE;
+ lrx_uart_write(sup->dmacr, sup, REG_FCCR);
+ sup->dmarx.running = false;
+
+ pending = dbuf->len - state.residue;
+ if (WARN_ONCE(pending > LRX_UART_DMA_BUFFER_SIZE,
+ "pending %zu exceeds DMA buffer size %zu\n",
+ pending, (size_t)LRX_UART_DMA_BUFFER_SIZE))
+ pending = LRX_UART_DMA_BUFFER_SIZE;
+ /* Then we terminate the transfer - we now know our residue */
+ dmaengine_terminate_all(rxchan);
+
+ /*
+ * This will take the chars we have so far and insert
+ * into the framework.
+ */
+ lrx_uart_dma_rx_chars(sup, pending, dmarx->use_buf_b, true);
+
+ /* Switch buffer & re-trigger DMA job */
+ dmarx->use_buf_b = !dmarx->use_buf_b;
+ if (lrx_uart_dma_rx_trigger_dma(sup)) {
+ dev_dbg(sup->port.dev,
+ "could not retrigger RX DMA job fall back to interrupt mode\n");
+ sup->im |= UARTIMSC_RXIM;
+ lrx_uart_write(sup->im, sup, REG_IMSC);
+ }
+}
+
+static void lrx_uart_dma_rx_callback(void *data)
+{
+ struct lrx_uart_port *sup = data;
+ struct lrx_uart_dmarx_data *dmarx = &sup->dmarx;
+ struct dma_chan *rxchan = dmarx->chan;
+ bool lastbuf = dmarx->use_buf_b;
+ struct lrx_uart_dmabuf *dbuf = dmarx->use_buf_b ?
+ &dmarx->dbuf_b : &dmarx->dbuf_a;
+ size_t pending;
+ struct dma_tx_state state;
+ int ret;
+
+ /*
+ * This completion interrupt occurs typically when the
+ * RX buffer is totally stuffed but no timeout has yet
+ * occurred. When that happens, we just want the RX
+ * routine to flush out the secondary DMA buffer while
+ * we immediately trigger the next DMA job.
+ */
+ uart_port_lock_irq(&sup->port);
+ /*
+ * Rx data can be taken by the UART interrupts during
+ * the DMA irq handler. So we check the residue here.
+ */
+ rxchan->device->device_tx_status(rxchan, dmarx->cookie, &state);
+ pending = dbuf->len - state.residue;
+ if (WARN_ONCE(pending > LRX_UART_DMA_BUFFER_SIZE,
+ "pending %zu exceeds DMA buffer size %zu\n",
+ pending, (size_t)LRX_UART_DMA_BUFFER_SIZE))
+ pending = LRX_UART_DMA_BUFFER_SIZE;
+ /* Then we terminate the transfer - we now know our residue */
+ dmaengine_terminate_all(rxchan);
+
+ sup->dmarx.running = false;
+ dmarx->use_buf_b = !lastbuf;
+ ret = lrx_uart_dma_rx_trigger_dma(sup);
+
+ lrx_uart_dma_rx_chars(sup, pending, lastbuf, false);
+ uart_unlock_and_check_sysrq(&sup->port);
+ /*
+ * Do this check after we picked the DMA chars so we don't
+ * get some IRQ immediately from RX.
+ */
+ if (ret) {
+ dev_dbg(sup->port.dev,
+ "could not retrigger RX DMA job fall back to interrupt mode\n");
+ sup->im |= UARTIMSC_RXIM;
+ lrx_uart_write(sup->im, sup, REG_IMSC);
+ }
+}
+
+/*
+ * Stop accepting received characters, when we're shutting down or
+ * suspending this port.
+ * Locking: called with port lock held and IRQs disabled.
+ */
+static inline void lrx_uart_dma_rx_stop(struct lrx_uart_port *sup)
+{
+ if (!sup->using_rx_dma)
+ return;
+
+ /* FIXME. Just disable the DMA enable */
+ sup->dmacr &= ~UARTFCCR_RXDMAE;
+ lrx_uart_write(sup->dmacr, sup, REG_FCCR);
+}
+
+/*
+ * Timer handler for Rx DMA polling.
+ * Every polling, It checks the residue in the dma buffer and transfer
+ * data to the tty. Also, last_residue is updated for the next polling.
+ */
+static void lrx_uart_dma_rx_poll(struct timer_list *t)
+{
+ struct lrx_uart_port *sup = timer_container_of(sup, t, dmarx.timer);
+ struct tty_port *port = &sup->port.state->port;
+ struct lrx_uart_dmarx_data *dmarx = &sup->dmarx;
+ struct dma_chan *rxchan = sup->dmarx.chan;
+ unsigned long flags;
+ unsigned int dmataken = 0;
+ unsigned int size = 0;
+ struct lrx_uart_dmabuf *dbuf;
+ int dma_count;
+ struct dma_tx_state state;
+
+ dbuf = dmarx->use_buf_b ? &sup->dmarx.dbuf_b : &sup->dmarx.dbuf_a;
+ rxchan->device->device_tx_status(rxchan, dmarx->cookie, &state);
+ if (likely(state.residue < dmarx->last_residue)) {
+ dmataken = dbuf->len - dmarx->last_residue;
+ size = dmarx->last_residue - state.residue;
+ dma_count = tty_insert_flip_string(port, dbuf->buf + dmataken,
+ size);
+ if (dma_count == size)
+ dmarx->last_residue = state.residue;
+ dmarx->last_jiffies = jiffies;
+ }
+ tty_flip_buffer_push(port);
+
+ /*
+ * If no data is received in poll_timeout, the driver will fall back
+ * to interrupt mode. We will retrigger DMA at the first interrupt.
+ */
+ if (jiffies_to_msecs(jiffies - dmarx->last_jiffies)
+ > sup->dmarx.poll_timeout) {
+ uart_port_lock_irqsave(&sup->port, &flags);
+ lrx_uart_dma_rx_stop(sup);
+ sup->im |= UARTIMSC_RXIM;
+ lrx_uart_write(sup->im, sup, REG_IMSC);
+ uart_port_unlock_irqrestore(&sup->port, flags);
+
+ sup->dmarx.running = false;
+ dmaengine_terminate_all(rxchan);
+ timer_delete(&sup->dmarx.timer);
+ } else {
+ mod_timer(&sup->dmarx.timer,
+ jiffies + msecs_to_jiffies(sup->dmarx.poll_rate));
+ }
+}
+
+static void lrx_uart_dma_startup(struct lrx_uart_port *sup)
+{
+ int ret;
+
+ if (!sup->dma_probed)
+ lrx_uart_dma_probe(sup);
+
+ if (!sup->dmatx.chan)
+ return;
+
+ sup->dmatx.buf = kmalloc(LRX_UART_DMA_BUFFER_SIZE, GFP_KERNEL | __GFP_DMA);
+ if (!sup->dmatx.buf) {
+ sup->port.fifosize = sup->fifosize;
+ return;
+ }
+
+ sup->dmatx.len = LRX_UART_DMA_BUFFER_SIZE;
+
+ /* The DMA buffer is now the FIFO the TTY subsystem can use */
+ sup->port.fifosize = LRX_UART_DMA_BUFFER_SIZE;
+ sup->using_tx_dma = true;
+
+ if (!sup->dmarx.chan)
+ goto skip_rx;
+
+ /* Allocate and map DMA RX buffers */
+ ret = lrx_uart_dmabuf_init(sup->dmarx.chan, &sup->dmarx.dbuf_a,
+ DMA_FROM_DEVICE);
+ if (ret) {
+ dev_err(sup->port.dev, "failed to init DMA %s: %d\n",
+ "RX buffer A", ret);
+ goto skip_rx;
+ }
+
+ ret = lrx_uart_dmabuf_init(sup->dmarx.chan, &sup->dmarx.dbuf_b,
+ DMA_FROM_DEVICE);
+ if (ret) {
+ dev_err(sup->port.dev, "failed to init DMA %s: %d\n",
+ "RX buffer B", ret);
+ lrx_uart_dmabuf_free(sup->dmarx.chan, &sup->dmarx.dbuf_a,
+ DMA_FROM_DEVICE);
+ goto skip_rx;
+ }
+
+ sup->using_rx_dma = true;
+
+skip_rx:
+ /* Turn on DMA error (RX/TX will be enabled on demand) */
+ sup->dmacr |= UARTFCCR_DMAONERR;
+ lrx_uart_write(sup->dmacr, sup, REG_FCCR);
+
+ if (sup->using_rx_dma) {
+ if (lrx_uart_dma_rx_trigger_dma(sup))
+ dev_dbg(sup->port.dev,
+ "could not trigger initial RX DMA job, fall back to interrupt mode\n");
+ if (sup->dmarx.poll_rate) {
+ timer_setup(&sup->dmarx.timer, lrx_uart_dma_rx_poll, 0);
+ mod_timer(&sup->dmarx.timer,
+ jiffies + msecs_to_jiffies(sup->dmarx.poll_rate));
+ sup->dmarx.last_residue = LRX_UART_DMA_BUFFER_SIZE;
+ sup->dmarx.last_jiffies = jiffies;
+ }
+ }
+}
+
+static void lrx_uart_dma_shutdown(struct lrx_uart_port *sup)
+{
+ if (!(sup->using_tx_dma || sup->using_rx_dma))
+ return;
+
+ /* Disable RX and TX DMA */
+ while (lrx_uart_read(sup, REG_FR) & sup->vendor->fr_busy)
+ cpu_relax();
+
+ uart_port_lock_irq(&sup->port);
+ sup->dmacr &= ~(UARTFCCR_DMAONERR | UARTFCCR_RXDMAE | UARTFCCR_TXDMAE);
+ lrx_uart_write(sup->dmacr, sup, REG_FCCR);
+ uart_port_unlock_irq(&sup->port);
+
+ if (sup->using_tx_dma) {
+ /* In theory, this should already be done by lrx_uart_dma_flush_buffer */
+ dmaengine_terminate_all(sup->dmatx.chan);
+ if (sup->dmatx.queued) {
+ dma_unmap_single(sup->dmatx.chan->device->dev,
+ sup->dmatx.dma, sup->dmatx.len,
+ DMA_TO_DEVICE);
+ sup->dmatx.queued = false;
+ }
+
+ kfree(sup->dmatx.buf);
+ sup->using_tx_dma = false;
+ }
+
+ if (sup->using_rx_dma) {
+ dmaengine_terminate_all(sup->dmarx.chan);
+ /* Clean up the RX DMA */
+ lrx_uart_dmabuf_free(sup->dmarx.chan, &sup->dmarx.dbuf_a, DMA_FROM_DEVICE);
+ lrx_uart_dmabuf_free(sup->dmarx.chan, &sup->dmarx.dbuf_b, DMA_FROM_DEVICE);
+ if (sup->dmarx.poll_rate)
+ timer_delete_sync(&sup->dmarx.timer);
+ sup->using_rx_dma = false;
+ }
+}
+
+static inline bool lrx_uart_dma_rx_available(struct lrx_uart_port *sup)
+{
+ return sup->using_rx_dma;
+}
+
+static inline bool lrx_uart_dma_rx_running(struct lrx_uart_port *sup)
+{
+ return sup->using_rx_dma && sup->dmarx.running;
+}
+
+#else
+/* Blank functions if the DMA engine is not available */
+static inline void lrx_uart_dma_remove(struct lrx_uart_port *sup)
+{
+}
+
+static inline void lrx_uart_dma_startup(struct lrx_uart_port *sup)
+{
+}
+
+static inline void lrx_uart_dma_shutdown(struct lrx_uart_port *sup)
+{
+}
+
+static inline bool lrx_uart_dma_tx_irq(struct lrx_uart_port *sup)
+{
+ return false;
+}
+
+static inline void lrx_uart_dma_tx_stop(struct lrx_uart_port *sup)
+{
+}
+
+static inline bool lrx_uart_dma_tx_start(struct lrx_uart_port *sup)
+{
+ return false;
+}
+
+static inline void lrx_uart_dma_rx_irq(struct lrx_uart_port *sup)
+{
+}
+
+static inline void lrx_uart_dma_rx_stop(struct lrx_uart_port *sup)
+{
+}
+
+static inline int lrx_uart_dma_rx_trigger_dma(struct lrx_uart_port *sup)
+{
+ return -EIO;
+}
+
+static inline bool lrx_uart_dma_rx_available(struct lrx_uart_port *sup)
+{
+ return false;
+}
+
+static inline bool lrx_uart_dma_rx_running(struct lrx_uart_port *sup)
+{
+ return false;
+}
+
+#define lrx_uart_dma_flush_buffer NULL
+#endif
+
+static void lrx_uart_rs485_tx_stop(struct lrx_uart_port *sup)
+{
+ /*
+ * To be on the safe side only time out after twice as many iterations
+ * as fifo size.
+ */
+ const int MAX_TX_DRAIN_ITERS = sup->port.fifosize * 2;
+ struct uart_port *port = &sup->port;
+ int i = 0;
+ u32 mcr, mcfg;
+
+ /* Wait until hardware tx queue is empty */
+ while (!lrx_uart_tx_empty(port)) {
+ if (i > MAX_TX_DRAIN_ITERS) {
+ dev_warn(port->dev,
+ "timeout while draining hardware tx queue\n");
+ break;
+ }
+
+ udelay(sup->rs485_tx_drain_interval);
+ i++;
+ }
+
+ if (port->rs485.delay_rts_after_send)
+ mdelay(port->rs485.delay_rts_after_send);
+
+ mcr = lrx_uart_read(sup, REG_MCR);
+
+ if (port->rs485.flags & SER_RS485_RTS_AFTER_SEND)
+ mcr &= ~UARTMCR_RTS;
+ else
+ mcr |= UARTMCR_RTS;
+
+ lrx_uart_write(mcr, sup, REG_MCR);
+
+ /* Disable the transmitter and reenable the transceiver */
+ mcfg = lrx_uart_read(sup, REG_MCFG);
+ mcfg &= ~UARTMCFG_TXE;
+ mcfg |= UARTMCFG_RXE;
+ lrx_uart_write(mcfg, sup, REG_MCFG);
+
+ sup->rs485_tx_started = false;
+}
+
+static void lrx_uart_stop_tx(struct uart_port *port)
+{
+ struct lrx_uart_port *sup =
+ container_of(port, struct lrx_uart_port, port);
+
+ sup->im &= ~UARTIMSC_TXIM;
+ lrx_uart_write(sup->im, sup, REG_IMSC);
+ lrx_uart_dma_tx_stop(sup);
+
+ if ((port->rs485.flags & SER_RS485_ENABLED) && sup->rs485_tx_started)
+ lrx_uart_rs485_tx_stop(sup);
+}
+
+static bool lrx_uart_tx_chars(struct lrx_uart_port *sup, bool from_irq);
+
+/* Start TX with programmed I/O only (no DMA) */
+static void lrx_uart_start_tx_pio(struct lrx_uart_port *sup)
+{
+ if (lrx_uart_tx_chars(sup, false)) {
+ sup->im |= UARTIMSC_TXIM;
+ lrx_uart_write(sup->im, sup, REG_IMSC);
+ }
+}
+
+static void lrx_uart_rs485_tx_start(struct lrx_uart_port *sup)
+{
+ struct uart_port *port = &sup->port;
+ u32 mcr, mcfg;
+
+ /* Enable transmitter */
+ mcfg = lrx_uart_read(sup, REG_MCFG);
+ mcfg |= UARTMCFG_TXE;
+
+ /* Disable receiver if half-duplex */
+ if (!(port->rs485.flags & SER_RS485_RX_DURING_TX))
+ mcfg &= ~UARTMCFG_RXE;
+
+ lrx_uart_write(mcfg, sup, REG_MCFG);
+
+ mcr = lrx_uart_read(sup, REG_MCR);
+ if (port->rs485.flags & SER_RS485_RTS_ON_SEND)
+ mcr &= ~UARTMCR_RTS;
+ else
+ mcr |= UARTMCR_RTS;
+
+ lrx_uart_write(mcr, sup, REG_MCR);
+
+ if (port->rs485.delay_rts_before_send)
+ mdelay(port->rs485.delay_rts_before_send);
+
+ sup->rs485_tx_started = true;
+}
+
+static void lrx_uart_start_tx(struct uart_port *port)
+{
+ struct lrx_uart_port *sup =
+ container_of(port, struct lrx_uart_port, port);
+
+ if ((sup->port.rs485.flags & SER_RS485_ENABLED) &&
+ !sup->rs485_tx_started)
+ lrx_uart_rs485_tx_start(sup);
+
+ if (!lrx_uart_dma_tx_start(sup))
+ lrx_uart_start_tx_pio(sup);
+}
+
+static void lrx_uart_stop_rx(struct uart_port *port)
+{
+ struct lrx_uart_port *sup =
+ container_of(port, struct lrx_uart_port, port);
+
+ sup->im &= ~(UARTIMSC_RXIM | UARTIMSC_RTIM | UARTIMSC_FEIM |
+ UARTIMSC_PEIM | UARTIMSC_BEIM | UARTIMSC_OEIM);
+ lrx_uart_write(sup->im, sup, REG_IMSC);
+
+ lrx_uart_dma_rx_stop(sup);
+}
+
+static void lrx_uart_throttle_rx(struct uart_port *port)
+{
+ unsigned long flags;
+
+ uart_port_lock_irqsave(port, &flags);
+ lrx_uart_stop_rx(port);
+ uart_port_unlock_irqrestore(port, flags);
+}
+
+static void lrx_uart_enable_ms(struct uart_port *port)
+{
+ struct lrx_uart_port *sup =
+ container_of(port, struct lrx_uart_port, port);
+
+ sup->im |= UARTIMSC_RIMIM | UARTIMSC_CTSMIM | UARTIMSC_DCDMIM | UARTIMSC_DSRMIM;
+ lrx_uart_write(sup->im, sup, REG_IMSC);
+}
+
+static void lrx_uart_rx_chars(struct lrx_uart_port *sup)
+__releases(&sup->port.lock)
+__acquires(&sup->port.lock)
+{
+ lrx_uart_fifo_to_tty(sup);
+
+ uart_port_unlock(&sup->port);
+ tty_flip_buffer_push(&sup->port.state->port);
+ /*
+ * If we were temporarily out of DMA mode for a while,
+ * attempt to switch back to DMA mode again.
+ */
+ if (lrx_uart_dma_rx_available(sup)) {
+ if (lrx_uart_dma_rx_trigger_dma(sup)) {
+ dev_dbg(sup->port.dev,
+ "could not trigger RX DMA job fall back to interrupt mode again\n");
+ sup->im |= UARTIMSC_RXIM;
+ lrx_uart_write(sup->im, sup, REG_IMSC);
+ } else {
+#ifdef CONFIG_DMA_ENGINE
+ /* Start Rx DMA poll */
+ if (sup->dmarx.poll_rate) {
+ sup->dmarx.last_jiffies = jiffies;
+ sup->dmarx.last_residue = LRX_UART_DMA_BUFFER_SIZE;
+ mod_timer(&sup->dmarx.timer,
+ jiffies + msecs_to_jiffies(sup->dmarx.poll_rate));
+ }
+#endif
+ }
+ }
+ uart_port_lock(&sup->port);
+}
+
+static bool lrx_uart_tx_char(struct lrx_uart_port *sup, unsigned char c,
+ bool from_irq)
+{
+ if (unlikely(!from_irq) &&
+ lrx_uart_read(sup, REG_FR) & UARTFR_TXFF)
+ return false; /* unable to transmit character */
+
+ lrx_uart_write(c, sup, REG_DR);
+ sup->port.icount.tx++;
+
+ return true;
+}
+
+/* Returns true if tx interrupts have to be (kept) enabled */
+static bool lrx_uart_tx_chars(struct lrx_uart_port *sup, bool from_irq)
+{
+ struct tty_port *tport = &sup->port.state->port;
+ int count = sup->fifosize >> 1;
+
+ if (sup->port.x_char) {
+ if (!lrx_uart_tx_char(sup, sup->port.x_char, from_irq))
+ return true;
+ sup->port.x_char = 0;
+ --count;
+ }
+ if (kfifo_is_empty(&tport->xmit_fifo) || uart_tx_stopped(&sup->port)) {
+ lrx_uart_stop_tx(&sup->port);
+ return false;
+ }
+
+ /* If we are using DMA mode, try to send some characters. */
+ if (lrx_uart_dma_tx_irq(sup))
+ return true;
+
+ while (1) {
+ unsigned char c;
+
+ if (likely(from_irq) && count-- == 0)
+ break;
+
+ if (!kfifo_peek(&tport->xmit_fifo, &c))
+ break;
+
+ if (!lrx_uart_tx_char(sup, c, from_irq))
+ break;
+
+ kfifo_skip(&tport->xmit_fifo);
+ }
+
+ if (kfifo_len(&tport->xmit_fifo) < WAKEUP_CHARS)
+ uart_write_wakeup(&sup->port);
+
+ if (kfifo_is_empty(&tport->xmit_fifo)) {
+ lrx_uart_stop_tx(&sup->port);
+ return false;
+ }
+ return true;
+}
+
+static void lrx_uart_modem_status(struct lrx_uart_port *sup)
+{
+ unsigned int status, delta;
+
+ status = lrx_uart_read(sup, REG_FR) & UARTFR_MODEM_ANY;
+
+ delta = status ^ sup->old_status;
+ sup->old_status = status;
+
+ if (!delta)
+ return;
+
+ if (delta & UARTFR_DCD)
+ uart_handle_dcd_change(&sup->port, status & UARTFR_DCD);
+
+ if (delta & sup->vendor->fr_dsr)
+ sup->port.icount.dsr++;
+
+ if (delta & sup->vendor->fr_cts)
+ uart_handle_cts_change(&sup->port,
+ status & sup->vendor->fr_cts);
+
+ wake_up_interruptible(&sup->port.state->port.delta_msr_wait);
+}
+
+static void check_apply_cts_event_workaround(struct lrx_uart_port *sup)
+{
+ if (!sup->vendor->cts_event_workaround)
+ return;
+
+ /* workaround to make sure that all bits are unlocked.. */
+ lrx_uart_write(0x00, sup, REG_ICR);
+
+ /*
+ * WA: introduce 26ns(1 uart clk) delay before W1C;
+ * single apb access will incur 2 pclk(133.12Mhz) delay,
+ * so add 2 dummy reads
+ */
+ lrx_uart_read(sup, REG_ICR);
+ lrx_uart_read(sup, REG_ICR);
+}
+
+static irqreturn_t lrx_uart_int(int irq, void *dev_id)
+{
+ struct lrx_uart_port *sup = dev_id;
+ unsigned int status, pass_counter = ISR_PASS_LIMIT;
+ int handled = 0;
+
+ uart_port_lock(&sup->port);
+ status = lrx_uart_read(sup, REG_RIS) & sup->im;
+ if (status) {
+ do {
+ check_apply_cts_event_workaround(sup);
+
+ lrx_uart_write(status & ~(UARTICR_TXIC | UARTICR_RTIC | UARTICR_RXIC),
+ sup, REG_ICR);
+
+ if (status & (UARTICR_RTIC | UARTICR_RXIC)) {
+ if (lrx_uart_dma_rx_running(sup))
+ lrx_uart_dma_rx_irq(sup);
+ else
+ lrx_uart_rx_chars(sup);
+ }
+ if (status & (UARTICR_DSRMIC | UARTICR_DCDMIC |
+ UARTICR_CTSMIC | UARTICR_RIMIC))
+ lrx_uart_modem_status(sup);
+ if (status & UARTICR_TXIC)
+ lrx_uart_tx_chars(sup, true);
+
+ if (pass_counter-- == 0)
+ break;
+
+ status = lrx_uart_read(sup, REG_RIS) & sup->im;
+ } while (status != 0);
+ handled = 1;
+ }
+
+ uart_unlock_and_check_sysrq(&sup->port);
+
+ return IRQ_RETVAL(handled);
+}
+
+static unsigned int lrx_uart_tx_empty(struct uart_port *port)
+{
+ struct lrx_uart_port *sup =
+ container_of(port, struct lrx_uart_port, port);
+
+ /* Allow feature register bits to be inverted to work around errata */
+ unsigned int status = lrx_uart_read(sup, REG_FR) ^ sup->vendor->inv_fr;
+
+ return status & (sup->vendor->fr_busy | UARTFR_TXFF) ?
+ 0 : TIOCSER_TEMT;
+}
+
+static void lrx_uart_maybe_set_bit(bool cond, unsigned int *ptr, unsigned int mask)
+{
+ if (cond)
+ *ptr |= mask;
+}
+
+static unsigned int lrx_uart_get_mctrl(struct uart_port *port)
+{
+ struct lrx_uart_port *sup =
+ container_of(port, struct lrx_uart_port, port);
+ unsigned int result = 0;
+ unsigned int status = lrx_uart_read(sup, REG_FR);
+
+ lrx_uart_maybe_set_bit(status & UARTFR_DCD, &result, TIOCM_CAR);
+ lrx_uart_maybe_set_bit(status & sup->vendor->fr_dsr, &result, TIOCM_DSR);
+ lrx_uart_maybe_set_bit(status & sup->vendor->fr_cts, &result, TIOCM_CTS);
+ lrx_uart_maybe_set_bit(status & sup->vendor->fr_ri, &result, TIOCM_RNG);
+
+ return result;
+}
+
+static void lrx_uart_assign_bit(bool cond, unsigned int *ptr, unsigned int mask)
+{
+ if (cond)
+ *ptr |= mask;
+ else
+ *ptr &= ~mask;
+}
+
+static void lrx_uart_set_mctrl(struct uart_port *port, unsigned int mctrl)
+{
+ struct lrx_uart_port *sup =
+ container_of(port, struct lrx_uart_port, port);
+ unsigned int mcr;
+ unsigned int mcfg;
+ unsigned int fccr;
+
+ mcr = lrx_uart_read(sup, REG_MCR);
+ mcfg = lrx_uart_read(sup, REG_MCFG);
+ fccr = lrx_uart_read(sup, REG_FCCR);
+
+ lrx_uart_assign_bit(mctrl & TIOCM_RTS, &mcr, UARTMCR_RTS);
+ lrx_uart_assign_bit(mctrl & TIOCM_DTR, &mcr, UARTMCR_DTR);
+ lrx_uart_assign_bit(mctrl & TIOCM_OUT1, &mcr, UARTMCR_OUT1);
+ lrx_uart_assign_bit(mctrl & TIOCM_OUT2, &mcr, UARTMCR_OUT2);
+ lrx_uart_assign_bit(mctrl & TIOCM_LOOP, &mcfg, UARTMCFG_LBE);
+
+ if (port->status & UPSTAT_AUTORTS) {
+ /* We need to disable auto-RTS if we want to turn RTS off */
+ lrx_uart_assign_bit(mctrl & TIOCM_RTS, &fccr, UARTFCCR_RTSEN);
+ }
+
+ lrx_uart_write(mcr, sup, REG_MCR);
+ lrx_uart_write(mcfg, sup, REG_MCFG);
+ lrx_uart_write(fccr, sup, REG_FCCR);
+}
+
+static void lrx_uart_break_ctl(struct uart_port *port, int break_state)
+{
+ struct lrx_uart_port *sup =
+ container_of(port, struct lrx_uart_port, port);
+ unsigned long flags;
+ unsigned int mcfg;
+
+ uart_port_lock_irqsave(&sup->port, &flags);
+ mcfg = lrx_uart_read(sup, REG_MCFG);
+ if (break_state == -1)
+ mcfg |= UARTMCFG_BRK;
+ else
+ mcfg &= ~UARTMCFG_BRK;
+ lrx_uart_write(mcfg, sup, REG_MCFG);
+ uart_port_unlock_irqrestore(&sup->port, flags);
+}
+
+#ifdef CONFIG_CONSOLE_POLL
+
+static void lrx_uart_quiesce_irqs(struct uart_port *port)
+{
+ struct lrx_uart_port *sup =
+ container_of(port, struct lrx_uart_port, port);
+
+ lrx_uart_write(lrx_uart_read(sup, REG_MIS), sup, REG_ICR);
+ /*
+ * There is no way to clear TXIM as this is "ready to transmit IRQ", so
+ * we simply mask it. start_tx() will unmask it.
+ *
+ * Note we can race with start_tx(), and if the race happens, the
+ * polling user might get another interrupt just after we clear it.
+ * But it should be OK and can happen even w/o the race, e.g.
+ * controller immediately got some new data and raised the IRQ.
+ *
+ * And whoever uses polling routines assumes that it manages the device
+ * (including tx queue), so we're also fine with start_tx()'s caller
+ * side.
+ */
+ lrx_uart_write(lrx_uart_read(sup, REG_IMSC) & ~UARTIMSC_TXIM, sup,
+ REG_IMSC);
+}
+
+static int lrx_uart_get_poll_char(struct uart_port *port)
+{
+ struct lrx_uart_port *sup =
+ container_of(port, struct lrx_uart_port, port);
+ unsigned int status;
+
+ /*
+ * The caller might need IRQs lowered, e.g. if used with KDB NMI
+ * debugger.
+ */
+ lrx_uart_quiesce_irqs(port);
+
+ status = lrx_uart_read(sup, REG_FR);
+ if (status & UARTFR_RXFE)
+ return NO_POLL_CHAR;
+
+ return lrx_uart_read(sup, REG_DR);
+}
+
+static void lrx_uart_put_poll_char(struct uart_port *port, unsigned char ch)
+{
+ struct lrx_uart_port *sup =
+ container_of(port, struct lrx_uart_port, port);
+
+ while (lrx_uart_read(sup, REG_FR) & UARTFR_TXFF)
+ cpu_relax();
+
+ lrx_uart_write(ch, sup, REG_DR);
+}
+
+#endif /* CONFIG_CONSOLE_POLL */
+
+static int lrx_uart_hwinit(struct uart_port *port)
+{
+ struct lrx_uart_port *sup =
+ container_of(port, struct lrx_uart_port, port);
+ int retval;
+ unsigned int clk;
+
+ /* Optionaly enable pins to be muxed in and configured */
+ pinctrl_pm_select_default_state(port->dev);
+
+ /*
+ * Try to enable the clock producer.
+ */
+ retval = clk_prepare_enable(sup->clk);
+ if (retval)
+ return retval;
+
+ if (has_acpi_companion(sup->port.dev)) {
+ device_property_read_u32(sup->port.dev, "clock-frequency", &clk);
+ sup->port.uartclk = clk;
+ } else {
+ sup->port.uartclk = clk_get_rate(sup->clk);
+ }
+
+ /* Clear pending error and receive interrupts */
+ lrx_uart_write(UARTICR_OEIC | UARTICR_BEIC | UARTICR_PEIC |
+ UARTICR_FEIC | UARTICR_RTIC | UARTICR_RXIC,
+ sup, REG_ICR);
+
+ /*
+ * Save interrupts enable mask, and enable RX interrupts in case if
+ * the interrupt is used for NMI entry.
+ */
+ sup->im = lrx_uart_read(sup, REG_IMSC);
+ lrx_uart_write(UARTIMSC_RTIM | UARTIMSC_RXIM, sup, REG_IMSC);
+
+ if (dev_get_platdata(sup->port.dev)) {
+ struct lrx_uart_data *plat;
+
+ plat = dev_get_platdata(sup->port.dev);
+ if (plat->init)
+ plat->init();
+ }
+ return 0;
+}
+
+static int lrx_uart_allocate_irq(struct lrx_uart_port *sup)
+{
+ lrx_uart_write(sup->im, sup, REG_IMSC);
+
+ return request_irq(sup->port.irq, lrx_uart_int, IRQF_SHARED, "lrx-uart", sup);
+}
+
+/*
+ * Enable interrupts, only timeouts when using DMA
+ * if initial RX DMA job failed, start in interrupt mode
+ * as well.
+ */
+static void lrx_uart_enable_interrupts(struct lrx_uart_port *sup)
+{
+ unsigned long flags;
+ unsigned int i;
+
+ uart_port_lock_irqsave(&sup->port, &flags);
+
+ /* Clear out any spuriously appearing RX interrupts */
+ lrx_uart_write(UARTICR_RTIC | UARTICR_RXIC, sup, REG_ICR);
+
+ /*
+ * RXIS is asserted only when the RX FIFO transitions from below
+ * to above the trigger threshold. If the RX FIFO is already
+ * full to the threshold this can't happen and RXIS will now be
+ * stuck off. Drain the RX FIFO explicitly to fix this:
+ */
+ for (i = 0; i < sup->fifosize * 2; ++i) {
+ if (lrx_uart_read(sup, REG_FR) & UARTFR_RXFE)
+ break;
+
+ lrx_uart_read(sup, REG_DR);
+ }
+
+ sup->im = UARTIMSC_RTIM;
+ if (!lrx_uart_dma_rx_running(sup))
+ sup->im |= UARTIMSC_RXIM;
+ lrx_uart_write(sup->im, sup, REG_IMSC);
+ uart_port_unlock_irqrestore(&sup->port, flags);
+}
+
+static void lrx_uart_unthrottle_rx(struct uart_port *port)
+{
+ struct lrx_uart_port *sup = container_of(port, struct lrx_uart_port, port);
+ unsigned long flags;
+
+ uart_port_lock_irqsave(&sup->port, &flags);
+
+ sup->im = UARTIMSC_RTIM;
+ if (!lrx_uart_dma_rx_running(sup))
+ sup->im |= UARTIMSC_RXIM;
+
+ lrx_uart_write(sup->im, sup, REG_IMSC);
+
+ uart_port_unlock_irqrestore(&sup->port, flags);
+}
+
+static int lrx_uart_startup(struct uart_port *port)
+{
+ struct lrx_uart_port *sup =
+ container_of(port, struct lrx_uart_port, port);
+ unsigned int mcr;
+ unsigned int mcfg;
+ int retval;
+
+ retval = lrx_uart_hwinit(port);
+ if (retval)
+ goto clk_dis;
+
+ retval = lrx_uart_allocate_irq(sup);
+ if (retval)
+ goto clk_dis;
+
+ lrx_uart_write(sup->vendor->fcr, sup, REG_FCR);
+
+ uart_port_lock_irq(&sup->port);
+
+ mcr = lrx_uart_read(sup, REG_MCR);
+ mcr &= UARTMCR_RTS | UARTMCR_DTR;
+
+ lrx_uart_write(mcr, sup, REG_MCR);
+
+ mcfg = lrx_uart_read(sup, REG_MCFG);
+
+ mcfg |= UARTMCFG_UARTEN | UARTMCFG_RXE;
+
+ if (!(port->rs485.flags & SER_RS485_ENABLED))
+ mcfg |= UARTMCFG_TXE;
+
+ lrx_uart_write(mcfg, sup, REG_MCFG);
+
+ uart_port_unlock_irq(&sup->port);
+
+ /*
+ * initialise the old status of the modem signals
+ */
+ sup->old_status = lrx_uart_read(sup, REG_FR) & UARTFR_MODEM_ANY;
+
+ /* Startup DMA */
+ lrx_uart_dma_startup(sup);
+
+ lrx_uart_enable_interrupts(sup);
+
+ return 0;
+
+ clk_dis:
+ clk_disable_unprepare(sup->clk);
+ return retval;
+}
+
+static void lrx_uart_shutdown_channel(struct lrx_uart_port *sup,
+ unsigned int mcfg, unsigned int fcr)
+{
+ unsigned long val;
+
+ val = lrx_uart_read(sup, mcfg);
+ val &= ~(UARTMCFG_BRK);
+ lrx_uart_write(val, sup, mcfg);
+
+ val = lrx_uart_read(sup, fcr);
+ val &= ~(UARTFCR_FEN);
+ lrx_uart_write(val, sup, fcr);
+}
+
+/*
+ * disable the port. It should not disable RTS and DTR.
+ * Also RTS and DTR state should be preserved to restore
+ * it during startup().
+ */
+static void lrx_uart_disable_uart(struct lrx_uart_port *sup)
+{
+ unsigned int mcr;
+ unsigned int mcfg;
+
+ sup->port.status &= ~(UPSTAT_AUTOCTS | UPSTAT_AUTORTS);
+ uart_port_lock_irq(&sup->port);
+ mcr = lrx_uart_read(sup, REG_MCR);
+ mcr &= UARTMCR_RTS | UARTMCR_DTR;
+ lrx_uart_write(mcr, sup, REG_MCR);
+
+ mcfg = lrx_uart_read(sup, REG_MCFG);
+ mcfg |= UARTMCFG_UARTEN | UARTMCFG_TXE;
+ lrx_uart_write(mcfg, sup, REG_MCFG);
+ uart_port_unlock_irq(&sup->port);
+
+ /*
+ * disable break condition and fifos
+ */
+ lrx_uart_shutdown_channel(sup, REG_MCFG, REG_FCR);
+}
+
+static void lrx_uart_disable_interrupts(struct lrx_uart_port *sup)
+{
+ uart_port_lock_irq(&sup->port);
+
+ /* mask all interrupts and clear all pending ones */
+ sup->im = 0;
+ lrx_uart_write(sup->im, sup, REG_IMSC);
+ lrx_uart_write(0xffff, sup, REG_ICR);
+
+ uart_port_unlock_irq(&sup->port);
+}
+
+static void lrx_uart_shutdown(struct uart_port *port)
+{
+ struct lrx_uart_port *sup =
+ container_of(port, struct lrx_uart_port, port);
+
+ lrx_uart_disable_interrupts(sup);
+
+ lrx_uart_dma_shutdown(sup);
+
+ if ((port->rs485.flags & SER_RS485_ENABLED) && sup->rs485_tx_started)
+ lrx_uart_rs485_tx_stop(sup);
+
+ free_irq(sup->port.irq, sup);
+
+ lrx_uart_disable_uart(sup);
+
+ /*
+ * Shut down the clock producer
+ */
+ clk_disable_unprepare(sup->clk);
+ /* Optionally let pins go into sleep states */
+ pinctrl_pm_select_sleep_state(port->dev);
+
+ if (dev_get_platdata(sup->port.dev)) {
+ struct lrx_uart_data *plat;
+
+ plat = dev_get_platdata(sup->port.dev);
+ if (plat->exit)
+ plat->exit();
+ }
+
+ if (sup->port.ops->flush_buffer)
+ sup->port.ops->flush_buffer(port);
+}
+
+static void
+lrx_uart_setup_status_masks(struct uart_port *port, struct ktermios *termios)
+{
+ port->read_status_mask = UARTDR_OE | 255;
+ if (termios->c_iflag & INPCK)
+ port->read_status_mask |= UARTDR_FE | UARTDR_PE;
+ if (termios->c_iflag & (IGNBRK | BRKINT | PARMRK))
+ port->read_status_mask |= UARTDR_BE;
+
+ /*
+ * Characters to ignore
+ */
+ port->ignore_status_mask = 0;
+ if (termios->c_iflag & IGNPAR)
+ port->ignore_status_mask |= UARTDR_FE | UARTDR_PE;
+ if (termios->c_iflag & IGNBRK) {
+ port->ignore_status_mask |= UARTDR_BE;
+ /*
+ * If we're ignoring parity and break indicators,
+ * ignore overruns too (for real raw support).
+ */
+ if (termios->c_iflag & IGNPAR)
+ port->ignore_status_mask |= UARTDR_OE;
+ }
+
+ /*
+ * Ignore all characters if CREAD is not set.
+ */
+ if ((termios->c_cflag & CREAD) == 0)
+ port->ignore_status_mask |= UART_DUMMY_DR_RX;
+}
+
+static void
+lrx_uart_set_termios(struct uart_port *port, struct ktermios *termios,
+ const struct ktermios *old)
+{
+ struct lrx_uart_port *sup =
+ container_of(port, struct lrx_uart_port, port);
+ unsigned int frcr;
+ unsigned int mcr, fccr;
+ unsigned int mcfg;
+ unsigned long flags;
+ unsigned int baud, quot, clkdiv;
+ unsigned int bits;
+ unsigned int clk;
+
+ if (sup->vendor->oversampling)
+ clkdiv = 8;
+ else
+ clkdiv = 16;
+
+ if (has_acpi_companion(sup->port.dev)) {
+ device_property_read_u32(sup->port.dev, "clock-frequency", &clk);
+ sup->port.uartclk = clk;
+ }
+
+ /*
+ * Ask the core to calculate the divisor for us.
+ */
+ baud = uart_get_baud_rate(port, termios, old, 0,
+ port->uartclk / clkdiv);
+
+#ifdef CONFIG_DMA_ENGINE
+ /*
+ * Adjust RX DMA polling rate with baud rate if not specified.
+ */
+ if (sup->dmarx.auto_poll_rate)
+ sup->dmarx.poll_rate = DIV_ROUND_UP(10000000, baud);
+#endif
+
+ if (baud > port->uartclk / 16)
+ quot = DIV_ROUND_CLOSEST(port->uartclk * 8, baud);
+ else
+ quot = DIV_ROUND_CLOSEST(port->uartclk * 4, baud);
+
+ switch (termios->c_cflag & CSIZE) {
+ case CS5:
+ frcr = UARTFRCR_WLEN_5;
+ break;
+ case CS6:
+ frcr = UARTFRCR_WLEN_6;
+ break;
+ case CS7:
+ frcr = UARTFRCR_WLEN_7;
+ break;
+ default: // CS8
+ frcr = UARTFRCR_WLEN_8;
+ break;
+ }
+
+ if (termios->c_cflag & CSTOPB)
+ frcr |= UARTFRCR_STP2;
+ if (termios->c_cflag & PARENB) {
+ frcr |= UARTFRCR_PEN;
+ if (!(termios->c_cflag & PARODD))
+ frcr |= UARTFRCR_EOP;
+ if (termios->c_cflag & CMSPAR)
+ frcr |= UARTFRCR_SPS;
+ }
+
+
+ bits = tty_get_frame_size(termios->c_cflag);
+
+ uart_port_lock_irqsave(port, &flags);
+
+ /*
+ * Update the per-port timeout.
+ */
+ uart_update_timeout(port, termios->c_cflag, baud);
+
+ /*
+ * Calculate the approximated time it takes to transmit one character
+ * with the given baud rate. We use this as the poll interval when we
+ * wait for the tx queue to empty.
+ */
+ sup->rs485_tx_drain_interval = DIV_ROUND_UP(bits * 1000 * 1000, baud);
+
+ lrx_uart_setup_status_masks(port, termios);
+
+ if (UART_ENABLE_MS(port, termios->c_cflag))
+ lrx_uart_enable_ms(port);
+
+ if (port->rs485.flags & SER_RS485_ENABLED)
+ termios->c_cflag &= ~CRTSCTS;
+
+ mcr = lrx_uart_read(sup, REG_MCR);
+ mcfg = lrx_uart_read(sup, REG_MCFG);
+ fccr = lrx_uart_read(sup, REG_FCCR);
+
+ if (termios->c_cflag & CRTSCTS) {
+ if (mcr & UARTMCR_RTS)
+ fccr |= UARTFCCR_RTSEN;
+
+ fccr |= UARTFCCR_CTSEN;
+ port->status |= UPSTAT_AUTOCTS | UPSTAT_AUTORTS;
+ } else {
+ fccr &= ~(UARTFCCR_CTSEN | UARTFCCR_RTSEN);
+ port->status &= ~(UPSTAT_AUTOCTS | UPSTAT_AUTORTS);
+ }
+
+ /* Set baud rate */
+ lrx_uart_write(quot & 0x3f, sup, REG_FD);
+ lrx_uart_write(quot >> 6, sup, REG_IND);
+
+ /*
+ * ----------v----------v----------v----------v-----
+ * NOTE: REG_FRCR MUST BE WRITTEN AFTER REG_FD & REG_IND.
+ * ----------^----------^----------^----------^-----
+ */
+ lrx_uart_write(frcr, sup, REG_FRCR);
+
+ lrx_uart_write(fccr, sup, REG_FCCR);
+
+ /*
+ * Receive was disabled by lrx_uart_disable_uart during shutdown.
+ * Need to reenable receive if you need to use a tty_driver
+ * returns from tty_find_polling_driver() after a port shutdown.
+ */
+ mcfg |= UARTMCFG_RXE;
+ lrx_uart_write(mcfg, sup, REG_MCFG);
+
+ uart_port_unlock_irqrestore(port, flags);
+}
+
+static const char *lrx_uart_type(struct uart_port *port)
+{
+ struct lrx_uart_port *sup =
+ container_of(port, struct lrx_uart_port, port);
+ return sup->port.type == PORT_GENERIC ? sup->type : NULL;
+}
+
+/*
+ * Configure/autoconfigure the port.
+ */
+static void lrx_uart_config_port(struct uart_port *port, int flags)
+{
+ if (flags & UART_CONFIG_TYPE)
+ port->type = PORT_GENERIC;
+}
+
+/*
+ * verify the new serial_struct (for TIOCSSERIAL).
+ */
+static int lrx_uart_verify_port(struct uart_port *port, struct serial_struct *ser)
+{
+ int ret = 0;
+
+ if (ser->type != PORT_UNKNOWN && ser->type != PORT_GENERIC)
+ ret = -EINVAL;
+ if (ser->irq < 0 || ser->irq >= irq_get_nr_irqs())
+ ret = -EINVAL;
+ if (ser->baud_base < 9600)
+ ret = -EINVAL;
+ if (port->mapbase != (unsigned long)ser->iomem_base)
+ ret = -EINVAL;
+ return ret;
+}
+
+static int lrx_uart_rs485_config(struct uart_port *port, struct ktermios *termios,
+ struct serial_rs485 *rs485)
+{
+ struct lrx_uart_port *sup =
+ container_of(port, struct lrx_uart_port, port);
+
+ if (port->rs485.flags & SER_RS485_ENABLED)
+ lrx_uart_rs485_tx_stop(sup);
+
+ /* Make sure auto RTS is disabled */
+ if (rs485->flags & SER_RS485_ENABLED) {
+ u32 fccr = lrx_uart_read(sup, REG_FCCR);
+
+ fccr &= ~UARTFCCR_RTSEN;
+ lrx_uart_write(fccr, sup, REG_FCCR);
+ port->status &= ~UPSTAT_AUTORTS;
+ }
+
+ return 0;
+}
+
+static const struct uart_ops lrx_uart_pops = {
+ .tx_empty = lrx_uart_tx_empty,
+ .set_mctrl = lrx_uart_set_mctrl,
+ .get_mctrl = lrx_uart_get_mctrl,
+ .stop_tx = lrx_uart_stop_tx,
+ .start_tx = lrx_uart_start_tx,
+ .stop_rx = lrx_uart_stop_rx,
+ .throttle = lrx_uart_throttle_rx,
+ .unthrottle = lrx_uart_unthrottle_rx,
+ .enable_ms = lrx_uart_enable_ms,
+ .break_ctl = lrx_uart_break_ctl,
+ .startup = lrx_uart_startup,
+ .shutdown = lrx_uart_shutdown,
+ .flush_buffer = lrx_uart_dma_flush_buffer,
+ .set_termios = lrx_uart_set_termios,
+ .type = lrx_uart_type,
+ .config_port = lrx_uart_config_port,
+ .verify_port = lrx_uart_verify_port,
+#ifdef CONFIG_CONSOLE_POLL
+ .poll_init = lrx_uart_hwinit,
+ .poll_get_char = lrx_uart_get_poll_char,
+ .poll_put_char = lrx_uart_put_poll_char,
+#endif
+};
+
+static struct lrx_uart_port *lrx_uart_console_ports[UART_NR];
+
+#ifdef CONFIG_SERIAL_LRX_UART_CONSOLE
+
+static void lrx_uart_console_putchar(struct uart_port *port, unsigned char ch)
+{
+ struct lrx_uart_port *sup =
+ container_of(port, struct lrx_uart_port, port);
+
+ while (lrx_uart_read(sup, REG_FR) & UARTFR_TXFF)
+ cpu_relax();
+ lrx_uart_write(ch, sup, REG_DR);
+}
+
+static void
+lrx_uart_console_write(struct console *co, const char *s, unsigned int count)
+{
+ struct lrx_uart_port *sup = lrx_uart_console_ports[co->index];
+ unsigned int old_fccr = 0, new_fccr = 0;
+ unsigned int old_mcfg = 0, new_mcfg = 0;
+ unsigned long flags;
+ int locked = 1;
+
+ clk_enable(sup->clk);
+
+ if (oops_in_progress)
+ locked = uart_port_trylock_irqsave(&sup->port, &flags);
+ else
+ uart_port_lock_irqsave(&sup->port, &flags);
+
+ /*
+ * First save the FCCR then disable the interrupts
+ */
+ if (!sup->vendor->always_enabled) {
+ old_fccr = lrx_uart_read(sup, REG_FCCR);
+ new_fccr = old_fccr & ~UARTFCCR_CTSEN;
+ lrx_uart_write(new_fccr, sup, REG_FCCR);
+
+ old_mcfg = lrx_uart_read(sup, REG_MCFG);
+ new_mcfg = old_mcfg | UARTMCFG_UARTEN | UARTMCFG_TXE;
+ lrx_uart_write(new_mcfg, sup, REG_MCFG);
+ }
+
+ uart_console_write(&sup->port, s, count, lrx_uart_console_putchar);
+
+ /*
+ * Finally, wait for transmitter to become empty and restore the
+ * TCR. Allow feature register bits to be inverted to work around
+ * errata.
+ */
+ while ((lrx_uart_read(sup, REG_FR) ^ sup->vendor->inv_fr)
+ & sup->vendor->fr_busy)
+ cpu_relax();
+ if (!sup->vendor->always_enabled) {
+ lrx_uart_write(old_fccr, sup, REG_FCCR);
+ lrx_uart_write(old_mcfg, sup, REG_MCFG);
+ }
+
+ if (locked)
+ uart_port_unlock_irqrestore(&sup->port, flags);
+
+ clk_disable(sup->clk);
+}
+
+static void lrx_uart_console_get_options(struct lrx_uart_port *sup, int *baud,
+ int *parity, int *bits)
+{
+ unsigned int frcr, ind, fd;
+
+ if (!(lrx_uart_read(sup, REG_MCFG) & UARTMCFG_UARTEN))
+ return;
+
+ frcr = lrx_uart_read(sup, REG_FRCR);
+
+ *parity = 'n';
+ if (frcr & UARTFRCR_PEN) {
+ if (frcr & UARTFRCR_EOP)
+ *parity = 'e';
+ else
+ *parity = 'o';
+ }
+
+ if ((frcr & 0x3) == UARTFRCR_WLEN_7)
+ *bits = 7;
+ else
+ *bits = 8;
+
+ ind = lrx_uart_read(sup, REG_IND);
+ fd = lrx_uart_read(sup, REG_FD);
+
+ *baud = sup->port.uartclk * 4 / (64 * ind + fd);
+}
+
+static int lrx_uart_console_setup(struct console *co, char *options)
+{
+ struct lrx_uart_port *sup;
+ int baud = 38400;
+ int bits = 8;
+ int parity = 'n';
+ int flow = 'n';
+ int ret;
+ unsigned int clk;
+
+ /*
+ * Check whether an invalid uart number has been specified, and
+ * if so, search for the first available port that does have
+ * console support.
+ */
+ if (co->index >= UART_NR)
+ co->index = 0;
+ sup = lrx_uart_console_ports[co->index];
+ if (!sup)
+ return -ENODEV;
+
+ /* Allow pins to be muxed in and configured */
+ pinctrl_pm_select_default_state(sup->port.dev);
+
+ ret = clk_prepare(sup->clk);
+ if (ret)
+ return ret;
+
+ if (dev_get_platdata(sup->port.dev)) {
+ struct lrx_uart_data *plat;
+
+ plat = dev_get_platdata(sup->port.dev);
+ if (plat->init)
+ plat->init();
+ }
+
+ if (has_acpi_companion(sup->port.dev)) {
+ device_property_read_u32(sup->port.dev, "clock-frequency", &clk);
+ sup->port.uartclk = clk;
+ } else {
+ sup->port.uartclk = clk_get_rate(sup->clk);
+ }
+
+ if (sup->vendor->fixed_options) {
+ baud = sup->fixed_baud;
+ } else {
+ if (options)
+ uart_parse_options(options,
+ &baud, &parity, &bits, &flow);
+ else
+ lrx_uart_console_get_options(sup, &baud, &parity, &bits);
+ }
+
+ return uart_set_options(&sup->port, co, baud, parity, bits, flow);
+}
+
+/**
+ * lrx_uart_console_match - non-standard console matching
+ * @co: registering console
+ * @name: name from console command line
+ * @idx: index from console command line
+ * @options: ptr to option string from console command line
+ *
+ * Only attempts to match console command lines of the form:
+ * console=lrx_uart,mmio|mmio32,<addr>[,<options>]
+ * console=lrx_uart,0x<addr>[,<options>]
+ * This form is used to register an initial earlycon boot console and
+ * replace it with the lrx_uart_console at lrx_uart driver init.
+ *
+ * Performs console setup for a match (as required by interface)
+ * If no <options> are specified, then assume the h/w is already setup.
+ *
+ * Returns 0 if console matches; otherwise non-zero to use default matching
+ */
+static int lrx_uart_console_match(struct console *co, char *name, int idx,
+ char *options)
+{
+ enum uart_iotype iotype;
+ resource_size_t addr;
+ int i;
+
+ if (strcmp(name, "lrx_uart") != 0)
+ return -ENODEV;
+
+ if (uart_parse_earlycon(options, &iotype, &addr, &options))
+ return -ENODEV;
+
+ if (iotype != UPIO_MEM && iotype != UPIO_MEM32)
+ return -ENODEV;
+
+ /* try to match the port specified on the command line */
+ for (i = 0; i < ARRAY_SIZE(lrx_uart_console_ports); i++) {
+ struct uart_port *port;
+
+ if (!lrx_uart_console_ports[i])
+ continue;
+
+ port = &lrx_uart_console_ports[i]->port;
+
+ if (port->mapbase != addr)
+ continue;
+
+ co->index = i;
+ uart_port_set_cons(port, co);
+ return lrx_uart_console_setup(co, options);
+ }
+
+ return -ENODEV;
+}
+
+static struct uart_driver lrx_uart_driver;
+
+static struct console lrx_uart_console = {
+ .name = LRX_UART_TTY_PREFIX,
+ .write = lrx_uart_console_write,
+ .device = uart_console_device,
+ .setup = lrx_uart_console_setup,
+ .match = lrx_uart_console_match,
+ .flags = CON_PRINTBUFFER | CON_ANYTIME,
+ .index = -1,
+ .data = &lrx_uart_driver,
+};
+
+#define LRX_UART_CONSOLE (&lrx_uart_console)
+
+static void lrx_uart_putc(struct uart_port *port, unsigned char c)
+{
+ while (readl(port->membase + UARTFR) & UARTFR_TXFF)
+ cpu_relax();
+ if (port->iotype == UPIO_MEM32)
+ writel(c, port->membase + UARTDR);
+ else
+ writeb(c, port->membase + UARTDR);
+ while (readl(port->membase + UARTFR) & UARTFR_BUSY)
+ cpu_relax();
+}
+
+static void lrx_uart_early_write(struct console *con, const char *s, unsigned int n)
+{
+ struct earlycon_device *dev = con->data;
+
+ uart_console_write(&dev->port, s, n, lrx_uart_putc);
+}
+
+#ifdef CONFIG_CONSOLE_POLL
+static int lrx_uart_getc(struct uart_port *port)
+{
+ if (readl(port->membase + UARTFR) & UARTFR_RXFE)
+ return NO_POLL_CHAR;
+
+ if (port->iotype == UPIO_MEM32)
+ return readl(port->membase + UARTDR);
+ else
+ return readb(port->membase + UARTDR);
+}
+
+static int lrx_uart_early_read(struct console *con, char *s, unsigned int n)
+{
+ struct earlycon_device *dev = con->data;
+ int ch, num_read = 0;
+
+ while (num_read < n) {
+ ch = lrx_uart_getc(&dev->port);
+ if (ch == NO_POLL_CHAR)
+ break;
+
+ s[num_read++] = ch;
+ }
+
+ return num_read;
+}
+#else
+#define lrx_uart_early_read NULL
+#endif
+
+/*
+ * On non-ACPI systems, earlycon is enabled by specifying
+ * "earlycon=lrx_uart,<address>" on the kernel command line.
+ *
+ * On ACPI ARM64 systems, an "early" console is enabled via the SPCR table,
+ * by specifying only "earlycon" on the command line. Because it requires
+ * SPCR, the console starts after ACPI is parsed, which is later than a
+ * traditional early console.
+ *
+ * To get the traditional early console that starts before ACPI is parsed,
+ * specify the full "earlycon=lrx_uart,<address>" option.
+ */
+static int __init lrx_uart_early_console_setup(struct earlycon_device *device,
+ const char *opt)
+{
+ if (!device->port.membase)
+ return -ENODEV;
+
+ device->con->write = lrx_uart_early_write;
+ device->con->read = lrx_uart_early_read;
+
+ return 0;
+}
+
+OF_EARLYCON_DECLARE(lrx_uart, "lrx-uart", lrx_uart_early_console_setup);
+
+#else
+#define LRX_UART_CONSOLE NULL
+#endif
+
+static struct uart_driver lrx_uart_driver = {
+ .owner = THIS_MODULE,
+ .driver_name = LRX_UART_NAME,
+ .dev_name = LRX_UART_TTY_PREFIX,
+ .nr = UART_NR,
+ .cons = LRX_UART_CONSOLE,
+};
+
+static int lrx_uart_probe_dt_alias(int index, struct device *dev)
+{
+ struct device_node *np;
+ static bool seen_dev_with_alias;
+ static bool seen_dev_without_alias;
+ int ret = index;
+
+ if (!IS_ENABLED(CONFIG_OF))
+ return ret;
+
+ np = dev->of_node;
+ if (!np)
+ return ret;
+
+ ret = of_alias_get_id(np, "serial");
+ if (ret < 0) {
+ seen_dev_without_alias = true;
+ ret = index;
+ } else {
+ seen_dev_with_alias = true;
+ if (ret >= ARRAY_SIZE(lrx_uart_console_ports) || lrx_uart_console_ports[ret]) {
+ dev_warn(dev, "requested serial port %d not available.\n", ret);
+ ret = index;
+ }
+ }
+
+ if (seen_dev_with_alias && seen_dev_without_alias)
+ dev_warn(dev, "aliased and non-aliased serial devices found in device tree. Serial port enumeration may be unpredictable.\n");
+
+ return ret;
+}
+
+/* unregisters the driver also if no more ports are left */
+static void lrx_uart_unregister_port(struct lrx_uart_port *sup)
+{
+ int i;
+ bool busy = false;
+
+ for (i = 0; i < ARRAY_SIZE(lrx_uart_console_ports); i++) {
+ if (lrx_uart_console_ports[i] == sup)
+ lrx_uart_console_ports[i] = NULL;
+ else if (lrx_uart_console_ports[i])
+ busy = true;
+ }
+ lrx_uart_dma_remove(sup);
+ if (!busy)
+ uart_unregister_driver(&lrx_uart_driver);
+}
+
+static int lrx_uart_find_free_port(void)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(lrx_uart_console_ports); i++)
+ if (!lrx_uart_console_ports[i])
+ return i;
+
+ return -EBUSY;
+}
+
+static int lrx_uart_setup_port(struct device *dev, struct lrx_uart_port *sup,
+ struct resource *mmiobase, int index)
+{
+ void __iomem *base;
+ int ret;
+
+ base = devm_ioremap_resource(dev, mmiobase);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ index = lrx_uart_probe_dt_alias(index, dev);
+
+ sup->port.dev = dev;
+ sup->port.mapbase = mmiobase->start;
+ sup->port.membase = base;
+ sup->port.fifosize = sup->fifosize;
+ sup->port.has_sysrq = IS_ENABLED(CONFIG_SERIAL_LRX_UART_CONSOLE);
+ sup->port.flags = UPF_BOOT_AUTOCONF;
+ sup->port.line = index;
+
+ ret = uart_get_rs485_mode(&sup->port);
+ if (ret)
+ return ret;
+
+ lrx_uart_console_ports[index] = sup;
+
+ return 0;
+}
+
+static int lrx_uart_register_port(struct lrx_uart_port *sup)
+{
+ int ret, i;
+
+ /* Ensure interrupts from this UART are masked and cleared */
+ lrx_uart_write(0, sup, REG_IMSC);
+ lrx_uart_write(0xffff, sup, REG_ICR);
+
+ if (!lrx_uart_driver.state) {
+ ret = uart_register_driver(&lrx_uart_driver);
+ if (ret < 0) {
+ dev_err(sup->port.dev,
+ "Failed to register ZTE LRX UART driver\n");
+ for (i = 0; i < ARRAY_SIZE(lrx_uart_console_ports); i++)
+ if (lrx_uart_console_ports[i] == sup)
+ lrx_uart_console_ports[i] = NULL;
+ return ret;
+ }
+ }
+
+ ret = uart_add_one_port(&lrx_uart_driver, &sup->port);
+ if (ret)
+ lrx_uart_unregister_port(sup);
+
+ return ret;
+}
+
+static const struct serial_rs485 lrx_uart_rs485_supported = {
+ .flags = SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND | SER_RS485_RTS_AFTER_SEND |
+ SER_RS485_RX_DURING_TX,
+ .delay_rts_before_send = 1,
+ .delay_rts_after_send = 1,
+};
+
+static int lrx_uart_probe(struct platform_device *pdev)
+{
+ struct lrx_uart_port *sup;
+ struct resource *r;
+ int portnr, ret;
+ unsigned int clk;
+ unsigned int baudrate;
+
+ /*
+ * Check the mandatory baud rate parameter in the DT node early
+ * so that we can easily exit with the error.
+ */
+ if (pdev->dev.of_node) {
+ struct device_node *np = pdev->dev.of_node;
+
+ ret = of_property_read_u32(np, "current-speed", &baudrate);
+ if (ret)
+ return ret;
+ } else if (has_acpi_companion(&pdev->dev)) {
+ ret = device_property_read_u32(&pdev->dev, "current-speed", &baudrate);
+ if (ret)
+ return ret;
+ } else {
+ baudrate = 115200;
+ }
+
+ portnr = lrx_uart_find_free_port();
+ if (portnr < 0)
+ return portnr;
+
+ sup = devm_kzalloc(&pdev->dev, sizeof(struct lrx_uart_port),
+ GFP_KERNEL);
+ if (!sup)
+ return -ENOMEM;
+
+ if (has_acpi_companion(&pdev->dev)) {
+ device_property_read_u32(&pdev->dev, "clock-frequency", &clk);
+ sup->port.uartclk = clk;
+ } else {
+ sup->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(sup->clk))
+ return PTR_ERR(sup->clk);
+ }
+
+ ret = platform_get_irq(pdev, 0);
+ if (ret < 0)
+ return ret;
+ sup->port.irq = ret;
+
+ sup->vendor = &vendor_lrx;
+
+ sup->reg_offset = sup->vendor->reg_offset;
+ sup->fifosize = LRX_UART_TX_FIFO_DEPTH;
+ sup->port.iotype = sup->vendor->access_32b ? UPIO_MEM32 : UPIO_MEM;
+ sup->port.ops = &lrx_uart_pops;
+ sup->port.rs485_config = lrx_uart_rs485_config;
+ sup->port.rs485_supported = lrx_uart_rs485_supported;
+ sup->fixed_baud = baudrate;
+
+ snprintf(sup->type, sizeof(sup->type), "ZTE LRX UART");
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+ ret = lrx_uart_setup_port(&pdev->dev, sup, r, portnr);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, sup);
+
+ return lrx_uart_register_port(sup);
+}
+
+static void lrx_uart_remove(struct platform_device *dev)
+{
+ struct lrx_uart_port *sup = platform_get_drvdata(dev);
+
+ uart_remove_one_port(&lrx_uart_driver, &sup->port);
+ lrx_uart_unregister_port(sup);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int lrx_uart_suspend(struct device *dev)
+{
+ struct lrx_uart_port *sup = dev_get_drvdata(dev);
+
+ if (!sup)
+ return -EINVAL;
+
+ return uart_suspend_port(&lrx_uart_driver, &sup->port);
+}
+
+static int lrx_uart_resume(struct device *dev)
+{
+ struct lrx_uart_port *sup = dev_get_drvdata(dev);
+
+ if (!sup)
+ return -EINVAL;
+
+ return uart_resume_port(&lrx_uart_driver, &sup->port);
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(lrx_uart_pm_ops, lrx_uart_suspend, lrx_uart_resume);
+
+static const struct of_device_id lrx_uart_of_match[] = {
+ { .compatible = "zte,lrx-uart" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, lrx_uart_of_match);
+
+static const struct acpi_device_id __maybe_unused lrx_uart_acpi_match[] = {
+ { "LRXX0000", 0 },
+ {},
+};
+MODULE_DEVICE_TABLE(acpi, lrx_uart_acpi_match);
+
+static struct platform_driver lrx_uart_platform_driver = {
+ .probe = lrx_uart_probe,
+ .remove = lrx_uart_remove,
+ .driver = {
+ .name = LRX_UART_NAME,
+ .pm = pm_sleep_ptr(&lrx_uart_pm_ops),
+ .of_match_table = of_match_ptr(lrx_uart_of_match),
+ .acpi_match_table = ACPI_PTR(lrx_uart_acpi_match),
+ .suppress_bind_attrs = IS_BUILTIN(CONFIG_SERIAL_LRX_UART),
+ },
+};
+
+static int __init lrx_uart_init(void)
+{
+ pr_info("Serial: ZTE LRX UART driver\n");
+
+ int ret;
+
+ ret = uart_register_driver(&lrx_uart_driver);
+ if (ret < 0) {
+ pr_err("Could not register %s driver\n",
+ lrx_uart_driver.driver_name);
+ return ret;
+ }
+
+ ret = platform_driver_register(&lrx_uart_platform_driver);
+ if (ret < 0) {
+ pr_err("ZTE LRX UART platform driver register failed, e = %d\n", ret);
+ uart_unregister_driver(&lrx_uart_driver);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void __exit lrx_uart_exit(void)
+{
+ platform_driver_unregister(&lrx_uart_platform_driver);
+ uart_unregister_driver(&lrx_uart_driver);
+}
+
+/*
+ * While this can be a module, if builtin it's most likely the console
+ * So let's leave module_exit but move module_init to an earlier place
+ */
+arch_initcall(lrx_uart_init);
+module_exit(lrx_uart_exit);
+
+MODULE_AUTHOR("Wenhong Liu/Qingtao Liu");
+MODULE_DESCRIPTION("ZTE LRX UART serial driver");
+MODULE_LICENSE("GPL");
--
2.27.0
^ permalink raw reply related
* [PATCH v2 1/2] Add ZTE LRX UART driver
From: liu.qingtao2 @ 2026-05-13 8:46 UTC (permalink / raw)
To: krzk, gregkh, jirislaby, robh, krzk+dt, conor+dt, marex, pjw,
palmer, aou, alex, rdunlap, geert+renesas, quic_zongjian,
arturs.artamonovs, robert.marko, hvilleneuve, thierry.bultel.yh,
julianbraha, flavra, prabhakar.mahadev-lad.rj, linux-serial,
linux-kernel, devicetree, linux-riscv, liu.wenhong35, liu.fei16,
dai.hualiang, deng.weixian, jia.yunxiang, he.yilin, bai.lu5,
yang.susheng, shen.lin1, zuo.jiang, hu.shengming, gao.rui, tan.hu,
liu.qingtao2
From 08610be731b6fc3919d5eebfd2ff9d67f38a094c Mon Sep 17 00:00:00 2001
From: Wenhong Liu <liu.wenhong35@zte.com.cn>
Date: Tue, 28 Apr 2026 22:30:31 +0800
Subject: [PATCH v2 1/2] dt-bindings: serial: Add zte,lrx-uart
Add devicetree binding for ZTE LRX UART controller.
Co-developed-by: Qingtao Liu <liu.qingtao2@zte.com.cn>
Signed-off-by: Qingtao Liu <liu.qingtao2@zte.com.cn>
Signed-off-by: Wenhong Liu <liu.wenhong35@zte.com.cn>
---
../bindings/serial/zte,lrx-uart.yaml | 51 +++++++++++++++++++
MAINTAINERS | 8 +++
2 files changed, 59 insertions(+)
create mode 100644 Documentation/devicetree/bindings/serial/zte,lrx-uart.yaml
diff --git a/Documentation/devicetree/bindings/serial/zte,lrx-uart.yaml b/Documentation/devicetree/bindings/serial/zte,lrx-uart.yaml
new file mode 100644
index 000000000000..20f470f04257
--- /dev/null
+++ b/Documentation/devicetree/bindings/serial/zte,lrx-uart.yaml
@@ -0,0 +1,51 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/serial/zte,lrx-uart.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: ZTE LRX UART
+
+maintainers:
+ - Wenhong Liu <liu.wenhong35@zte.com.cn>
+ - Qingtao Liu <liu.qingtao2@zte.com.cn>
+
+allOf:
+ - $ref: serial.yaml#
+
+properties:
+ compatible:
+ const: zte,lrx-uart
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ clocks:
+ maxItems: 1
+
+ current-speed:
+ description:
+ Initial baud rate configuration for the UART. This is a required
+ property to configure the serial port baud rate at boot time.
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+ - current-speed
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ serial@e0001800 {
+ compatible = "zte,lrx-uart";
+ reg = <0xe0001800 0x100>;
+ interrupts = <18>;
+ clocks = <&clk_uart0>;
+ current-speed = <115200>;
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 0dfad67f66c0..79be2dde3b71 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -29494,6 +29494,14 @@ F: include/linux/zswap.h
F: mm/zswap.c
F: tools/testing/selftests/cgroup/test_zswap.c
+ZTE LRX SERIAL DRIVER
+M: Wenhong Liu <liu.wenhong35@zte.com.cn>
+R: Qingtao Liu <liu.qingtao2@zte.com.cn>
+L: linux-serial@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/serial/zte,lrx-uart.yaml
+F: drivers/tty/serial/lrx_uart.c
+
SENARYTECH AUDIO CODEC DRIVER
M: bo liu <bo.liu@senarytech.com>
S: Maintained
--
2.27.0
^ permalink raw reply related
* [PATCH v2 6.12.y 10/10] serial: 8250_dw: Ensure BUSY is deasserted
From: Ionut Nechita (Wind River) @ 2026-05-13 6:50 UTC (permalink / raw)
To: ilpo.jarvinen, gregkh
Cc: stable, andriy.shevchenko, wander, chris.friesen, linux-serial,
qianfan Zhao, Adriana Nicolae, Bandal, Shankar, Murthy, Shanth,
Ionut Nechita
In-Reply-To: <20260513065028.112468-1-ionut.nechita@windriver.com>
From: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
commit a7b9ce39fbe4ae2919fe4f7ac16c293cb6632d30 upstream.
DW UART cannot write to LCR, DLL, and DLH while BUSY is asserted.
Existance of BUSY depends on uart_16550_compatible, if UART HW is
configured with it those registers can always be written.
There currently is dw8250_force_idle() which attempts to achieve
non-BUSY state by disabling FIFO, however, the solution is unreliable
when Rx keeps getting more and more characters.
Create a sequence of operations that ensures UART cannot keep BUSY
asserted indefinitely. The new sequence relies on enabling loopback mode
temporarily to prevent incoming Rx characters keeping UART BUSY.
Ensure no Tx in ongoing while the UART is switches into the loopback
mode (requires exporting serial8250_fifo_wait_for_lsr_thre() and adding
DMA Tx pause/resume functions).
According to tests performed by Adriana Nicolae <adriana@arista.com>,
simply disabling FIFO or clearing FIFOs only once does not always
ensure BUSY is deasserted but up to two tries may be needed. This could
be related to ongoing Rx of a character (a guess, not known for sure).
Therefore, retry FIFO clearing a few times (retry limit 4 is arbitrary
number but using, e.g., p->fifosize seems overly large). Tests
performed by others did not exhibit similar challenge but it does not
seem harmful to leave the FIFO clearing loop in place for all DW UARTs
with BUSY functionality.
Use the new dw8250_idle_enter/exit() to do divisor writes and LCR
writes. In case of plain LCR writes, opportunistically try to update
LCR first and only invoke dw8250_idle_enter() if the write did not
succeed (it has been observed that in practice most LCR writes do
succeed without complications).
This issue was first reported by qianfan Zhao who put lots of debugging
effort into understanding the solution space.
Fixes: c49436b657d0 ("serial: 8250_dw: Improve unwritable LCR workaround")
Fixes: 7d4008ebb1c9 ("tty: add a DesignWare 8250 driver")
Cc: stable <stable@kernel.org>
Reported-by: qianfan Zhao <qianfanguijin@163.com>
Link: https://lore.kernel.org/linux-serial/289bb78a-7509-1c5c-2923-a04ed3b6487d@163.com/
Reported-by: Adriana Nicolae <adriana@arista.com>
Link: https://lore.kernel.org/linux-serial/20250819182322.3451959-1-adriana@arista.com/
Reported-by: Bandal, Shankar <shankar.bandal@intel.com>
Tested-by: Bandal, Shankar <shankar.bandal@intel.com>
Tested-by: Murthy, Shanth <shanth.murthy@intel.com>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Link: https://patch.msgid.link/20260203171049.4353-8-ilpo.jarvinen@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
[Ionut: adapt to 6.12.y - two adaptations:
(1) in dw8250_check_lcr(), drop the trivial conflict against the
pre-existing 6.12.y "while (tries--)" / "int tries = 1000" retry
loop that this commit removes (per Ilpo's note: trivial
resolution because the conflicting code is removed by the BUSY
change anyway, see commit fc9ceb501e38 "serial: 8250: sanitize
uart_port::serial_{in,out}() types" not backported here);
(2) use bare-identifier form for namespace exports in 6.12.y, i.e.
EXPORT_SYMBOL_NS_GPL(..., SERIAL_8250) and
MODULE_IMPORT_NS(SERIAL_8250); 6.12.y EXPORT_SYMBOL_NS_GPL() and
MODULE_IMPORT_NS() apply __stringify(ns) to the namespace
argument, so the upstream string-literal form fails the
.vmlinux.export.c link step.]
Signed-off-by: Ionut Nechita <ionut.nechita@windriver.com>
---
drivers/tty/serial/8250/8250.h | 25 +++++
drivers/tty/serial/8250/8250_dw.c | 165 ++++++++++++++++++++--------
drivers/tty/serial/8250/8250_port.c | 28 ++---
3 files changed, 162 insertions(+), 56 deletions(-)
diff --git a/drivers/tty/serial/8250/8250.h b/drivers/tty/serial/8250/8250.h
index 10a706fe4b24..1fe8222ffacd 100644
--- a/drivers/tty/serial/8250/8250.h
+++ b/drivers/tty/serial/8250/8250.h
@@ -184,7 +184,9 @@ static unsigned int __maybe_unused serial_icr_read(struct uart_8250_port *up,
return value;
}
+void serial8250_clear_fifos(struct uart_8250_port *p);
void serial8250_clear_and_reinit_fifos(struct uart_8250_port *p);
+void serial8250_fifo_wait_for_lsr_thre(struct uart_8250_port *up, unsigned int count);
static inline u32 serial_dl_read(struct uart_8250_port *up)
{
@@ -402,6 +404,26 @@ static inline bool serial8250_tx_dma_running(struct uart_8250_port *p)
return dma && dma->tx_running;
}
+
+static inline void serial8250_tx_dma_pause(struct uart_8250_port *p)
+{
+ struct uart_8250_dma *dma = p->dma;
+
+ if (!dma->tx_running)
+ return;
+
+ dmaengine_pause(dma->txchan);
+}
+
+static inline void serial8250_tx_dma_resume(struct uart_8250_port *p)
+{
+ struct uart_8250_dma *dma = p->dma;
+
+ if (!dma->tx_running)
+ return;
+
+ dmaengine_resume(dma->txchan);
+}
#else
static inline int serial8250_tx_dma(struct uart_8250_port *p)
{
@@ -423,6 +445,9 @@ static inline bool serial8250_tx_dma_running(struct uart_8250_port *p)
{
return false;
}
+
+static inline void serial8250_tx_dma_pause(struct uart_8250_port *p) { }
+static inline void serial8250_tx_dma_resume(struct uart_8250_port *p) { }
#endif
static inline int ns16550a_goto_highspeed(struct uart_8250_port *up)
diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
index c24659f0b309..13cc7db10327 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -16,6 +16,7 @@
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/io.h>
+#include <linux/lockdep.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/notifier.h>
@@ -47,6 +48,8 @@
#define DW_UART_MCR_SIRE BIT(6)
+#define DW_UART_USR_BUSY BIT(0)
+
/* Renesas specific register fields */
#define RZN1_UART_xDMACR_DMA_EN BIT(0)
#define RZN1_UART_xDMACR_1_WORD_BURST (0 << 1)
@@ -89,6 +92,7 @@ struct dw8250_data {
unsigned int skip_autocfg:1;
unsigned int uart_16550_compatible:1;
+ unsigned int in_idle:1;
u8 no_int_count;
};
@@ -121,78 +125,151 @@ static inline int dw8250_modify_msr(struct uart_port *p, int offset, int value)
return value;
}
+static void dw8250_idle_exit(struct uart_port *p)
+{
+ struct dw8250_data *d = to_dw8250_data(p->private_data);
+ struct uart_8250_port *up = up_to_u8250p(p);
+
+ if (d->uart_16550_compatible)
+ return;
+
+ if (up->capabilities & UART_CAP_FIFO)
+ serial_port_out(p, UART_FCR, up->fcr);
+ serial_port_out(p, UART_MCR, up->mcr);
+ serial_port_out(p, UART_IER, up->ier);
+
+ /* DMA Rx is restarted by IRQ handler as needed. */
+ if (up->dma)
+ serial8250_tx_dma_resume(up);
+
+ d->in_idle = 0;
+}
+
/*
- * This function is being called as part of the uart_port::serial_out()
- * routine. Hence, it must not call serial_port_out() or serial_out()
- * against the modified registers here, i.e. LCR.
+ * Ensure BUSY is not asserted. If DW UART is configured with
+ * !uart_16550_compatible, the writes to LCR, DLL, and DLH fail while
+ * BUSY is asserted.
+ *
+ * Context: port's lock must be held
*/
-static void dw8250_force_idle(struct uart_port *p)
+static int dw8250_idle_enter(struct uart_port *p)
{
+ struct dw8250_data *d = to_dw8250_data(p->private_data);
+ unsigned int usr_reg = d->pdata ? d->pdata->usr_reg : DW_UART_USR;
struct uart_8250_port *up = up_to_u8250p(p);
- unsigned int lsr;
+ int retries;
+ u32 lsr;
- /*
- * The following call currently performs serial_out()
- * against the FCR register. Because it differs to LCR
- * there will be no infinite loop, but if it ever gets
- * modified, we might need a new custom version of it
- * that avoids infinite recursion.
- */
- serial8250_clear_and_reinit_fifos(up);
+ lockdep_assert_held_once(&p->lock);
+
+ if (d->uart_16550_compatible)
+ return 0;
+
+ d->in_idle = 1;
+
+ /* Prevent triggering interrupt from RBR filling */
+ serial_port_out(p, UART_IER, 0);
+
+ if (up->dma) {
+ serial8250_rx_dma_flush(up);
+ if (serial8250_tx_dma_running(up))
+ serial8250_tx_dma_pause(up);
+ }
/*
- * With PSLVERR_RESP_EN parameter set to 1, the device generates an
- * error response when an attempt to read an empty RBR with FIFO
- * enabled.
+ * Wait until Tx becomes empty + one extra frame time to ensure all bits
+ * have been sent on the wire.
+ *
+ * FIXME: frame_time delay is too long with very low baudrates.
*/
- if (up->fcr & UART_FCR_ENABLE_FIFO) {
- lsr = serial_port_in(p, UART_LSR);
- if (!(lsr & UART_LSR_DR))
- return;
+ serial8250_fifo_wait_for_lsr_thre(up, p->fifosize);
+ ndelay(p->frame_time);
+
+ serial_port_out(p, UART_MCR, up->mcr | UART_MCR_LOOP);
+
+ retries = 4; /* Arbitrary limit, 2 was always enough in tests */
+ do {
+ serial8250_clear_fifos(up);
+ if (!(serial_port_in(p, usr_reg) & DW_UART_USR_BUSY))
+ break;
+ /* FIXME: frame_time delay is too long with very low baudrates. */
+ ndelay(p->frame_time);
+ } while (--retries);
+
+ lsr = serial_lsr_in(up);
+ if (lsr & UART_LSR_DR) {
+ serial_port_in(p, UART_RX);
+ up->lsr_saved_flags = 0;
}
- serial_port_in(p, UART_RX);
+ /* Now guaranteed to have BUSY deasserted? Just sanity check */
+ if (serial_port_in(p, usr_reg) & DW_UART_USR_BUSY) {
+ dw8250_idle_exit(p);
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+static void dw8250_set_divisor(struct uart_port *p, unsigned int baud,
+ unsigned int quot, unsigned int quot_frac)
+{
+ struct uart_8250_port *up = up_to_u8250p(p);
+ int ret;
+
+ ret = dw8250_idle_enter(p);
+ if (ret < 0)
+ return;
+
+ serial_port_out(p, UART_LCR, up->lcr | UART_LCR_DLAB);
+ if (!(serial_port_in(p, UART_LCR) & UART_LCR_DLAB))
+ goto idle_failed;
+
+ serial_dl_write(up, quot);
+ serial_port_out(p, UART_LCR, up->lcr);
+
+idle_failed:
+ dw8250_idle_exit(p);
}
/*
* This function is being called as part of the uart_port::serial_out()
- * routine. Hence, it must not call serial_port_out() or serial_out()
- * against the modified registers here, i.e. LCR.
+ * routine. Hence, special care must be taken when serial_port_out() or
+ * serial_out() against the modified registers here, i.e. LCR (d->in_idle is
+ * used to break recursion loop).
*/
static void dw8250_check_lcr(struct uart_port *p, int offset, int value)
{
struct dw8250_data *d = to_dw8250_data(p->private_data);
- void __iomem *addr = p->membase + (offset << p->regshift);
- int tries = 1000;
+ u32 lcr;
+ int ret;
if (offset != UART_LCR || d->uart_16550_compatible)
return;
+ lcr = serial_port_in(p, UART_LCR);
+
/* Make sure LCR write wasn't ignored */
- while (tries--) {
- unsigned int lcr = serial_port_in(p, offset);
+ if ((value & ~UART_LCR_SPAR) == (lcr & ~UART_LCR_SPAR))
+ return;
- if ((value & ~UART_LCR_SPAR) == (lcr & ~UART_LCR_SPAR))
- return;
+ if (d->in_idle)
+ goto write_err;
- dw8250_force_idle(p);
+ ret = dw8250_idle_enter(p);
+ if (ret < 0)
+ goto write_err;
-#ifdef CONFIG_64BIT
- if (p->type == PORT_OCTEON)
- __raw_writeq(value & 0xff, addr);
- else
-#endif
- if (p->iotype == UPIO_MEM32)
- writel(value, addr);
- else if (p->iotype == UPIO_MEM32BE)
- iowrite32be(value, addr);
- else
- writeb(value, addr);
- }
+ serial_port_out(p, UART_LCR, value);
+ dw8250_idle_exit(p);
+ return;
+
+write_err:
/*
* FIXME: this deadlocks if port->lock is already held
* dev_err(p->dev, "Couldn't set LCR to %d\n", value);
*/
+ return; /* Silences "label at the end of compound statement" */
}
/*
@@ -629,8 +706,10 @@ static int dw8250_probe(struct platform_device *pdev)
p->type = PORT_8250;
p->flags = UPF_FIXED_PORT;
p->dev = dev;
+
p->set_ldisc = dw8250_set_ldisc;
p->set_termios = dw8250_set_termios;
+ p->set_divisor = dw8250_set_divisor;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
@@ -923,7 +1002,7 @@ static struct platform_driver dw8250_platform_driver = {
module_platform_driver(dw8250_platform_driver);
-MODULE_IMPORT_NS("SERIAL_8250");
+MODULE_IMPORT_NS(SERIAL_8250);
MODULE_AUTHOR("Jamie Iles");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Synopsys DesignWare 8250 serial port driver");
diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
index d05254fb115a..aade663b46a3 100644
--- a/drivers/tty/serial/8250/8250_port.c
+++ b/drivers/tty/serial/8250/8250_port.c
@@ -478,7 +478,7 @@ serial_port_out_sync(struct uart_port *p, int offset, int value)
/*
* FIFO support.
*/
-static void serial8250_clear_fifos(struct uart_8250_port *p)
+void serial8250_clear_fifos(struct uart_8250_port *p)
{
if (p->capabilities & UART_CAP_FIFO) {
serial_out(p, UART_FCR, UART_FCR_ENABLE_FIFO);
@@ -487,6 +487,7 @@ static void serial8250_clear_fifos(struct uart_8250_port *p)
serial_out(p, UART_FCR, 0);
}
}
+EXPORT_SYMBOL_NS_GPL(serial8250_clear_fifos, SERIAL_8250);
static enum hrtimer_restart serial8250_em485_handle_start_tx(struct hrtimer *t);
static enum hrtimer_restart serial8250_em485_handle_stop_tx(struct hrtimer *t);
@@ -3284,6 +3285,17 @@ void serial8250_set_defaults(struct uart_8250_port *up)
}
EXPORT_SYMBOL_GPL(serial8250_set_defaults);
+void serial8250_fifo_wait_for_lsr_thre(struct uart_8250_port *up, unsigned int count)
+{
+ unsigned int i;
+
+ for (i = 0; i < count; i++) {
+ if (wait_for_lsr(up, UART_LSR_THRE))
+ return;
+ }
+}
+EXPORT_SYMBOL_NS_GPL(serial8250_fifo_wait_for_lsr_thre, SERIAL_8250);
+
#ifdef CONFIG_SERIAL_8250_CONSOLE
static void serial8250_console_putchar(struct uart_port *port, unsigned char ch)
@@ -3320,16 +3332,6 @@ static void serial8250_console_restore(struct uart_8250_port *up)
serial8250_out_MCR(up, up->mcr | UART_MCR_DTR | UART_MCR_RTS);
}
-static void fifo_wait_for_lsr(struct uart_8250_port *up, unsigned int count)
-{
- unsigned int i;
-
- for (i = 0; i < count; i++) {
- if (wait_for_lsr(up, UART_LSR_THRE))
- return;
- }
-}
-
/*
* Print a string to the serial port using the device FIFO
*
@@ -3347,7 +3349,7 @@ static void serial8250_console_fifo_write(struct uart_8250_port *up,
while (s != end) {
/* Allow timeout for each byte of a possibly full FIFO */
- fifo_wait_for_lsr(up, fifosize);
+ serial8250_fifo_wait_for_lsr_thre(up, fifosize);
for (i = 0; i < fifosize && s != end; ++i) {
if (*s == '\n' && !cr_sent) {
@@ -3365,7 +3367,7 @@ static void serial8250_console_fifo_write(struct uart_8250_port *up,
* Allow timeout for each byte written since the caller will only wait
* for UART_LSR_BOTH_EMPTY using the timeout of a single character
*/
- fifo_wait_for_lsr(up, tx_count);
+ serial8250_fifo_wait_for_lsr_thre(up, tx_count);
}
/*
--
2.54.0
^ permalink raw reply related
* [PATCH v2 6.12.y 09/10] serial: 8250_dw: Rework IIR_NO_INT handling to stop interrupt storm
From: Ionut Nechita (Wind River) @ 2026-05-13 6:50 UTC (permalink / raw)
To: ilpo.jarvinen, gregkh
Cc: stable, andriy.shevchenko, wander, chris.friesen, linux-serial,
Bandal, Shankar, Murthy, Shanth, Ionut Nechita
In-Reply-To: <20260513065028.112468-1-ionut.nechita@windriver.com>
From: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
commit 73a4ed8f9efaaaf8207614ccc1c9d5ca1888f23a upstream.
INTC10EE UART can end up into an interrupt storm where it reports
IIR_NO_INT (0x1). If the storm happens during active UART operation, it
is promptly stopped by IIR value change due to Rx or Tx events.
However, when there is no activity, either due to idle serial line or
due to specific circumstances such as during shutdown that writes
IER=0, there is nothing to stop the storm.
During shutdown the storm is particularly problematic because
serial8250_do_shutdown() calls synchronize_irq() that will hang in
waiting for the storm to finish which never happens.
This problem can also result in triggering a warning:
irq 45: nobody cared (try booting with the "irqpoll" option)
[...snip...]
handlers:
serial8250_interrupt
Disabling IRQ #45
Normal means to reset interrupt status by reading LSR, MSR, USR, or RX
register do not result in the UART deasserting the IRQ.
Add a quirk to INTC10EE UARTs to enable Tx interrupts if UART's Tx is
currently empty and inactive. Rework IIR_NO_INT to keep track of the
number of consecutive IIR_NO_INT, and on fourth one perform the quirk.
Enabling Tx interrupts should change IIR value from IIR_NO_INT to
IIR_THRI which has been observed to stop the storm.
Fixes: e92fad024929 ("serial: 8250_dw: Add ACPI ID for Granite Rapids-D UART")
Cc: stable <stable@kernel.org>
Reported-by: Bandal, Shankar <shankar.bandal@intel.com>
Tested-by: Bandal, Shankar <shankar.bandal@intel.com>
Tested-by: Murthy, Shanth <shanth.murthy@intel.com>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Link: https://patch.msgid.link/20260203171049.4353-6-ilpo.jarvinen@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
[Ionut: adapt to 6.12.y - in dw8250_probe() resolve the trivial
conflict against commit c213375e3283 ("serial: 8250_dw: Call
dw8250_quirks() conditionally") not backported here: keep the
existing 6.12.y unconditional dw8250_quirks() call and add only
p->shutdown = dw8250_shutdown in an else-if branch; do not add
the upstream-only dw8250_setup_dma_filter(p, data) call nor
the redundant p->handle_irq = dw8250_handle_irq assignment
(already done unconditionally earlier in dw8250_probe()).]
Signed-off-by: Ionut Nechita <ionut.nechita@windriver.com>
---
drivers/tty/serial/8250/8250_dw.c | 66 +++++++++++++++++++++++++++++--
1 file changed, 63 insertions(+), 3 deletions(-)
diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
index 1460eaf2a3a3..c24659f0b309 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -61,6 +61,13 @@
#define DW_UART_QUIRK_IS_DMA_FC BIT(3)
#define DW_UART_QUIRK_APMC0D08 BIT(4)
#define DW_UART_QUIRK_CPR_VALUE BIT(5)
+#define DW_UART_QUIRK_IER_KICK BIT(6)
+
+/*
+ * Number of consecutive IIR_NO_INT interrupts required to trigger interrupt
+ * storm prevention code.
+ */
+#define DW_UART_QUIRK_IER_KICK_THRES 4
struct dw8250_platform_data {
u8 usr_reg;
@@ -82,6 +89,8 @@ struct dw8250_data {
unsigned int skip_autocfg:1;
unsigned int uart_16550_compatible:1;
+
+ u8 no_int_count;
};
static inline struct dw8250_data *to_dw8250_data(struct dw8250_port_data *data)
@@ -308,6 +317,29 @@ static unsigned int dw8250_serial_in32be(struct uart_port *p, int offset)
return dw8250_modify_msr(p, offset, value);
}
+/*
+ * INTC10EE UART can IRQ storm while reporting IIR_NO_INT. Inducing IIR value
+ * change has been observed to break the storm.
+ *
+ * If Tx is empty (THRE asserted), we use here IER_THRI to cause IIR_NO_INT ->
+ * IIR_THRI transition.
+ */
+static void dw8250_quirk_ier_kick(struct uart_port *p)
+{
+ struct uart_8250_port *up = up_to_u8250p(p);
+ u32 lsr;
+
+ if (up->ier & UART_IER_THRI)
+ return;
+
+ lsr = serial_lsr_in(up);
+ if (!(lsr & UART_LSR_THRE))
+ return;
+
+ serial_port_out(p, UART_IER, up->ier | UART_IER_THRI);
+ serial_port_in(p, UART_LCR); /* safe, no side-effects */
+ serial_port_out(p, UART_IER, up->ier);
+}
static int dw8250_handle_irq(struct uart_port *p)
{
@@ -318,18 +350,30 @@ static int dw8250_handle_irq(struct uart_port *p)
unsigned int quirks = d->pdata->quirks;
unsigned int status;
+ guard(uart_port_lock_irqsave)(p);
+
switch (FIELD_GET(DW_UART_IIR_IID, iir)) {
case UART_IIR_NO_INT:
+ if (d->uart_16550_compatible || up->dma)
+ return 0;
+
+ if (quirks & DW_UART_QUIRK_IER_KICK &&
+ d->no_int_count == (DW_UART_QUIRK_IER_KICK_THRES - 1))
+ dw8250_quirk_ier_kick(p);
+ d->no_int_count = (d->no_int_count + 1) % DW_UART_QUIRK_IER_KICK_THRES;
+
return 0;
case UART_IIR_BUSY:
/* Clear the USR */
serial_port_in(p, d->pdata->usr_reg);
+ d->no_int_count = 0;
+
return 1;
}
- guard(uart_port_lock_irqsave)(p);
+ d->no_int_count = 0;
/*
* There are ways to get Designware-based UARTs into a state where
@@ -558,6 +602,14 @@ static void dw8250_reset_control_assert(void *data)
reset_control_assert(data);
}
+static void dw8250_shutdown(struct uart_port *port)
+{
+ struct dw8250_data *d = to_dw8250_data(port->private_data);
+
+ serial8250_do_shutdown(port);
+ d->no_int_count = 0;
+}
+
static int dw8250_probe(struct platform_device *pdev)
{
struct uart_8250_port uart = {}, *up = &uart;
@@ -683,8 +735,11 @@ static int dw8250_probe(struct platform_device *pdev)
dw8250_quirks(p, data);
/* If the Busy Functionality is not implemented, don't handle it */
- if (data->uart_16550_compatible)
+ if (data->uart_16550_compatible) {
p->handle_irq = NULL;
+ } else if (data->pdata) {
+ p->shutdown = dw8250_shutdown;
+ }
if (!data->skip_autocfg)
dw8250_setup_port(p);
@@ -816,6 +871,11 @@ static const struct dw8250_platform_data dw8250_skip_set_rate_data = {
.quirks = DW_UART_QUIRK_SKIP_SET_RATE,
};
+static const struct dw8250_platform_data dw8250_intc10ee = {
+ .usr_reg = DW_UART_USR,
+ .quirks = DW_UART_QUIRK_IER_KICK,
+};
+
static const struct of_device_id dw8250_of_match[] = {
{ .compatible = "snps,dw-apb-uart", .data = &dw8250_dw_apb },
{ .compatible = "cavium,octeon-3860-uart", .data = &dw8250_octeon_3860_data },
@@ -845,7 +905,7 @@ static const struct acpi_device_id dw8250_acpi_match[] = {
{ "INT33C5", (kernel_ulong_t)&dw8250_dw_apb },
{ "INT3434", (kernel_ulong_t)&dw8250_dw_apb },
{ "INT3435", (kernel_ulong_t)&dw8250_dw_apb },
- { "INTC10EE", (kernel_ulong_t)&dw8250_dw_apb },
+ { "INTC10EE", (kernel_ulong_t)&dw8250_intc10ee },
{ },
};
MODULE_DEVICE_TABLE(acpi, dw8250_acpi_match);
--
2.54.0
^ permalink raw reply related
* [PATCH v2 6.12.y 08/10] serial: 8250_dw: Rework dw8250_handle_irq() locking and IIR handling
From: Ionut Nechita (Wind River) @ 2026-05-13 6:50 UTC (permalink / raw)
To: ilpo.jarvinen, gregkh
Cc: stable, andriy.shevchenko, wander, chris.friesen, linux-serial,
Bandal, Shankar, Murthy, Shanth, Ionut Nechita
In-Reply-To: <20260513065028.112468-1-ionut.nechita@windriver.com>
From: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
commit 883c5a2bc934c165c4491d1ef7da0ac4e9765077 upstream.
dw8250_handle_irq() takes port's lock multiple times with no good
reason to release it in between and calls serial8250_handle_irq()
that also takes port's lock.
Take port's lock only once in dw8250_handle_irq() and use
serial8250_handle_irq_locked() to avoid releasing port's lock in
between.
As IIR_NO_INT check in serial8250_handle_irq() was outside of port's
lock, it has to be done already in dw8250_handle_irq().
DW UART can, in addition to IIR_NO_INT, report BUSY_DETECT (0x7) which
collided with the IIR_NO_INT (0x1) check in serial8250_handle_irq()
(because & is used instead of ==) meaning that no other work is done by
serial8250_handle_irq() during an BUSY_DETECT interrupt.
This allows reorganizing code in dw8250_handle_irq() to do both
IIR_NO_INT and BUSY_DETECT handling right at the start simplifying
the logic.
Tested-by: Bandal, Shankar <shankar.bandal@intel.com>
Tested-by: Murthy, Shanth <shanth.murthy@intel.com>
Cc: stable <stable@kernel.org>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Link: https://patch.msgid.link/20260203171049.4353-5-ilpo.jarvinen@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Ionut Nechita <ionut.nechita@windriver.com>
---
drivers/tty/serial/8250/8250_dw.c | 37 ++++++++++++++++++-------------
1 file changed, 21 insertions(+), 16 deletions(-)
diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
index 05e45b63e5f5..1460eaf2a3a3 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -9,6 +9,9 @@
* LCR is written whilst busy. If it is, then a busy detect interrupt is
* raised, the LCR needs to be rewritten and the uart status register read.
*/
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/cleanup.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/device.h>
@@ -40,6 +43,8 @@
#define RZN1_UART_RDMACR 0x110 /* DMA Control Register Receive Mode */
/* DesignWare specific register fields */
+#define DW_UART_IIR_IID GENMASK(3, 0)
+
#define DW_UART_MCR_SIRE BIT(6)
/* Renesas specific register fields */
@@ -312,7 +317,19 @@ static int dw8250_handle_irq(struct uart_port *p)
bool rx_timeout = (iir & 0x3f) == UART_IIR_RX_TIMEOUT;
unsigned int quirks = d->pdata->quirks;
unsigned int status;
- unsigned long flags;
+
+ switch (FIELD_GET(DW_UART_IIR_IID, iir)) {
+ case UART_IIR_NO_INT:
+ return 0;
+
+ case UART_IIR_BUSY:
+ /* Clear the USR */
+ serial_port_in(p, d->pdata->usr_reg);
+
+ return 1;
+ }
+
+ guard(uart_port_lock_irqsave)(p);
/*
* There are ways to get Designware-based UARTs into a state where
@@ -325,20 +342,15 @@ static int dw8250_handle_irq(struct uart_port *p)
* so we limit the workaround only to non-DMA mode.
*/
if (!up->dma && rx_timeout) {
- uart_port_lock_irqsave(p, &flags);
status = serial_lsr_in(up);
if (!(status & (UART_LSR_DR | UART_LSR_BI)))
serial_port_in(p, UART_RX);
-
- uart_port_unlock_irqrestore(p, flags);
}
/* Manually stop the Rx DMA transfer when acting as flow controller */
if (quirks & DW_UART_QUIRK_IS_DMA_FC && up->dma && up->dma->rx_running && rx_timeout) {
- uart_port_lock_irqsave(p, &flags);
status = serial_lsr_in(up);
- uart_port_unlock_irqrestore(p, flags);
if (status & (UART_LSR_DR | UART_LSR_BI)) {
dw8250_writel_ext(p, RZN1_UART_RDMACR, 0);
@@ -346,17 +358,9 @@ static int dw8250_handle_irq(struct uart_port *p)
}
}
- if (serial8250_handle_irq(p, iir))
- return 1;
-
- if ((iir & UART_IIR_BUSY) == UART_IIR_BUSY) {
- /* Clear the USR */
- serial_port_in(p, d->pdata->usr_reg);
+ serial8250_handle_irq_locked(p, iir);
- return 1;
- }
-
- return 0;
+ return 1;
}
static void dw8250_clk_work_cb(struct work_struct *work)
@@ -859,6 +863,7 @@ static struct platform_driver dw8250_platform_driver = {
module_platform_driver(dw8250_platform_driver);
+MODULE_IMPORT_NS("SERIAL_8250");
MODULE_AUTHOR("Jamie Iles");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Synopsys DesignWare 8250 serial port driver");
--
2.54.0
^ permalink raw reply related
* [PATCH v2 6.12.y 06/10] serial: 8250_dw: Avoid unnecessary LCR writes
From: Ionut Nechita (Wind River) @ 2026-05-13 6:50 UTC (permalink / raw)
To: ilpo.jarvinen, gregkh
Cc: stable, andriy.shevchenko, wander, chris.friesen, linux-serial,
Bandal, Shankar, Murthy, Shanth, Ionut Nechita
In-Reply-To: <20260513065028.112468-1-ionut.nechita@windriver.com>
From: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
commit 8002d6d6d0d8a36a7d6ca523b17a51cb0fa7c3c3 upstream.
When DW UART is configured with BUSY flag, LCR writes may not always
succeed which can make any LCR write complex and very expensive.
Performing write directly can trigger IRQ and the driver has to perform
complex and distruptive sequence while retrying the write.
Therefore, it's better to avoid doing LCR write that would not change
the value of the LCR register. Add LCR write avoidance code into the
8250_dw driver's .serial_out() functions.
Reported-by: Bandal, Shankar <shankar.bandal@intel.com>
Tested-by: Bandal, Shankar <shankar.bandal@intel.com>
Tested-by: Murthy, Shanth <shanth.murthy@intel.com>
Cc: stable <stable@kernel.org>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Link: https://patch.msgid.link/20260203171049.4353-3-ilpo.jarvinen@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Ionut Nechita <ionut.nechita@windriver.com>
---
drivers/tty/serial/8250/8250_dw.c | 31 +++++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
index 88c55336d50f..05e45b63e5f5 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -181,6 +181,22 @@ static void dw8250_check_lcr(struct uart_port *p, int offset, int value)
*/
}
+/*
+ * With BUSY, LCR writes can be very expensive (IRQ + complex retry logic).
+ * If the write does not change the value of the LCR register, skip it entirely.
+ */
+static bool dw8250_can_skip_reg_write(struct uart_port *p, unsigned int offset, u32 value)
+{
+ struct dw8250_data *d = to_dw8250_data(p->private_data);
+ u32 lcr;
+
+ if (offset != UART_LCR || d->uart_16550_compatible)
+ return false;
+
+ lcr = serial_port_in(p, offset);
+ return lcr == value;
+}
+
/* Returns once the transmitter is empty or we run out of retries */
static void dw8250_tx_wait_empty(struct uart_port *p)
{
@@ -207,12 +223,18 @@ static void dw8250_tx_wait_empty(struct uart_port *p)
static void dw8250_serial_out(struct uart_port *p, int offset, int value)
{
+ if (dw8250_can_skip_reg_write(p, offset, value))
+ return;
+
writeb(value, p->membase + (offset << p->regshift));
dw8250_check_lcr(p, offset, value);
}
static void dw8250_serial_out38x(struct uart_port *p, int offset, int value)
{
+ if (dw8250_can_skip_reg_write(p, offset, value))
+ return;
+
/* Allow the TX to drain before we reconfigure */
if (offset == UART_LCR)
dw8250_tx_wait_empty(p);
@@ -237,6 +259,9 @@ static unsigned int dw8250_serial_inq(struct uart_port *p, int offset)
static void dw8250_serial_outq(struct uart_port *p, int offset, int value)
{
+ if (dw8250_can_skip_reg_write(p, offset, value))
+ return;
+
value &= 0xff;
__raw_writeq(value, p->membase + (offset << p->regshift));
/* Read back to ensure register write ordering. */
@@ -248,6 +273,9 @@ static void dw8250_serial_outq(struct uart_port *p, int offset, int value)
static void dw8250_serial_out32(struct uart_port *p, int offset, int value)
{
+ if (dw8250_can_skip_reg_write(p, offset, value))
+ return;
+
writel(value, p->membase + (offset << p->regshift));
dw8250_check_lcr(p, offset, value);
}
@@ -261,6 +289,9 @@ static unsigned int dw8250_serial_in32(struct uart_port *p, int offset)
static void dw8250_serial_out32be(struct uart_port *p, int offset, int value)
{
+ if (dw8250_can_skip_reg_write(p, offset, value))
+ return;
+
iowrite32be(value, p->membase + (offset << p->regshift));
dw8250_check_lcr(p, offset, value);
}
--
2.54.0
^ permalink raw reply related
* [PATCH v2 6.12.y 07/10] serial: 8250: Add serial8250_handle_irq_locked()
From: Ionut Nechita (Wind River) @ 2026-05-13 6:50 UTC (permalink / raw)
To: ilpo.jarvinen, gregkh
Cc: stable, andriy.shevchenko, wander, chris.friesen, linux-serial,
Bandal, Shankar, Murthy, Shanth, Ionut Nechita
In-Reply-To: <20260513065028.112468-1-ionut.nechita@windriver.com>
From: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
commit 8324a54f604da18f21070702a8ad82ab2062787b upstream.
8250_port exports serial8250_handle_irq() to HW specific 8250 drivers.
It takes port's lock within but a HW specific 8250 driver may want to
take port's lock itself, do something, and then call the generic
handler in 8250_port but to do that, the caller has to release port's
lock for no good reason.
Introduce serial8250_handle_irq_locked() which a HW specific driver can
call while already holding port's lock.
As this is new export, put it straight into a namespace (where all 8250
exports should eventually be moved).
Tested-by: Bandal, Shankar <shankar.bandal@intel.com>
Tested-by: Murthy, Shanth <shanth.murthy@intel.com>
Cc: stable <stable@kernel.org>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Link: https://patch.msgid.link/20260203171049.4353-4-ilpo.jarvinen@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
[Ionut: adapt to 6.12.y - use bare-identifier form in
EXPORT_SYMBOL_NS_GPL(serial8250_handle_irq_locked, SERIAL_8250)
because 6.12.y EXPORT_SYMBOL_NS_GPL() applies __stringify(ns) to
the namespace argument; the upstream string-literal form fails the
.vmlinux.export.c link step with "expected ':' or ')' before
'SERIAL_8250'".]
Signed-off-by: Ionut Nechita <ionut.nechita@windriver.com>
---
drivers/tty/serial/8250/8250_port.c | 24 ++++++++++++++++--------
include/linux/serial_8250.h | 1 +
2 files changed, 17 insertions(+), 8 deletions(-)
diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
index 4252138ccc3d..d05254fb115a 100644
--- a/drivers/tty/serial/8250/8250_port.c
+++ b/drivers/tty/serial/8250/8250_port.c
@@ -18,6 +18,7 @@
#include <linux/irq.h>
#include <linux/console.h>
#include <linux/gpio/consumer.h>
+#include <linux/lockdep.h>
#include <linux/sysrq.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
@@ -1884,20 +1885,16 @@ static bool handle_rx_dma(struct uart_8250_port *up, unsigned int iir)
}
/*
- * This handles the interrupt from one port.
+ * Context: port's lock must be held by the caller.
*/
-int serial8250_handle_irq(struct uart_port *port, unsigned int iir)
+void serial8250_handle_irq_locked(struct uart_port *port, unsigned int iir)
{
struct uart_8250_port *up = up_to_u8250p(port);
struct tty_port *tport = &port->state->port;
bool skip_rx = false;
- unsigned long flags;
u16 status;
- if (iir & UART_IIR_NO_INT)
- return 0;
-
- uart_port_lock_irqsave(port, &flags);
+ lockdep_assert_held_once(&port->lock);
status = serial_lsr_in(up);
@@ -1930,8 +1927,19 @@ int serial8250_handle_irq(struct uart_port *port, unsigned int iir)
else if (!up->dma->tx_running)
__stop_tx(up);
}
+}
+EXPORT_SYMBOL_NS_GPL(serial8250_handle_irq_locked, SERIAL_8250);
+
+/*
+ * This handles the interrupt from one port.
+ */
+int serial8250_handle_irq(struct uart_port *port, unsigned int iir)
+{
+ if (iir & UART_IIR_NO_INT)
+ return 0;
- uart_unlock_and_check_sysrq_irqrestore(port, flags);
+ guard(uart_port_lock_irqsave)(port);
+ serial8250_handle_irq_locked(port, iir);
return 1;
}
diff --git a/include/linux/serial_8250.h b/include/linux/serial_8250.h
index e0717c8393d7..7e0439ecd496 100644
--- a/include/linux/serial_8250.h
+++ b/include/linux/serial_8250.h
@@ -195,6 +195,7 @@ void serial8250_do_set_mctrl(struct uart_port *port, unsigned int mctrl);
void serial8250_do_set_divisor(struct uart_port *port, unsigned int baud,
unsigned int quot);
int fsl8250_handle_irq(struct uart_port *port);
+void serial8250_handle_irq_locked(struct uart_port *port, unsigned int iir);
int serial8250_handle_irq(struct uart_port *port, unsigned int iir);
u16 serial8250_rx_chars(struct uart_8250_port *up, u16 lsr);
void serial8250_read_char(struct uart_8250_port *up, u16 lsr);
--
2.54.0
^ permalink raw reply related
* [PATCH v2 6.12.y 04/10] serial: 8250: convert serial8250_do_shutdown() to scoped_guard()
From: Ionut Nechita (Wind River) @ 2026-05-13 6:50 UTC (permalink / raw)
To: ilpo.jarvinen, gregkh
Cc: stable, andriy.shevchenko, wander, chris.friesen, linux-serial,
Jiri Slaby (SUSE), Ionut Nechita
In-Reply-To: <20260513065028.112468-1-ionut.nechita@windriver.com>
From: "Jiri Slaby (SUSE)" <jirislaby@kernel.org>
This is a partial backport of upstream commit b339809edda1 ("serial:
8250: use guard()s"). Only the serial8250_do_shutdown() hunks of
drivers/tty/serial/8250/8250_port.c are taken; the rest of the
upstream commit is not needed for the BUSY deassert series.
This conversion is a textual prerequisite for applying
commit 59a33d83bbe6 ("serial: 8250: Protect LCR write in shutdown")
without conflicts, because that commit moves the LCR write into a
scoped_guard(uart_port_lock_irqsave) block that does not yet exist
in 6.12.y.
This depends on commit 0fd60b689b0d ("serial: introduce
uart_port_lock() guard()s") (also backported in this series) which
introduces DEFINE_LOCK_GUARD_1(uart_port_lock_irqsave, ...).
Signed-off-by: "Jiri Slaby (SUSE)" <jirislaby@kernel.org>
Link: https://lore.kernel.org/r/20250814072456.182853-10-jirislaby@kernel.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
[Ionut: partial backport - only the serial8250_do_shutdown() hunks
from b339809edda1, per Ilpo's guidance to take "8250_port.c shutdown
hunks" from that commit; the remaining hunks are not required for
the BUSY deassert series.]
Signed-off-by: Ionut Nechita <ionut.nechita@windriver.com>
---
drivers/tty/serial/8250/8250_port.c | 27 +++++++++++++--------------
1 file changed, 13 insertions(+), 14 deletions(-)
diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
index b4c8388ea6fc..21fa7907c0d6 100644
--- a/drivers/tty/serial/8250/8250_port.c
+++ b/drivers/tty/serial/8250/8250_port.c
@@ -2461,7 +2461,6 @@ static int serial8250_startup(struct uart_port *port)
void serial8250_do_shutdown(struct uart_port *port)
{
struct uart_8250_port *up = up_to_u8250p(port);
- unsigned long flags;
serial8250_rpm_get(up);
/*
@@ -2469,26 +2468,26 @@ void serial8250_do_shutdown(struct uart_port *port)
*
* Synchronize UART_IER access against the console.
*/
- uart_port_lock_irqsave(port, &flags);
- up->ier = 0;
- serial_port_out(port, UART_IER, 0);
- uart_port_unlock_irqrestore(port, flags);
+ scoped_guard(uart_port_lock_irqsave, port) {
+ up->ier = 0;
+ serial_port_out(port, UART_IER, 0);
+ }
synchronize_irq(port->irq);
if (up->dma)
serial8250_release_dma(up);
- uart_port_lock_irqsave(port, &flags);
- if (port->flags & UPF_FOURPORT) {
- /* reset interrupts on the AST Fourport board */
- inb((port->iobase & 0xfe0) | 0x1f);
- port->mctrl |= TIOCM_OUT1;
- } else
- port->mctrl &= ~TIOCM_OUT2;
+ scoped_guard(uart_port_lock_irqsave, port) {
+ if (port->flags & UPF_FOURPORT) {
+ /* reset interrupts on the AST Fourport board */
+ inb((port->iobase & 0xfe0) | 0x1f);
+ port->mctrl |= TIOCM_OUT1;
+ } else
+ port->mctrl &= ~TIOCM_OUT2;
- serial8250_set_mctrl(port, port->mctrl);
- uart_port_unlock_irqrestore(port, flags);
+ serial8250_set_mctrl(port, port->mctrl);
+ }
/*
* Disable break condition and FIFOs
--
2.54.0
^ permalink raw reply related
* [PATCH v2 6.12.y 05/10] serial: 8250: Protect LCR write in shutdown
From: Ionut Nechita (Wind River) @ 2026-05-13 6:50 UTC (permalink / raw)
To: ilpo.jarvinen, gregkh
Cc: stable, andriy.shevchenko, wander, chris.friesen, linux-serial,
Bandal, Shankar, Murthy, Shanth, Ionut Nechita
In-Reply-To: <20260513065028.112468-1-ionut.nechita@windriver.com>
From: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
commit 59a33d83bbe6d73d2071d7ae21590b29faed0503 upstream.
The 8250_dw driver needs to potentially perform very complex operations
during LCR writes because its BUSY handling prevents updates to LCR
while UART is BUSY (which is not fully under our control without those
complex operations). Thus, LCR writes should occur under port's lock.
Move LCR write under port's lock in serial8250_do_shutdown(). Also
split the LCR RMW so that the logic is on a separate line for clarity.
Reported-by: Bandal, Shankar <shankar.bandal@intel.com>
Tested-by: Bandal, Shankar <shankar.bandal@intel.com>
Tested-by: Murthy, Shanth <shanth.murthy@intel.com>
Cc: stable <stable@kernel.org>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Link: https://patch.msgid.link/20260203171049.4353-2-ilpo.jarvinen@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Ionut Nechita <ionut.nechita@windriver.com>
---
drivers/tty/serial/8250/8250_port.c | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
index 21fa7907c0d6..4252138ccc3d 100644
--- a/drivers/tty/serial/8250/8250_port.c
+++ b/drivers/tty/serial/8250/8250_port.c
@@ -2461,6 +2461,7 @@ static int serial8250_startup(struct uart_port *port)
void serial8250_do_shutdown(struct uart_port *port)
{
struct uart_8250_port *up = up_to_u8250p(port);
+ u32 lcr;
serial8250_rpm_get(up);
/*
@@ -2487,13 +2488,13 @@ void serial8250_do_shutdown(struct uart_port *port)
port->mctrl &= ~TIOCM_OUT2;
serial8250_set_mctrl(port, port->mctrl);
+
+ /* Disable break condition */
+ lcr = serial_port_in(port, UART_LCR);
+ lcr &= ~UART_LCR_SBC;
+ serial_port_out(port, UART_LCR, lcr);
}
- /*
- * Disable break condition and FIFOs
- */
- serial_port_out(port, UART_LCR,
- serial_port_in(port, UART_LCR) & ~UART_LCR_SBC);
serial8250_clear_fifos(up);
#ifdef CONFIG_SERIAL_8250_RSA
--
2.54.0
^ permalink raw reply related
* [PATCH v2 6.12.y 00/10] serial: 8250_dw: backport BUSY deassert series
From: Ionut Nechita (Wind River) @ 2026-05-13 6:50 UTC (permalink / raw)
To: ilpo.jarvinen, gregkh
Cc: stable, andriy.shevchenko, wander, chris.friesen, linux-serial,
Ionut Nechita
From: Ionut Nechita <ionut.nechita@windriver.com>
Hi Greg, Ilpo,
This is v2 of the 8250_dw BUSY deassert backport to 6.12.y,
addressing Ilpo's review feedback on v1.
v1 thread:
https://lore.kernel.org/linux-serial/20260510134011.618215-1-ionut.nechita@windriver.com/
v1 review (Ilpo, 2026-05-11):
https://lore.kernel.org/linux-serial/d4b4e4db-505f-4400-c1b9-fe589786456e@linux.intel.com/
Changes in v2:
- Keep guard()/scoped_guard() from upstream verbatim in patches
5, 7, 8 and 9 (no more explicit uart_port_lock_irqsave/
unlock_irqrestore + goto-out adaptations), per Ilpo's review:
"By doing this you had to add some gotos which makes code
control flow more complicated than it was in the original
changes. From reviewer's point of view, the code is more
complicated without guard() than with them."
To make that work on 6.12.y, two small additional prerequisites
are now part of the series (see new patches 3/10 and 4/10
below).
- Stop replacing guard()s with explicit lock/unlock pairs; v2
diff against upstream is now significantly smaller.
Series structure (10 patches):
Prerequisites (per Ilpo's original guidance, plus one implicit
guard-defs prereq):
1/10 serial: 8250: use serial_port_in/out() helpers [dbd26a886e94]
2/10 serial: 8250_dw: Comment possible corner cases ... [bd8cad85561b]
3/10 serial: introduce uart_port_lock() guard()s [0fd60b689b0d] (NEW in v2)
4/10 serial: 8250: convert serial8250_do_shutdown()
to scoped_guard() [partial b339809edda1] (NEW in v2)
BUSY deassert series:
5/10 serial: 8250: Protect LCR write in shutdown [59a33d83bbe6]
6/10 serial: 8250_dw: Avoid unnecessary LCR writes [8002d6d6d0d8]
7/10 serial: 8250: Add serial8250_handle_irq_locked() [8324a54f604d]
8/10 serial: 8250_dw: Rework dw8250_handle_irq() ... [883c5a2bc934]
9/10 serial: 8250_dw: Rework IIR_NO_INT handling ... [73a4ed8f9efa]
10/10 serial: 8250_dw: Ensure BUSY is deasserted [a7b9ce39fbe4]
Notes on the new prerequisites (3/10 and 4/10):
- Patch 3/10 cherry-picks commit 0fd60b689b0d ("serial: introduce
uart_port_lock() guard()s") unmodified (13 additive lines in
include/linux/serial_core.h). It introduces DEFINE_GUARD() /
DEFINE_LOCK_GUARD_1() invocations for uart_port_lock,
uart_port_lock_irq and uart_port_lock_irqsave, which are
required so that scoped_guard(uart_port_lock_irqsave, port) and
guard(uart_port_lock_irqsave)(port) compile in 6.12.y. The
underlying cleanup.h infrastructure (DEFINE_GUARD,
DEFINE_LOCK_GUARD_1) already exists in 6.12.y; only this small
serial-specific wiring is missing.
- Patch 4/10 is a *partial* backport of commit b339809edda1
("serial: 8250: use guard()s"), per Ilpo's original request:
"b339809edda1 ('serial: 8250: use guard()s')
(8250_port.c shutdown hunks)"
The commit is authored by Jiri Slaby (SUSE) and carries the
upstream Link/SoB chain, plus an explicit [Ionut: partial
backport - only the serial8250_do_shutdown() hunks from
b339809edda1] note. The remaining (much larger) refactor of
8250_port.c and 8250_core.c is not needed for the BUSY deassert
series and is intentionally not backported.
Notes on the BUSY series itself:
- Patch 6/7 of the original mainline BUSY series,
commit e0a368ae7953 ("serial: 8250: Add late synchronize_irq()
to shutdown to handle DW UART BUSY"), is *already* in 6.12.y as
commit 0bae1c670aa8 (Ilpo, 2026-02-03), so it is not re-sent
here. Functionally this means patches 5-10 above land on top of
the existing late-synchronize_irq() fix.
- Ilpo's other suggested-but-not-required prerequisites remain
*not* included:
* commit fc9ceb501e38 ("serial: 8250: sanitize
uart_port::serial_{in,out}() types") only causes a trivial
conflict in patch 10 in code that the BUSY change removes
anyway (per Ilpo's note);
* commit c213375e3283 ("serial: 8250_dw: Call dw8250_quirks()
conditionally") only causes a conflict in patch 9 around
the dw8250_setup_dma_filter() helper and the conditional
p->handle_irq assignment, neither of which exist in 6.12.y
and neither of which is needed for the BUSY fix.
Both of those conflicts are resolved trivially in patches 9 and
10 with explicit [Ionut: adapt to 6.12.y - ...] notes.
- Namespace export syntax: in 6.12.y both EXPORT_SYMBOL_NS_GPL()
and MODULE_IMPORT_NS() apply __stringify(ns) to the namespace
argument, so it must be a bare identifier. Mainline (where the
upstream patches were written) accepts a string literal. Patches
7 and 10 here use the bare-identifier form (SERIAL_8250) instead
of the upstream string form ("SERIAL_8250"); without this fix
the .vmlinux.export.c link step fails with "expected ':' or ')'
before 'SERIAL_8250'". This is noted in the [Ionut: ...] block
of the affected patches.
Only patches 4, 7, 9 and 10 carry "[Ionut: adapt to 6.12.y - ...]"
notes; the rest are clean cherry-picks of the upstream commits
using the standard "commit <hash> upstream." stable convention.
Build:
- HEAD of the series builds to a complete vmlinux on 6.12.87 with
CONFIG_SERIAL_8250=y, CONFIG_SERIAL_8250_DW=m on x86_64. The
.vmlinux.export.c link step (which v1 originally tripped on
during development) passes cleanly.
Testing plan:
- Same as v1: test on Intel platforms with DW APB UART
(snps,dw-apb-uart) running 6.12.x-rt (PREEMPT_RT) to confirm
the original symptom (LCR writes silently ignored under Rx
load -> baud / framing mismatch after set_termios) is gone.
Will report Tested-by once cycles complete.
Based on: linux-6.12.y at v6.12.87 (8bf2f55ef536).
Andy Shevchenko (1):
serial: 8250_dw: Comment possible corner cases in serial_out()
implementation
Ilpo Järvinen (6):
serial: 8250: Protect LCR write in shutdown
serial: 8250_dw: Avoid unnecessary LCR writes
serial: 8250: Add serial8250_handle_irq_locked()
serial: 8250_dw: Rework dw8250_handle_irq() locking and IIR handling
serial: 8250_dw: Rework IIR_NO_INT handling to stop interrupt storm
serial: 8250_dw: Ensure BUSY is deasserted
Jiri Slaby (SUSE) (3):
serial: 8250: use serial_port_in/out() helpers
serial: introduce uart_port_lock() guard()s
serial: 8250: convert serial8250_do_shutdown() to scoped_guard()
drivers/tty/serial/8250/8250.h | 25 +++
drivers/tty/serial/8250/8250_dw.c | 296 +++++++++++++++++++++++-----
drivers/tty/serial/8250/8250_fsl.c | 8 +-
drivers/tty/serial/8250/8250_omap.c | 2 +-
drivers/tty/serial/8250/8250_port.c | 90 +++++----
include/linux/serial_8250.h | 1 +
include/linux/serial_core.h | 13 ++
7 files changed, 338 insertions(+), 97 deletions(-)
--
2.54.0
^ permalink raw reply
* [PATCH v2 6.12.y 02/10] serial: 8250_dw: Comment possible corner cases in serial_out() implementation
From: Ionut Nechita (Wind River) @ 2026-05-13 6:50 UTC (permalink / raw)
To: ilpo.jarvinen, gregkh
Cc: stable, andriy.shevchenko, wander, chris.friesen, linux-serial,
Ionut Nechita
In-Reply-To: <20260513065028.112468-1-ionut.nechita@windriver.com>
From: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
commit bd8cad85561b8de36f099404c332bf3e3dbe90f5 upstream.
8250 DesignWare driver uses a few custom implementations of the serial_out().
These implementations are carefully made to avoid infinite loops. But this is
not obvious from looking at the code. Comment the possible corner cases in
the respective functions.
Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Link: https://lore.kernel.org/r/20250317094021.1201512-1-andriy.shevchenko@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Ionut Nechita <ionut.nechita@windriver.com>
---
drivers/tty/serial/8250/8250_dw.c | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
index 3225011fd772..88c55336d50f 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -107,11 +107,23 @@ static inline int dw8250_modify_msr(struct uart_port *p, int offset, int value)
return value;
}
+/*
+ * This function is being called as part of the uart_port::serial_out()
+ * routine. Hence, it must not call serial_port_out() or serial_out()
+ * against the modified registers here, i.e. LCR.
+ */
static void dw8250_force_idle(struct uart_port *p)
{
struct uart_8250_port *up = up_to_u8250p(p);
unsigned int lsr;
+ /*
+ * The following call currently performs serial_out()
+ * against the FCR register. Because it differs to LCR
+ * there will be no infinite loop, but if it ever gets
+ * modified, we might need a new custom version of it
+ * that avoids infinite recursion.
+ */
serial8250_clear_and_reinit_fifos(up);
/*
@@ -128,6 +140,11 @@ static void dw8250_force_idle(struct uart_port *p)
serial_port_in(p, UART_RX);
}
+/*
+ * This function is being called as part of the uart_port::serial_out()
+ * routine. Hence, it must not call serial_port_out() or serial_out()
+ * against the modified registers here, i.e. LCR.
+ */
static void dw8250_check_lcr(struct uart_port *p, int offset, int value)
{
struct dw8250_data *d = to_dw8250_data(p->private_data);
--
2.54.0
^ permalink raw reply related
* [PATCH v2 6.12.y 01/10] serial: 8250: use serial_port_in/out() helpers
From: Ionut Nechita (Wind River) @ 2026-05-13 6:50 UTC (permalink / raw)
To: ilpo.jarvinen, gregkh
Cc: stable, andriy.shevchenko, wander, chris.friesen, linux-serial,
Jiri Slaby (SUSE), Ionut Nechita
In-Reply-To: <20260513065028.112468-1-ionut.nechita@windriver.com>
From: "Jiri Slaby (SUSE)" <jirislaby@kernel.org>
commit dbd26a886e94deb7fda9050f9195ccb41f9a5d93 upstream.
There are serial_port_in/out() helpers to be used instead of direct
p->serial_in/out(). Use them in various 8250 drivers.
Signed-off-by: Jiri Slaby (SUSE) <jirislaby@kernel.org>
Cc: "Ilpo Järvinen" <ilpo.jarvinen@linux.intel.com>
Cc: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
--
[v2]
* Use serial_port_in/out() and not serial_in/out() [Andy]
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> # 8250_dw
Link: https://lore.kernel.org/r/20250317070046.24386-28-jirislaby@kernel.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Ionut Nechita <ionut.nechita@windriver.com>
---
drivers/tty/serial/8250/8250_dw.c | 16 ++++++++--------
drivers/tty/serial/8250/8250_fsl.c | 8 ++++----
drivers/tty/serial/8250/8250_omap.c | 2 +-
3 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/drivers/tty/serial/8250/8250_dw.c b/drivers/tty/serial/8250/8250_dw.c
index f17dc3de020c..3225011fd772 100644
--- a/drivers/tty/serial/8250/8250_dw.c
+++ b/drivers/tty/serial/8250/8250_dw.c
@@ -120,12 +120,12 @@ static void dw8250_force_idle(struct uart_port *p)
* enabled.
*/
if (up->fcr & UART_FCR_ENABLE_FIFO) {
- lsr = p->serial_in(p, UART_LSR);
+ lsr = serial_port_in(p, UART_LSR);
if (!(lsr & UART_LSR_DR))
return;
}
- (void)p->serial_in(p, UART_RX);
+ serial_port_in(p, UART_RX);
}
static void dw8250_check_lcr(struct uart_port *p, int offset, int value)
@@ -139,7 +139,7 @@ static void dw8250_check_lcr(struct uart_port *p, int offset, int value)
/* Make sure LCR write wasn't ignored */
while (tries--) {
- unsigned int lcr = p->serial_in(p, offset);
+ unsigned int lcr = serial_port_in(p, offset);
if ((value & ~UART_LCR_SPAR) == (lcr & ~UART_LCR_SPAR))
return;
@@ -260,7 +260,7 @@ static int dw8250_handle_irq(struct uart_port *p)
{
struct uart_8250_port *up = up_to_u8250p(p);
struct dw8250_data *d = to_dw8250_data(p->private_data);
- unsigned int iir = p->serial_in(p, UART_IIR);
+ unsigned int iir = serial_port_in(p, UART_IIR);
bool rx_timeout = (iir & 0x3f) == UART_IIR_RX_TIMEOUT;
unsigned int quirks = d->pdata->quirks;
unsigned int status;
@@ -281,7 +281,7 @@ static int dw8250_handle_irq(struct uart_port *p)
status = serial_lsr_in(up);
if (!(status & (UART_LSR_DR | UART_LSR_BI)))
- (void) p->serial_in(p, UART_RX);
+ serial_port_in(p, UART_RX);
uart_port_unlock_irqrestore(p, flags);
}
@@ -303,7 +303,7 @@ static int dw8250_handle_irq(struct uart_port *p)
if ((iir & UART_IIR_BUSY) == UART_IIR_BUSY) {
/* Clear the USR */
- (void)p->serial_in(p, d->pdata->usr_reg);
+ serial_port_in(p, d->pdata->usr_reg);
return 1;
}
@@ -390,7 +390,7 @@ static void dw8250_set_termios(struct uart_port *p, struct ktermios *termios,
static void dw8250_set_ldisc(struct uart_port *p, struct ktermios *termios)
{
struct uart_8250_port *up = up_to_u8250p(p);
- unsigned int mcr = p->serial_in(p, UART_MCR);
+ unsigned int mcr = serial_port_in(p, UART_MCR);
if (up->capabilities & UART_CAP_IRDA) {
if (termios->c_line == N_IRDA)
@@ -398,7 +398,7 @@ static void dw8250_set_ldisc(struct uart_port *p, struct ktermios *termios)
else
mcr &= ~DW_UART_MCR_SIRE;
- p->serial_out(p, UART_MCR, mcr);
+ serial_port_out(p, UART_MCR, mcr);
}
serial8250_do_set_ldisc(p, termios);
}
diff --git a/drivers/tty/serial/8250/8250_fsl.c b/drivers/tty/serial/8250/8250_fsl.c
index b4ed442082a8..59d3d2801c2e 100644
--- a/drivers/tty/serial/8250/8250_fsl.c
+++ b/drivers/tty/serial/8250/8250_fsl.c
@@ -32,7 +32,7 @@ int fsl8250_handle_irq(struct uart_port *port)
uart_port_lock_irqsave(&up->port, &flags);
- iir = port->serial_in(port, UART_IIR);
+ iir = serial_port_in(port, UART_IIR);
if (iir & UART_IIR_NO_INT) {
uart_port_unlock_irqrestore(&up->port, flags);
return 0;
@@ -54,12 +54,12 @@ int fsl8250_handle_irq(struct uart_port *port)
if (unlikely((iir & UART_IIR_ID) == UART_IIR_RLSI &&
(up->lsr_saved_flags & UART_LSR_BI))) {
up->lsr_saved_flags &= ~UART_LSR_BI;
- port->serial_in(port, UART_RX);
+ serial_port_in(port, UART_RX);
uart_port_unlock_irqrestore(&up->port, flags);
return 1;
}
- lsr = orig_lsr = up->port.serial_in(&up->port, UART_LSR);
+ lsr = orig_lsr = serial_port_in(port, UART_LSR);
/* Process incoming characters first */
if ((lsr & (UART_LSR_DR | UART_LSR_BI)) &&
@@ -71,7 +71,7 @@ int fsl8250_handle_irq(struct uart_port *port)
if ((orig_lsr & UART_LSR_OE) && (up->overrun_backoff_time_ms > 0)) {
unsigned long delay;
- up->ier = port->serial_in(port, UART_IER);
+ up->ier = serial_port_in(port, UART_IER);
if (up->ier & (UART_IER_RLSI | UART_IER_RDI)) {
port->ops->stop_rx(port);
} else {
diff --git a/drivers/tty/serial/8250/8250_omap.c b/drivers/tty/serial/8250/8250_omap.c
index 0f4ce0c69114..dc9e3e25d55f 100644
--- a/drivers/tty/serial/8250/8250_omap.c
+++ b/drivers/tty/serial/8250/8250_omap.c
@@ -688,7 +688,7 @@ static irqreturn_t omap8250_irq(int irq, void *dev_id)
/* Synchronize UART_IER access against the console. */
uart_port_lock(port);
- up->ier = port->serial_in(port, UART_IER);
+ up->ier = serial_port_in(port, UART_IER);
if (up->ier & (UART_IER_RLSI | UART_IER_RDI)) {
port->ops->stop_rx(port);
} else {
--
2.54.0
^ permalink raw reply related
* [PATCH v2 6.12.y 03/10] serial: introduce uart_port_lock() guard()s
From: Ionut Nechita (Wind River) @ 2026-05-13 6:50 UTC (permalink / raw)
To: ilpo.jarvinen, gregkh
Cc: stable, andriy.shevchenko, wander, chris.friesen, linux-serial,
Jiri Slaby (SUSE), Ionut Nechita
In-Reply-To: <20260513065028.112468-1-ionut.nechita@windriver.com>
From: "Jiri Slaby (SUSE)" <jirislaby@kernel.org>
commit 0fd60b689b0dacce659253ec15cb3d3bf660e30b upstream.
Having this, guards like these work:
guard(uart_port_lock_irq)(&up->port);
or
scoped_guard(uart_port_lock_irqsave, port) {
...
}
See e.g. "serial: 8250: use guard()s" later in this series.
Signed-off-by: "Jiri Slaby (SUSE)" <jirislaby@kernel.org>
Link: https://lore.kernel.org/r/20250814072456.182853-4-jirislaby@kernel.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Ionut Nechita <ionut.nechita@windriver.com>
---
include/linux/serial_core.h | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h
index 4ab65874a850..191154b8ada2 100644
--- a/include/linux/serial_core.h
+++ b/include/linux/serial_core.h
@@ -782,6 +782,19 @@ static inline void uart_port_unlock_irqrestore(struct uart_port *up, unsigned lo
spin_unlock_irqrestore(&up->lock, flags);
}
+DEFINE_GUARD(uart_port_lock, struct uart_port *, uart_port_lock(_T), uart_port_unlock(_T));
+DEFINE_GUARD_COND(uart_port_lock, _try, uart_port_trylock(_T));
+
+DEFINE_GUARD(uart_port_lock_irq, struct uart_port *, uart_port_lock_irq(_T),
+ uart_port_unlock_irq(_T));
+
+DEFINE_LOCK_GUARD_1(uart_port_lock_irqsave, struct uart_port,
+ uart_port_lock_irqsave(_T->lock, &_T->flags),
+ uart_port_unlock_irqrestore(_T->lock, _T->flags),
+ unsigned long flags);
+DEFINE_LOCK_GUARD_1_COND(uart_port_lock_irqsave, _try,
+ uart_port_trylock_irqsave(_T->lock, &_T->flags));
+
static inline int serial_port_in(struct uart_port *up, int offset)
{
return up->serial_in(up, offset);
--
2.54.0
^ permalink raw reply related
* [PATCH] Bluetooth: hci_uart: fix UAF in hci_uart_tty_close()
From: w15303746062 @ 2026-05-13 6:45 UTC (permalink / raw)
To: marcel, luiz.dentz
Cc: linux-bluetooth, linux-serial, linux-kernel, Mingyu Wang
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);
+
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
* BUG: General Protection Fault in h5_recv due to TTY line discipline race condition (TIOCSTI)
From: 王明煜 @ 2026-05-13 2:54 UTC (permalink / raw)
To: Marcel Holtmann, Luiz Augusto von Dentz
Cc: linux-bluetooth, linux-kernel, linux-serial
Hi all,
We have discovered a severe NULL pointer dereference and subsequent General Protection Fault in `h5_recv()` within `drivers/bluetooth/hci_h5.c`. This crash is triggered by a race condition between the initialization of the HCI UART line discipline and concurrent data injection via the `TIOCSTI` ioctl.
This issue was found during fuzzing with a custom LLM-assisted device modeling framework.
### Crash Trace (KASAN)
Oops: general protection fault, probably for non-canonical address 0xdffffc000000005f: 0000 [#1] SMP KASAN PTI
KASAN: null-ptr-deref in range [0x00000000000002f8-0x00000000000002ff]
CPU: 3 UID: 0 PID: 75833 Comm: syz.0.5605 Not tainted 6.18.0 #1 PREEMPT(full)
RIP: 0010:h5_recv+0xfc/0x8f0 drivers/bluetooth/hci_h5.c:572
...
Call Trace:
<TASK>
hci_uart_tty_receive+0x25b/0x800 drivers/bluetooth/hci_ldisc.c:627
tiocsti drivers/tty/tty_io.c:2290 [inline]
tty_ioctl+0x502/0x1690 drivers/tty/tty_io.c:2706
__x64_sys_ioctl+0x18f/0x210 fs/ioctl.c:583
do_syscall_64+0xcb/0xfa0 arch/x86/entry/syscall_64.c:94
...
### Vulnerability Analysis
The crash occurs due to an architectural race window during the line discipline setup:
1. Thread A executes `ioctl(HCIUARTSETPROTO)` to set the line discipline to HCI_UART_H5. The kernel sets the `HCI_UART_PROTO_INIT` bit in `hu->flags` and proceeds to call `hci_uart_register_dev()`, which eventually invokes `h5_open()`.
2. Before `h5_open()` finishes executing and allocates memory for `hu->priv` (i.e., before `hu->priv = h5;` is executed), Thread B concurrently executes `ioctl(TIOCSTI)`.
3. The `TIOCSTI` command forces data into the TTY receive path, calling `hci_uart_tty_receive()`.
4. Inside `hci_uart_tty_receive()`, the condition `!test_bit(HCI_UART_PROTO_INIT, &hu->flags)` evaluates to FALSE (because Thread A set it), allowing the execution to proceed without dropping the data.
5. The execution flows into `hu->proto->recv()` -> `h5_recv()`.
6. `h5_recv()` blindly dereferences `hu->priv` (which is still NULL), leading to the GPF when accessing `h5->rx_pending` on line 572: `BT_DBG("... %zu ...", ..., h5->rx_pending, ...);`.
### Proposed Fix Direction
While adding a simple `if (!h5) return 0;` at the beginning of `h5_recv()` prevents the crash, it seems the root cause lies in the permissiveness of the `HCI_UART_PROTO_INIT` state in `hci_uart_tty_receive()`, or the lack of proper synchronization between the `open` callback and the `recv` path during initialization.
Since we are not entirely familiar with the specific handshake requirements of the H5 protocol during the `PROTO_INIT` phase, we are reporting this vulnerability here rather than submitting a potentially flawed patch.
Please let us know if you need more information or the full reproducer.
Reported-by: Mingyu Wang <25181214217@stu.xidian.edu.cn>
Best regards,
Mingyu Wang
^ permalink raw reply
* Re: [PATCH v2] dt-bindings: qcom: geni-se-qup: Add compatible for SA8797P SoC
From: Bjorn Andersson @ 2026-05-12 20:23 UTC (permalink / raw)
To: Shawn Guo
Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Praveen Talari,
Konrad Dybcio, Dmitry Baryshkov, Bartosz Golaszewski,
Deepti Jaggi, linux-serial, devicetree, linux-arm-msm,
linux-kernel
In-Reply-To: <20260427005901.230237-1-shengchao.guo@oss.qualcomm.com>
On Mon, 27 Apr 2026 08:59:01 +0800, Shawn Guo wrote:
> Document GENI Serial Engine QUP Wrapper Controller on Nord SA8797P SoC
> which is compatible with SA8255P one.
>
>
Applied, thanks!
[1/1] dt-bindings: qcom: geni-se-qup: Add compatible for SA8797P SoC
commit: 35466ef5db1fbdff49c4142026c4c56514d5ff47
Best regards,
--
Bjorn Andersson <andersson@kernel.org>
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox