Merge tag 'tty-6.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/tty

Pull tty/serial updates from Greg KH:
 "Here is the big set of tty/serial driver changes for 6.19-rc1. Nothing
  major at all, just small constant churn to make the tty layer
  "cleaner" as well as serial driver updates and even a new test added!
  Included in here are:

   - More tty/serial cleanups from Jiri

   - tty tiocsti test added to hopefully ensure we don't regress in this
     area again

   - sc16is7xx driver updates

   - imx serial driver updates

   - 8250 driver updates

   - new hardware device ids added

   - other minor serial/tty driver cleanups and tweaks

  All of these have been in linux-next for a while with no reported
  issues"

* tag 'tty-6.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/tty: (60 commits)
  serial: sh-sci: Fix deadlock during RSCI FIFO overrun error
  dt-bindings: serial: rsci: Drop "uart-has-rtscts: false"
  LoongArch: dts: Add uart new compatible string
  serial: 8250: Add Loongson uart driver support
  dt-bindings: serial: 8250: Add Loongson uart compatible
  serial: 8250: add driver for KEBA UART
  serial: Keep rs485 settings for devices without firmware node
  serial: qcom-geni: Enable Serial on SA8255p Qualcomm platforms
  serial: qcom-geni: Enable PM runtime for serial driver
  serial: sprd: Return -EPROBE_DEFER when uart clock is not ready
  tty: serial: samsung: Declare earlycon for Exynos850
  serial: icom: Convert PCIBIOS_* return codes to errnos
  serial: 8250-of: Fix style issues in 8250_of.c
  serial: add support of CPCI cards
  serial: mux: Fix kernel doc for mux_poll()
  tty: replace use of system_unbound_wq with system_dfl_wq
  serial: 8250_platform: simplify IRQF_SHARED handling
  serial: 8250: make share_irqs local to 8250_platform
  serial: 8250: move skip_txen_test to core
  serial: drop SERIAL_8250_DEPRECATED_OPTIONS
  ...
This commit is contained in:
Linus Torvalds
2025-12-06 18:38:19 -08:00
71 changed files with 2338 additions and 1161 deletions

View File

@@ -125,6 +125,8 @@ properties:
- nxp,lpc1850-uart - nxp,lpc1850-uart
- opencores,uart16550-rtlsvn105 - opencores,uart16550-rtlsvn105
- ti,da830-uart - ti,da830-uart
- loongson,ls2k0500-uart
- loongson,ls2k1500-uart
- const: ns16550a - const: ns16550a
- items: - items:
- enum: - enum:
@@ -169,6 +171,18 @@ properties:
- nvidia,tegra194-uart - nvidia,tegra194-uart
- nvidia,tegra234-uart - nvidia,tegra234-uart
- const: nvidia,tegra20-uart - const: nvidia,tegra20-uart
- items:
- enum:
- loongson,ls2k1000-uart
- const: loongson,ls2k0500-uart
- const: ns16550a
- items:
- enum:
- loongson,ls3a5000-uart
- loongson,ls3a6000-uart
- loongson,ls2k2000-uart
- const: loongson,ls2k1500-uart
- const: ns16550a
reg: reg:
maxItems: 1 maxItems: 1

View File

@@ -54,8 +54,6 @@ properties:
power-domains: power-domains:
maxItems: 1 maxItems: 1
uart-has-rtscts: false
required: required:
- compatible - compatible
- reg - reg

View File

@@ -48,7 +48,9 @@ properties:
- const: samsung,exynos850-uart - const: samsung,exynos850-uart
- items: - items:
- enum: - enum:
- axis,artpec9-uart
- samsung,exynos7870-uart - samsung,exynos7870-uart
- samsung,exynos8890-uart
- const: samsung,exynos8895-uart - const: samsung,exynos8895-uart
reg: reg:

View File

@@ -65,6 +65,7 @@ properties:
- rockchip,rk3328-uart - rockchip,rk3328-uart
- rockchip,rk3368-uart - rockchip,rk3368-uart
- rockchip,rk3399-uart - rockchip,rk3399-uart
- rockchip,rk3506-uart
- rockchip,rk3528-uart - rockchip,rk3528-uart
- rockchip,rk3562-uart - rockchip,rk3562-uart
- rockchip,rk3568-uart - rockchip,rk3568-uart

View File

@@ -117,7 +117,6 @@ CONFIG_KEYBOARD_GPIO_POLLED=y
# CONFIG_VT is not set # CONFIG_VT is not set
# CONFIG_LEGACY_PTYS is not set # CONFIG_LEGACY_PTYS is not set
CONFIG_SERIAL_8250=y CONFIG_SERIAL_8250=y
# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set
CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_8250_CONSOLE=y
CONFIG_SERIAL_8250_NR_UARTS=6 CONFIG_SERIAL_8250_NR_UARTS=6
CONFIG_SERIAL_8250_RUNTIME_UARTS=6 CONFIG_SERIAL_8250_RUNTIME_UARTS=6

View File

@@ -138,7 +138,6 @@ CONFIG_SERIO_RAW=y
# CONFIG_VT is not set # CONFIG_VT is not set
# CONFIG_LEGACY_PTYS is not set # CONFIG_LEGACY_PTYS is not set
CONFIG_SERIAL_8250=y CONFIG_SERIAL_8250=y
# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set
CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_8250_CONSOLE=y
CONFIG_SERIAL_8250_NR_UARTS=6 CONFIG_SERIAL_8250_NR_UARTS=6
CONFIG_SERIAL_8250_RUNTIME_UARTS=6 CONFIG_SERIAL_8250_RUNTIME_UARTS=6

View File

@@ -35,7 +35,6 @@ CONFIG_NETDEVICES=y
CONFIG_HIX5HD2_GMAC=y CONFIG_HIX5HD2_GMAC=y
CONFIG_HIP04_ETH=y CONFIG_HIP04_ETH=y
CONFIG_SERIAL_8250=y CONFIG_SERIAL_8250=y
CONFIG_SERIAL_8250_DEPRECATED_OPTIONS=y
CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_8250_CONSOLE=y
CONFIG_SERIAL_8250_NR_UARTS=2 CONFIG_SERIAL_8250_NR_UARTS=2
CONFIG_SERIAL_8250_RUNTIME_UARTS=2 CONFIG_SERIAL_8250_RUNTIME_UARTS=2

View File

@@ -90,7 +90,6 @@ CONFIG_KEYBOARD_GPIO_POLLED=y
# CONFIG_UNIX98_PTYS is not set # CONFIG_UNIX98_PTYS is not set
# CONFIG_LEGACY_PTYS is not set # CONFIG_LEGACY_PTYS is not set
CONFIG_SERIAL_8250=y CONFIG_SERIAL_8250=y
# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set
CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_8250_CONSOLE=y
CONFIG_SERIAL_OF_PLATFORM=y CONFIG_SERIAL_OF_PLATFORM=y
CONFIG_SERIAL_NONSTANDARD=y CONFIG_SERIAL_NONSTANDARD=y

View File

@@ -75,7 +75,6 @@ CONFIG_INPUT_DA9063_ONKEY=y
CONFIG_INPUT_ADXL34X=y CONFIG_INPUT_ADXL34X=y
# CONFIG_LEGACY_PTYS is not set # CONFIG_LEGACY_PTYS is not set
CONFIG_SERIAL_8250=y CONFIG_SERIAL_8250=y
# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set
# CONFIG_SERIAL_8250_16550A_VARIANTS is not set # CONFIG_SERIAL_8250_16550A_VARIANTS is not set
CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_8250_CONSOLE=y
# CONFIG_SERIAL_8250_PCI is not set # CONFIG_SERIAL_8250_PCI is not set

View File

@@ -380,7 +380,7 @@
}; };
uart0: serial@1ff40800 { uart0: serial@1ff40800 {
compatible = "ns16550a"; compatible = "loongson,ls2k0500-uart", "ns16550a";
reg = <0x0 0x1ff40800 0x0 0x10>; reg = <0x0 0x1ff40800 0x0 0x10>;
clock-frequency = <100000000>; clock-frequency = <100000000>;
interrupt-parent = <&eiointc>; interrupt-parent = <&eiointc>;

View File

@@ -297,7 +297,7 @@
}; };
uart0: serial@1fe20000 { uart0: serial@1fe20000 {
compatible = "ns16550a"; compatible = "loongson,ls2k1000-uart", "loongson,ls2k0500-uart", "ns16550a";
reg = <0x0 0x1fe20000 0x0 0x10>; reg = <0x0 0x1fe20000 0x0 0x10>;
clock-frequency = <125000000>; clock-frequency = <125000000>;
interrupt-parent = <&liointc0>; interrupt-parent = <&liointc0>;

View File

@@ -250,7 +250,7 @@
}; };
uart0: serial@1fe001e0 { uart0: serial@1fe001e0 {
compatible = "ns16550a"; compatible = "loongson,ls2k2000-uart", "loongson,ls2k1500-uart", "ns16550a";
reg = <0x0 0x1fe001e0 0x0 0x10>; reg = <0x0 0x1fe001e0 0x0 0x10>;
clock-frequency = <100000000>; clock-frequency = <100000000>;
interrupt-parent = <&liointc>; interrupt-parent = <&liointc>;

View File

@@ -51,7 +51,6 @@ CONFIG_B43LEGACY=y
CONFIG_BRCMSMAC=y CONFIG_BRCMSMAC=y
CONFIG_ISDN=y CONFIG_ISDN=y
CONFIG_SERIAL_8250=y CONFIG_SERIAL_8250=y
# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set
CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_8250_CONSOLE=y
# CONFIG_SERIAL_8250_PCI is not set # CONFIG_SERIAL_8250_PCI is not set
CONFIG_SERIAL_8250_NR_UARTS=2 CONFIG_SERIAL_8250_NR_UARTS=2

View File

@@ -119,7 +119,6 @@ CONFIG_INPUT_UINPUT=y
CONFIG_VT=y CONFIG_VT=y
CONFIG_VT_HW_CONSOLE_BINDING=y CONFIG_VT_HW_CONSOLE_BINDING=y
CONFIG_SERIAL_8250=y CONFIG_SERIAL_8250=y
# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set
CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_8250_CONSOLE=y
CONFIG_SERIAL_OF_PLATFORM=y CONFIG_SERIAL_OF_PLATFORM=y
# CONFIG_HW_RANDOM is not set # CONFIG_HW_RANDOM is not set

View File

@@ -52,7 +52,6 @@ CONFIG_INPUT_UINPUT=y
CONFIG_INPUT_PWM_VIBRA=y CONFIG_INPUT_PWM_VIBRA=y
# CONFIG_SERIO is not set # CONFIG_SERIO is not set
CONFIG_SERIAL_8250=y CONFIG_SERIAL_8250=y
# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set
CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_8250_CONSOLE=y
CONFIG_SERIAL_8250_INGENIC=y CONFIG_SERIAL_8250_INGENIC=y
CONFIG_HW_RANDOM=y CONFIG_HW_RANDOM=y

View File

@@ -51,7 +51,6 @@ CONFIG_MARVELL_PHY=y
# CONFIG_SERIO_SERPORT is not set # CONFIG_SERIO_SERPORT is not set
# CONFIG_VT is not set # CONFIG_VT is not set
CONFIG_SERIAL_8250=y CONFIG_SERIAL_8250=y
# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set
CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_8250_CONSOLE=y
CONFIG_SERIAL_OF_PLATFORM=y CONFIG_SERIAL_OF_PLATFORM=y
CONFIG_SERIAL_ALTERA_JTAGUART=y CONFIG_SERIAL_ALTERA_JTAGUART=y

View File

@@ -119,7 +119,6 @@ CONFIG_INPUT_MISC=y
CONFIG_INPUT_UINPUT=m CONFIG_INPUT_UINPUT=m
CONFIG_LEGACY_PTY_COUNT=64 CONFIG_LEGACY_PTY_COUNT=64
CONFIG_SERIAL_8250=y CONFIG_SERIAL_8250=y
# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set
CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_8250_CONSOLE=y
CONFIG_SERIAL_8250_NR_UARTS=8 CONFIG_SERIAL_8250_NR_UARTS=8
CONFIG_SERIAL_8250_EXTENDED=y CONFIG_SERIAL_8250_EXTENDED=y

View File

@@ -158,7 +158,6 @@ CONFIG_SERIO_SERPORT=m
CONFIG_SERIO_RAW=m CONFIG_SERIO_RAW=m
# CONFIG_LEGACY_PTYS is not set # CONFIG_LEGACY_PTYS is not set
CONFIG_SERIAL_8250=y CONFIG_SERIAL_8250=y
# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set
CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_8250_CONSOLE=y
CONFIG_SERIAL_8250_NR_UARTS=8 CONFIG_SERIAL_8250_NR_UARTS=8
CONFIG_SERIAL_8250_RUNTIME_UARTS=8 CONFIG_SERIAL_8250_RUNTIME_UARTS=8

View File

@@ -85,7 +85,6 @@ CONFIG_IBM_EMAC=y
# CONFIG_SERIO is not set # CONFIG_SERIO is not set
# CONFIG_VT is not set # CONFIG_VT is not set
CONFIG_SERIAL_8250=y CONFIG_SERIAL_8250=y
# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set
CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_8250_CONSOLE=y
CONFIG_SERIAL_8250_EXTENDED=y CONFIG_SERIAL_8250_EXTENDED=y
CONFIG_SERIAL_8250_SHARE_IRQ=y CONFIG_SERIAL_8250_SHARE_IRQ=y

View File

@@ -62,7 +62,6 @@ CONFIG_LITEX_LITEETH=y
# CONFIG_VT is not set # CONFIG_VT is not set
# CONFIG_LEGACY_PTYS is not set # CONFIG_LEGACY_PTYS is not set
CONFIG_SERIAL_8250=y CONFIG_SERIAL_8250=y
# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set
CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_8250_CONSOLE=y
CONFIG_SERIAL_OF_PLATFORM=y CONFIG_SERIAL_OF_PLATFORM=y
CONFIG_SERIAL_NONSTANDARD=y CONFIG_SERIAL_NONSTANDARD=y

View File

@@ -48,7 +48,6 @@ CONFIG_VIRTIO_BLK=y
# CONFIG_LEGACY_PTYS is not set # CONFIG_LEGACY_PTYS is not set
# CONFIG_LDISC_AUTOLOAD is not set # CONFIG_LDISC_AUTOLOAD is not set
CONFIG_SERIAL_8250=y CONFIG_SERIAL_8250=y
# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set
CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_8250_CONSOLE=y
CONFIG_SERIAL_8250_NR_UARTS=1 CONFIG_SERIAL_8250_NR_UARTS=1
CONFIG_SERIAL_8250_RUNTIME_UARTS=1 CONFIG_SERIAL_8250_RUNTIME_UARTS=1

View File

@@ -81,7 +81,6 @@ CONFIG_MARVELL_PHY=y
# CONFIG_INPUT_MOUSE is not set # CONFIG_INPUT_MOUSE is not set
# CONFIG_SERIO is not set # CONFIG_SERIO is not set
CONFIG_SERIAL_8250=y CONFIG_SERIAL_8250=y
# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set
CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_8250_CONSOLE=y
CONFIG_SERIAL_OF_PLATFORM=y CONFIG_SERIAL_OF_PLATFORM=y
CONFIG_HW_RANDOM=y CONFIG_HW_RANDOM=y

View File

@@ -79,7 +79,6 @@ CONFIG_MARVELL_PHY=y
# CONFIG_INPUT_MOUSE is not set # CONFIG_INPUT_MOUSE is not set
# CONFIG_SERIO is not set # CONFIG_SERIO is not set
CONFIG_SERIAL_8250=y CONFIG_SERIAL_8250=y
# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set
CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_8250_CONSOLE=y
CONFIG_SERIAL_OF_PLATFORM=y CONFIG_SERIAL_OF_PLATFORM=y
CONFIG_HW_RANDOM=y CONFIG_HW_RANDOM=y

View File

@@ -81,7 +81,6 @@ CONFIG_MARVELL_PHY=y
# CONFIG_INPUT_MOUSE is not set # CONFIG_INPUT_MOUSE is not set
# CONFIG_SERIO is not set # CONFIG_SERIO is not set
CONFIG_SERIAL_8250=y CONFIG_SERIAL_8250=y
# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set
CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_8250_CONSOLE=y
CONFIG_SERIAL_OF_PLATFORM=y CONFIG_SERIAL_OF_PLATFORM=y
CONFIG_HW_RANDOM=y CONFIG_HW_RANDOM=y

View File

@@ -83,7 +83,6 @@ CONFIG_MARVELL_PHY=y
# CONFIG_INPUT_MOUSE is not set # CONFIG_INPUT_MOUSE is not set
# CONFIG_SERIO is not set # CONFIG_SERIO is not set
CONFIG_SERIAL_8250=y CONFIG_SERIAL_8250=y
# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set
CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_8250_CONSOLE=y
CONFIG_SERIAL_OF_PLATFORM=y CONFIG_SERIAL_OF_PLATFORM=y
CONFIG_HW_RANDOM=y CONFIG_HW_RANDOM=y

View File

@@ -72,7 +72,6 @@ CONFIG_MARVELL_PHY=y
# CONFIG_INPUT_MOUSE is not set # CONFIG_INPUT_MOUSE is not set
# CONFIG_SERIO is not set # CONFIG_SERIO is not set
CONFIG_SERIAL_8250=y CONFIG_SERIAL_8250=y
# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set
CONFIG_SERIAL_8250_CONSOLE=y CONFIG_SERIAL_8250_CONSOLE=y
CONFIG_SERIAL_OF_PLATFORM=y CONFIG_SERIAL_OF_PLATFORM=y
# CONFIG_HWMON is not set # CONFIG_HWMON is not set

View File

@@ -487,25 +487,20 @@ static void moxa_wait_finish(void __iomem *ofsAddr)
static void moxafunc(void __iomem *ofsAddr, u16 cmd, u16 arg) static void moxafunc(void __iomem *ofsAddr, u16 cmd, u16 arg)
{ {
unsigned long flags; guard(spinlock_irqsave)(&moxafunc_lock);
spin_lock_irqsave(&moxafunc_lock, flags);
writew(arg, ofsAddr + FuncArg); writew(arg, ofsAddr + FuncArg);
writew(cmd, ofsAddr + FuncCode); writew(cmd, ofsAddr + FuncCode);
moxa_wait_finish(ofsAddr); moxa_wait_finish(ofsAddr);
spin_unlock_irqrestore(&moxafunc_lock, flags);
} }
static int moxafuncret(void __iomem *ofsAddr, u16 cmd, u16 arg) static int moxafuncret(void __iomem *ofsAddr, u16 cmd, u16 arg)
{ {
unsigned long flags; guard(spinlock_irqsave)(&moxafunc_lock);
u16 ret;
spin_lock_irqsave(&moxafunc_lock, flags);
writew(arg, ofsAddr + FuncArg); writew(arg, ofsAddr + FuncArg);
writew(cmd, ofsAddr + FuncCode); writew(cmd, ofsAddr + FuncCode);
moxa_wait_finish(ofsAddr); moxa_wait_finish(ofsAddr);
ret = readw(ofsAddr + FuncArg);
spin_unlock_irqrestore(&moxafunc_lock, flags); return readw(ofsAddr + FuncArg);
return ret;
} }
static void moxa_low_water_check(void __iomem *ofsAddr) static void moxa_low_water_check(void __iomem *ofsAddr)
@@ -1002,11 +997,11 @@ static int moxa_init_board(struct moxa_board_conf *brd, struct device *dev)
if (ret) if (ret)
goto err_free; goto err_free;
spin_lock_bh(&moxa_lock); scoped_guard(spinlock_bh, &moxa_lock) {
brd->ready = 1; brd->ready = 1;
if (!timer_pending(&moxaTimer)) if (!timer_pending(&moxaTimer))
mod_timer(&moxaTimer, jiffies + HZ / 50); mod_timer(&moxaTimer, jiffies + HZ / 50);
spin_unlock_bh(&moxa_lock); }
first_idx = (brd - moxa_boards) * MAX_PORTS_PER_BOARD; first_idx = (brd - moxa_boards) * MAX_PORTS_PER_BOARD;
for (i = 0; i < brd->numPorts; i++) for (i = 0; i < brd->numPorts; i++)
@@ -1026,29 +1021,29 @@ static void moxa_board_deinit(struct moxa_board_conf *brd)
{ {
unsigned int a, opened, first_idx; unsigned int a, opened, first_idx;
mutex_lock(&moxa_openlock); scoped_guard(mutex, &moxa_openlock) {
spin_lock_bh(&moxa_lock); scoped_guard(spinlock_bh, &moxa_lock)
brd->ready = 0; brd->ready = 0;
spin_unlock_bh(&moxa_lock);
/* pci hot-un-plug support */ /* pci hot-un-plug support */
for (a = 0; a < brd->numPorts; a++)
if (tty_port_initialized(&brd->ports[a].port))
tty_port_tty_hangup(&brd->ports[a].port, false);
for (a = 0; a < MAX_PORTS_PER_BOARD; a++)
tty_port_destroy(&brd->ports[a].port);
while (1) {
opened = 0;
for (a = 0; a < brd->numPorts; a++) for (a = 0; a < brd->numPorts; a++)
if (tty_port_initialized(&brd->ports[a].port)) if (tty_port_initialized(&brd->ports[a].port))
opened++; tty_port_tty_hangup(&brd->ports[a].port, false);
mutex_unlock(&moxa_openlock);
if (!opened) for (a = 0; a < MAX_PORTS_PER_BOARD; a++)
break; tty_port_destroy(&brd->ports[a].port);
msleep(50);
mutex_lock(&moxa_openlock); while (1) {
opened = 0;
for (a = 0; a < brd->numPorts; a++)
if (tty_port_initialized(&brd->ports[a].port))
opened++;
if (!opened)
break;
mutex_unlock(&moxa_openlock);
msleep(50);
mutex_lock(&moxa_openlock);
}
} }
first_idx = (brd - moxa_boards) * MAX_PORTS_PER_BOARD; first_idx = (brd - moxa_boards) * MAX_PORTS_PER_BOARD;
@@ -1206,12 +1201,9 @@ static void moxa_shutdown(struct tty_port *port)
static bool moxa_carrier_raised(struct tty_port *port) static bool moxa_carrier_raised(struct tty_port *port)
{ {
struct moxa_port *ch = container_of(port, struct moxa_port, port); struct moxa_port *ch = container_of(port, struct moxa_port, port);
int dcd;
spin_lock_irq(&port->lock); guard(spinlock_irq)(&port->lock);
dcd = ch->DCDState; return ch->DCDState;
spin_unlock_irq(&port->lock);
return dcd;
} }
static void moxa_dtr_rts(struct tty_port *port, bool active) static void moxa_dtr_rts(struct tty_port *port, bool active)
@@ -1225,37 +1217,31 @@ static int moxa_open(struct tty_struct *tty, struct file *filp)
{ {
struct moxa_board_conf *brd; struct moxa_board_conf *brd;
struct moxa_port *ch; struct moxa_port *ch;
int port; int port = tty->index;
port = tty->index; scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &moxa_openlock) {
if (mutex_lock_interruptible(&moxa_openlock)) brd = &moxa_boards[port / MAX_PORTS_PER_BOARD];
return -ERESTARTSYS; if (!brd->ready)
brd = &moxa_boards[port / MAX_PORTS_PER_BOARD]; return -ENODEV;
if (!brd->ready) {
mutex_unlock(&moxa_openlock);
return -ENODEV;
}
if (port % MAX_PORTS_PER_BOARD >= brd->numPorts) { if (port % MAX_PORTS_PER_BOARD >= brd->numPorts)
mutex_unlock(&moxa_openlock); return -ENODEV;
return -ENODEV;
}
ch = &brd->ports[port % MAX_PORTS_PER_BOARD]; ch = &brd->ports[port % MAX_PORTS_PER_BOARD];
ch->port.count++; ch->port.count++;
tty->driver_data = ch; tty->driver_data = ch;
tty_port_tty_set(&ch->port, tty); tty_port_tty_set(&ch->port, tty);
mutex_lock(&ch->port.mutex);
if (!tty_port_initialized(&ch->port)) { guard(mutex)(&ch->port.mutex);
ch->statusflags = 0; if (!tty_port_initialized(&ch->port)) {
moxa_set_tty_param(tty, &tty->termios); ch->statusflags = 0;
MoxaPortLineCtrl(ch, true, true); moxa_set_tty_param(tty, &tty->termios);
MoxaPortEnable(ch); MoxaPortLineCtrl(ch, true, true);
MoxaSetFifo(ch, ch->type == PORT_16550A); MoxaPortEnable(ch);
tty_port_set_initialized(&ch->port, true); MoxaSetFifo(ch, ch->type == PORT_16550A);
tty_port_set_initialized(&ch->port, true);
}
} }
mutex_unlock(&ch->port.mutex);
mutex_unlock(&moxa_openlock);
return tty_port_block_til_ready(&ch->port, tty, filp); return tty_port_block_til_ready(&ch->port, tty, filp);
} }
@@ -1270,15 +1256,13 @@ static void moxa_close(struct tty_struct *tty, struct file *filp)
static ssize_t moxa_write(struct tty_struct *tty, const u8 *buf, size_t count) static ssize_t moxa_write(struct tty_struct *tty, const u8 *buf, size_t count)
{ {
struct moxa_port *ch = tty->driver_data; struct moxa_port *ch = tty->driver_data;
unsigned long flags;
int len; int len;
if (ch == NULL) if (ch == NULL)
return 0; return 0;
spin_lock_irqsave(&moxa_lock, flags); scoped_guard(spinlock_irqsave, &moxa_lock)
len = MoxaPortWriteData(tty, buf, count); len = MoxaPortWriteData(tty, buf, count);
spin_unlock_irqrestore(&moxa_lock, flags);
set_bit(LOWWAIT, &ch->statusflags); set_bit(LOWWAIT, &ch->statusflags);
return len; return len;
@@ -1349,12 +1333,10 @@ static int moxa_tiocmset(struct tty_struct *tty,
bool dtr_active, rts_active; bool dtr_active, rts_active;
struct moxa_port *ch; struct moxa_port *ch;
mutex_lock(&moxa_openlock); guard(mutex)(&moxa_openlock);
ch = tty->driver_data; ch = tty->driver_data;
if (!ch) { if (!ch)
mutex_unlock(&moxa_openlock);
return -EINVAL; return -EINVAL;
}
MoxaPortGetLineOut(ch, &dtr_active, &rts_active); MoxaPortGetLineOut(ch, &dtr_active, &rts_active);
if (set & TIOCM_RTS) if (set & TIOCM_RTS)
@@ -1366,7 +1348,7 @@ static int moxa_tiocmset(struct tty_struct *tty,
if (clear & TIOCM_DTR) if (clear & TIOCM_DTR)
dtr_active = false; dtr_active = false;
MoxaPortLineCtrl(ch, dtr_active, rts_active); MoxaPortLineCtrl(ch, dtr_active, rts_active);
mutex_unlock(&moxa_openlock);
return 0; return 0;
} }
@@ -1415,18 +1397,17 @@ static void moxa_hangup(struct tty_struct *tty)
static void moxa_new_dcdstate(struct moxa_port *p, u8 dcd) static void moxa_new_dcdstate(struct moxa_port *p, u8 dcd)
{ {
unsigned long flags;
dcd = !!dcd; dcd = !!dcd;
spin_lock_irqsave(&p->port.lock, flags); scoped_guard(spinlock_irqsave, &p->port.lock) {
if (dcd != p->DCDState) { if (dcd == p->DCDState)
p->DCDState = dcd; return;
spin_unlock_irqrestore(&p->port.lock, flags);
if (!dcd) p->DCDState = dcd;
tty_port_tty_hangup(&p->port, true);
} }
else
spin_unlock_irqrestore(&p->port.lock, flags); if (!dcd)
tty_port_tty_hangup(&p->port, true);
} }
static int moxa_poll_port(struct moxa_port *p, unsigned int handle, static int moxa_poll_port(struct moxa_port *p, unsigned int handle,
@@ -1494,7 +1475,7 @@ static void moxa_poll(struct timer_list *unused)
u16 __iomem *ip; u16 __iomem *ip;
unsigned int card, port, served = 0; unsigned int card, port, served = 0;
spin_lock(&moxa_lock); guard(spinlock)(&moxa_lock);
for (card = 0; card < MAX_BOARDS; card++) { for (card = 0; card < MAX_BOARDS; card++) {
brd = &moxa_boards[card]; brd = &moxa_boards[card];
if (!brd->ready) if (!brd->ready)
@@ -1525,7 +1506,6 @@ static void moxa_poll(struct timer_list *unused)
if (served) if (served)
mod_timer(&moxaTimer, jiffies + HZ / 50); mod_timer(&moxaTimer, jiffies + HZ / 50);
spin_unlock(&moxa_lock);
} }
/******************************************************************************/ /******************************************************************************/
@@ -1861,13 +1841,11 @@ static int MoxaPortSetTermio(struct moxa_port *port, struct ktermios *termio,
baud = MoxaPortSetBaud(port, baud); baud = MoxaPortSetBaud(port, baud);
if (termio->c_iflag & (IXON | IXOFF | IXANY)) { if (termio->c_iflag & (IXON | IXOFF | IXANY)) {
spin_lock_irq(&moxafunc_lock); guard(spinlock_irq)(&moxafunc_lock);
writeb(termio->c_cc[VSTART], ofsAddr + FuncArg); writeb(termio->c_cc[VSTART], ofsAddr + FuncArg);
writeb(termio->c_cc[VSTOP], ofsAddr + FuncArg1); writeb(termio->c_cc[VSTOP], ofsAddr + FuncArg1);
writeb(FC_SetXonXoff, ofsAddr + FuncCode); writeb(FC_SetXonXoff, ofsAddr + FuncCode);
moxa_wait_finish(ofsAddr); moxa_wait_finish(ofsAddr);
spin_unlock_irq(&moxafunc_lock);
} }
return baud; return baud;
} }
@@ -2098,13 +2076,13 @@ static int moxa_get_serial_info(struct tty_struct *tty,
if (!info) if (!info)
return -ENODEV; return -ENODEV;
mutex_lock(&info->port.mutex); guard(mutex)(&info->port.mutex);
ss->type = info->type; ss->type = info->type;
ss->line = info->port.tty->index; ss->line = info->port.tty->index;
ss->flags = info->port.flags; ss->flags = info->port.flags;
ss->baud_base = 921600; ss->baud_base = 921600;
ss->close_delay = jiffies_to_msecs(info->port.close_delay) / 10; ss->close_delay = jiffies_to_msecs(info->port.close_delay) / 10;
mutex_unlock(&info->port.mutex);
return 0; return 0;
} }
@@ -2120,13 +2098,12 @@ static int moxa_set_serial_info(struct tty_struct *tty,
close_delay = msecs_to_jiffies(ss->close_delay * 10); close_delay = msecs_to_jiffies(ss->close_delay * 10);
mutex_lock(&info->port.mutex); guard(mutex)(&info->port.mutex);
if (!capable(CAP_SYS_ADMIN)) { if (!capable(CAP_SYS_ADMIN)) {
if (close_delay != info->port.close_delay || if (close_delay != info->port.close_delay ||
ss->type != info->type || ss->type != info->type ||
((ss->flags & ~ASYNC_USR_MASK) != ((ss->flags & ~ASYNC_USR_MASK) !=
(info->port.flags & ~ASYNC_USR_MASK))) { (info->port.flags & ~ASYNC_USR_MASK))) {
mutex_unlock(&info->port.mutex);
return -EPERM; return -EPERM;
} }
} else { } else {
@@ -2136,7 +2113,7 @@ static int moxa_set_serial_info(struct tty_struct *tty,
info->type = ss->type; info->type = ss->type;
} }
mutex_unlock(&info->port.mutex);
return 0; return 0;
} }

View File

@@ -4165,7 +4165,7 @@ static int gsm_modem_upd_via_msc(struct gsm_dlci *dlci, u8 brk)
/** /**
* gsm_modem_send_initial_msc - Send initial modem status message * gsm_modem_send_initial_msc - Send initial modem status message
* *
* @dlci channel * @dlci: channel
* *
* Send an initial MSC message after DLCI open to set the initial * Send an initial MSC message after DLCI open to set the initial
* modem status lines. This is only done for basic mode. * modem status lines. This is only done for basic mode.

View File

@@ -263,21 +263,18 @@ static int n_hdlc_tty_open(struct tty_struct *tty)
*/ */
static void n_hdlc_send_frames(struct n_hdlc *n_hdlc, struct tty_struct *tty) static void n_hdlc_send_frames(struct n_hdlc *n_hdlc, struct tty_struct *tty)
{ {
unsigned long flags;
struct n_hdlc_buf *tbuf; struct n_hdlc_buf *tbuf;
ssize_t actual; ssize_t actual;
check_again: check_again:
scoped_guard(spinlock_irqsave, &n_hdlc->tx_buf_list.spinlock) {
spin_lock_irqsave(&n_hdlc->tx_buf_list.spinlock, flags); if (n_hdlc->tbusy) {
if (n_hdlc->tbusy) { n_hdlc->woke_up = true;
n_hdlc->woke_up = true; return;
spin_unlock_irqrestore(&n_hdlc->tx_buf_list.spinlock, flags); }
return; n_hdlc->tbusy = true;
n_hdlc->woke_up = false;
} }
n_hdlc->tbusy = true;
n_hdlc->woke_up = false;
spin_unlock_irqrestore(&n_hdlc->tx_buf_list.spinlock, flags);
tbuf = n_hdlc_buf_get(&n_hdlc->tx_buf_list); tbuf = n_hdlc_buf_get(&n_hdlc->tx_buf_list);
while (tbuf) { while (tbuf) {
@@ -324,9 +321,8 @@ check_again:
clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
/* Clear the re-entry flag */ /* Clear the re-entry flag */
spin_lock_irqsave(&n_hdlc->tx_buf_list.spinlock, flags); scoped_guard(spinlock_irqsave, &n_hdlc->tx_buf_list.spinlock)
n_hdlc->tbusy = false; n_hdlc->tbusy = false;
spin_unlock_irqrestore(&n_hdlc->tx_buf_list.spinlock, flags);
if (n_hdlc->woke_up) if (n_hdlc->woke_up)
goto check_again; goto check_again;
@@ -584,9 +580,7 @@ static int n_hdlc_tty_ioctl(struct tty_struct *tty, unsigned int cmd,
unsigned long arg) unsigned long arg)
{ {
struct n_hdlc *n_hdlc = tty->disc_data; struct n_hdlc *n_hdlc = tty->disc_data;
int error = 0;
int count; int count;
unsigned long flags;
struct n_hdlc_buf *buf = NULL; struct n_hdlc_buf *buf = NULL;
pr_debug("%s() called %d\n", __func__, cmd); pr_debug("%s() called %d\n", __func__, cmd);
@@ -595,29 +589,27 @@ static int n_hdlc_tty_ioctl(struct tty_struct *tty, unsigned int cmd,
case FIONREAD: case FIONREAD:
/* report count of read data available */ /* report count of read data available */
/* in next available frame (if any) */ /* in next available frame (if any) */
spin_lock_irqsave(&n_hdlc->rx_buf_list.spinlock, flags); scoped_guard(spinlock_irqsave, &n_hdlc->rx_buf_list.spinlock) {
buf = list_first_entry_or_null(&n_hdlc->rx_buf_list.list, buf = list_first_entry_or_null(&n_hdlc->rx_buf_list.list,
struct n_hdlc_buf, list_item); struct n_hdlc_buf, list_item);
if (buf) if (buf)
count = buf->count; count = buf->count;
else else
count = 0; count = 0;
spin_unlock_irqrestore(&n_hdlc->rx_buf_list.spinlock, flags); }
error = put_user(count, (int __user *)arg); return put_user(count, (int __user *)arg);
break;
case TIOCOUTQ: case TIOCOUTQ:
/* get the pending tx byte count in the driver */ /* get the pending tx byte count in the driver */
count = tty_chars_in_buffer(tty); count = tty_chars_in_buffer(tty);
/* add size of next output frame in queue */ /* add size of next output frame in queue */
spin_lock_irqsave(&n_hdlc->tx_buf_list.spinlock, flags); scoped_guard(spinlock_irqsave, &n_hdlc->tx_buf_list.spinlock) {
buf = list_first_entry_or_null(&n_hdlc->tx_buf_list.list, buf = list_first_entry_or_null(&n_hdlc->tx_buf_list.list,
struct n_hdlc_buf, list_item); struct n_hdlc_buf, list_item);
if (buf) if (buf)
count += buf->count; count += buf->count;
spin_unlock_irqrestore(&n_hdlc->tx_buf_list.spinlock, flags); }
error = put_user(count, (int __user *)arg); return put_user(count, (int __user *)arg);
break;
case TCFLSH: case TCFLSH:
switch (arg) { switch (arg) {
@@ -628,11 +620,8 @@ static int n_hdlc_tty_ioctl(struct tty_struct *tty, unsigned int cmd,
fallthrough; /* to default */ fallthrough; /* to default */
default: default:
error = n_tty_ioctl_helper(tty, cmd, arg); return n_tty_ioctl_helper(tty, cmd, arg);
break;
} }
return error;
} /* end of n_hdlc_tty_ioctl() */ } /* end of n_hdlc_tty_ioctl() */
/** /**
@@ -726,14 +715,10 @@ static struct n_hdlc *n_hdlc_alloc(void)
static void n_hdlc_buf_return(struct n_hdlc_buf_list *buf_list, static void n_hdlc_buf_return(struct n_hdlc_buf_list *buf_list,
struct n_hdlc_buf *buf) struct n_hdlc_buf *buf)
{ {
unsigned long flags; guard(spinlock_irqsave)(&buf_list->spinlock);
spin_lock_irqsave(&buf_list->spinlock, flags);
list_add(&buf->list_item, &buf_list->list); list_add(&buf->list_item, &buf_list->list);
buf_list->count++; buf_list->count++;
spin_unlock_irqrestore(&buf_list->spinlock, flags);
} }
/** /**
@@ -744,14 +729,10 @@ static void n_hdlc_buf_return(struct n_hdlc_buf_list *buf_list,
static void n_hdlc_buf_put(struct n_hdlc_buf_list *buf_list, static void n_hdlc_buf_put(struct n_hdlc_buf_list *buf_list,
struct n_hdlc_buf *buf) struct n_hdlc_buf *buf)
{ {
unsigned long flags; guard(spinlock_irqsave)(&buf_list->spinlock);
spin_lock_irqsave(&buf_list->spinlock, flags);
list_add_tail(&buf->list_item, &buf_list->list); list_add_tail(&buf->list_item, &buf_list->list);
buf_list->count++; buf_list->count++;
spin_unlock_irqrestore(&buf_list->spinlock, flags);
} /* end of n_hdlc_buf_put() */ } /* end of n_hdlc_buf_put() */
/** /**
@@ -764,10 +745,9 @@ static void n_hdlc_buf_put(struct n_hdlc_buf_list *buf_list,
*/ */
static struct n_hdlc_buf *n_hdlc_buf_get(struct n_hdlc_buf_list *buf_list) static struct n_hdlc_buf *n_hdlc_buf_get(struct n_hdlc_buf_list *buf_list)
{ {
unsigned long flags;
struct n_hdlc_buf *buf; struct n_hdlc_buf *buf;
spin_lock_irqsave(&buf_list->spinlock, flags); guard(spinlock_irqsave)(&buf_list->spinlock);
buf = list_first_entry_or_null(&buf_list->list, buf = list_first_entry_or_null(&buf_list->list,
struct n_hdlc_buf, list_item); struct n_hdlc_buf, list_item);
@@ -776,7 +756,6 @@ static struct n_hdlc_buf *n_hdlc_buf_get(struct n_hdlc_buf_list *buf_list)
buf_list->count--; buf_list->count--;
} }
spin_unlock_irqrestore(&buf_list->spinlock, flags);
return buf; return buf;
} /* end of n_hdlc_buf_get() */ } /* end of n_hdlc_buf_get() */

View File

@@ -324,12 +324,9 @@ static void reset_buffer_flags(struct n_tty_data *ldata)
static void n_tty_packet_mode_flush(struct tty_struct *tty) static void n_tty_packet_mode_flush(struct tty_struct *tty)
{ {
unsigned long flags;
if (tty->link->ctrl.packet) { if (tty->link->ctrl.packet) {
spin_lock_irqsave(&tty->ctrl.lock, flags); scoped_guard(spinlock_irqsave, &tty->ctrl.lock)
tty->ctrl.pktstatus |= TIOCPKT_FLUSHREAD; tty->ctrl.pktstatus |= TIOCPKT_FLUSHREAD;
spin_unlock_irqrestore(&tty->ctrl.lock, flags);
wake_up_interruptible(&tty->link->read_wait); wake_up_interruptible(&tty->link->read_wait);
} }
} }
@@ -349,13 +346,12 @@ static void n_tty_packet_mode_flush(struct tty_struct *tty)
*/ */
static void n_tty_flush_buffer(struct tty_struct *tty) static void n_tty_flush_buffer(struct tty_struct *tty)
{ {
down_write(&tty->termios_rwsem); guard(rwsem_write)(&tty->termios_rwsem);
reset_buffer_flags(tty->disc_data); reset_buffer_flags(tty->disc_data);
n_tty_kick_worker(tty); n_tty_kick_worker(tty);
if (tty->link) if (tty->link)
n_tty_packet_mode_flush(tty); n_tty_packet_mode_flush(tty);
up_write(&tty->termios_rwsem);
} }
/** /**
@@ -737,25 +733,23 @@ static void commit_echoes(struct tty_struct *tty)
size_t nr, old, echoed; size_t nr, old, echoed;
size_t head; size_t head;
mutex_lock(&ldata->output_lock); scoped_guard(mutex, &ldata->output_lock) {
head = ldata->echo_head; head = ldata->echo_head;
ldata->echo_mark = head; ldata->echo_mark = head;
old = ldata->echo_commit - ldata->echo_tail; old = ldata->echo_commit - ldata->echo_tail;
/* Process committed echoes if the accumulated # of bytes /*
* is over the threshold (and try again each time another * Process committed echoes if the accumulated # of bytes is over the threshold
* block is accumulated) */ * (and try again each time another block is accumulated)
nr = head - ldata->echo_tail; */
if (nr < ECHO_COMMIT_WATERMARK || nr = head - ldata->echo_tail;
(nr % ECHO_BLOCK > old % ECHO_BLOCK)) { if (nr < ECHO_COMMIT_WATERMARK || (nr % ECHO_BLOCK > old % ECHO_BLOCK))
mutex_unlock(&ldata->output_lock); return;
return;
ldata->echo_commit = head;
echoed = __process_echoes(tty);
} }
ldata->echo_commit = head;
echoed = __process_echoes(tty);
mutex_unlock(&ldata->output_lock);
if (echoed && tty->ops->flush_chars) if (echoed && tty->ops->flush_chars)
tty->ops->flush_chars(tty); tty->ops->flush_chars(tty);
} }
@@ -768,10 +762,10 @@ static void process_echoes(struct tty_struct *tty)
if (ldata->echo_mark == ldata->echo_tail) if (ldata->echo_mark == ldata->echo_tail)
return; return;
mutex_lock(&ldata->output_lock); scoped_guard(mutex, &ldata->output_lock) {
ldata->echo_commit = ldata->echo_mark; ldata->echo_commit = ldata->echo_mark;
echoed = __process_echoes(tty); echoed = __process_echoes(tty);
mutex_unlock(&ldata->output_lock); }
if (echoed && tty->ops->flush_chars) if (echoed && tty->ops->flush_chars)
tty->ops->flush_chars(tty); tty->ops->flush_chars(tty);
@@ -786,10 +780,9 @@ static void flush_echoes(struct tty_struct *tty)
ldata->echo_commit == ldata->echo_head) ldata->echo_commit == ldata->echo_head)
return; return;
mutex_lock(&ldata->output_lock); guard(mutex)(&ldata->output_lock);
ldata->echo_commit = ldata->echo_head; ldata->echo_commit = ldata->echo_head;
__process_echoes(tty); __process_echoes(tty);
mutex_unlock(&ldata->output_lock);
} }
/** /**
@@ -1078,18 +1071,19 @@ static void isig(int sig, struct tty_struct *tty)
if (L_NOFLSH(tty)) { if (L_NOFLSH(tty)) {
/* signal only */ /* signal only */
__isig(sig, tty); __isig(sig, tty);
return;
}
} else { /* signal and flush */ /* signal and flush */
up_read(&tty->termios_rwsem); up_read(&tty->termios_rwsem);
down_write(&tty->termios_rwsem); scoped_guard(rwsem_write, &tty->termios_rwsem) {
__isig(sig, tty); __isig(sig, tty);
/* clear echo buffer */ /* clear echo buffer */
mutex_lock(&ldata->output_lock); scoped_guard(mutex, &ldata->output_lock) {
ldata->echo_head = ldata->echo_tail = 0; ldata->echo_head = ldata->echo_tail = 0;
ldata->echo_mark = ldata->echo_commit = 0; ldata->echo_mark = ldata->echo_commit = 0;
mutex_unlock(&ldata->output_lock); }
/* clear output buffer */ /* clear output buffer */
tty_driver_flush_buffer(tty); tty_driver_flush_buffer(tty);
@@ -1100,10 +1094,8 @@ static void isig(int sig, struct tty_struct *tty)
/* notify pty master of flush */ /* notify pty master of flush */
if (tty->link) if (tty->link)
n_tty_packet_mode_flush(tty); n_tty_packet_mode_flush(tty);
up_write(&tty->termios_rwsem);
down_read(&tty->termios_rwsem);
} }
down_read(&tty->termios_rwsem);
} }
/** /**
@@ -1683,7 +1675,7 @@ n_tty_receive_buf_common(struct tty_struct *tty, const u8 *cp, const u8 *fp,
size_t n, rcvd = 0; size_t n, rcvd = 0;
int room, overflow; int room, overflow;
down_read(&tty->termios_rwsem); guard(rwsem_read)(&tty->termios_rwsem);
do { do {
/* /*
@@ -1752,8 +1744,6 @@ n_tty_receive_buf_common(struct tty_struct *tty, const u8 *cp, const u8 *fp,
n_tty_kick_worker(tty); n_tty_kick_worker(tty);
} }
up_read(&tty->termios_rwsem);
return rcvd; return rcvd;
} }
@@ -1879,10 +1869,9 @@ static void n_tty_close(struct tty_struct *tty)
if (tty->link) if (tty->link)
n_tty_packet_mode_flush(tty); n_tty_packet_mode_flush(tty);
down_write(&tty->termios_rwsem); guard(rwsem_write)(&tty->termios_rwsem);
vfree(ldata); vfree(ldata);
tty->disc_data = NULL; tty->disc_data = NULL;
up_write(&tty->termios_rwsem);
} }
/** /**
@@ -2247,10 +2236,10 @@ static ssize_t n_tty_read(struct tty_struct *tty, struct file *file, u8 *kbuf,
u8 cs; u8 cs;
if (kb != kbuf) if (kb != kbuf)
break; break;
spin_lock_irq(&tty->link->ctrl.lock); scoped_guard(spinlock_irq, &tty->link->ctrl.lock) {
cs = tty->link->ctrl.pktstatus; cs = tty->link->ctrl.pktstatus;
tty->link->ctrl.pktstatus = 0; tty->link->ctrl.pktstatus = 0;
spin_unlock_irq(&tty->link->ctrl.lock); }
*kb++ = cs; *kb++ = cs;
nr--; nr--;
break; break;
@@ -2357,7 +2346,7 @@ static ssize_t n_tty_write(struct tty_struct *tty, struct file *file,
return retval; return retval;
} }
down_read(&tty->termios_rwsem); guard(rwsem_read)(&tty->termios_rwsem);
/* Write out any echoed characters that are still pending */ /* Write out any echoed characters that are still pending */
process_echoes(tty); process_echoes(tty);
@@ -2395,9 +2384,8 @@ static ssize_t n_tty_write(struct tty_struct *tty, struct file *file,
struct n_tty_data *ldata = tty->disc_data; struct n_tty_data *ldata = tty->disc_data;
while (nr > 0) { while (nr > 0) {
mutex_lock(&ldata->output_lock); scoped_guard(mutex, &ldata->output_lock)
num = tty->ops->write(tty, b, nr); num = tty->ops->write(tty, b, nr);
mutex_unlock(&ldata->output_lock);
if (num < 0) { if (num < 0) {
retval = num; retval = num;
goto break_out; goto break_out;
@@ -2424,7 +2412,7 @@ break_out:
remove_wait_queue(&tty->write_wait, &wait); remove_wait_queue(&tty->write_wait, &wait);
if (nr && tty->fasync) if (nr && tty->fasync)
set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
up_read(&tty->termios_rwsem);
return (b - buf) ? b - buf : retval; return (b - buf) ? b - buf : retval;
} }
@@ -2498,12 +2486,11 @@ static int n_tty_ioctl(struct tty_struct *tty, unsigned int cmd,
case TIOCOUTQ: case TIOCOUTQ:
return put_user(tty_chars_in_buffer(tty), (int __user *) arg); return put_user(tty_chars_in_buffer(tty), (int __user *) arg);
case TIOCINQ: case TIOCINQ:
down_write(&tty->termios_rwsem); scoped_guard(rwsem_write, &tty->termios_rwsem)
if (L_ICANON(tty) && !L_EXTPROC(tty)) if (L_ICANON(tty) && !L_EXTPROC(tty))
num = inq_canon(ldata); num = inq_canon(ldata);
else else
num = read_cnt(ldata); num = read_cnt(ldata);
up_write(&tty->termios_rwsem);
return put_user(num, (unsigned int __user *) arg); return put_user(num, (unsigned int __user *) arg);
default: default:
return n_tty_ioctl_helper(tty, cmd, arg); return n_tty_ioctl_helper(tty, cmd, arg);

View File

@@ -57,9 +57,8 @@ static void pty_close(struct tty_struct *tty, struct file *filp)
set_bit(TTY_IO_ERROR, &tty->flags); set_bit(TTY_IO_ERROR, &tty->flags);
wake_up_interruptible(&tty->read_wait); wake_up_interruptible(&tty->read_wait);
wake_up_interruptible(&tty->write_wait); wake_up_interruptible(&tty->write_wait);
spin_lock_irq(&tty->ctrl.lock); scoped_guard(spinlock_irq, &tty->ctrl.lock)
tty->ctrl.packet = false; tty->ctrl.packet = false;
spin_unlock_irq(&tty->ctrl.lock);
/* Review - krefs on tty_link ?? */ /* Review - krefs on tty_link ?? */
if (!tty->link) if (!tty->link)
return; return;
@@ -70,10 +69,9 @@ static void pty_close(struct tty_struct *tty, struct file *filp)
set_bit(TTY_OTHER_CLOSED, &tty->flags); set_bit(TTY_OTHER_CLOSED, &tty->flags);
#ifdef CONFIG_UNIX98_PTYS #ifdef CONFIG_UNIX98_PTYS
if (tty->driver == ptm_driver) { if (tty->driver == ptm_driver) {
mutex_lock(&devpts_mutex); guard(mutex)(&devpts_mutex);
if (tty->link->driver_data) if (tty->link->driver_data)
devpts_pty_kill(tty->link->driver_data); devpts_pty_kill(tty->link->driver_data);
mutex_unlock(&devpts_mutex);
} }
#endif #endif
tty_vhangup(tty->link); tty_vhangup(tty->link);
@@ -157,21 +155,23 @@ static int pty_get_lock(struct tty_struct *tty, int __user *arg)
/* Set the packet mode on a pty */ /* Set the packet mode on a pty */
static int pty_set_pktmode(struct tty_struct *tty, int __user *arg) static int pty_set_pktmode(struct tty_struct *tty, int __user *arg)
{ {
int pktmode; int want_pktmode;
if (get_user(pktmode, arg)) if (get_user(want_pktmode, arg))
return -EFAULT; return -EFAULT;
spin_lock_irq(&tty->ctrl.lock); guard(spinlock_irq)(&tty->ctrl.lock);
if (pktmode) { if (!want_pktmode) {
if (!tty->ctrl.packet) {
tty->link->ctrl.pktstatus = 0;
smp_mb();
tty->ctrl.packet = true;
}
} else
tty->ctrl.packet = false; tty->ctrl.packet = false;
spin_unlock_irq(&tty->ctrl.lock); return 0;
}
if (tty->ctrl.packet)
return 0;
tty->link->ctrl.pktstatus = 0;
smp_mb();
tty->ctrl.packet = true;
return 0; return 0;
} }
@@ -210,10 +210,9 @@ static void pty_flush_buffer(struct tty_struct *tty)
tty_buffer_flush(to, NULL); tty_buffer_flush(to, NULL);
if (to->ctrl.packet) { if (to->ctrl.packet) {
spin_lock_irq(&tty->ctrl.lock); guard(spinlock_irq)(&tty->ctrl.lock);
tty->ctrl.pktstatus |= TIOCPKT_FLUSHWRITE; tty->ctrl.pktstatus |= TIOCPKT_FLUSHWRITE;
wake_up_interruptible(&to->read_wait); wake_up_interruptible(&to->read_wait);
spin_unlock_irq(&tty->ctrl.lock);
} }
} }
@@ -252,17 +251,17 @@ static void pty_set_termios(struct tty_struct *tty,
STOP_CHAR(tty) == '\023' && STOP_CHAR(tty) == '\023' &&
START_CHAR(tty) == '\021'); START_CHAR(tty) == '\021');
if ((old_flow != new_flow) || extproc) { if ((old_flow != new_flow) || extproc) {
spin_lock_irq(&tty->ctrl.lock); scoped_guard(spinlock_irq, &tty->ctrl.lock) {
if (old_flow != new_flow) { if (old_flow != new_flow) {
tty->ctrl.pktstatus &= ~(TIOCPKT_DOSTOP | TIOCPKT_NOSTOP); tty->ctrl.pktstatus &= ~(TIOCPKT_DOSTOP | TIOCPKT_NOSTOP);
if (new_flow) if (new_flow)
tty->ctrl.pktstatus |= TIOCPKT_DOSTOP; tty->ctrl.pktstatus |= TIOCPKT_DOSTOP;
else else
tty->ctrl.pktstatus |= TIOCPKT_NOSTOP; tty->ctrl.pktstatus |= TIOCPKT_NOSTOP;
}
if (extproc)
tty->ctrl.pktstatus |= TIOCPKT_IOCTL;
} }
if (extproc)
tty->ctrl.pktstatus |= TIOCPKT_IOCTL;
spin_unlock_irq(&tty->ctrl.lock);
wake_up_interruptible(&tty->link->read_wait); wake_up_interruptible(&tty->link->read_wait);
} }
} }
@@ -286,9 +285,9 @@ static int pty_resize(struct tty_struct *tty, struct winsize *ws)
struct tty_struct *pty = tty->link; struct tty_struct *pty = tty->link;
/* For a PTY we need to lock the tty side */ /* For a PTY we need to lock the tty side */
mutex_lock(&tty->winsize_mutex); guard(mutex)(&tty->winsize_mutex);
if (!memcmp(ws, &tty->winsize, sizeof(*ws))) if (!memcmp(ws, &tty->winsize, sizeof(*ws)))
goto done; return 0;
/* Signal the foreground process group of both ptys */ /* Signal the foreground process group of both ptys */
pgrp = tty_get_pgrp(tty); pgrp = tty_get_pgrp(tty);
@@ -304,8 +303,7 @@ static int pty_resize(struct tty_struct *tty, struct winsize *ws)
tty->winsize = *ws; tty->winsize = *ws;
pty->winsize = *ws; /* Never used so will go away soon */ pty->winsize = *ws; /* Never used so will go away soon */
done:
mutex_unlock(&tty->winsize_mutex);
return 0; return 0;
} }
@@ -321,28 +319,26 @@ done:
*/ */
static void pty_start(struct tty_struct *tty) static void pty_start(struct tty_struct *tty)
{ {
unsigned long flags; if (!tty->link || !tty->link->ctrl.packet)
return;
if (tty->link && tty->link->ctrl.packet) { scoped_guard(spinlock_irqsave, &tty->ctrl.lock) {
spin_lock_irqsave(&tty->ctrl.lock, flags);
tty->ctrl.pktstatus &= ~TIOCPKT_STOP; tty->ctrl.pktstatus &= ~TIOCPKT_STOP;
tty->ctrl.pktstatus |= TIOCPKT_START; tty->ctrl.pktstatus |= TIOCPKT_START;
spin_unlock_irqrestore(&tty->ctrl.lock, flags);
wake_up_interruptible_poll(&tty->link->read_wait, EPOLLIN);
} }
wake_up_interruptible_poll(&tty->link->read_wait, EPOLLIN);
} }
static void pty_stop(struct tty_struct *tty) static void pty_stop(struct tty_struct *tty)
{ {
unsigned long flags; if (!tty->link || !tty->link->ctrl.packet)
return;
if (tty->link && tty->link->ctrl.packet) { scoped_guard(spinlock_irqsave, &tty->ctrl.lock) {
spin_lock_irqsave(&tty->ctrl.lock, flags);
tty->ctrl.pktstatus &= ~TIOCPKT_START; tty->ctrl.pktstatus &= ~TIOCPKT_START;
tty->ctrl.pktstatus |= TIOCPKT_STOP; tty->ctrl.pktstatus |= TIOCPKT_STOP;
spin_unlock_irqrestore(&tty->ctrl.lock, flags);
wake_up_interruptible_poll(&tty->link->read_wait, EPOLLIN);
} }
wake_up_interruptible_poll(&tty->link->read_wait, EPOLLIN);
} }
/** /**
@@ -690,15 +686,9 @@ static struct tty_struct *ptm_unix98_lookup(struct tty_driver *driver,
static struct tty_struct *pts_unix98_lookup(struct tty_driver *driver, static struct tty_struct *pts_unix98_lookup(struct tty_driver *driver,
struct file *file, int idx) struct file *file, int idx)
{ {
struct tty_struct *tty; guard(mutex)(&devpts_mutex);
mutex_lock(&devpts_mutex);
tty = devpts_get_priv(file->f_path.dentry);
mutex_unlock(&devpts_mutex);
/* Master must be open before slave */ /* Master must be open before slave */
if (!tty) return devpts_get_priv(file->f_path.dentry) ? : ERR_PTR(-EIO);
return ERR_PTR(-EIO);
return tty;
} }
static int pty_unix98_install(struct tty_driver *driver, struct tty_struct *tty) static int pty_unix98_install(struct tty_driver *driver, struct tty_struct *tty)
@@ -796,20 +786,17 @@ static int ptmx_open(struct inode *inode, struct file *filp)
} }
/* find a device that is not in use. */ /* find a device that is not in use. */
mutex_lock(&devpts_mutex); scoped_guard(mutex, &devpts_mutex)
index = devpts_new_index(fsi); index = devpts_new_index(fsi);
mutex_unlock(&devpts_mutex);
retval = index; retval = index;
if (index < 0) if (index < 0)
goto out_put_fsi; goto out_put_fsi;
mutex_lock(&tty_mutex); /* The tty returned here is locked so we can safely drop the mutex */
tty = tty_init_dev(ptm_driver, index); scoped_guard(mutex, &tty_mutex)
/* The tty returned here is locked so we can safely tty = tty_init_dev(ptm_driver, index);
drop the mutex */
mutex_unlock(&tty_mutex);
retval = PTR_ERR(tty); retval = PTR_ERR(tty);
if (IS_ERR(tty)) if (IS_ERR(tty))

View File

@@ -98,15 +98,6 @@ struct serial8250_config {
extern unsigned int nr_uarts; extern unsigned int nr_uarts;
#ifdef CONFIG_SERIAL_8250_SHARE_IRQ
#define SERIAL8250_SHARE_IRQS 1
#else
#define SERIAL8250_SHARE_IRQS 0
#endif
extern unsigned int share_irqs;
extern unsigned int skip_txen_test;
#define SERIAL8250_PORT_FLAGS(_base, _irq, _flags) \ #define SERIAL8250_PORT_FLAGS(_base, _irq, _flags) \
{ \ { \
.iobase = _base, \ .iobase = _base, \

View File

@@ -52,6 +52,10 @@ struct irq_info {
static DEFINE_HASHTABLE(irq_lists, IRQ_HASH_BITS); static DEFINE_HASHTABLE(irq_lists, IRQ_HASH_BITS);
static DEFINE_MUTEX(hash_mutex); /* Used to walk the hash */ static DEFINE_MUTEX(hash_mutex); /* Used to walk the hash */
static bool skip_txen_test;
module_param(skip_txen_test, bool, 0644);
MODULE_PARM_DESC(skip_txen_test, "Skip checking for the TXEN bug at init time");
/* /*
* This is the serial driver's interrupt routine. * This is the serial driver's interrupt routine.
* *

View File

@@ -361,7 +361,7 @@ static int dw8250_clk_notifier_cb(struct notifier_block *nb,
* deferred event handling complication. * deferred event handling complication.
*/ */
if (event == POST_RATE_CHANGE) { if (event == POST_RATE_CHANGE) {
queue_work(system_unbound_wq, &d->clk_work); queue_work(system_dfl_wq, &d->clk_work);
return NOTIFY_OK; return NOTIFY_OK;
} }
@@ -680,7 +680,7 @@ static int dw8250_probe(struct platform_device *pdev)
err = clk_notifier_register(data->clk, &data->clk_notifier); err = clk_notifier_register(data->clk, &data->clk_notifier);
if (err) if (err)
return dev_err_probe(dev, err, "Failed to set the clock notifier\n"); return dev_err_probe(dev, err, "Failed to set the clock notifier\n");
queue_work(system_unbound_wq, &data->clk_work); queue_work(system_dfl_wq, &data->clk_work);
} }
platform_set_drvdata(pdev, data); platform_set_drvdata(pdev, data);

View File

@@ -505,7 +505,7 @@ static int default_setup(struct exar8250 *priv, struct pci_dev *pcidev,
unsigned char status; unsigned char status;
int err; int err;
err = serial8250_pci_setup_port(pcidev, port, 0, offset, board->reg_shift); err = serial8250_pci_setup_port(pcidev, port, 0, offset, board->reg_shift, priv->virt);
if (err) if (err)
return err; return err;
@@ -833,7 +833,7 @@ static int cti_port_setup_common(struct exar8250 *priv,
port->port.port_id = idx; port->port.port_id = idx;
port->port.uartclk = priv->osc_freq; port->port.uartclk = priv->osc_freq;
ret = serial8250_pci_setup_port(pcidev, port, 0, offset, 0); ret = serial8250_pci_setup_port(pcidev, port, 0, offset, 0, priv->virt);
if (ret) if (ret)
return ret; return ret;

View File

@@ -0,0 +1,280 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2025 KEBA Industrial Automation GmbH
*
* Driver for KEBA UART FPGA IP core
*/
#include <linux/auxiliary_bus.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/misc/keba.h>
#include <linux/module.h>
#include "8250.h"
#define KUART "kuart"
/* flags */
#define KUART_RS485 BIT(0)
#define KUART_USE_CAPABILITY BIT(1)
/* registers */
#define KUART_VERSION 0x0000
#define KUART_REVISION 0x0001
#define KUART_CAPABILITY 0x0002
#define KUART_CONTROL 0x0004
#define KUART_BASE 0x000C
#define KUART_REGSHIFT 2
#define KUART_CLK 1843200
/* mode flags */
enum kuart_mode {
KUART_MODE_NONE = 0,
KUART_MODE_RS485,
KUART_MODE_RS422,
KUART_MODE_RS232
};
/* capability flags */
#define KUART_CAPABILITY_NONE BIT(KUART_MODE_NONE)
#define KUART_CAPABILITY_RS485 BIT(KUART_MODE_RS485)
#define KUART_CAPABILITY_RS422 BIT(KUART_MODE_RS422)
#define KUART_CAPABILITY_RS232 BIT(KUART_MODE_RS232)
#define KUART_CAPABILITY_MASK GENMASK(3, 0)
/* Additional Control Register DTR line configuration */
#define UART_ACR_DTRLC_MASK 0x18
#define UART_ACR_DTRLC_COMPAT 0x00
#define UART_ACR_DTRLC_ENABLE_LOW 0x10
struct kuart {
struct keba_uart_auxdev *auxdev;
void __iomem *base;
unsigned int line;
unsigned int flags;
u8 capability;
enum kuart_mode mode;
};
static void kuart_set_phy_mode(struct kuart *kuart, enum kuart_mode mode)
{
iowrite8(mode, kuart->base + KUART_CONTROL);
}
static void kuart_enhanced_mode(struct uart_8250_port *up, bool enable)
{
u8 lcr, efr;
/* backup LCR register */
lcr = serial_in(up, UART_LCR);
/* enable 650 compatible register set (EFR, ...) */
serial_out(up, UART_LCR, UART_LCR_CONF_MODE_B);
/* enable/disable enhanced mode with indexed control registers */
efr = serial_in(up, UART_EFR);
if (enable)
efr |= UART_EFR_ECB;
else
efr &= ~UART_EFR_ECB;
serial_out(up, UART_EFR, efr);
/* disable 650 compatible register set, restore LCR */
serial_out(up, UART_LCR, lcr);
}
static void kuart_dtr_line_config(struct uart_8250_port *up, u8 dtrlc)
{
u8 acr;
/* set index register to 0 to access ACR register */
serial_out(up, UART_SCR, UART_ACR);
/* set value register to 0x10 writing DTR mode (1,0) */
acr = serial_in(up, UART_LSR);
acr &= ~UART_ACR_DTRLC_MASK;
acr |= dtrlc;
serial_out(up, UART_LSR, acr);
}
static int kuart_rs485_config(struct uart_port *port, struct ktermios *termios,
struct serial_rs485 *rs485)
{
struct uart_8250_port *up = up_to_u8250p(port);
struct kuart *kuart = port->private_data;
enum kuart_mode mode;
u8 dtrlc;
if (rs485->flags & SER_RS485_ENABLED) {
if (rs485->flags & SER_RS485_MODE_RS422)
mode = KUART_MODE_RS422;
else
mode = KUART_MODE_RS485;
} else {
mode = KUART_MODE_RS232;
}
if (mode == kuart->mode)
return 0;
if (kuart->flags & KUART_USE_CAPABILITY) {
/* deactivate physical interface, break before make */
kuart_set_phy_mode(kuart, KUART_MODE_NONE);
}
if (mode == KUART_MODE_RS485) {
/*
* Set DTR line configuration of 95x UART to DTR mode (1,0).
* In this mode the DTR pin drives the active-low enable pin of
* an external RS485 buffer. The DTR pin will be forced low
* whenever the transmitter is not empty, otherwise DTR pin is
* high.
*/
dtrlc = UART_ACR_DTRLC_ENABLE_LOW;
} else {
/*
* Set DTR line configuration of 95x UART to DTR mode (0,0).
* In this mode the DTR pin is compatible with 16C450, 16C550,
* 16C650 and 16c670 (i.e. normal).
*/
dtrlc = UART_ACR_DTRLC_COMPAT;
}
kuart_enhanced_mode(up, true);
kuart_dtr_line_config(up, dtrlc);
kuart_enhanced_mode(up, false);
if (kuart->flags & KUART_USE_CAPABILITY) {
/* activate selected physical interface */
kuart_set_phy_mode(kuart, mode);
}
kuart->mode = mode;
return 0;
}
static int kuart_probe(struct auxiliary_device *auxdev,
const struct auxiliary_device_id *id)
{
struct device *dev = &auxdev->dev;
struct uart_8250_port uart = {};
struct resource res;
struct kuart *kuart;
int retval;
kuart = devm_kzalloc(dev, sizeof(*kuart), GFP_KERNEL);
if (!kuart)
return -ENOMEM;
kuart->auxdev = container_of(auxdev, struct keba_uart_auxdev, auxdev);
kuart->flags = id->driver_data;
auxiliary_set_drvdata(auxdev, kuart);
/*
* map only memory in front of UART registers, UART registers will be
* mapped by serial port
*/
res = kuart->auxdev->io;
res.end = res.start + KUART_BASE - 1;
kuart->base = devm_ioremap_resource(dev, &res);
if (IS_ERR(kuart->base))
return PTR_ERR(kuart->base);
if (kuart->flags & KUART_USE_CAPABILITY) {
/*
* supported modes are read from capability register, at least
* one mode other than none must be supported
*/
kuart->capability = ioread8(kuart->base + KUART_CAPABILITY) &
KUART_CAPABILITY_MASK;
if ((kuart->capability & ~KUART_CAPABILITY_NONE) == 0)
return -EIO;
}
spin_lock_init(&uart.port.lock);
uart.port.dev = dev;
uart.port.mapbase = kuart->auxdev->io.start + KUART_BASE;
uart.port.irq = kuart->auxdev->irq;
uart.port.uartclk = KUART_CLK;
uart.port.private_data = kuart;
/* 8 bit registers are 32 bit aligned => shift register offset */
uart.port.iotype = UPIO_MEM32;
uart.port.regshift = KUART_REGSHIFT;
/*
* UART mixes 16550, 16750 and 16C950 (for RS485) standard => auto
* configuration works best
*/
uart.port.flags = UPF_SKIP_TEST | UPF_BOOT_AUTOCONF | UPF_IOREMAP;
/*
* UART supports RS485, RS422 and RS232 with switching of physical
* interface
*/
uart.port.rs485_config = kuart_rs485_config;
if (kuart->flags & KUART_RS485) {
uart.port.rs485_supported.flags = SER_RS485_ENABLED |
SER_RS485_RTS_ON_SEND;
uart.port.rs485.flags = SER_RS485_ENABLED |
SER_RS485_RTS_ON_SEND;
}
if (kuart->flags & KUART_USE_CAPABILITY) {
/* default mode priority is RS485 > RS422 > RS232 */
if (kuart->capability & KUART_CAPABILITY_RS422) {
uart.port.rs485_supported.flags |= SER_RS485_ENABLED |
SER_RS485_RTS_ON_SEND |
SER_RS485_MODE_RS422;
uart.port.rs485.flags = SER_RS485_ENABLED |
SER_RS485_RTS_ON_SEND |
SER_RS485_MODE_RS422;
}
if (kuart->capability & KUART_CAPABILITY_RS485) {
uart.port.rs485_supported.flags |= SER_RS485_ENABLED |
SER_RS485_RTS_ON_SEND;
uart.port.rs485.flags = SER_RS485_ENABLED |
SER_RS485_RTS_ON_SEND;
}
}
retval = serial8250_register_8250_port(&uart);
if (retval < 0) {
dev_err(&auxdev->dev, "UART registration failed!\n");
return retval;
}
kuart->line = retval;
return 0;
}
static void kuart_remove(struct auxiliary_device *auxdev)
{
struct kuart *kuart = auxiliary_get_drvdata(auxdev);
if (kuart->flags & KUART_USE_CAPABILITY)
kuart_set_phy_mode(kuart, KUART_MODE_NONE);
serial8250_unregister_port(kuart->line);
}
static const struct auxiliary_device_id kuart_devtype_aux[] = {
{ .name = "keba.rs485-uart", .driver_data = KUART_RS485 },
{ .name = "keba.rs232-uart", .driver_data = 0 },
{ .name = "keba.uart", .driver_data = KUART_USE_CAPABILITY },
{}
};
MODULE_DEVICE_TABLE(auxiliary, kuart_devtype_aux);
static struct auxiliary_driver kuart_driver_aux = {
.name = KUART,
.id_table = kuart_devtype_aux,
.probe = kuart_probe,
.remove = kuart_remove,
};
module_auxiliary_driver(kuart_driver_aux);
MODULE_AUTHOR("Gerhard Engleder <eg@keba.com>");
MODULE_DESCRIPTION("KEBA 8250 serial port driver");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,238 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Serial Port driver for Loongson family chips
*
* Copyright (C) 2020-2025 Loongson Technology Corporation Limited
*/
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/clk.h>
#include <linux/console.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/property.h>
#include <linux/math.h>
#include <linux/mod_devicetable.h>
#include <linux/pm.h>
#include <linux/reset.h>
#include "8250.h"
/* Divisor Latch Fraction Register */
#define LOONGSON_UART_DLF 0x2
#define LOONGSON_QUOT_FRAC_MASK GENMASK(7, 0)
#define LOONGSON_QUOT_DIV_MASK GENMASK(15, 8)
struct loongson_uart_ddata {
bool has_frac;
u8 mcr_invert;
u8 msr_invert;
};
static const struct loongson_uart_ddata ls2k0500_uart_data = {
.has_frac = false,
.mcr_invert = UART_MCR_RTS | UART_MCR_DTR,
.msr_invert = UART_MSR_CTS | UART_MSR_DSR,
};
static const struct loongson_uart_ddata ls2k1500_uart_data = {
.has_frac = true,
.mcr_invert = UART_MCR_RTS | UART_MCR_DTR,
.msr_invert = 0,
};
struct loongson_uart_priv {
int line;
struct clk *clk;
struct resource *res;
struct reset_control *rst;
const struct loongson_uart_ddata *ddata;
};
static u8 serial_fixup(struct uart_port *p, unsigned int offset, u8 val)
{
struct loongson_uart_priv *priv = p->private_data;
switch (offset) {
case UART_MCR:
return val ^ priv->ddata->mcr_invert;
case UART_MSR:
return val ^ priv->ddata->msr_invert;
default:
return val;
}
}
static u32 loongson_serial_in(struct uart_port *p, unsigned int offset)
{
u8 val;
val = readb(p->membase + (offset << p->regshift));
return serial_fixup(p, offset, val);
}
static void loongson_serial_out(struct uart_port *p, unsigned int offset, unsigned int value)
{
u8 val;
offset <<= p->regshift;
val = serial_fixup(p, offset, value);
writeb(val, p->membase + offset);
}
static unsigned int loongson_frac_get_divisor(struct uart_port *port, unsigned int baud,
unsigned int *frac)
{
unsigned int quot;
quot = DIV_ROUND_CLOSEST((port->uartclk << 4), baud);
*frac = FIELD_GET(LOONGSON_QUOT_FRAC_MASK, quot);
return FIELD_GET(LOONGSON_QUOT_DIV_MASK, quot);
}
static void loongson_frac_set_divisor(struct uart_port *port, unsigned int baud,
unsigned int quot, unsigned int quot_frac)
{
struct uart_8250_port *up = up_to_u8250p(port);
serial_port_out(port, UART_LCR, up->lcr | UART_LCR_DLAB);
serial_dl_write(up, quot);
serial_port_out(port, LOONGSON_UART_DLF, quot_frac);
}
static int loongson_uart_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct uart_8250_port uart = {};
struct loongson_uart_priv *priv;
struct uart_port *port;
int ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->ddata = device_get_match_data(dev);
port = &uart.port;
spin_lock_init(&port->lock);
port->flags = UPF_SHARE_IRQ | UPF_FIXED_PORT | UPF_FIXED_TYPE | UPF_IOREMAP;
port->iotype = UPIO_MEM;
port->regshift = 0;
port->dev = dev;
port->type = PORT_16550A;
port->private_data = priv;
port->membase = devm_platform_get_and_ioremap_resource(pdev, 0, &priv->res);
if (!port->membase)
return -ENOMEM;
port->mapbase = priv->res->start;
port->mapsize = resource_size(priv->res);
port->serial_in = loongson_serial_in;
port->serial_out = loongson_serial_out;
if (priv->ddata->has_frac) {
port->get_divisor = loongson_frac_get_divisor;
port->set_divisor = loongson_frac_set_divisor;
}
ret = uart_read_port_properties(port);
if (ret)
return ret;
if (!port->uartclk) {
priv->clk = devm_clk_get_enabled(dev, NULL);
if (IS_ERR(priv->clk))
return dev_err_probe(dev, PTR_ERR(priv->clk),
"Unable to determine clock frequency!\n");
port->uartclk = clk_get_rate(priv->clk);
}
priv->rst = devm_reset_control_get_optional_shared(dev, NULL);
if (IS_ERR(priv->rst))
return PTR_ERR(priv->rst);
ret = reset_control_deassert(priv->rst);
if (ret)
return ret;
ret = serial8250_register_8250_port(&uart);
if (ret < 0) {
reset_control_assert(priv->rst);
return ret;
}
priv->line = ret;
platform_set_drvdata(pdev, priv);
return 0;
}
static void loongson_uart_remove(struct platform_device *pdev)
{
struct loongson_uart_priv *priv = platform_get_drvdata(pdev);
serial8250_unregister_port(priv->line);
reset_control_assert(priv->rst);
}
static int loongson_uart_suspend(struct device *dev)
{
struct loongson_uart_priv *priv = dev_get_drvdata(dev);
struct uart_8250_port *up = serial8250_get_port(priv->line);
serial8250_suspend_port(priv->line);
if (!uart_console(&up->port) || console_suspend_enabled)
clk_disable_unprepare(priv->clk);
return 0;
}
static int loongson_uart_resume(struct device *dev)
{
struct loongson_uart_priv *priv = dev_get_drvdata(dev);
struct uart_8250_port *up = serial8250_get_port(priv->line);
int ret;
if (!uart_console(&up->port) || console_suspend_enabled) {
ret = clk_prepare_enable(priv->clk);
if (ret)
return ret;
}
serial8250_resume_port(priv->line);
return 0;
}
static DEFINE_SIMPLE_DEV_PM_OPS(loongson_uart_pm_ops, loongson_uart_suspend,
loongson_uart_resume);
static const struct of_device_id loongson_uart_of_ids[] = {
{ .compatible = "loongson,ls2k0500-uart", .data = &ls2k0500_uart_data },
{ .compatible = "loongson,ls2k1500-uart", .data = &ls2k1500_uart_data },
{ },
};
MODULE_DEVICE_TABLE(of, loongson_uart_of_ids);
static struct platform_driver loongson_uart_driver = {
.probe = loongson_uart_probe,
.remove = loongson_uart_remove,
.driver = {
.name = "loongson-uart",
.pm = pm_ptr(&loongson_uart_pm_ops),
.of_match_table = loongson_uart_of_ids,
},
};
module_platform_driver(loongson_uart_driver);
MODULE_DESCRIPTION("Loongson UART driver");
MODULE_AUTHOR("Loongson Technology Corporation Limited.");
MODULE_LICENSE("GPL");

View File

@@ -95,7 +95,7 @@ static int of_platform_serial_setup(struct platform_device *ofdev,
u32 spd; u32 spd;
int ret; int ret;
memset(port, 0, sizeof *port); memset(port, 0, sizeof(*port));
pm_runtime_enable(&ofdev->dev); pm_runtime_enable(&ofdev->dev);
pm_runtime_get_sync(&ofdev->dev); pm_runtime_get_sync(&ofdev->dev);

View File

@@ -95,6 +95,11 @@
#define PCI_DEVICE_ID_MOXA_CP138E_A 0x1381 #define PCI_DEVICE_ID_MOXA_CP138E_A 0x1381
#define PCI_DEVICE_ID_MOXA_CP168EL_A 0x1683 #define PCI_DEVICE_ID_MOXA_CP168EL_A 0x1683
#define PCI_DEVICE_ID_ADDIDATA_CPCI7500 0x7003
#define PCI_DEVICE_ID_ADDIDATA_CPCI7500_NG 0x7024
#define PCI_DEVICE_ID_ADDIDATA_CPCI7420_NG 0x7025
#define PCI_DEVICE_ID_ADDIDATA_CPCI7300_NG 0x7026
/* Unknown vendors/cards - this should not be in linux/pci_ids.h */ /* Unknown vendors/cards - this should not be in linux/pci_ids.h */
#define PCI_SUBDEVICE_ID_UNKNOWN_0x1584 0x1584 #define PCI_SUBDEVICE_ID_UNKNOWN_0x1584 0x1584
#define PCI_SUBDEVICE_ID_UNKNOWN_0x1588 0x1588 #define PCI_SUBDEVICE_ID_UNKNOWN_0x1588 0x1588
@@ -165,7 +170,15 @@ static int
setup_port(struct serial_private *priv, struct uart_8250_port *port, setup_port(struct serial_private *priv, struct uart_8250_port *port,
u8 bar, unsigned int offset, int regshift) u8 bar, unsigned int offset, int regshift)
{ {
return serial8250_pci_setup_port(priv->dev, port, bar, offset, regshift); void __iomem *iomem = NULL;
if (pci_resource_flags(priv->dev, bar) & IORESOURCE_MEM) {
iomem = pcim_iomap(priv->dev, bar, 0);
if (!iomem)
return -ENOMEM;
}
return serial8250_pci_setup_port(priv->dev, port, bar, offset, regshift, iomem);
} }
/* /*
@@ -5996,6 +6009,38 @@ static const struct pci_device_id serial_pci_tbl[] = {
0, 0,
pbn_ADDIDATA_PCIe_8_3906250 }, pbn_ADDIDATA_PCIe_8_3906250 },
{ PCI_VENDOR_ID_ADDIDATA,
PCI_DEVICE_ID_ADDIDATA_CPCI7500,
PCI_ANY_ID,
PCI_ANY_ID,
0,
0,
pbn_b0_4_115200 },
{ PCI_VENDOR_ID_ADDIDATA,
PCI_DEVICE_ID_ADDIDATA_CPCI7500_NG,
PCI_ANY_ID,
PCI_ANY_ID,
0,
0,
pbn_b0_4_115200 },
{ PCI_VENDOR_ID_ADDIDATA,
PCI_DEVICE_ID_ADDIDATA_CPCI7420_NG,
PCI_ANY_ID,
PCI_ANY_ID,
0,
0,
pbn_b0_2_115200 },
{ PCI_VENDOR_ID_ADDIDATA,
PCI_DEVICE_ID_ADDIDATA_CPCI7300_NG,
PCI_ANY_ID,
PCI_ANY_ID,
0,
0,
pbn_b0_1_115200 },
{ PCI_VENDOR_ID_NETMOS, PCI_DEVICE_ID_NETMOS_9835, { PCI_VENDOR_ID_NETMOS, PCI_DEVICE_ID_NETMOS_9835,
PCI_VENDOR_ID_IBM, 0x0299, PCI_VENDOR_ID_IBM, 0x0299,
0, 0, pbn_b0_bt_2_115200 }, 0, 0, pbn_b0_bt_2_115200 },

View File

@@ -671,7 +671,7 @@ static int pci1xxxx_resume(struct device *dev)
} }
static int pci1xxxx_setup(struct pci_dev *pdev, static int pci1xxxx_setup(struct pci_dev *pdev,
struct uart_8250_port *port, int port_idx, int rev) struct uart_8250_port *port, int port_idx, struct pci1xxxx_8250 *priv)
{ {
int ret; int ret;
@@ -698,12 +698,12 @@ static int pci1xxxx_setup(struct pci_dev *pdev,
* C0 and later revisions support Burst operation. * C0 and later revisions support Burst operation.
* RTS workaround in mctrl is applicable only to B0. * RTS workaround in mctrl is applicable only to B0.
*/ */
if (rev >= 0xC0) if (priv->dev_rev >= 0xC0)
port->port.handle_irq = pci1xxxx_handle_irq; port->port.handle_irq = pci1xxxx_handle_irq;
else if (rev == 0xB0) else if (priv->dev_rev == 0xB0)
port->port.set_mctrl = pci1xxxx_set_mctrl; port->port.set_mctrl = pci1xxxx_set_mctrl;
ret = serial8250_pci_setup_port(pdev, port, 0, PORT_OFFSET * port_idx, 0); ret = serial8250_pci_setup_port(pdev, port, 0, PORT_OFFSET * port_idx, 0, priv->membase);
if (ret < 0) if (ret < 0)
return ret; return ret;
@@ -821,7 +821,7 @@ static int pci1xxxx_serial_probe(struct pci_dev *pdev,
else else
uart.port.irq = pci_irq_vector(pdev, 0); uart.port.irq = pci_irq_vector(pdev, 0);
rc = pci1xxxx_setup(pdev, &uart, port_idx, priv->dev_rev); rc = pci1xxxx_setup(pdev, &uart, port_idx, priv);
if (rc) { if (rc) {
dev_warn(dev, "Failed to setup port %u\n", i); dev_warn(dev, "Failed to setup port %u\n", i);
continue; continue;

View File

@@ -22,19 +22,16 @@ int serial_8250_warn_need_ioport(struct pci_dev *dev)
EXPORT_SYMBOL_NS_GPL(serial_8250_warn_need_ioport, "SERIAL_8250_PCI"); EXPORT_SYMBOL_NS_GPL(serial_8250_warn_need_ioport, "SERIAL_8250_PCI");
int serial8250_pci_setup_port(struct pci_dev *dev, struct uart_8250_port *port, int serial8250_pci_setup_port(struct pci_dev *dev, struct uart_8250_port *port,
u8 bar, unsigned int offset, int regshift) u8 bar, unsigned int offset, int regshift, void __iomem *iomem)
{ {
if (bar >= PCI_STD_NUM_BARS) if (bar >= PCI_STD_NUM_BARS)
return -EINVAL; return -EINVAL;
if (pci_resource_flags(dev, bar) & IORESOURCE_MEM) { if (pci_resource_flags(dev, bar) & IORESOURCE_MEM) {
if (!pcim_iomap(dev, bar, 0) && !pcim_iomap_table(dev))
return -ENOMEM;
port->port.iotype = UPIO_MEM; port->port.iotype = UPIO_MEM;
port->port.iobase = 0; port->port.iobase = 0;
port->port.mapbase = pci_resource_start(dev, bar) + offset; port->port.mapbase = pci_resource_start(dev, bar) + offset;
port->port.membase = pcim_iomap_table(dev)[bar] + offset; port->port.membase = iomem + offset;
port->port.regshift = regshift; port->port.regshift = regshift;
} else if (IS_ENABLED(CONFIG_HAS_IOPORT)) { } else if (IS_ENABLED(CONFIG_HAS_IOPORT)) {
port->port.iotype = UPIO_PORT; port->port.iotype = UPIO_PORT;

View File

@@ -12,6 +12,6 @@ struct pci_dev;
struct uart_8250_port; struct uart_8250_port;
int serial8250_pci_setup_port(struct pci_dev *dev, struct uart_8250_port *port, u8 bar, int serial8250_pci_setup_port(struct pci_dev *dev, struct uart_8250_port *port, u8 bar,
unsigned int offset, int regshift); unsigned int offset, int regshift, void __iomem *iomem);
int serial_8250_warn_need_ioport(struct pci_dev *dev); int serial_8250_warn_need_ioport(struct pci_dev *dev);

View File

@@ -29,10 +29,8 @@
* Configuration: * Configuration:
* share_irqs: Whether we pass IRQF_SHARED to request_irq(). * share_irqs: Whether we pass IRQF_SHARED to request_irq().
* This option is unsafe when used on edge-triggered interrupts. * This option is unsafe when used on edge-triggered interrupts.
* skip_txen_test: Force skip of txen test at init time.
*/ */
unsigned int share_irqs = SERIAL8250_SHARE_IRQS; static bool share_irqs = IS_ENABLED(CONFIG_SERIAL_8250_SHARE_IRQ);
unsigned int skip_txen_test;
unsigned int nr_uarts = CONFIG_SERIAL_8250_RUNTIME_UARTS; unsigned int nr_uarts = CONFIG_SERIAL_8250_RUNTIME_UARTS;
@@ -60,7 +58,7 @@ EXPORT_SYMBOL(serial8250_set_isa_configurator);
static void __init __serial8250_isa_init_ports(void) static void __init __serial8250_isa_init_ports(void)
{ {
int i, irqflag = 0; int i;
if (nr_uarts > UART_NR) if (nr_uarts > UART_NR)
nr_uarts = UART_NR; nr_uarts = UART_NR;
@@ -77,9 +75,6 @@ static void __init __serial8250_isa_init_ports(void)
univ8250_port_ops = *univ8250_port_base_ops; univ8250_port_ops = *univ8250_port_base_ops;
univ8250_rsa_support(&univ8250_port_ops, univ8250_port_base_ops); univ8250_rsa_support(&univ8250_port_ops, univ8250_port_base_ops);
if (share_irqs)
irqflag = IRQF_SHARED;
for (i = 0; i < ARRAY_SIZE(old_serial_port) && i < nr_uarts; i++) { for (i = 0; i < ARRAY_SIZE(old_serial_port) && i < nr_uarts; i++) {
struct uart_8250_port *up = serial8250_get_port(i); struct uart_8250_port *up = serial8250_get_port(i);
struct uart_port *port = &up->port; struct uart_port *port = &up->port;
@@ -94,7 +89,9 @@ static void __init __serial8250_isa_init_ports(void)
port->iotype = old_serial_port[i].io_type; port->iotype = old_serial_port[i].io_type;
port->regshift = old_serial_port[i].iomem_reg_shift; port->regshift = old_serial_port[i].iomem_reg_shift;
port->irqflags |= irqflag; if (share_irqs)
port->irqflags |= IRQF_SHARED;
if (serial8250_isa_config != NULL) if (serial8250_isa_config != NULL)
serial8250_isa_config(i, &up->port, &up->capabilities); serial8250_isa_config(i, &up->port, &up->capabilities);
} }
@@ -157,15 +154,12 @@ static int serial8250_probe_acpi(struct platform_device *pdev)
static int serial8250_probe_platform(struct platform_device *dev, struct plat_serial8250_port *p) static int serial8250_probe_platform(struct platform_device *dev, struct plat_serial8250_port *p)
{ {
int ret, i, irqflag = 0; int ret, i;
struct uart_8250_port *uart __free(kfree) = kzalloc(sizeof(*uart), GFP_KERNEL); struct uart_8250_port *uart __free(kfree) = kzalloc(sizeof(*uart), GFP_KERNEL);
if (!uart) if (!uart)
return -ENOMEM; return -ENOMEM;
if (share_irqs)
irqflag = IRQF_SHARED;
for (i = 0; p && p->flags != 0; p++, i++) { for (i = 0; p && p->flags != 0; p++, i++) {
uart->port.iobase = p->iobase; uart->port.iobase = p->iobase;
uart->port.membase = p->membase; uart->port.membase = p->membase;
@@ -193,7 +187,10 @@ static int serial8250_probe_platform(struct platform_device *dev, struct plat_se
uart->port.get_mctrl = p->get_mctrl; uart->port.get_mctrl = p->get_mctrl;
uart->port.pm = p->pm; uart->port.pm = p->pm;
uart->port.dev = &dev->dev; uart->port.dev = &dev->dev;
uart->port.irqflags |= irqflag;
if (share_irqs)
uart->port.irqflags |= IRQF_SHARED;
ret = serial8250_register_8250_port(uart); ret = serial8250_register_8250_port(uart);
if (ret < 0) { if (ret < 0) {
dev_err(&dev->dev, "unable to register port at index %d " dev_err(&dev->dev, "unable to register port at index %d "
@@ -380,40 +377,10 @@ module_exit(serial8250_exit);
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Generic 8250/16x50 serial platform driver"); MODULE_DESCRIPTION("Generic 8250/16x50 serial platform driver");
module_param_hw(share_irqs, uint, other, 0644); module_param_hw(share_irqs, bool, other, 0644);
MODULE_PARM_DESC(share_irqs, "Share IRQs with other non-8250/16x50 devices (unsafe)"); MODULE_PARM_DESC(share_irqs, "Share IRQs with other non-8250/16x50 devices (unsafe)");
module_param(nr_uarts, uint, 0644); module_param(nr_uarts, uint, 0644);
MODULE_PARM_DESC(nr_uarts, "Maximum number of UARTs supported. (1-" __MODULE_STRING(CONFIG_SERIAL_8250_NR_UARTS) ")"); MODULE_PARM_DESC(nr_uarts, "Maximum number of UARTs supported. (1-" __MODULE_STRING(CONFIG_SERIAL_8250_NR_UARTS) ")");
module_param(skip_txen_test, uint, 0644);
MODULE_PARM_DESC(skip_txen_test, "Skip checking for the TXEN bug at init time");
MODULE_ALIAS_CHARDEV_MAJOR(TTY_MAJOR); MODULE_ALIAS_CHARDEV_MAJOR(TTY_MAJOR);
#ifdef CONFIG_SERIAL_8250_DEPRECATED_OPTIONS
#ifndef MODULE
/*
* This module was renamed to 8250_core in 3.7. Keep the old "8250" name
* working as well for the module options so we don't break people. We
* need to keep the names identical and the convenient macros will happily
* refuse to let us do that by failing the build with redefinition errors
* of global variables. So we stick them inside a dummy function to avoid
* those conflicts. The options still get parsed, and the redefined
* MODULE_PARAM_PREFIX lets us keep the "8250." syntax alive.
*
* This is hacky. I'm sorry.
*/
static void __used s8250_options(void)
{
#undef MODULE_PARAM_PREFIX
#define MODULE_PARAM_PREFIX "8250_core."
module_param_cb(share_irqs, &param_ops_uint, &share_irqs, 0644);
module_param_cb(nr_uarts, &param_ops_uint, &nr_uarts, 0644);
module_param_cb(skip_txen_test, &param_ops_uint, &skip_txen_test, 0644);
}
#else
MODULE_ALIAS("8250_core");
#endif
#endif

View File

@@ -209,27 +209,3 @@ void rsa_reset(struct uart_8250_port *up)
serial_out(up, UART_RSA_FRR, 0); serial_out(up, UART_RSA_FRR, 0);
} }
#ifdef CONFIG_SERIAL_8250_DEPRECATED_OPTIONS
#ifndef MODULE
/*
* Keep the old "8250" name working as well for the module options so we don't
* break people. We need to keep the names identical and the convenient macros
* will happily refuse to let us do that by failing the build with redefinition
* errors of global variables. So we stick them inside a dummy function to
* avoid those conflicts. The options still get parsed, and the redefined
* MODULE_PARAM_PREFIX lets us keep the "8250." syntax alive.
*
* This is hacky. I'm sorry.
*/
static void __used rsa8250_options(void)
{
#undef MODULE_PARAM_PREFIX
#define MODULE_PARAM_PREFIX "8250_core."
__module_param_call(MODULE_PARAM_PREFIX, probe_rsa,
&param_array_ops, .arr = &__param_arr_probe_rsa,
0444, -1, 0);
}
#endif
#endif

View File

@@ -34,23 +34,6 @@ config SERIAL_8250
Most people will say Y or M here, so that they can use serial mice, Most people will say Y or M here, so that they can use serial mice,
modems and similar devices connecting to the standard serial ports. modems and similar devices connecting to the standard serial ports.
config SERIAL_8250_DEPRECATED_OPTIONS
bool "Support 8250_core.* kernel options (DEPRECATED)"
depends on SERIAL_8250
default y
help
In 3.7 we renamed 8250 to 8250_core by mistake, so now we have to
accept kernel parameters in both forms like 8250_core.nr_uarts=4 and
8250.nr_uarts=4. We now renamed the module back to 8250, but if
anybody noticed in 3.7 and changed their userspace we still have to
keep the 8250_core.* options around until they revert the changes
they already did.
If 8250 is built as a module, this adds 8250_core alias instead.
If you did not notice yet and/or you have userspace from pre-3.7, it
is safe (and recommended) to say N here.
config SERIAL_8250_PNP config SERIAL_8250_PNP
bool "8250/16550 PNP device support" if EXPERT bool "8250/16550 PNP device support" if EXPERT
depends on SERIAL_8250 && PNP depends on SERIAL_8250 && PNP
@@ -430,6 +413,19 @@ config SERIAL_8250_IOC3
behind the IOC3 device on those systems. Maximum baud speed is behind the IOC3 device on those systems. Maximum baud speed is
38400bps using this driver. 38400bps using this driver.
config SERIAL_8250_KEBA
tristate "Support for KEBA 8250 UART"
depends on SERIAL_8250
depends on KEBA_CP500
help
Selecting this option will add support for KEBA UARTs. These UARTs
are used for the serial interfaces of KEBA PLCs.
This driver can also be built as a module. If so, the module will
be called 8250_keba.
If unsure, say N.
config SERIAL_8250_RT288X config SERIAL_8250_RT288X
bool "Ralink RT288x/RT305x/RT3662/RT3883 serial port support" bool "Ralink RT288x/RT305x/RT3662/RT3883 serial port support"
depends on SERIAL_8250 depends on SERIAL_8250
@@ -468,6 +464,16 @@ config SERIAL_8250_OMAP_TTYO_FIXUP
not booting kernel because the serial console remains silent in case not booting kernel because the serial console remains silent in case
they forgot to update the command line. they forgot to update the command line.
config SERIAL_8250_LOONGSON
tristate "Loongson 8250 based serial port"
depends on SERIAL_8250
depends on LOONGARCH || COMPILE_TEST
help
If you have a machine based on LoongArch CPU you can enable
its onboard serial ports by enabling this option. The option
is applicable to both devicetree and ACPI, say Y to this option.
If unsure, say N.
config SERIAL_8250_LPC18XX config SERIAL_8250_LPC18XX
tristate "NXP LPC18xx/43xx serial port support" tristate "NXP LPC18xx/43xx serial port support"
depends on SERIAL_8250 && OF && (ARCH_LPC18XX || COMPILE_TEST) depends on SERIAL_8250 && OF && (ARCH_LPC18XX || COMPILE_TEST)

View File

@@ -38,6 +38,8 @@ obj-$(CONFIG_SERIAL_8250_HP300) += 8250_hp300.o
obj-$(CONFIG_SERIAL_8250_HUB6) += 8250_hub6.o obj-$(CONFIG_SERIAL_8250_HUB6) += 8250_hub6.o
obj-$(CONFIG_SERIAL_8250_INGENIC) += 8250_ingenic.o obj-$(CONFIG_SERIAL_8250_INGENIC) += 8250_ingenic.o
obj-$(CONFIG_SERIAL_8250_IOC3) += 8250_ioc3.o obj-$(CONFIG_SERIAL_8250_IOC3) += 8250_ioc3.o
obj-$(CONFIG_SERIAL_8250_KEBA) += 8250_keba.o
obj-$(CONFIG_SERIAL_8250_LOONGSON) += 8250_loongson.o
obj-$(CONFIG_SERIAL_8250_LPC18XX) += 8250_lpc18xx.o obj-$(CONFIG_SERIAL_8250_LPC18XX) += 8250_lpc18xx.o
obj-$(CONFIG_SERIAL_8250_LPSS) += 8250_lpss.o obj-$(CONFIG_SERIAL_8250_LPSS) += 8250_lpss.o
obj-$(CONFIG_SERIAL_8250_MEN_MCB) += 8250_men_mcb.o obj-$(CONFIG_SERIAL_8250_MEN_MCB) += 8250_men_mcb.o

View File

@@ -1044,7 +1044,7 @@ config SERIAL_SCCNXP_CONSOLE
config SERIAL_SC16IS7XX config SERIAL_SC16IS7XX
tristate "NXP SC16IS7xx UART support" tristate "NXP SC16IS7xx UART support"
depends on SPI_MASTER || I2C depends on SPI_MASTER || I2C || COMPILE_TEST
select SERIAL_CORE select SERIAL_CORE
select SERIAL_SC16IS7XX_SPI if SPI_MASTER select SERIAL_SC16IS7XX_SPI if SPI_MASTER
select SERIAL_SC16IS7XX_I2C if I2C select SERIAL_SC16IS7XX_I2C if I2C

View File

@@ -560,6 +560,64 @@ static int ar933x_uart_verify_port(struct uart_port *port,
return 0; return 0;
} }
#ifdef CONFIG_CONSOLE_POLL
static int ar933x_poll_get_char(struct uart_port *port)
{
struct ar933x_uart_port *up =
container_of(port, struct ar933x_uart_port, port);
unsigned int rdata;
unsigned char ch;
u32 imr;
/* Disable all interrupts */
imr = ar933x_uart_read(up, AR933X_UART_INT_EN_REG);
ar933x_uart_write(up, AR933X_UART_INT_EN_REG, 0);
rdata = ar933x_uart_read(up, AR933X_UART_DATA_REG);
if ((rdata & AR933X_UART_DATA_RX_CSR) == 0) {
/* Enable interrupts */
ar933x_uart_write(up, AR933X_UART_INT_EN_REG, imr);
return NO_POLL_CHAR;
}
/* remove the character from the FIFO */
ar933x_uart_write(up, AR933X_UART_DATA_REG,
AR933X_UART_DATA_RX_CSR);
ch = rdata & AR933X_UART_DATA_TX_RX_MASK;
/* Enable interrupts */
ar933x_uart_write(up, AR933X_UART_INT_EN_REG, imr);
return ch;
}
static void ar933x_poll_put_char(struct uart_port *port, unsigned char c)
{
struct ar933x_uart_port *up =
container_of(port, struct ar933x_uart_port, port);
u32 imr;
/* Disable all interrupts */
imr = ar933x_uart_read(up, AR933X_UART_INT_EN_REG);
ar933x_uart_write(up, AR933X_UART_INT_EN_REG, 0);
/* Wait until FIFO is empty */
while (!(ar933x_uart_read(up, AR933X_UART_DATA_REG) & AR933X_UART_DATA_TX_CSR))
cpu_relax();
/* Write a character */
ar933x_uart_putc(up, c);
/* Wait until FIFO is empty */
while (!(ar933x_uart_read(up, AR933X_UART_DATA_REG) & AR933X_UART_DATA_TX_CSR))
cpu_relax();
/* Enable interrupts */
ar933x_uart_write(up, AR933X_UART_INT_EN_REG, imr);
}
#endif
static const struct uart_ops ar933x_uart_ops = { static const struct uart_ops ar933x_uart_ops = {
.tx_empty = ar933x_uart_tx_empty, .tx_empty = ar933x_uart_tx_empty,
.set_mctrl = ar933x_uart_set_mctrl, .set_mctrl = ar933x_uart_set_mctrl,
@@ -576,6 +634,10 @@ static const struct uart_ops ar933x_uart_ops = {
.request_port = ar933x_uart_request_port, .request_port = ar933x_uart_request_port,
.config_port = ar933x_uart_config_port, .config_port = ar933x_uart_config_port,
.verify_port = ar933x_uart_verify_port, .verify_port = ar933x_uart_verify_port,
#ifdef CONFIG_CONSOLE_POLL
.poll_get_char = ar933x_poll_get_char,
.poll_put_char = ar933x_poll_put_char,
#endif
}; };
static int ar933x_config_rs485(struct uart_port *port, struct ktermios *termios, static int ar933x_config_rs485(struct uart_port *port, struct ktermios *termios,

View File

@@ -3087,6 +3087,8 @@ static int lpuart_suspend_noirq(struct device *dev)
static int lpuart_resume_noirq(struct device *dev) static int lpuart_resume_noirq(struct device *dev)
{ {
struct lpuart_port *sport = dev_get_drvdata(dev); struct lpuart_port *sport = dev_get_drvdata(dev);
struct tty_port *port = &sport->port.state->port;
bool wake_active;
u32 stat; u32 stat;
pinctrl_pm_select_default_state(dev); pinctrl_pm_select_default_state(dev);
@@ -3098,6 +3100,12 @@ static int lpuart_resume_noirq(struct device *dev)
if (lpuart_is_32(sport)) { if (lpuart_is_32(sport)) {
stat = lpuart32_read(&sport->port, UARTSTAT); stat = lpuart32_read(&sport->port, UARTSTAT);
lpuart32_write(&sport->port, stat, UARTSTAT); lpuart32_write(&sport->port, stat, UARTSTAT);
/* check whether lpuart wakeup was triggered */
wake_active = stat & (UARTSTAT_RDRF | UARTSTAT_RXEDGIF);
if (wake_active && irqd_is_wakeup_set(irq_get_irq_data(sport->port.irq)))
pm_wakeup_event(tty_port_tty_get(port)->dev, 0);
} }
} }

View File

@@ -1723,6 +1723,7 @@ static int icom_probe(struct pci_dev *dev,
retval = pci_read_config_dword(dev, PCI_COMMAND, &command_reg); retval = pci_read_config_dword(dev, PCI_COMMAND, &command_reg);
if (retval) { if (retval) {
dev_err(&dev->dev, "PCI Config read FAILED\n"); dev_err(&dev->dev, "PCI Config read FAILED\n");
retval = pcibios_err_to_errno(retval);
goto probe_exit0; goto probe_exit0;
} }

View File

@@ -30,7 +30,7 @@
#include <linux/iopoll.h> #include <linux/iopoll.h>
#include <linux/dma-mapping.h> #include <linux/dma-mapping.h>
#include <asm/irq.h> #include <linux/irq.h>
#include <linux/dma/imx-dma.h> #include <linux/dma/imx-dma.h>
#include "serial_mctrl_gpio.h" #include "serial_mctrl_gpio.h"
@@ -2697,16 +2697,32 @@ static void imx_uart_save_context(struct imx_port *sport)
/* called with irq off */ /* called with irq off */
static void imx_uart_enable_wakeup(struct imx_port *sport, bool on) static void imx_uart_enable_wakeup(struct imx_port *sport, bool on)
{ {
u32 ucr3; struct tty_port *port = &sport->port.state->port;
struct device *tty_dev;
bool may_wake = false, wake_active = false;
u32 ucr3, usr1;
scoped_guard(tty_port_tty, port) {
struct tty_struct *tty = scoped_tty();
tty_dev = tty->dev;
may_wake = tty_dev && device_may_wakeup(tty_dev);
}
/* only configure the wake register when device set as wakeup source */
if (!may_wake)
return;
uart_port_lock_irq(&sport->port); uart_port_lock_irq(&sport->port);
usr1 = imx_uart_readl(sport, USR1);
ucr3 = imx_uart_readl(sport, UCR3); ucr3 = imx_uart_readl(sport, UCR3);
if (on) { if (on) {
imx_uart_writel(sport, USR1_AWAKE, USR1); imx_uart_writel(sport, USR1_AWAKE, USR1);
ucr3 |= UCR3_AWAKEN; ucr3 |= UCR3_AWAKEN;
} else { } else {
ucr3 &= ~UCR3_AWAKEN; ucr3 &= ~UCR3_AWAKEN;
wake_active = usr1 & USR1_AWAKE;
} }
imx_uart_writel(sport, ucr3, UCR3); imx_uart_writel(sport, ucr3, UCR3);
@@ -2717,10 +2733,14 @@ static void imx_uart_enable_wakeup(struct imx_port *sport, bool on)
ucr1 |= UCR1_RTSDEN; ucr1 |= UCR1_RTSDEN;
} else { } else {
ucr1 &= ~UCR1_RTSDEN; ucr1 &= ~UCR1_RTSDEN;
wake_active = wake_active || (usr1 & USR1_RTSD);
} }
imx_uart_writel(sport, ucr1, UCR1); imx_uart_writel(sport, ucr1, UCR1);
} }
if (wake_active && irqd_is_wakeup_set(irq_get_irq_data(sport->port.irq)))
pm_wakeup_event(tty_port_tty_get(port)->dev, 0);
uart_port_unlock_irq(&sport->port); uart_port_unlock_irq(&sport->port);
} }

View File

@@ -343,7 +343,7 @@ static int mux_verify_port(struct uart_port *port, struct serial_struct *ser)
} }
/** /**
* mux_drv_poll - Mux poll function. * mux_poll - Mux poll function.
* @unused: Unused variable * @unused: Unused variable
* *
* This function periodically polls the Serial MUX to check for new data. * This function periodically polls the Serial MUX to check for new data.

View File

@@ -14,6 +14,7 @@
#include <linux/irq.h> #include <linux/irq.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/of.h> #include <linux/of.h>
#include <linux/pm_domain.h>
#include <linux/pm_opp.h> #include <linux/pm_opp.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/pm_runtime.h> #include <linux/pm_runtime.h>
@@ -101,10 +102,16 @@
#define DMA_RX_BUF_SIZE 2048 #define DMA_RX_BUF_SIZE 2048
static DEFINE_IDA(port_ida); static DEFINE_IDA(port_ida);
#define DOMAIN_IDX_POWER 0
#define DOMAIN_IDX_PERF 1
struct qcom_geni_device_data { struct qcom_geni_device_data {
bool console; bool console;
enum geni_se_xfer_mode mode; enum geni_se_xfer_mode mode;
struct dev_pm_domain_attach_data pd_data;
int (*resources_init)(struct uart_port *uport);
int (*set_rate)(struct uart_port *uport, unsigned int baud);
int (*power_state)(struct uart_port *uport, bool state);
}; };
struct qcom_geni_private_data { struct qcom_geni_private_data {
@@ -142,6 +149,7 @@ struct qcom_geni_serial_port {
struct qcom_geni_private_data private_data; struct qcom_geni_private_data private_data;
const struct qcom_geni_device_data *dev_data; const struct qcom_geni_device_data *dev_data;
struct dev_pm_domain_list *pd_list;
}; };
static const struct uart_ops qcom_geni_console_pops; static const struct uart_ops qcom_geni_console_pops;
@@ -1299,6 +1307,42 @@ static int geni_serial_set_rate(struct uart_port *uport, unsigned int baud)
return 0; return 0;
} }
static int geni_serial_set_level(struct uart_port *uport, unsigned int baud)
{
struct qcom_geni_serial_port *port = to_dev_port(uport);
struct device *perf_dev = port->pd_list->pd_devs[DOMAIN_IDX_PERF];
/*
* The performance protocol sets UART communication
* speeds by selecting different performance levels
* through the OPP framework.
*
* Supported perf levels for baudrates in firmware are below
* +---------------------+--------------------+
* | Perf level value | Baudrate values |
* +---------------------+--------------------+
* | 300 | 300 |
* | 1200 | 1200 |
* | 2400 | 2400 |
* | 4800 | 4800 |
* | 9600 | 9600 |
* | 19200 | 19200 |
* | 38400 | 38400 |
* | 57600 | 57600 |
* | 115200 | 115200 |
* | 230400 | 230400 |
* | 460800 | 460800 |
* | 921600 | 921600 |
* | 2000000 | 2000000 |
* | 3000000 | 3000000 |
* | 3200000 | 3200000 |
* | 4000000 | 4000000 |
* +---------------------+--------------------+
*/
return dev_pm_opp_set_level(perf_dev, baud);
}
static void qcom_geni_serial_set_termios(struct uart_port *uport, static void qcom_geni_serial_set_termios(struct uart_port *uport,
struct ktermios *termios, struct ktermios *termios,
const struct ktermios *old) const struct ktermios *old)
@@ -1317,7 +1361,7 @@ static void qcom_geni_serial_set_termios(struct uart_port *uport,
/* baud rate */ /* baud rate */
baud = uart_get_baud_rate(uport, termios, old, 300, 8000000); baud = uart_get_baud_rate(uport, termios, old, 300, 8000000);
ret = geni_serial_set_rate(uport, baud); ret = port->dev_data->set_rate(uport, baud);
if (ret) if (ret)
return; return;
@@ -1604,8 +1648,27 @@ static int geni_serial_resources_off(struct uart_port *uport)
return 0; return 0;
} }
static int geni_serial_resource_init(struct qcom_geni_serial_port *port) static int geni_serial_resource_state(struct uart_port *uport, bool power_on)
{ {
return power_on ? geni_serial_resources_on(uport) : geni_serial_resources_off(uport);
}
static int geni_serial_pwr_init(struct uart_port *uport)
{
struct qcom_geni_serial_port *port = to_dev_port(uport);
int ret;
ret = dev_pm_domain_attach_list(port->se.dev,
&port->dev_data->pd_data, &port->pd_list);
if (ret <= 0)
return -EINVAL;
return 0;
}
static int geni_serial_resource_init(struct uart_port *uport)
{
struct qcom_geni_serial_port *port = to_dev_port(uport);
int ret; int ret;
port->se.clk = devm_clk_get(port->se.dev, "se"); port->se.clk = devm_clk_get(port->se.dev, "se");
@@ -1650,10 +1713,10 @@ static void qcom_geni_serial_pm(struct uart_port *uport,
old_state = UART_PM_STATE_OFF; old_state = UART_PM_STATE_OFF;
if (new_state == UART_PM_STATE_ON && old_state == UART_PM_STATE_OFF) if (new_state == UART_PM_STATE_ON && old_state == UART_PM_STATE_OFF)
geni_serial_resources_on(uport); pm_runtime_resume_and_get(uport->dev);
else if (new_state == UART_PM_STATE_OFF && else if (new_state == UART_PM_STATE_OFF &&
old_state == UART_PM_STATE_ON) old_state == UART_PM_STATE_ON)
geni_serial_resources_off(uport); pm_runtime_put_sync(uport->dev);
} }
@@ -1756,13 +1819,16 @@ static int qcom_geni_serial_probe(struct platform_device *pdev)
port->se.dev = &pdev->dev; port->se.dev = &pdev->dev;
port->se.wrapper = dev_get_drvdata(pdev->dev.parent); port->se.wrapper = dev_get_drvdata(pdev->dev.parent);
ret = geni_serial_resource_init(port); ret = port->dev_data->resources_init(uport);
if (ret) if (ret)
return ret; return ret;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) if (!res) {
return -EINVAL; ret = -EINVAL;
goto error;
}
uport->mapbase = res->start; uport->mapbase = res->start;
uport->rs485_config = qcom_geni_rs485_config; uport->rs485_config = qcom_geni_rs485_config;
@@ -1774,19 +1840,26 @@ static int qcom_geni_serial_probe(struct platform_device *pdev)
if (!data->console) { if (!data->console) {
port->rx_buf = devm_kzalloc(uport->dev, port->rx_buf = devm_kzalloc(uport->dev,
DMA_RX_BUF_SIZE, GFP_KERNEL); DMA_RX_BUF_SIZE, GFP_KERNEL);
if (!port->rx_buf) if (!port->rx_buf) {
return -ENOMEM; ret = -ENOMEM;
goto error;
}
} }
port->name = devm_kasprintf(uport->dev, GFP_KERNEL, port->name = devm_kasprintf(uport->dev, GFP_KERNEL,
"qcom_geni_serial_%s%d", "qcom_geni_serial_%s%d",
uart_console(uport) ? "console" : "uart", uport->line); uart_console(uport) ? "console" : "uart", uport->line);
if (!port->name) if (!port->name) {
return -ENOMEM; ret = -ENOMEM;
goto error;
}
irq = platform_get_irq(pdev, 0); irq = platform_get_irq(pdev, 0);
if (irq < 0) if (irq < 0) {
return irq; ret = irq;
goto error;
}
uport->irq = irq; uport->irq = irq;
uport->has_sysrq = IS_ENABLED(CONFIG_SERIAL_QCOM_GENI_CONSOLE); uport->has_sysrq = IS_ENABLED(CONFIG_SERIAL_QCOM_GENI_CONSOLE);
@@ -1808,16 +1881,18 @@ static int qcom_geni_serial_probe(struct platform_device *pdev)
IRQF_TRIGGER_HIGH, port->name, uport); IRQF_TRIGGER_HIGH, port->name, uport);
if (ret) { if (ret) {
dev_err(uport->dev, "Failed to get IRQ ret %d\n", ret); dev_err(uport->dev, "Failed to get IRQ ret %d\n", ret);
return ret; goto error;
} }
ret = uart_get_rs485_mode(uport); ret = uart_get_rs485_mode(uport);
if (ret) if (ret)
return ret; goto error;
devm_pm_runtime_enable(port->se.dev);
ret = uart_add_one_port(drv, uport); ret = uart_add_one_port(drv, uport);
if (ret) if (ret)
return ret; goto error;
if (port->wakeup_irq > 0) { if (port->wakeup_irq > 0) {
device_init_wakeup(&pdev->dev, true); device_init_wakeup(&pdev->dev, true);
@@ -1827,11 +1902,15 @@ static int qcom_geni_serial_probe(struct platform_device *pdev)
device_init_wakeup(&pdev->dev, false); device_init_wakeup(&pdev->dev, false);
ida_free(&port_ida, uport->line); ida_free(&port_ida, uport->line);
uart_remove_one_port(drv, uport); uart_remove_one_port(drv, uport);
return ret; goto error;
} }
} }
return 0; return 0;
error:
dev_pm_domain_detach_list(port->pd_list);
return ret;
} }
static void qcom_geni_serial_remove(struct platform_device *pdev) static void qcom_geni_serial_remove(struct platform_device *pdev)
@@ -1844,6 +1923,31 @@ static void qcom_geni_serial_remove(struct platform_device *pdev)
device_init_wakeup(&pdev->dev, false); device_init_wakeup(&pdev->dev, false);
ida_free(&port_ida, uport->line); ida_free(&port_ida, uport->line);
uart_remove_one_port(drv, &port->uport); uart_remove_one_port(drv, &port->uport);
dev_pm_domain_detach_list(port->pd_list);
}
static int __maybe_unused qcom_geni_serial_runtime_suspend(struct device *dev)
{
struct qcom_geni_serial_port *port = dev_get_drvdata(dev);
struct uart_port *uport = &port->uport;
int ret = 0;
if (port->dev_data->power_state)
ret = port->dev_data->power_state(uport, false);
return ret;
}
static int __maybe_unused qcom_geni_serial_runtime_resume(struct device *dev)
{
struct qcom_geni_serial_port *port = dev_get_drvdata(dev);
struct uart_port *uport = &port->uport;
int ret = 0;
if (port->dev_data->power_state)
ret = port->dev_data->power_state(uport, true);
return ret;
} }
static int qcom_geni_serial_suspend(struct device *dev) static int qcom_geni_serial_suspend(struct device *dev)
@@ -1881,14 +1985,46 @@ static int qcom_geni_serial_resume(struct device *dev)
static const struct qcom_geni_device_data qcom_geni_console_data = { static const struct qcom_geni_device_data qcom_geni_console_data = {
.console = true, .console = true,
.mode = GENI_SE_FIFO, .mode = GENI_SE_FIFO,
.resources_init = geni_serial_resource_init,
.set_rate = geni_serial_set_rate,
.power_state = geni_serial_resource_state,
}; };
static const struct qcom_geni_device_data qcom_geni_uart_data = { static const struct qcom_geni_device_data qcom_geni_uart_data = {
.console = false, .console = false,
.mode = GENI_SE_DMA, .mode = GENI_SE_DMA,
.resources_init = geni_serial_resource_init,
.set_rate = geni_serial_set_rate,
.power_state = geni_serial_resource_state,
};
static const struct qcom_geni_device_data sa8255p_qcom_geni_console_data = {
.console = true,
.mode = GENI_SE_FIFO,
.pd_data = {
.pd_flags = PD_FLAG_DEV_LINK_ON,
.pd_names = (const char*[]) { "power", "perf" },
.num_pd_names = 2,
},
.resources_init = geni_serial_pwr_init,
.set_rate = geni_serial_set_level,
};
static const struct qcom_geni_device_data sa8255p_qcom_geni_uart_data = {
.console = false,
.mode = GENI_SE_DMA,
.pd_data = {
.pd_flags = PD_FLAG_DEV_LINK_ON,
.pd_names = (const char*[]) { "power", "perf" },
.num_pd_names = 2,
},
.resources_init = geni_serial_pwr_init,
.set_rate = geni_serial_set_level,
}; };
static const struct dev_pm_ops qcom_geni_serial_pm_ops = { static const struct dev_pm_ops qcom_geni_serial_pm_ops = {
SET_RUNTIME_PM_OPS(qcom_geni_serial_runtime_suspend,
qcom_geni_serial_runtime_resume, NULL)
SYSTEM_SLEEP_PM_OPS(qcom_geni_serial_suspend, qcom_geni_serial_resume) SYSTEM_SLEEP_PM_OPS(qcom_geni_serial_suspend, qcom_geni_serial_resume)
}; };
@@ -1897,10 +2033,18 @@ static const struct of_device_id qcom_geni_serial_match_table[] = {
.compatible = "qcom,geni-debug-uart", .compatible = "qcom,geni-debug-uart",
.data = &qcom_geni_console_data, .data = &qcom_geni_console_data,
}, },
{
.compatible = "qcom,sa8255p-geni-debug-uart",
.data = &sa8255p_qcom_geni_console_data,
},
{ {
.compatible = "qcom,geni-uart", .compatible = "qcom,geni-uart",
.data = &qcom_geni_uart_data, .data = &qcom_geni_uart_data,
}, },
{
.compatible = "qcom,sa8255p-geni-uart",
.data = &sa8255p_qcom_geni_uart_data,
},
{} {}
}; };
MODULE_DEVICE_TABLE(of, qcom_geni_serial_match_table); MODULE_DEVICE_TABLE(of, qcom_geni_serial_match_table);

View File

@@ -2830,6 +2830,8 @@ OF_EARLYCON_DECLARE(exynos4210, "samsung,exynos4210-uart",
s5pv210_early_console_setup); s5pv210_early_console_setup);
OF_EARLYCON_DECLARE(artpec8, "axis,artpec8-uart", OF_EARLYCON_DECLARE(artpec8, "axis,artpec8-uart",
s5pv210_early_console_setup); s5pv210_early_console_setup);
OF_EARLYCON_DECLARE(exynos850, "samsung,exynos850-uart",
s5pv210_early_console_setup);
static int __init gs101_early_console_setup(struct earlycon_device *device, static int __init gs101_early_console_setup(struct earlycon_device *device,
const char *opt) const char *opt)

View File

@@ -11,6 +11,7 @@
#define DEFAULT_SYMBOL_NAMESPACE "SERIAL_NXP_SC16IS7XX" #define DEFAULT_SYMBOL_NAMESPACE "SERIAL_NXP_SC16IS7XX"
#include <linux/bits.h> #include <linux/bits.h>
#include <linux/cleanup.h>
#include <linux/clk.h> #include <linux/clk.h>
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/device.h> #include <linux/device.h>
@@ -49,18 +50,10 @@
#define SC16IS7XX_SPR_REG (0x07) /* Scratch Pad */ #define SC16IS7XX_SPR_REG (0x07) /* Scratch Pad */
#define SC16IS7XX_TXLVL_REG (0x08) /* TX FIFO level */ #define SC16IS7XX_TXLVL_REG (0x08) /* TX FIFO level */
#define SC16IS7XX_RXLVL_REG (0x09) /* RX FIFO level */ #define SC16IS7XX_RXLVL_REG (0x09) /* RX FIFO level */
#define SC16IS7XX_IODIR_REG (0x0a) /* I/O Direction #define SC16IS7XX_IODIR_REG (0x0a) /* I/O Direction - only on 75x/76x */
* - only on 75x/76x #define SC16IS7XX_IOSTATE_REG (0x0b) /* I/O State - only on 75x/76x */
*/ #define SC16IS7XX_IOINTENA_REG (0x0c) /* I/O Interrupt Enable - only on 75x/76x */
#define SC16IS7XX_IOSTATE_REG (0x0b) /* I/O State #define SC16IS7XX_IOCONTROL_REG (0x0e) /* I/O Control - only on 75x/76x */
* - only on 75x/76x
*/
#define SC16IS7XX_IOINTENA_REG (0x0c) /* I/O Interrupt Enable
* - only on 75x/76x
*/
#define SC16IS7XX_IOCONTROL_REG (0x0e) /* I/O Control
* - only on 75x/76x
*/
#define SC16IS7XX_EFCR_REG (0x0f) /* Extra Features Control */ #define SC16IS7XX_EFCR_REG (0x0f) /* Extra Features Control */
/* TCR/TLR Register set: Only if ((MCR[2] == 1) && (EFR[4] == 1)) */ /* TCR/TLR Register set: Only if ((MCR[2] == 1) && (EFR[4] == 1)) */
@@ -80,12 +73,9 @@
/* IER register bits */ /* IER register bits */
#define SC16IS7XX_IER_RDI_BIT BIT(0) /* Enable RX data interrupt */ #define SC16IS7XX_IER_RDI_BIT BIT(0) /* Enable RX data interrupt */
#define SC16IS7XX_IER_THRI_BIT BIT(1) /* Enable TX holding register #define SC16IS7XX_IER_THRI_BIT BIT(1) /* Enable TX holding register interrupt */
* interrupt */ #define SC16IS7XX_IER_RLSI_BIT BIT(2) /* Enable RX line status interrupt */
#define SC16IS7XX_IER_RLSI_BIT BIT(2) /* Enable RX line status #define SC16IS7XX_IER_MSI_BIT BIT(3) /* Enable Modem status interrupt */
* interrupt */
#define SC16IS7XX_IER_MSI_BIT BIT(3) /* Enable Modem status
* interrupt */
/* IER register bits - write only if (EFR[4] == 1) */ /* IER register bits - write only if (EFR[4] == 1) */
#define SC16IS7XX_IER_SLEEP_BIT BIT(4) /* Enable Sleep mode */ #define SC16IS7XX_IER_SLEEP_BIT BIT(4) /* Enable Sleep mode */
@@ -118,9 +108,8 @@
* - only on 75x/76x * - only on 75x/76x
*/ */
#define SC16IS7XX_IIR_XOFFI_SRC 0x10 /* Received Xoff */ #define SC16IS7XX_IIR_XOFFI_SRC 0x10 /* Received Xoff */
#define SC16IS7XX_IIR_CTSRTS_SRC 0x20 /* nCTS,nRTS change of state #define SC16IS7XX_IIR_CTSRTS_SRC 0x20 /* nCTS,nRTS change of state from active
* from active (LOW) * (LOW) to inactive (HIGH)
* to inactive (HIGH)
*/ */
/* LCR register bits */ /* LCR register bits */
#define SC16IS7XX_LCR_LENGTH0_BIT BIT(0) /* Word length bit 0 */ #define SC16IS7XX_LCR_LENGTH0_BIT BIT(0) /* Word length bit 0 */
@@ -136,8 +125,7 @@
* *
* STOP length bit table: * STOP length bit table:
* 0 -> 1 stop bit * 0 -> 1 stop bit
* 1 -> 1-1.5 stop bits if * 1 -> 1-1.5 stop bits if word length is 5,
* word length is 5,
* 2 stop bits otherwise * 2 stop bits otherwise
*/ */
#define SC16IS7XX_LCR_PARITY_BIT BIT(3) /* Parity bit enable */ #define SC16IS7XX_LCR_PARITY_BIT BIT(3) /* Parity bit enable */
@@ -149,29 +137,22 @@
#define SC16IS7XX_LCR_WORD_LEN_6 (0x01) #define SC16IS7XX_LCR_WORD_LEN_6 (0x01)
#define SC16IS7XX_LCR_WORD_LEN_7 (0x02) #define SC16IS7XX_LCR_WORD_LEN_7 (0x02)
#define SC16IS7XX_LCR_WORD_LEN_8 (0x03) #define SC16IS7XX_LCR_WORD_LEN_8 (0x03)
#define SC16IS7XX_LCR_CONF_MODE_A SC16IS7XX_LCR_DLAB_BIT /* Special #define SC16IS7XX_LCR_REG_SET_SPECIAL SC16IS7XX_LCR_DLAB_BIT /* Special reg set */
* reg set */ #define SC16IS7XX_LCR_REG_SET_ENHANCED 0xBF /* Enhanced reg set */
#define SC16IS7XX_LCR_CONF_MODE_B 0xBF /* Enhanced
* reg set */
/* MCR register bits */ /* MCR register bits */
#define SC16IS7XX_MCR_DTR_BIT BIT(0) /* DTR complement #define SC16IS7XX_MCR_DTR_BIT BIT(0) /* DTR complement - only on 75x/76x */
* - only on 75x/76x
*/
#define SC16IS7XX_MCR_RTS_BIT BIT(1) /* RTS complement */ #define SC16IS7XX_MCR_RTS_BIT BIT(1) /* RTS complement */
#define SC16IS7XX_MCR_TCRTLR_BIT BIT(2) /* TCR/TLR register enable */ #define SC16IS7XX_MCR_TCRTLR_BIT BIT(2) /* TCR/TLR registers enable */
#define SC16IS7XX_MCR_LOOP_BIT BIT(4) /* Enable loopback test mode */ #define SC16IS7XX_MCR_LOOP_BIT BIT(4) /* Enable loopback test mode */
#define SC16IS7XX_MCR_XONANY_BIT BIT(5) /* Enable Xon Any #define SC16IS7XX_MCR_XONANY_BIT BIT(5) /* Enable Xon Any
* - write enabled * - write enabled if (EFR[4] == 1)
* if (EFR[4] == 1)
*/ */
#define SC16IS7XX_MCR_IRDA_BIT BIT(6) /* Enable IrDA mode #define SC16IS7XX_MCR_IRDA_BIT BIT(6) /* Enable IrDA mode
* - write enabled * - write enabled if (EFR[4] == 1)
* if (EFR[4] == 1)
*/ */
#define SC16IS7XX_MCR_CLKSEL_BIT BIT(7) /* Divide clock by 4 #define SC16IS7XX_MCR_CLKSEL_BIT BIT(7) /* Divide clock by 4
* - write enabled * - write enabled if (EFR[4] == 1)
* if (EFR[4] == 1)
*/ */
/* LSR register bits */ /* LSR register bits */
@@ -192,28 +173,19 @@
/* MSR register bits */ /* MSR register bits */
#define SC16IS7XX_MSR_DCTS_BIT BIT(0) /* Delta CTS Clear To Send */ #define SC16IS7XX_MSR_DCTS_BIT BIT(0) /* Delta CTS Clear To Send */
#define SC16IS7XX_MSR_DDSR_BIT BIT(1) /* Delta DSR Data Set Ready #define SC16IS7XX_MSR_DDSR_BIT BIT(1) /* Delta DSR Data Set Ready or (IO4)
* or (IO4)
* - only on 75x/76x * - only on 75x/76x
*/ */
#define SC16IS7XX_MSR_DRI_BIT BIT(2) /* Delta RI Ring Indicator #define SC16IS7XX_MSR_DRI_BIT BIT(2) /* Delta RI Ring Indicator or (IO7)
* or (IO7)
* - only on 75x/76x * - only on 75x/76x
*/ */
#define SC16IS7XX_MSR_DCD_BIT BIT(3) /* Delta CD Carrier Detect #define SC16IS7XX_MSR_DCD_BIT BIT(3) /* Delta CD Carrier Detect or (IO6)
* or (IO6)
* - only on 75x/76x * - only on 75x/76x
*/ */
#define SC16IS7XX_MSR_CTS_BIT BIT(4) /* CTS */ #define SC16IS7XX_MSR_CTS_BIT BIT(4) /* CTS */
#define SC16IS7XX_MSR_DSR_BIT BIT(5) /* DSR (IO4) #define SC16IS7XX_MSR_DSR_BIT BIT(5) /* DSR (IO4) - only on 75x/76x */
* - only on 75x/76x #define SC16IS7XX_MSR_RI_BIT BIT(6) /* RI (IO7) - only on 75x/76x */
*/ #define SC16IS7XX_MSR_CD_BIT BIT(7) /* CD (IO6) - only on 75x/76x */
#define SC16IS7XX_MSR_RI_BIT BIT(6) /* RI (IO7)
* - only on 75x/76x
*/
#define SC16IS7XX_MSR_CD_BIT BIT(7) /* CD (IO6)
* - only on 75x/76x
*/
/* /*
* TCR register bits * TCR register bits
@@ -252,54 +224,42 @@
#define SC16IS7XX_IOCONTROL_SRESET_BIT BIT(3) /* Software Reset */ #define SC16IS7XX_IOCONTROL_SRESET_BIT BIT(3) /* Software Reset */
/* EFCR register bits */ /* EFCR register bits */
#define SC16IS7XX_EFCR_9BIT_MODE_BIT BIT(0) /* Enable 9-bit or Multidrop #define SC16IS7XX_EFCR_9BIT_MODE_BIT BIT(0) /* Enable 9-bit or Multidrop mode (RS485) */
* mode (RS485) */
#define SC16IS7XX_EFCR_RXDISABLE_BIT BIT(1) /* Disable receiver */ #define SC16IS7XX_EFCR_RXDISABLE_BIT BIT(1) /* Disable receiver */
#define SC16IS7XX_EFCR_TXDISABLE_BIT BIT(2) /* Disable transmitter */ #define SC16IS7XX_EFCR_TXDISABLE_BIT BIT(2) /* Disable transmitter */
#define SC16IS7XX_EFCR_AUTO_RS485_BIT BIT(4) /* Auto RS485 RTS direction */ #define SC16IS7XX_EFCR_AUTO_RS485_BIT BIT(4) /* Auto RS485 RTS direction */
#define SC16IS7XX_EFCR_RTS_INVERT_BIT BIT(5) /* RTS output inversion */ #define SC16IS7XX_EFCR_RTS_INVERT_BIT BIT(5) /* RTS output inversion */
#define SC16IS7XX_EFCR_IRDA_MODE_BIT BIT(7) /* IrDA mode #define SC16IS7XX_EFCR_IRDA_MODE_BIT BIT(7) /* IrDA mode
* 0 = rate upto 115.2 kbit/s * 0 = rate up to 115.2 kbit/s - Only 75x/76x
* - Only 75x/76x * 1 = rate up to 1.152 Mbit/s - Only 76x
* 1 = rate upto 1.152 Mbit/s
* - Only 76x
*/ */
/* EFR register bits */ /* EFR register bits */
#define SC16IS7XX_EFR_AUTORTS_BIT BIT(6) /* Auto RTS flow ctrl enable */ #define SC16IS7XX_EFR_AUTORTS_BIT BIT(6) /* Auto RTS flow ctrl enable */
#define SC16IS7XX_EFR_AUTOCTS_BIT BIT(7) /* Auto CTS flow ctrl enable */ #define SC16IS7XX_EFR_AUTOCTS_BIT BIT(7) /* Auto CTS flow ctrl enable */
#define SC16IS7XX_EFR_XOFF2_DETECT_BIT BIT(5) /* Enable Xoff2 detection */ #define SC16IS7XX_EFR_XOFF2_DETECT_BIT BIT(5) /* Enable Xoff2 detection */
#define SC16IS7XX_EFR_ENABLE_BIT BIT(4) /* Enable enhanced functions #define SC16IS7XX_EFR_ENABLE_BIT BIT(4) /* Enable enhanced functions and writing to
* and writing to IER[7:4], * IER[7:4], FCR[5:4], MCR[7:5]
* FCR[5:4], MCR[7:5]
*/ */
#define SC16IS7XX_EFR_SWFLOW3_BIT BIT(3) #define SC16IS7XX_EFR_SWFLOW3_BIT BIT(3)
#define SC16IS7XX_EFR_SWFLOW2_BIT BIT(2) #define SC16IS7XX_EFR_SWFLOW2_BIT BIT(2)
/* /*
* SWFLOW bits 3 & 2 table: * SWFLOW bits 3 & 2 table:
* 00 -> no transmitter flow * 00 -> no transmitter flow control
* control * 01 -> transmitter generates XON2 and XOFF2
* 01 -> transmitter generates * 10 -> transmitter generates XON1 and XOFF1
* XON2 and XOFF2 * 11 -> transmitter generates XON1, XON2,
* 10 -> transmitter generates * XOFF1 and XOFF2
* XON1 and XOFF1
* 11 -> transmitter generates
* XON1, XON2, XOFF1 and
* XOFF2
*/ */
#define SC16IS7XX_EFR_SWFLOW1_BIT BIT(1) #define SC16IS7XX_EFR_SWFLOW1_BIT BIT(1)
#define SC16IS7XX_EFR_SWFLOW0_BIT BIT(0) #define SC16IS7XX_EFR_SWFLOW0_BIT BIT(0)
/* /*
* SWFLOW bits 1 & 0 table: * SWFLOW bits 1 & 0 table:
* 00 -> no received flow * 00 -> no received flow control
* control * 01 -> receiver compares XON2 and XOFF2
* 01 -> receiver compares * 10 -> receiver compares XON1 and XOFF1
* XON2 and XOFF2 * 11 -> receiver compares XON1, XON2,
* 10 -> receiver compares * XOFF1 and XOFF2
* XON1 and XOFF1
* 11 -> receiver compares
* XON1, XON2, XOFF1 and
* XOFF2
*/ */
#define SC16IS7XX_EFR_FLOWCTRL_BITS (SC16IS7XX_EFR_AUTORTS_BIT | \ #define SC16IS7XX_EFR_FLOWCTRL_BITS (SC16IS7XX_EFR_AUTORTS_BIT | \
SC16IS7XX_EFR_AUTOCTS_BIT | \ SC16IS7XX_EFR_AUTOCTS_BIT | \
@@ -328,7 +288,7 @@ struct sc16is7xx_one_config {
struct sc16is7xx_one { struct sc16is7xx_one {
struct uart_port port; struct uart_port port;
struct regmap *regmap; struct regmap *regmap;
struct mutex efr_lock; /* EFR registers access */ struct mutex lock; /* For registers sharing same address space. */
struct kthread_work tx_work; struct kthread_work tx_work;
struct kthread_work reg_work; struct kthread_work reg_work;
struct kthread_delayed_work ms_work; struct kthread_delayed_work ms_work;
@@ -358,16 +318,16 @@ static DEFINE_IDA(sc16is7xx_lines);
static struct uart_driver sc16is7xx_uart = { static struct uart_driver sc16is7xx_uart = {
.owner = THIS_MODULE, .owner = THIS_MODULE,
.driver_name = SC16IS7XX_NAME, .driver_name = KBUILD_MODNAME,
.dev_name = "ttySC", .dev_name = "ttySC",
.nr = SC16IS7XX_MAX_DEVS, .nr = SC16IS7XX_MAX_DEVS,
}; };
#define to_sc16is7xx_one(p,e) ((container_of((p), struct sc16is7xx_one, e))) #define to_sc16is7xx_one(p) container_of((p), struct sc16is7xx_one, port)
static u8 sc16is7xx_port_read(struct uart_port *port, u8 reg) static u8 sc16is7xx_port_read(struct uart_port *port, u8 reg)
{ {
struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); struct sc16is7xx_one *one = to_sc16is7xx_one(port);
unsigned int val = 0; unsigned int val = 0;
regmap_read(one->regmap, reg, &val); regmap_read(one->regmap, reg, &val);
@@ -377,21 +337,21 @@ static u8 sc16is7xx_port_read(struct uart_port *port, u8 reg)
static void sc16is7xx_port_write(struct uart_port *port, u8 reg, u8 val) static void sc16is7xx_port_write(struct uart_port *port, u8 reg, u8 val)
{ {
struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); struct sc16is7xx_one *one = to_sc16is7xx_one(port);
regmap_write(one->regmap, reg, val); regmap_write(one->regmap, reg, val);
} }
static void sc16is7xx_fifo_read(struct uart_port *port, u8 *rxbuf, unsigned int rxlen) static void sc16is7xx_fifo_read(struct uart_port *port, u8 *rxbuf, unsigned int rxlen)
{ {
struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); struct sc16is7xx_one *one = to_sc16is7xx_one(port);
regmap_noinc_read(one->regmap, SC16IS7XX_RHR_REG, rxbuf, rxlen); regmap_noinc_read(one->regmap, SC16IS7XX_RHR_REG, rxbuf, rxlen);
} }
static void sc16is7xx_fifo_write(struct uart_port *port, u8 *txbuf, u8 to_send) static void sc16is7xx_fifo_write(struct uart_port *port, u8 *txbuf, u8 to_send)
{ {
struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); struct sc16is7xx_one *one = to_sc16is7xx_one(port);
/* /*
* Don't send zero-length data, at least on SPI it confuses the chip * Don't send zero-length data, at least on SPI it confuses the chip
@@ -406,7 +366,7 @@ static void sc16is7xx_fifo_write(struct uart_port *port, u8 *txbuf, u8 to_send)
static void sc16is7xx_port_update(struct uart_port *port, u8 reg, static void sc16is7xx_port_update(struct uart_port *port, u8 reg,
u8 mask, u8 val) u8 mask, u8 val)
{ {
struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); struct sc16is7xx_one *one = to_sc16is7xx_one(port);
regmap_update_bits(one->regmap, reg, mask, val); regmap_update_bits(one->regmap, reg, mask, val);
} }
@@ -419,52 +379,56 @@ static void sc16is7xx_power(struct uart_port *port, int on)
} }
/* /*
* In an amazing feat of design, the Enhanced Features Register (EFR) * In an amazing feat of design, the enhanced register set shares the
* shares the address of the Interrupt Identification Register (IIR). * addresses 0x02 and 0x04-0x07 with the general register set.
* Access to EFR is switched on by writing a magic value (0xbf) to the * The special register set also shares the addresses 0x00-0x01 with the
* Line Control Register (LCR). Any interrupt firing during this time will * general register set.
* see the EFR where it expects the IIR to be, leading to *
* Access to the enhanced or special register set is enabled by writing a magic
* value to the Line Control Register (LCR). When enhanced register set access
* is enabled, for example, any interrupt firing during this time will see the
* EFR where it expects the IIR to be, leading to
* "Unexpected interrupt" messages. * "Unexpected interrupt" messages.
* *
* Prevent this possibility by claiming a mutex while accessing the EFR, * Prevent this possibility by claiming a mutex when access to the enhanced
* and claiming the same mutex from within the interrupt handler. This is * or special register set is enabled, and claiming the same mutex from within
* similar to disabling the interrupt, but that doesn't work because the * the interrupt handler. This is similar to disabling the interrupt, but that
* bulk of the interrupt processing is run as a workqueue job in thread * doesn't work because the bulk of the interrupt processing is run as a
* context. * workqueue job in thread context.
*/ */
static void sc16is7xx_efr_lock(struct uart_port *port) static void sc16is7xx_regs_lock(struct uart_port *port, u8 register_set)
{ {
struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); struct sc16is7xx_one *one = to_sc16is7xx_one(port);
mutex_lock(&one->efr_lock); mutex_lock(&one->lock);
/* Backup content of LCR. */ /* Backup content of LCR. */
one->old_lcr = sc16is7xx_port_read(port, SC16IS7XX_LCR_REG); one->old_lcr = sc16is7xx_port_read(port, SC16IS7XX_LCR_REG);
/* Enable access to Enhanced register set */ /* Enable access to the desired register set */
sc16is7xx_port_write(port, SC16IS7XX_LCR_REG, SC16IS7XX_LCR_CONF_MODE_B); sc16is7xx_port_write(port, SC16IS7XX_LCR_REG, register_set);
/* Disable cache updates when writing to EFR registers */ /* Disable cache updates when writing to non-general registers */
regcache_cache_bypass(one->regmap, true); regcache_cache_bypass(one->regmap, true);
} }
static void sc16is7xx_efr_unlock(struct uart_port *port) static void sc16is7xx_regs_unlock(struct uart_port *port)
{ {
struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); struct sc16is7xx_one *one = to_sc16is7xx_one(port);
/* Re-enable cache updates when writing to normal registers */ /* Re-enable cache updates when writing to general registers */
regcache_cache_bypass(one->regmap, false); regcache_cache_bypass(one->regmap, false);
/* Restore original content of LCR */ /* Restore original content of LCR */
sc16is7xx_port_write(port, SC16IS7XX_LCR_REG, one->old_lcr); sc16is7xx_port_write(port, SC16IS7XX_LCR_REG, one->old_lcr);
mutex_unlock(&one->efr_lock); mutex_unlock(&one->lock);
} }
static void sc16is7xx_ier_clear(struct uart_port *port, u8 bit) static void sc16is7xx_ier_clear(struct uart_port *port, u8 bit)
{ {
struct sc16is7xx_port *s = dev_get_drvdata(port->dev); struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); struct sc16is7xx_one *one = to_sc16is7xx_one(port);
lockdep_assert_held_once(&port->lock); lockdep_assert_held_once(&port->lock);
@@ -477,7 +441,7 @@ static void sc16is7xx_ier_clear(struct uart_port *port, u8 bit)
static void sc16is7xx_ier_set(struct uart_port *port, u8 bit) static void sc16is7xx_ier_set(struct uart_port *port, u8 bit)
{ {
struct sc16is7xx_port *s = dev_get_drvdata(port->dev); struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); struct sc16is7xx_one *one = to_sc16is7xx_one(port);
lockdep_assert_held_once(&port->lock); lockdep_assert_held_once(&port->lock);
@@ -535,10 +499,11 @@ EXPORT_SYMBOL_GPL(sc16is762_devtype);
static bool sc16is7xx_regmap_volatile(struct device *dev, unsigned int reg) static bool sc16is7xx_regmap_volatile(struct device *dev, unsigned int reg)
{ {
switch (reg) { switch (reg) {
case SC16IS7XX_RHR_REG: case SC16IS7XX_RHR_REG: /* Shared address space with THR & DLL */
case SC16IS7XX_IIR_REG: case SC16IS7XX_IIR_REG: /* Shared address space with FCR & EFR */
case SC16IS7XX_LSR_REG: case SC16IS7XX_LSR_REG: /* Shared address space with XON2 */
case SC16IS7XX_MSR_REG: case SC16IS7XX_MSR_REG: /* Shared address space with TCR & XOFF1 */
case SC16IS7XX_SPR_REG: /* Shared address space with TLR & XOFF2 */
case SC16IS7XX_TXLVL_REG: case SC16IS7XX_TXLVL_REG:
case SC16IS7XX_RXLVL_REG: case SC16IS7XX_RXLVL_REG:
case SC16IS7XX_IOSTATE_REG: case SC16IS7XX_IOSTATE_REG:
@@ -578,8 +543,6 @@ static bool sc16is7xx_regmap_noinc(struct device *dev, unsigned int reg)
*/ */
static int sc16is7xx_set_baud(struct uart_port *port, int baud) static int sc16is7xx_set_baud(struct uart_port *port, int baud)
{ {
struct sc16is7xx_one *one = to_sc16is7xx_one(port, port);
u8 lcr;
unsigned int prescaler = 1; unsigned int prescaler = 1;
unsigned long clk = port->uartclk, div = clk / 16 / baud; unsigned long clk = port->uartclk, div = clk / 16 / baud;
@@ -593,23 +556,15 @@ static int sc16is7xx_set_baud(struct uart_port *port, int baud)
SC16IS7XX_MCR_CLKSEL_BIT, SC16IS7XX_MCR_CLKSEL_BIT,
prescaler == 1 ? 0 : SC16IS7XX_MCR_CLKSEL_BIT); prescaler == 1 ? 0 : SC16IS7XX_MCR_CLKSEL_BIT);
mutex_lock(&one->efr_lock); /* Access special register set (DLL/DLH) */
sc16is7xx_regs_lock(port, SC16IS7XX_LCR_REG_SET_SPECIAL);
/* Backup LCR and access special register set (DLL/DLH) */
lcr = sc16is7xx_port_read(port, SC16IS7XX_LCR_REG);
sc16is7xx_port_write(port, SC16IS7XX_LCR_REG,
SC16IS7XX_LCR_CONF_MODE_A);
/* Write the new divisor */ /* Write the new divisor */
regcache_cache_bypass(one->regmap, true);
sc16is7xx_port_write(port, SC16IS7XX_DLH_REG, div / 256); sc16is7xx_port_write(port, SC16IS7XX_DLH_REG, div / 256);
sc16is7xx_port_write(port, SC16IS7XX_DLL_REG, div % 256); sc16is7xx_port_write(port, SC16IS7XX_DLL_REG, div % 256);
regcache_cache_bypass(one->regmap, false);
/* Restore LCR and access to general register set */ /* Restore access to general register set */
sc16is7xx_port_write(port, SC16IS7XX_LCR_REG, lcr); sc16is7xx_regs_unlock(port);
mutex_unlock(&one->efr_lock);
return DIV_ROUND_CLOSEST((clk / prescaler) / 16, div); return DIV_ROUND_CLOSEST((clk / prescaler) / 16, div);
} }
@@ -617,7 +572,7 @@ static int sc16is7xx_set_baud(struct uart_port *port, int baud)
static void sc16is7xx_handle_rx(struct uart_port *port, unsigned int rxlen, static void sc16is7xx_handle_rx(struct uart_port *port, unsigned int rxlen,
unsigned int iir) unsigned int iir)
{ {
struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); struct sc16is7xx_one *one = to_sc16is7xx_one(port);
unsigned int lsr = 0, bytes_read, i; unsigned int lsr = 0, bytes_read, i;
bool read_lsr = (iir == SC16IS7XX_IIR_RLSE_SRC); bool read_lsr = (iir == SC16IS7XX_IIR_RLSE_SRC);
u8 ch, flag; u8 ch, flag;
@@ -756,7 +711,8 @@ static void sc16is7xx_update_mlines(struct sc16is7xx_one *one)
unsigned long flags; unsigned long flags;
unsigned int status, changed; unsigned int status, changed;
lockdep_assert_held_once(&one->efr_lock); /* Lock required as MSR address is shared with TCR and XOFF1. */
lockdep_assert_held_once(&one->lock);
status = sc16is7xx_get_hwmctrl(port); status = sc16is7xx_get_hwmctrl(port);
changed = status ^ one->old_mctrl; changed = status ^ one->old_mctrl;
@@ -782,18 +738,15 @@ static void sc16is7xx_update_mlines(struct sc16is7xx_one *one)
static bool sc16is7xx_port_irq(struct sc16is7xx_port *s, int portno) static bool sc16is7xx_port_irq(struct sc16is7xx_port *s, int portno)
{ {
bool rc = true;
unsigned int iir, rxlen; unsigned int iir, rxlen;
struct uart_port *port = &s->p[portno].port; struct uart_port *port = &s->p[portno].port;
struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); struct sc16is7xx_one *one = to_sc16is7xx_one(port);
mutex_lock(&one->efr_lock); guard(mutex)(&one->lock);
iir = sc16is7xx_port_read(port, SC16IS7XX_IIR_REG); iir = sc16is7xx_port_read(port, SC16IS7XX_IIR_REG);
if (iir & SC16IS7XX_IIR_NO_INT_BIT) { if (iir & SC16IS7XX_IIR_NO_INT_BIT)
rc = false; return false;
goto out_port_irq;
}
iir &= SC16IS7XX_IIR_ID_MASK; iir &= SC16IS7XX_IIR_ID_MASK;
@@ -833,18 +786,14 @@ static bool sc16is7xx_port_irq(struct sc16is7xx_port *s, int portno)
break; break;
} }
out_port_irq: return true;
mutex_unlock(&one->efr_lock);
return rc;
} }
static irqreturn_t sc16is7xx_irq(int irq, void *dev_id) static irqreturn_t sc16is7xx_irq(int irq, void *dev_id)
{ {
struct sc16is7xx_port *s = dev_id;
bool keep_polling; bool keep_polling;
struct sc16is7xx_port *s = (struct sc16is7xx_port *)dev_id;
do { do {
int i; int i;
@@ -871,16 +820,15 @@ static void sc16is7xx_poll_proc(struct kthread_work *ws)
static void sc16is7xx_tx_proc(struct kthread_work *ws) static void sc16is7xx_tx_proc(struct kthread_work *ws)
{ {
struct uart_port *port = &(to_sc16is7xx_one(ws, tx_work)->port); struct sc16is7xx_one *one = container_of(ws, struct sc16is7xx_one, tx_work);
struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); struct uart_port *port = &one->port;
if ((port->rs485.flags & SER_RS485_ENABLED) && if ((port->rs485.flags & SER_RS485_ENABLED) &&
(port->rs485.delay_rts_before_send > 0)) (port->rs485.delay_rts_before_send > 0))
msleep(port->rs485.delay_rts_before_send); msleep(port->rs485.delay_rts_before_send);
mutex_lock(&one->efr_lock); guard(mutex)(&one->lock);
sc16is7xx_handle_tx(port); sc16is7xx_handle_tx(port);
mutex_unlock(&one->efr_lock);
} }
static void sc16is7xx_reconf_rs485(struct uart_port *port) static void sc16is7xx_reconf_rs485(struct uart_port *port)
@@ -905,7 +853,7 @@ static void sc16is7xx_reconf_rs485(struct uart_port *port)
static void sc16is7xx_reg_proc(struct kthread_work *ws) static void sc16is7xx_reg_proc(struct kthread_work *ws)
{ {
struct sc16is7xx_one *one = to_sc16is7xx_one(ws, reg_work); struct sc16is7xx_one *one = container_of(ws, struct sc16is7xx_one, reg_work);
struct sc16is7xx_one_config config; struct sc16is7xx_one_config config;
unsigned long irqflags; unsigned long irqflags;
@@ -943,13 +891,12 @@ static void sc16is7xx_reg_proc(struct kthread_work *ws)
static void sc16is7xx_ms_proc(struct kthread_work *ws) static void sc16is7xx_ms_proc(struct kthread_work *ws)
{ {
struct sc16is7xx_one *one = to_sc16is7xx_one(ws, ms_work.work); struct sc16is7xx_one *one = container_of(ws, struct sc16is7xx_one, ms_work.work);
struct sc16is7xx_port *s = dev_get_drvdata(one->port.dev); struct sc16is7xx_port *s = dev_get_drvdata(one->port.dev);
if (one->port.state) { if (one->port.state) {
mutex_lock(&one->efr_lock); scoped_guard(mutex, &one->lock)
sc16is7xx_update_mlines(one); sc16is7xx_update_mlines(one);
mutex_unlock(&one->efr_lock);
kthread_queue_delayed_work(&s->kworker, &one->ms_work, HZ); kthread_queue_delayed_work(&s->kworker, &one->ms_work, HZ);
} }
@@ -957,7 +904,7 @@ static void sc16is7xx_ms_proc(struct kthread_work *ws)
static void sc16is7xx_enable_ms(struct uart_port *port) static void sc16is7xx_enable_ms(struct uart_port *port)
{ {
struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); struct sc16is7xx_one *one = to_sc16is7xx_one(port);
struct sc16is7xx_port *s = dev_get_drvdata(port->dev); struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
lockdep_assert_held_once(&port->lock); lockdep_assert_held_once(&port->lock);
@@ -968,7 +915,7 @@ static void sc16is7xx_enable_ms(struct uart_port *port)
static void sc16is7xx_start_tx(struct uart_port *port) static void sc16is7xx_start_tx(struct uart_port *port)
{ {
struct sc16is7xx_port *s = dev_get_drvdata(port->dev); struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); struct sc16is7xx_one *one = to_sc16is7xx_one(port);
kthread_queue_work(&s->kworker, &one->tx_work); kthread_queue_work(&s->kworker, &one->tx_work);
} }
@@ -1007,7 +954,7 @@ static unsigned int sc16is7xx_tx_empty(struct uart_port *port)
static unsigned int sc16is7xx_get_mctrl(struct uart_port *port) static unsigned int sc16is7xx_get_mctrl(struct uart_port *port)
{ {
struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); struct sc16is7xx_one *one = to_sc16is7xx_one(port);
/* Called with port lock taken so we can only return cached value */ /* Called with port lock taken so we can only return cached value */
return one->old_mctrl; return one->old_mctrl;
@@ -1016,7 +963,7 @@ static unsigned int sc16is7xx_get_mctrl(struct uart_port *port)
static void sc16is7xx_set_mctrl(struct uart_port *port, unsigned int mctrl) static void sc16is7xx_set_mctrl(struct uart_port *port, unsigned int mctrl)
{ {
struct sc16is7xx_port *s = dev_get_drvdata(port->dev); struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); struct sc16is7xx_one *one = to_sc16is7xx_one(port);
one->config.flags |= SC16IS7XX_RECONF_MD; one->config.flags |= SC16IS7XX_RECONF_MD;
kthread_queue_work(&s->kworker, &one->reg_work); kthread_queue_work(&s->kworker, &one->reg_work);
@@ -1033,7 +980,7 @@ static void sc16is7xx_set_termios(struct uart_port *port,
struct ktermios *termios, struct ktermios *termios,
const struct ktermios *old) const struct ktermios *old)
{ {
struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); struct sc16is7xx_one *one = to_sc16is7xx_one(port);
unsigned int lcr, flow = 0; unsigned int lcr, flow = 0;
int baud; int baud;
unsigned long flags; unsigned long flags;
@@ -1106,12 +1053,12 @@ static void sc16is7xx_set_termios(struct uart_port *port,
sc16is7xx_port_write(port, SC16IS7XX_LCR_REG, lcr); sc16is7xx_port_write(port, SC16IS7XX_LCR_REG, lcr);
/* Update EFR registers */ /* Update EFR registers */
sc16is7xx_efr_lock(port); sc16is7xx_regs_lock(port, SC16IS7XX_LCR_REG_SET_ENHANCED);
sc16is7xx_port_write(port, SC16IS7XX_XON1_REG, termios->c_cc[VSTART]); sc16is7xx_port_write(port, SC16IS7XX_XON1_REG, termios->c_cc[VSTART]);
sc16is7xx_port_write(port, SC16IS7XX_XOFF1_REG, termios->c_cc[VSTOP]); sc16is7xx_port_write(port, SC16IS7XX_XOFF1_REG, termios->c_cc[VSTOP]);
sc16is7xx_port_update(port, SC16IS7XX_EFR_REG, sc16is7xx_port_update(port, SC16IS7XX_EFR_REG,
SC16IS7XX_EFR_FLOWCTRL_BITS, flow); SC16IS7XX_EFR_FLOWCTRL_BITS, flow);
sc16is7xx_efr_unlock(port); sc16is7xx_regs_unlock(port);
/* Get baud rate generator configuration */ /* Get baud rate generator configuration */
baud = uart_get_baud_rate(port, termios, old, baud = uart_get_baud_rate(port, termios, old,
@@ -1136,7 +1083,7 @@ static int sc16is7xx_config_rs485(struct uart_port *port, struct ktermios *termi
struct serial_rs485 *rs485) struct serial_rs485 *rs485)
{ {
struct sc16is7xx_port *s = dev_get_drvdata(port->dev); struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); struct sc16is7xx_one *one = to_sc16is7xx_one(port);
if (rs485->flags & SER_RS485_ENABLED) { if (rs485->flags & SER_RS485_ENABLED) {
/* /*
@@ -1156,14 +1103,14 @@ static int sc16is7xx_config_rs485(struct uart_port *port, struct ktermios *termi
static int sc16is7xx_startup(struct uart_port *port) static int sc16is7xx_startup(struct uart_port *port)
{ {
struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); struct sc16is7xx_one *one = to_sc16is7xx_one(port);
struct sc16is7xx_port *s = dev_get_drvdata(port->dev); struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
unsigned int val; unsigned int val;
unsigned long flags; unsigned long flags;
sc16is7xx_power(port, 1); sc16is7xx_power(port, 1);
/* Reset FIFOs*/ /* Reset FIFOs */
val = SC16IS7XX_FCR_RXRESET_BIT | SC16IS7XX_FCR_TXRESET_BIT; val = SC16IS7XX_FCR_RXRESET_BIT | SC16IS7XX_FCR_TXRESET_BIT;
sc16is7xx_port_write(port, SC16IS7XX_FCR_REG, val); sc16is7xx_port_write(port, SC16IS7XX_FCR_REG, val);
udelay(5); udelay(5);
@@ -1191,8 +1138,7 @@ static int sc16is7xx_startup(struct uart_port *port)
/* This bit must be written with LCR[7] = 0 */ /* This bit must be written with LCR[7] = 0 */
sc16is7xx_port_update(port, SC16IS7XX_MCR_REG, sc16is7xx_port_update(port, SC16IS7XX_MCR_REG,
SC16IS7XX_MCR_IRDA_BIT, SC16IS7XX_MCR_IRDA_BIT,
one->irda_mode ? one->irda_mode ? SC16IS7XX_MCR_IRDA_BIT : 0);
SC16IS7XX_MCR_IRDA_BIT : 0);
/* Enable the Rx and Tx FIFO */ /* Enable the Rx and Tx FIFO */
sc16is7xx_port_update(port, SC16IS7XX_EFCR_REG, sc16is7xx_port_update(port, SC16IS7XX_EFCR_REG,
@@ -1220,7 +1166,7 @@ static int sc16is7xx_startup(struct uart_port *port)
static void sc16is7xx_shutdown(struct uart_port *port) static void sc16is7xx_shutdown(struct uart_port *port)
{ {
struct sc16is7xx_port *s = dev_get_drvdata(port->dev); struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
struct sc16is7xx_one *one = to_sc16is7xx_one(port, port); struct sc16is7xx_one *one = to_sc16is7xx_one(port);
kthread_cancel_delayed_work_sync(&one->ms_work); kthread_cancel_delayed_work_sync(&one->ms_work);
@@ -1510,6 +1456,75 @@ static int sc16is7xx_reset(struct device *dev, struct regmap *regmap)
return 0; return 0;
} }
static int sc16is7xx_setup_channel(struct sc16is7xx_one *one, int i,
bool *port_registered)
{
struct uart_port *port = &one->port;
int ret;
ret = ida_alloc_max(&sc16is7xx_lines, SC16IS7XX_MAX_DEVS - 1, GFP_KERNEL);
if (ret < 0)
return ret;
port->line = ret;
/* Initialize port data */
port->type = PORT_SC16IS7XX;
port->fifosize = SC16IS7XX_FIFO_SIZE;
port->flags = UPF_FIXED_TYPE | UPF_LOW_LATENCY;
port->iobase = i;
/*
* Use all ones as membase to make sure uart_configure_port() in
* serial_core.c does not abort for SPI/I2C devices where the
* membase address is not applicable.
*/
port->membase = (void __iomem *)~0;
port->iotype = UPIO_PORT;
port->rs485_config = sc16is7xx_config_rs485;
port->rs485_supported = sc16is7xx_rs485_supported;
port->ops = &sc16is7xx_ops;
one->old_mctrl = 0;
mutex_init(&one->lock);
ret = uart_get_rs485_mode(port);
if (ret)
return ret;
/* Enable access to general register set */
sc16is7xx_port_write(port, SC16IS7XX_LCR_REG, 0x00);
/* Disable all interrupts */
sc16is7xx_port_write(port, SC16IS7XX_IER_REG, 0);
/* Disable TX/RX */
sc16is7xx_port_write(port, SC16IS7XX_EFCR_REG,
SC16IS7XX_EFCR_RXDISABLE_BIT |
SC16IS7XX_EFCR_TXDISABLE_BIT);
/* Initialize kthread work structs */
kthread_init_work(&one->tx_work, sc16is7xx_tx_proc);
kthread_init_work(&one->reg_work, sc16is7xx_reg_proc);
kthread_init_delayed_work(&one->ms_work, sc16is7xx_ms_proc);
/* Register port */
ret = uart_add_one_port(&sc16is7xx_uart, port);
if (ret)
return ret;
*port_registered = true;
sc16is7xx_regs_lock(port, SC16IS7XX_LCR_REG_SET_ENHANCED);
/* Enable write access to enhanced features */
sc16is7xx_port_write(port, SC16IS7XX_EFR_REG,
SC16IS7XX_EFR_ENABLE_BIT);
sc16is7xx_regs_unlock(port);
/* Go to suspend mode */
sc16is7xx_power(port, 0);
return 0;
}
int sc16is7xx_probe(struct device *dev, const struct sc16is7xx_devtype *devtype, int sc16is7xx_probe(struct device *dev, const struct sc16is7xx_devtype *devtype,
struct regmap *regmaps[], int irq) struct regmap *regmaps[], int irq)
{ {
@@ -1539,10 +1554,8 @@ int sc16is7xx_probe(struct device *dev, const struct sc16is7xx_devtype *devtype,
/* Alloc port structure */ /* Alloc port structure */
s = devm_kzalloc(dev, struct_size(s, p, devtype->nr_uart), GFP_KERNEL); s = devm_kzalloc(dev, struct_size(s, p, devtype->nr_uart), GFP_KERNEL);
if (!s) { if (!s)
dev_err(dev, "Error allocating port structure\n");
return -ENOMEM; return -ENOMEM;
}
/* Always ask for fixed clock rate from a property. */ /* Always ask for fixed clock rate from a property. */
device_property_read_u32(dev, "clock-frequency", &uartclk); device_property_read_u32(dev, "clock-frequency", &uartclk);
@@ -1595,76 +1608,14 @@ int sc16is7xx_probe(struct device *dev, const struct sc16is7xx_devtype *devtype,
} }
for (i = 0; i < devtype->nr_uart; ++i) { for (i = 0; i < devtype->nr_uart; ++i) {
ret = ida_alloc_max(&sc16is7xx_lines,
SC16IS7XX_MAX_DEVS - 1, GFP_KERNEL);
if (ret < 0)
goto out_ports;
s->p[i].port.line = ret;
/* Initialize port data */
s->p[i].port.dev = dev; s->p[i].port.dev = dev;
s->p[i].port.irq = irq; s->p[i].port.irq = irq;
s->p[i].port.type = PORT_SC16IS7XX;
s->p[i].port.fifosize = SC16IS7XX_FIFO_SIZE;
s->p[i].port.flags = UPF_FIXED_TYPE | UPF_LOW_LATENCY;
s->p[i].port.iobase = i;
/*
* Use all ones as membase to make sure uart_configure_port() in
* serial_core.c does not abort for SPI/I2C devices where the
* membase address is not applicable.
*/
s->p[i].port.membase = (void __iomem *)~0;
s->p[i].port.iotype = UPIO_PORT;
s->p[i].port.uartclk = freq; s->p[i].port.uartclk = freq;
s->p[i].port.rs485_config = sc16is7xx_config_rs485;
s->p[i].port.rs485_supported = sc16is7xx_rs485_supported;
s->p[i].port.ops = &sc16is7xx_ops;
s->p[i].old_mctrl = 0;
s->p[i].regmap = regmaps[i]; s->p[i].regmap = regmaps[i];
mutex_init(&s->p[i].efr_lock); ret = sc16is7xx_setup_channel(&s->p[i], i, &port_registered[i]);
ret = uart_get_rs485_mode(&s->p[i].port);
if (ret) if (ret)
goto out_ports; goto out_ports;
/* Disable all interrupts */
sc16is7xx_port_write(&s->p[i].port, SC16IS7XX_IER_REG, 0);
/* Disable TX/RX */
sc16is7xx_port_write(&s->p[i].port, SC16IS7XX_EFCR_REG,
SC16IS7XX_EFCR_RXDISABLE_BIT |
SC16IS7XX_EFCR_TXDISABLE_BIT);
/* Initialize kthread work structs */
kthread_init_work(&s->p[i].tx_work, sc16is7xx_tx_proc);
kthread_init_work(&s->p[i].reg_work, sc16is7xx_reg_proc);
kthread_init_delayed_work(&s->p[i].ms_work, sc16is7xx_ms_proc);
/* Register port */
ret = uart_add_one_port(&sc16is7xx_uart, &s->p[i].port);
if (ret)
goto out_ports;
port_registered[i] = true;
/* Enable EFR */
sc16is7xx_port_write(&s->p[i].port, SC16IS7XX_LCR_REG,
SC16IS7XX_LCR_CONF_MODE_B);
regcache_cache_bypass(regmaps[i], true);
/* Enable write access to enhanced features */
sc16is7xx_port_write(&s->p[i].port, SC16IS7XX_EFR_REG,
SC16IS7XX_EFR_ENABLE_BIT);
regcache_cache_bypass(regmaps[i], false);
/* Restore access to general registers */
sc16is7xx_port_write(&s->p[i].port, SC16IS7XX_LCR_REG, 0x00);
/* Go to suspend mode */
sc16is7xx_power(&s->p[i].port, 0);
} }
sc16is7xx_setup_irda_ports(s); sc16is7xx_setup_irda_ports(s);
@@ -1814,4 +1765,4 @@ module_exit(sc16is7xx_exit);
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jon Ringle <jringle@gridpoint.com>"); MODULE_AUTHOR("Jon Ringle <jringle@gridpoint.com>");
MODULE_DESCRIPTION("SC16IS7xx tty serial core driver"); MODULE_DESCRIPTION(KBUILD_MODNAME " tty serial core driver");

View File

@@ -8,7 +8,6 @@
#include <linux/regmap.h> #include <linux/regmap.h>
#include <linux/types.h> #include <linux/types.h>
#define SC16IS7XX_NAME "sc16is7xx"
#define SC16IS7XX_MAX_PORTS 2 /* Maximum number of UART ports per IC. */ #define SC16IS7XX_MAX_PORTS 2 /* Maximum number of UART ports per IC. */
struct device; struct device;

View File

@@ -52,7 +52,7 @@ MODULE_DEVICE_TABLE(i2c, sc16is7xx_i2c_id_table);
static struct i2c_driver sc16is7xx_i2c_driver = { static struct i2c_driver sc16is7xx_i2c_driver = {
.driver = { .driver = {
.name = SC16IS7XX_NAME, .name = KBUILD_MODNAME,
.of_match_table = sc16is7xx_dt_ids, .of_match_table = sc16is7xx_dt_ids,
}, },
.probe = sc16is7xx_i2c_probe, .probe = sc16is7xx_i2c_probe,
@@ -63,5 +63,5 @@ static struct i2c_driver sc16is7xx_i2c_driver = {
module_i2c_driver(sc16is7xx_i2c_driver); module_i2c_driver(sc16is7xx_i2c_driver);
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("SC16IS7xx I2C interface driver"); MODULE_DESCRIPTION(KBUILD_MODNAME " interface driver");
MODULE_IMPORT_NS("SERIAL_NXP_SC16IS7XX"); MODULE_IMPORT_NS("SERIAL_NXP_SC16IS7XX");

View File

@@ -75,7 +75,7 @@ MODULE_DEVICE_TABLE(spi, sc16is7xx_spi_id_table);
static struct spi_driver sc16is7xx_spi_driver = { static struct spi_driver sc16is7xx_spi_driver = {
.driver = { .driver = {
.name = SC16IS7XX_NAME, .name = KBUILD_MODNAME,
.of_match_table = sc16is7xx_dt_ids, .of_match_table = sc16is7xx_dt_ids,
}, },
.probe = sc16is7xx_spi_probe, .probe = sc16is7xx_spi_probe,
@@ -86,5 +86,5 @@ static struct spi_driver sc16is7xx_spi_driver = {
module_spi_driver(sc16is7xx_spi_driver); module_spi_driver(sc16is7xx_spi_driver);
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("SC16IS7xx SPI interface driver"); MODULE_DESCRIPTION(KBUILD_MODNAME " interface driver");
MODULE_IMPORT_NS("SERIAL_NXP_SC16IS7XX"); MODULE_IMPORT_NS("SERIAL_NXP_SC16IS7XX");

View File

@@ -1034,9 +1034,8 @@ static int uart_set_info_user(struct tty_struct *tty, struct serial_struct *ss)
{ {
struct uart_state *state = tty->driver_data; struct uart_state *state = tty->driver_data;
struct tty_port *port = &state->port; struct tty_port *port = &state->port;
int retval;
down_write(&tty->termios_rwsem); guard(rwsem_write)(&tty->termios_rwsem);
/* /*
* This semaphore protects port->count. It is also * This semaphore protects port->count. It is also
* very useful to prevent opens. Also, take the * very useful to prevent opens. Also, take the
@@ -1044,11 +1043,8 @@ static int uart_set_info_user(struct tty_struct *tty, struct serial_struct *ss)
* module insertion/removal doesn't change anything * module insertion/removal doesn't change anything
* under us. * under us.
*/ */
mutex_lock(&port->mutex); guard(mutex)(&port->mutex);
retval = uart_set_info(tty, port, state, ss); return uart_set_info(tty, port, state, ss);
mutex_unlock(&port->mutex);
up_write(&tty->termios_rwsem);
return retval;
} }
/** /**
@@ -1560,85 +1556,66 @@ uart_ioctl(struct tty_struct *tty, unsigned int cmd, unsigned long arg)
void __user *uarg = (void __user *)arg; void __user *uarg = (void __user *)arg;
int ret = -ENOIOCTLCMD; int ret = -ENOIOCTLCMD;
/* This ioctl doesn't rely on the hardware to be present. */
/* if (cmd == TIOCSERCONFIG) {
* These ioctls don't rely on the hardware to be present. guard(rwsem_write)(&tty->termios_rwsem);
*/ return uart_do_autoconfig(tty, state);
switch (cmd) {
case TIOCSERCONFIG:
down_write(&tty->termios_rwsem);
ret = uart_do_autoconfig(tty, state);
up_write(&tty->termios_rwsem);
break;
} }
if (ret != -ENOIOCTLCMD) if (tty_io_error(tty))
goto out; return -EIO;
if (tty_io_error(tty)) { /* This should only be used when the hardware is present. */
ret = -EIO; if (cmd == TIOCMIWAIT)
goto out; return uart_wait_modem_status(state, arg);
}
/*
* The following should only be used when hardware is present.
*/
switch (cmd) {
case TIOCMIWAIT:
ret = uart_wait_modem_status(state, arg);
break;
}
if (ret != -ENOIOCTLCMD)
goto out;
/* rs485_config requires more locking than others */ /* rs485_config requires more locking than others */
if (cmd == TIOCSRS485) if (cmd == TIOCSRS485)
down_write(&tty->termios_rwsem); down_write(&tty->termios_rwsem);
mutex_lock(&port->mutex); scoped_guard(mutex, &port->mutex) {
uport = uart_port_check(state); uport = uart_port_check(state);
if (!uport || tty_io_error(tty)) { if (!uport || tty_io_error(tty)) {
ret = -EIO; ret = -EIO;
goto out_up; break;
}
/*
* All these rely on hardware being present and need to be
* protected against the tty being hung up.
*/
switch (cmd) {
case TIOCSERGETLSR: /* Get line status register */
ret = uart_get_lsr_info(tty, state, uarg);
break;
case TIOCGRS485:
ret = uart_get_rs485_config(uport, uarg);
break;
case TIOCSRS485:
ret = uart_set_rs485_config(tty, uport, uarg);
break;
case TIOCSISO7816:
ret = uart_set_iso7816_config(state->uart_port, uarg);
break;
case TIOCGISO7816:
ret = uart_get_iso7816_config(state->uart_port, uarg);
break;
default:
if (uport->ops->ioctl)
ret = uport->ops->ioctl(uport, cmd, arg);
break;
}
} }
/*
* All these rely on hardware being present and need to be
* protected against the tty being hung up.
*/
switch (cmd) {
case TIOCSERGETLSR: /* Get line status register */
ret = uart_get_lsr_info(tty, state, uarg);
break;
case TIOCGRS485:
ret = uart_get_rs485_config(uport, uarg);
break;
case TIOCSRS485:
ret = uart_set_rs485_config(tty, uport, uarg);
break;
case TIOCSISO7816:
ret = uart_set_iso7816_config(state->uart_port, uarg);
break;
case TIOCGISO7816:
ret = uart_get_iso7816_config(state->uart_port, uarg);
break;
default:
if (uport->ops->ioctl)
ret = uport->ops->ioctl(uport, cmd, arg);
break;
}
out_up:
mutex_unlock(&port->mutex);
if (cmd == TIOCSRS485) if (cmd == TIOCSRS485)
up_write(&tty->termios_rwsem); up_write(&tty->termios_rwsem);
out:
return ret; return ret;
} }
@@ -1651,11 +1628,10 @@ static void uart_set_ldisc(struct tty_struct *tty)
if (!tty_port_initialized(port)) if (!tty_port_initialized(port))
return; return;
mutex_lock(&state->port.mutex); guard(mutex)(&state->port.mutex);
uport = uart_port_check(state); uport = uart_port_check(state);
if (uport && uport->ops->set_ldisc) if (uport && uport->ops->set_ldisc)
uport->ops->set_ldisc(uport, &tty->termios); uport->ops->set_ldisc(uport, &tty->termios);
mutex_unlock(&state->port.mutex);
} }
static void uart_set_termios(struct tty_struct *tty, static void uart_set_termios(struct tty_struct *tty,
@@ -1729,9 +1705,8 @@ static void uart_close(struct tty_struct *tty, struct file *filp)
state = drv->state + tty->index; state = drv->state + tty->index;
port = &state->port; port = &state->port;
spin_lock_irq(&port->lock); guard(spinlock_irq)(&port->lock);
--port->count; --port->count;
spin_unlock_irq(&port->lock);
return; return;
} }
@@ -1843,20 +1818,18 @@ static void uart_hangup(struct tty_struct *tty)
struct uart_state *state = tty->driver_data; struct uart_state *state = tty->driver_data;
struct tty_port *port = &state->port; struct tty_port *port = &state->port;
struct uart_port *uport; struct uart_port *uport;
unsigned long flags;
pr_debug("uart_hangup(%d)\n", tty->index); pr_debug("uart_hangup(%d)\n", tty->index);
mutex_lock(&port->mutex); guard(mutex)(&port->mutex);
uport = uart_port_check(state); uport = uart_port_check(state);
WARN(!uport, "hangup of detached port!\n"); WARN(!uport, "hangup of detached port!\n");
if (tty_port_active(port)) { if (tty_port_active(port)) {
uart_flush_buffer(tty); uart_flush_buffer(tty);
uart_shutdown(tty, state); uart_shutdown(tty, state);
spin_lock_irqsave(&port->lock, flags); scoped_guard(spinlock_irqsave, &port->lock)
port->count = 0; port->count = 0;
spin_unlock_irqrestore(&port->lock, flags);
tty_port_set_active(port, false); tty_port_set_active(port, false);
tty_port_tty_set(port, NULL); tty_port_tty_set(port, NULL);
if (uport && !uart_console(uport)) if (uport && !uart_console(uport))
@@ -1864,7 +1837,6 @@ static void uart_hangup(struct tty_struct *tty)
wake_up_interruptible(&port->open_wait); wake_up_interruptible(&port->open_wait);
wake_up_interruptible(&port->delta_msr_wait); wake_up_interruptible(&port->delta_msr_wait);
} }
mutex_unlock(&port->mutex);
} }
/* uport == NULL if uart_port has already been removed */ /* uport == NULL if uart_port has already been removed */
@@ -2969,11 +2941,11 @@ static ssize_t console_show(struct device *dev,
struct uart_port *uport; struct uart_port *uport;
bool console = false; bool console = false;
mutex_lock(&port->mutex); scoped_guard(mutex, &port->mutex) {
uport = uart_port_check(state); uport = uart_port_check(state);
if (uport) if (uport)
console = uart_console_registered(uport); console = uart_console_registered(uport);
mutex_unlock(&port->mutex); }
return sprintf(buf, "%c\n", console ? 'Y' : 'N'); return sprintf(buf, "%c\n", console ? 'Y' : 'N');
} }
@@ -3158,17 +3130,14 @@ static void serial_core_remove_one_port(struct uart_driver *drv,
struct tty_port *port = &state->port; struct tty_port *port = &state->port;
struct uart_port *uart_port; struct uart_port *uart_port;
mutex_lock(&port->mutex); scoped_guard(mutex, &port->mutex) {
uart_port = uart_port_check(state); uart_port = uart_port_check(state);
if (uart_port != uport) if (uart_port != uport)
dev_alert(uport->dev, "Removing wrong port: %p != %p\n", dev_alert(uport->dev, "Removing wrong port: %p != %p\n", uart_port, uport);
uart_port, uport);
if (!uart_port) { if (!uart_port)
mutex_unlock(&port->mutex); return;
return;
} }
mutex_unlock(&port->mutex);
/* /*
* Remove the devices from the tty layer * Remove the devices from the tty layer
@@ -3197,11 +3166,10 @@ static void serial_core_remove_one_port(struct uart_driver *drv,
uport->type = PORT_UNKNOWN; uport->type = PORT_UNKNOWN;
uport->port_dev = NULL; uport->port_dev = NULL;
mutex_lock(&port->mutex); guard(mutex)(&port->mutex);
WARN_ON(atomic_dec_return(&state->refcount) < 0); WARN_ON(atomic_dec_return(&state->refcount) < 0);
wait_event(state->remove_wait, !atomic_read(&state->refcount)); wait_event(state->remove_wait, !atomic_read(&state->refcount));
state->uart_port = NULL; state->uart_port = NULL;
mutex_unlock(&port->mutex);
} }
/** /**
@@ -3354,7 +3322,7 @@ void serial_core_unregister_port(struct uart_driver *drv, struct uart_port *port
struct serial_ctrl_device *ctrl_dev = serial_core_get_ctrl_dev(port_dev); struct serial_ctrl_device *ctrl_dev = serial_core_get_ctrl_dev(port_dev);
int ctrl_id = port->ctrl_id; int ctrl_id = port->ctrl_id;
mutex_lock(&port_mutex); guard(mutex)(&port_mutex);
port->flags |= UPF_DEAD; port->flags |= UPF_DEAD;
@@ -3366,8 +3334,6 @@ void serial_core_unregister_port(struct uart_driver *drv, struct uart_port *port
/* Drop the serial core controller device if no ports are using it */ /* Drop the serial core controller device if no ports are using it */
if (!serial_core_ctrl_find(drv, phys_dev, ctrl_id)) if (!serial_core_ctrl_find(drv, phys_dev, ctrl_id))
serial_base_ctrl_device_remove(ctrl_dev); serial_base_ctrl_device_remove(ctrl_dev);
mutex_unlock(&port_mutex);
} }
/** /**
@@ -3536,6 +3502,14 @@ int uart_get_rs485_mode(struct uart_port *port)
if (!(port->rs485_supported.flags & SER_RS485_ENABLED)) if (!(port->rs485_supported.flags & SER_RS485_ENABLED))
return 0; return 0;
/*
* Retrieve properties only if a firmware node exists. If no firmware
* node exists, then don't touch rs485 config and keep initial rs485
* properties set by driver.
*/
if (!dev_fwnode(dev))
return 0;
ret = device_property_read_u32_array(dev, "rs485-rts-delay", ret = device_property_read_u32_array(dev, "rs485-rts-delay",
rs485_delay, 2); rs485_delay, 2);
if (!ret) { if (!ret) {

View File

@@ -17,29 +17,32 @@
*/ */
#undef DEBUG #undef DEBUG
#include <linux/bitops.h>
#include <linux/clk.h> #include <linux/clk.h>
#include <linux/console.h> #include <linux/console.h>
#include <linux/ctype.h>
#include <linux/cpufreq.h> #include <linux/cpufreq.h>
#include <linux/ctype.h>
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/dmaengine.h>
#include <linux/dma-mapping.h> #include <linux/dma-mapping.h>
#include <linux/dmaengine.h>
#include <linux/err.h> #include <linux/err.h>
#include <linux/errno.h> #include <linux/errno.h>
#include <linux/init.h> #include <linux/init.h>
#include <linux/interrupt.h> #include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/ioport.h> #include <linux/ioport.h>
#include <linux/ktime.h> #include <linux/ktime.h>
#include <linux/major.h> #include <linux/major.h>
#include <linux/minmax.h> #include <linux/minmax.h>
#include <linux/module.h>
#include <linux/mm.h> #include <linux/mm.h>
#include <linux/module.h>
#include <linux/of.h> #include <linux/of.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/pm_runtime.h> #include <linux/pm_runtime.h>
#include <linux/reset.h> #include <linux/reset.h>
#include <linux/scatterlist.h> #include <linux/scatterlist.h>
#include <linux/serial.h> #include <linux/serial.h>
#include <linux/serial_core.h>
#include <linux/serial_sci.h> #include <linux/serial_sci.h>
#include <linux/sh_dma.h> #include <linux/sh_dma.h>
#include <linux/slab.h> #include <linux/slab.h>
@@ -50,15 +53,186 @@
#include <linux/tty_flip.h> #include <linux/tty_flip.h>
#ifdef CONFIG_SUPERH #ifdef CONFIG_SUPERH
#include <asm/sh_bios.h>
#include <asm/platform_early.h> #include <asm/platform_early.h>
#include <asm/sh_bios.h>
#endif #endif
#include "rsci.h" #include "rsci.h"
#include "serial_mctrl_gpio.h" #include "serial_mctrl_gpio.h"
#include "sh-sci.h"
#include "sh-sci-common.h" #include "sh-sci-common.h"
#define SCI_MAJOR 204
#define SCI_MINOR_START 8
/*
* SCI register subset common for all port types.
* Not all registers will exist on all parts.
*/
enum {
SCSMR, /* Serial Mode Register */
SCBRR, /* Bit Rate Register */
SCSCR, /* Serial Control Register */
SCxSR, /* Serial Status Register */
SCFCR, /* FIFO Control Register */
SCFDR, /* FIFO Data Count Register */
SCxTDR, /* Transmit (FIFO) Data Register */
SCxRDR, /* Receive (FIFO) Data Register */
SCLSR, /* Line Status Register */
SCTFDR, /* Transmit FIFO Data Count Register */
SCRFDR, /* Receive FIFO Data Count Register */
SCSPTR, /* Serial Port Register */
HSSRR, /* Sampling Rate Register */
SCPCR, /* Serial Port Control Register */
SCPDR, /* Serial Port Data Register */
SCDL, /* BRG Frequency Division Register */
SCCKS, /* BRG Clock Select Register */
HSRTRGR, /* Rx FIFO Data Count Trigger Register */
HSTTRGR, /* Tx FIFO Data Count Trigger Register */
SEMR, /* Serial extended mode register */
};
/* SCSMR (Serial Mode Register) */
#define SCSMR_C_A BIT(7) /* Communication Mode */
#define SCSMR_CSYNC BIT(7) /* - Clocked synchronous mode */
#define SCSMR_ASYNC 0 /* - Asynchronous mode */
#define SCSMR_CHR BIT(6) /* 7-bit Character Length */
#define SCSMR_PE BIT(5) /* Parity Enable */
#define SCSMR_ODD BIT(4) /* Odd Parity */
#define SCSMR_STOP BIT(3) /* Stop Bit Length */
#define SCSMR_CKS 0x0003 /* Clock Select */
/* Serial Mode Register, SCIFA/SCIFB only bits */
#define SCSMR_CKEDG BIT(12) /* Transmit/Receive Clock Edge Select */
#define SCSMR_SRC_MASK 0x0700 /* Sampling Control */
#define SCSMR_SRC_16 0x0000 /* Sampling rate 1/16 */
#define SCSMR_SRC_5 0x0100 /* Sampling rate 1/5 */
#define SCSMR_SRC_7 0x0200 /* Sampling rate 1/7 */
#define SCSMR_SRC_11 0x0300 /* Sampling rate 1/11 */
#define SCSMR_SRC_13 0x0400 /* Sampling rate 1/13 */
#define SCSMR_SRC_17 0x0500 /* Sampling rate 1/17 */
#define SCSMR_SRC_19 0x0600 /* Sampling rate 1/19 */
#define SCSMR_SRC_27 0x0700 /* Sampling rate 1/27 */
/* Serial Control Register, SCI only bits */
#define SCSCR_TEIE BIT(2) /* Transmit End Interrupt Enable */
/* Serial Control Register, SCIFA/SCIFB only bits */
#define SCSCR_TDRQE BIT(15) /* Tx Data Transfer Request Enable */
#define SCSCR_RDRQE BIT(14) /* Rx Data Transfer Request Enable */
/* Serial Control Register, HSCIF-only bits */
#define HSSCR_TOT_SHIFT 14
/* SCxSR (Serial Status Register) on SCI */
#define SCI_TDRE BIT(7) /* Transmit Data Register Empty */
#define SCI_RDRF BIT(6) /* Receive Data Register Full */
#define SCI_ORER BIT(5) /* Overrun Error */
#define SCI_FER BIT(4) /* Framing Error */
#define SCI_PER BIT(3) /* Parity Error */
#define SCI_TEND BIT(2) /* Transmit End */
#define SCI_RESERVED 0x03 /* All reserved bits */
#define SCI_DEFAULT_ERROR_MASK (SCI_PER | SCI_FER)
#define SCI_RDxF_CLEAR (u32)(~(SCI_RESERVED | SCI_RDRF))
#define SCI_ERROR_CLEAR (u32)(~(SCI_RESERVED | SCI_PER | SCI_FER | SCI_ORER))
#define SCI_TDxE_CLEAR (u32)(~(SCI_RESERVED | SCI_TEND | SCI_TDRE))
#define SCI_BREAK_CLEAR (u32)(~(SCI_RESERVED | SCI_PER | SCI_FER | SCI_ORER))
/* SCxSR (Serial Status Register) on SCIF, SCIFA, SCIFB, HSCIF */
#define SCIF_ER BIT(7) /* Receive Error */
#define SCIF_TEND BIT(6) /* Transmission End */
#define SCIF_TDFE BIT(5) /* Transmit FIFO Data Empty */
#define SCIF_BRK BIT(4) /* Break Detect */
#define SCIF_FER BIT(3) /* Framing Error */
#define SCIF_PER BIT(2) /* Parity Error */
#define SCIF_RDF BIT(1) /* Receive FIFO Data Full */
#define SCIF_DR BIT(0) /* Receive Data Ready */
/* SCIF only (optional) */
#define SCIF_PERC 0xf000 /* Number of Parity Errors */
#define SCIF_FERC 0x0f00 /* Number of Framing Errors */
/*SCIFA/SCIFB and SCIF on SH7705/SH7720/SH7721 only */
#define SCIFA_ORER BIT(9) /* Overrun Error */
#define SCIF_DEFAULT_ERROR_MASK (SCIF_PER | SCIF_FER | SCIF_BRK | SCIF_ER)
#define SCIF_RDxF_CLEAR (u32)(~(SCIF_DR | SCIF_RDF))
#define SCIF_ERROR_CLEAR (u32)(~(SCIF_PER | SCIF_FER | SCIF_ER))
#define SCIF_TDxE_CLEAR (u32)(~(SCIF_TDFE))
#define SCIF_BREAK_CLEAR (u32)(~(SCIF_PER | SCIF_FER | SCIF_BRK))
/* SCFCR (FIFO Control Register) */
#define SCFCR_RTRG1 BIT(7) /* Receive FIFO Data Count Trigger */
#define SCFCR_RTRG0 BIT(6)
#define SCFCR_TTRG1 BIT(5) /* Transmit FIFO Data Count Trigger */
#define SCFCR_TTRG0 BIT(4)
#define SCFCR_MCE BIT(3) /* Modem Control Enable */
#define SCFCR_TFRST BIT(2) /* Transmit FIFO Data Register Reset */
#define SCFCR_RFRST BIT(1) /* Receive FIFO Data Register Reset */
#define SCFCR_LOOP BIT(0) /* Loopback Test */
/* SCLSR (Line Status Register) on (H)SCIF */
#define SCLSR_TO BIT(2) /* Timeout */
#define SCLSR_ORER BIT(0) /* Overrun Error */
/* SCSPTR (Serial Port Register), optional */
#define SCSPTR_RTSIO BIT(7) /* Serial Port RTS# Pin Input/Output */
#define SCSPTR_RTSDT BIT(6) /* Serial Port RTS# Pin Data */
#define SCSPTR_CTSIO BIT(5) /* Serial Port CTS# Pin Input/Output */
#define SCSPTR_CTSDT BIT(4) /* Serial Port CTS# Pin Data */
#define SCSPTR_SCKIO BIT(3) /* Serial Port Clock Pin Input/Output */
#define SCSPTR_SCKDT BIT(2) /* Serial Port Clock Pin Data */
#define SCSPTR_SPB2IO BIT(1) /* Serial Port Break Input/Output */
#define SCSPTR_SPB2DT BIT(0) /* Serial Port Break Data */
/* HSSRR HSCIF */
#define HSCIF_SRE BIT(15) /* Sampling Rate Register Enable */
#define HSCIF_SRDE BIT(14) /* Sampling Point Register Enable */
#define HSCIF_SRHP_SHIFT 8
#define HSCIF_SRHP_MASK 0x0f00
/* SCPCR (Serial Port Control Register), SCIFA/SCIFB only */
#define SCPCR_RTSC BIT(4) /* Serial Port RTS# Pin / Output Pin */
#define SCPCR_CTSC BIT(3) /* Serial Port CTS# Pin / Input Pin */
#define SCPCR_SCKC BIT(2) /* Serial Port SCK Pin / Output Pin */
#define SCPCR_RXDC BIT(1) /* Serial Port RXD Pin / Input Pin */
#define SCPCR_TXDC BIT(0) /* Serial Port TXD Pin / Output Pin */
/* SCPDR (Serial Port Data Register), SCIFA/SCIFB only */
#define SCPDR_RTSD BIT(4) /* Serial Port RTS# Output Pin Data */
#define SCPDR_CTSD BIT(3) /* Serial Port CTS# Input Pin Data */
#define SCPDR_SCKD BIT(2) /* Serial Port SCK Output Pin Data */
#define SCPDR_RXDD BIT(1) /* Serial Port RXD Input Pin Data */
#define SCPDR_TXDD BIT(0) /* Serial Port TXD Output Pin Data */
/*
* BRG Clock Select Register (Some SCIF and HSCIF)
* The Baud Rate Generator for external clock can provide a clock source for
* the sampling clock. It outputs either its frequency divided clock, or the
* (undivided) (H)SCK external clock.
*/
#define SCCKS_CKS BIT(15) /* Select (H)SCK (1) or divided SC_CLK (0) */
#define SCCKS_XIN BIT(14) /* SC_CLK uses bus clock (1) or SCIF_CLK (0) */
#define SCxSR_TEND(port) (((port)->type == PORT_SCI) ? SCI_TEND : SCIF_TEND)
#define SCxSR_RDxF(port) (((port)->type == PORT_SCI) ? SCI_RDRF : SCIF_DR | SCIF_RDF)
#define SCxSR_TDxE(port) (((port)->type == PORT_SCI) ? SCI_TDRE : SCIF_TDFE)
#define SCxSR_FER(port) (((port)->type == PORT_SCI) ? SCI_FER : SCIF_FER)
#define SCxSR_PER(port) (((port)->type == PORT_SCI) ? SCI_PER : SCIF_PER)
#define SCxSR_BRK(port) (((port)->type == PORT_SCI) ? 0x00 : SCIF_BRK)
#define SCxSR_ERRORS(port) (to_sci_port(port)->params->error_mask)
#define SCxSR_RDxF_CLEAR(port) \
(((port)->type == PORT_SCI) ? SCI_RDxF_CLEAR : SCIF_RDxF_CLEAR)
#define SCxSR_ERROR_CLEAR(port) \
(to_sci_port(port)->params->error_clear)
#define SCxSR_TDxE_CLEAR(port) \
(((port)->type == PORT_SCI) ? SCI_TDxE_CLEAR : SCIF_TDxE_CLEAR)
#define SCxSR_BREAK_CLEAR(port) \
(((port)->type == PORT_SCI) ? SCI_BREAK_CLEAR : SCIF_BREAK_CLEAR)
#define SCIx_IRQ_IS_MUXED(port) \ #define SCIx_IRQ_IS_MUXED(port) \
((port)->irqs[SCIx_ERI_IRQ] == \ ((port)->irqs[SCIx_ERI_IRQ] == \
(port)->irqs[SCIx_RXI_IRQ]) || \ (port)->irqs[SCIx_RXI_IRQ]) || \
@@ -1024,8 +1198,16 @@ static int sci_handle_fifo_overrun(struct uart_port *port)
status = s->ops->read_reg(port, s->params->overrun_reg); status = s->ops->read_reg(port, s->params->overrun_reg);
if (status & s->params->overrun_mask) { if (status & s->params->overrun_mask) {
status &= ~s->params->overrun_mask; if (s->type == SCI_PORT_RSCI) {
s->ops->write_reg(port, s->params->overrun_reg, status); /*
* All of the CFCLR_*C clearing bits match the corresponding
* CSR_*status bits. So, reuse the overrun mask for clearing.
*/
s->ops->clear_SCxSR(port, s->params->overrun_mask);
} else {
status &= ~s->params->overrun_mask;
s->ops->write_reg(port, s->params->overrun_reg, status);
}
port->icount.overrun++; port->icount.overrun++;

View File

@@ -1,178 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0 */
#include <linux/bitops.h>
#include <linux/serial_core.h>
#include <linux/io.h>
#define SCI_MAJOR 204
#define SCI_MINOR_START 8
/*
* SCI register subset common for all port types.
* Not all registers will exist on all parts.
*/
enum {
SCSMR, /* Serial Mode Register */
SCBRR, /* Bit Rate Register */
SCSCR, /* Serial Control Register */
SCxSR, /* Serial Status Register */
SCFCR, /* FIFO Control Register */
SCFDR, /* FIFO Data Count Register */
SCxTDR, /* Transmit (FIFO) Data Register */
SCxRDR, /* Receive (FIFO) Data Register */
SCLSR, /* Line Status Register */
SCTFDR, /* Transmit FIFO Data Count Register */
SCRFDR, /* Receive FIFO Data Count Register */
SCSPTR, /* Serial Port Register */
HSSRR, /* Sampling Rate Register */
SCPCR, /* Serial Port Control Register */
SCPDR, /* Serial Port Data Register */
SCDL, /* BRG Frequency Division Register */
SCCKS, /* BRG Clock Select Register */
HSRTRGR, /* Rx FIFO Data Count Trigger Register */
HSTTRGR, /* Tx FIFO Data Count Trigger Register */
SEMR, /* Serial extended mode register */
};
/* SCSMR (Serial Mode Register) */
#define SCSMR_C_A BIT(7) /* Communication Mode */
#define SCSMR_CSYNC BIT(7) /* - Clocked synchronous mode */
#define SCSMR_ASYNC 0 /* - Asynchronous mode */
#define SCSMR_CHR BIT(6) /* 7-bit Character Length */
#define SCSMR_PE BIT(5) /* Parity Enable */
#define SCSMR_ODD BIT(4) /* Odd Parity */
#define SCSMR_STOP BIT(3) /* Stop Bit Length */
#define SCSMR_CKS 0x0003 /* Clock Select */
/* Serial Mode Register, SCIFA/SCIFB only bits */
#define SCSMR_CKEDG BIT(12) /* Transmit/Receive Clock Edge Select */
#define SCSMR_SRC_MASK 0x0700 /* Sampling Control */
#define SCSMR_SRC_16 0x0000 /* Sampling rate 1/16 */
#define SCSMR_SRC_5 0x0100 /* Sampling rate 1/5 */
#define SCSMR_SRC_7 0x0200 /* Sampling rate 1/7 */
#define SCSMR_SRC_11 0x0300 /* Sampling rate 1/11 */
#define SCSMR_SRC_13 0x0400 /* Sampling rate 1/13 */
#define SCSMR_SRC_17 0x0500 /* Sampling rate 1/17 */
#define SCSMR_SRC_19 0x0600 /* Sampling rate 1/19 */
#define SCSMR_SRC_27 0x0700 /* Sampling rate 1/27 */
/* Serial Control Register, SCI only bits */
#define SCSCR_TEIE BIT(2) /* Transmit End Interrupt Enable */
/* Serial Control Register, SCIFA/SCIFB only bits */
#define SCSCR_TDRQE BIT(15) /* Tx Data Transfer Request Enable */
#define SCSCR_RDRQE BIT(14) /* Rx Data Transfer Request Enable */
/* Serial Control Register, HSCIF-only bits */
#define HSSCR_TOT_SHIFT 14
/* SCxSR (Serial Status Register) on SCI */
#define SCI_TDRE BIT(7) /* Transmit Data Register Empty */
#define SCI_RDRF BIT(6) /* Receive Data Register Full */
#define SCI_ORER BIT(5) /* Overrun Error */
#define SCI_FER BIT(4) /* Framing Error */
#define SCI_PER BIT(3) /* Parity Error */
#define SCI_TEND BIT(2) /* Transmit End */
#define SCI_RESERVED 0x03 /* All reserved bits */
#define SCI_DEFAULT_ERROR_MASK (SCI_PER | SCI_FER)
#define SCI_RDxF_CLEAR (u32)(~(SCI_RESERVED | SCI_RDRF))
#define SCI_ERROR_CLEAR (u32)(~(SCI_RESERVED | SCI_PER | SCI_FER | SCI_ORER))
#define SCI_TDxE_CLEAR (u32)(~(SCI_RESERVED | SCI_TEND | SCI_TDRE))
#define SCI_BREAK_CLEAR (u32)(~(SCI_RESERVED | SCI_PER | SCI_FER | SCI_ORER))
/* SCxSR (Serial Status Register) on SCIF, SCIFA, SCIFB, HSCIF */
#define SCIF_ER BIT(7) /* Receive Error */
#define SCIF_TEND BIT(6) /* Transmission End */
#define SCIF_TDFE BIT(5) /* Transmit FIFO Data Empty */
#define SCIF_BRK BIT(4) /* Break Detect */
#define SCIF_FER BIT(3) /* Framing Error */
#define SCIF_PER BIT(2) /* Parity Error */
#define SCIF_RDF BIT(1) /* Receive FIFO Data Full */
#define SCIF_DR BIT(0) /* Receive Data Ready */
/* SCIF only (optional) */
#define SCIF_PERC 0xf000 /* Number of Parity Errors */
#define SCIF_FERC 0x0f00 /* Number of Framing Errors */
/*SCIFA/SCIFB and SCIF on SH7705/SH7720/SH7721 only */
#define SCIFA_ORER BIT(9) /* Overrun Error */
#define SCIF_DEFAULT_ERROR_MASK (SCIF_PER | SCIF_FER | SCIF_BRK | SCIF_ER)
#define SCIF_RDxF_CLEAR (u32)(~(SCIF_DR | SCIF_RDF))
#define SCIF_ERROR_CLEAR (u32)(~(SCIF_PER | SCIF_FER | SCIF_ER))
#define SCIF_TDxE_CLEAR (u32)(~(SCIF_TDFE))
#define SCIF_BREAK_CLEAR (u32)(~(SCIF_PER | SCIF_FER | SCIF_BRK))
/* SCFCR (FIFO Control Register) */
#define SCFCR_RTRG1 BIT(7) /* Receive FIFO Data Count Trigger */
#define SCFCR_RTRG0 BIT(6)
#define SCFCR_TTRG1 BIT(5) /* Transmit FIFO Data Count Trigger */
#define SCFCR_TTRG0 BIT(4)
#define SCFCR_MCE BIT(3) /* Modem Control Enable */
#define SCFCR_TFRST BIT(2) /* Transmit FIFO Data Register Reset */
#define SCFCR_RFRST BIT(1) /* Receive FIFO Data Register Reset */
#define SCFCR_LOOP BIT(0) /* Loopback Test */
/* SCLSR (Line Status Register) on (H)SCIF */
#define SCLSR_TO BIT(2) /* Timeout */
#define SCLSR_ORER BIT(0) /* Overrun Error */
/* SCSPTR (Serial Port Register), optional */
#define SCSPTR_RTSIO BIT(7) /* Serial Port RTS# Pin Input/Output */
#define SCSPTR_RTSDT BIT(6) /* Serial Port RTS# Pin Data */
#define SCSPTR_CTSIO BIT(5) /* Serial Port CTS# Pin Input/Output */
#define SCSPTR_CTSDT BIT(4) /* Serial Port CTS# Pin Data */
#define SCSPTR_SCKIO BIT(3) /* Serial Port Clock Pin Input/Output */
#define SCSPTR_SCKDT BIT(2) /* Serial Port Clock Pin Data */
#define SCSPTR_SPB2IO BIT(1) /* Serial Port Break Input/Output */
#define SCSPTR_SPB2DT BIT(0) /* Serial Port Break Data */
/* HSSRR HSCIF */
#define HSCIF_SRE BIT(15) /* Sampling Rate Register Enable */
#define HSCIF_SRDE BIT(14) /* Sampling Point Register Enable */
#define HSCIF_SRHP_SHIFT 8
#define HSCIF_SRHP_MASK 0x0f00
/* SCPCR (Serial Port Control Register), SCIFA/SCIFB only */
#define SCPCR_RTSC BIT(4) /* Serial Port RTS# Pin / Output Pin */
#define SCPCR_CTSC BIT(3) /* Serial Port CTS# Pin / Input Pin */
#define SCPCR_SCKC BIT(2) /* Serial Port SCK Pin / Output Pin */
#define SCPCR_RXDC BIT(1) /* Serial Port RXD Pin / Input Pin */
#define SCPCR_TXDC BIT(0) /* Serial Port TXD Pin / Output Pin */
/* SCPDR (Serial Port Data Register), SCIFA/SCIFB only */
#define SCPDR_RTSD BIT(4) /* Serial Port RTS# Output Pin Data */
#define SCPDR_CTSD BIT(3) /* Serial Port CTS# Input Pin Data */
#define SCPDR_SCKD BIT(2) /* Serial Port SCK Output Pin Data */
#define SCPDR_RXDD BIT(1) /* Serial Port RXD Input Pin Data */
#define SCPDR_TXDD BIT(0) /* Serial Port TXD Output Pin Data */
/*
* BRG Clock Select Register (Some SCIF and HSCIF)
* The Baud Rate Generator for external clock can provide a clock source for
* the sampling clock. It outputs either its frequency divided clock, or the
* (undivided) (H)SCK external clock.
*/
#define SCCKS_CKS BIT(15) /* Select (H)SCK (1) or divided SC_CLK (0) */
#define SCCKS_XIN BIT(14) /* SC_CLK uses bus clock (1) or SCIF_CLK (0) */
#define SCxSR_TEND(port) (((port)->type == PORT_SCI) ? SCI_TEND : SCIF_TEND)
#define SCxSR_RDxF(port) (((port)->type == PORT_SCI) ? SCI_RDRF : SCIF_DR | SCIF_RDF)
#define SCxSR_TDxE(port) (((port)->type == PORT_SCI) ? SCI_TDRE : SCIF_TDFE)
#define SCxSR_FER(port) (((port)->type == PORT_SCI) ? SCI_FER : SCIF_FER)
#define SCxSR_PER(port) (((port)->type == PORT_SCI) ? SCI_PER : SCIF_PER)
#define SCxSR_BRK(port) (((port)->type == PORT_SCI) ? 0x00 : SCIF_BRK)
#define SCxSR_ERRORS(port) (to_sci_port(port)->params->error_mask)
#define SCxSR_RDxF_CLEAR(port) \
(((port)->type == PORT_SCI) ? SCI_RDxF_CLEAR : SCIF_RDxF_CLEAR)
#define SCxSR_ERROR_CLEAR(port) \
(to_sci_port(port)->params->error_clear)
#define SCxSR_TDxE_CLEAR(port) \
(((port)->type == PORT_SCI) ? SCI_TDxE_CLEAR : SCIF_TDxE_CLEAR)
#define SCxSR_BREAK_CLEAR(port) \
(((port)->type == PORT_SCI) ? SCI_BREAK_CLEAR : SCIF_BREAK_CLEAR)

View File

@@ -1133,6 +1133,9 @@ static int sprd_clk_init(struct uart_port *uport)
clk_uart = devm_clk_get(uport->dev, "uart"); clk_uart = devm_clk_get(uport->dev, "uart");
if (IS_ERR(clk_uart)) { if (IS_ERR(clk_uart)) {
if (PTR_ERR(clk_uart) == -EPROBE_DEFER)
return -EPROBE_DEFER;
dev_warn(uport->dev, "uart%d can't get uart clock\n", dev_warn(uport->dev, "uart%d can't get uart clock\n",
uport->line); uport->line);
clk_uart = NULL; clk_uart = NULL;
@@ -1140,6 +1143,9 @@ static int sprd_clk_init(struct uart_port *uport)
clk_parent = devm_clk_get(uport->dev, "source"); clk_parent = devm_clk_get(uport->dev, "source");
if (IS_ERR(clk_parent)) { if (IS_ERR(clk_parent)) {
if (PTR_ERR(clk_parent) == -EPROBE_DEFER)
return -EPROBE_DEFER;
dev_warn(uport->dev, "uart%d can't get source clock\n", dev_warn(uport->dev, "uart%d can't get source clock\n",
uport->line); uport->line);
clk_parent = NULL; clk_parent = NULL;

View File

@@ -190,7 +190,6 @@ MODULE_PARM_DESC(rx_timeout, "Rx timeout, 1-255");
* @port: Pointer to the UART port * @port: Pointer to the UART port
* @uartclk: Reference clock * @uartclk: Reference clock
* @pclk: APB clock * @pclk: APB clock
* @cdns_uart_driver: Pointer to UART driver
* @baud: Current baud rate * @baud: Current baud rate
* @clk_rate_change_nb: Notifier block for clock changes * @clk_rate_change_nb: Notifier block for clock changes
* @quirks: Flags for RXBS support. * @quirks: Flags for RXBS support.
@@ -204,7 +203,6 @@ struct cdns_uart {
struct uart_port *port; struct uart_port *port;
struct clk *uartclk; struct clk *uartclk;
struct clk *pclk; struct clk *pclk;
struct uart_driver *cdns_uart_driver;
unsigned int baud; unsigned int baud;
struct notifier_block clk_rate_change_nb; struct notifier_block clk_rate_change_nb;
u32 quirks; u32 quirks;
@@ -1465,7 +1463,6 @@ static struct console cdns_uart_console = {
static int cdns_uart_suspend(struct device *device) static int cdns_uart_suspend(struct device *device)
{ {
struct uart_port *port = dev_get_drvdata(device); struct uart_port *port = dev_get_drvdata(device);
struct cdns_uart *cdns_uart = port->private_data;
int may_wake; int may_wake;
may_wake = device_may_wakeup(device); may_wake = device_may_wakeup(device);
@@ -1489,7 +1486,7 @@ static int cdns_uart_suspend(struct device *device)
* Call the API provided in serial_core.c file which handles * Call the API provided in serial_core.c file which handles
* the suspend. * the suspend.
*/ */
return uart_suspend_port(cdns_uart->cdns_uart_driver, port); return uart_suspend_port(&cdns_uart_uart_driver, port);
} }
/** /**
@@ -1550,7 +1547,7 @@ static int cdns_uart_resume(struct device *device)
uart_port_unlock_irqrestore(port, flags); uart_port_unlock_irqrestore(port, flags);
} }
return uart_resume_port(cdns_uart->cdns_uart_driver, port); return uart_resume_port(&cdns_uart_uart_driver, port);
} }
#endif /* ! CONFIG_PM_SLEEP */ #endif /* ! CONFIG_PM_SLEEP */
static int __maybe_unused cdns_runtime_suspend(struct device *dev) static int __maybe_unused cdns_runtime_suspend(struct device *dev)
@@ -1686,8 +1683,6 @@ static int cdns_uart_probe(struct platform_device *pdev)
} }
} }
cdns_uart_data->cdns_uart_driver = &cdns_uart_uart_driver;
match = of_match_node(cdns_uart_of_match, pdev->dev.of_node); match = of_match_node(cdns_uart_of_match, pdev->dev.of_node);
if (match && match->data) { if (match && match->data) {
const struct cdns_platform_data *data = match->data; const struct cdns_platform_data *data = match->data;
@@ -1862,7 +1857,7 @@ err_out_clk_dis_pclk:
clk_disable_unprepare(cdns_uart_data->pclk); clk_disable_unprepare(cdns_uart_data->pclk);
err_out_unregister_driver: err_out_unregister_driver:
if (!instances) if (!instances)
uart_unregister_driver(cdns_uart_data->cdns_uart_driver); uart_unregister_driver(&cdns_uart_uart_driver);
return rc; return rc;
} }
@@ -1880,7 +1875,7 @@ static void cdns_uart_remove(struct platform_device *pdev)
clk_notifier_unregister(cdns_uart_data->uartclk, clk_notifier_unregister(cdns_uart_data->uartclk,
&cdns_uart_data->clk_rate_change_nb); &cdns_uart_data->clk_rate_change_nb);
#endif #endif
uart_remove_one_port(cdns_uart_data->cdns_uart_driver, port); uart_remove_one_port(&cdns_uart_uart_driver, port);
port->mapbase = 0; port->mapbase = 0;
clk_disable_unprepare(cdns_uart_data->uartclk); clk_disable_unprepare(cdns_uart_data->uartclk);
clk_disable_unprepare(cdns_uart_data->pclk); clk_disable_unprepare(cdns_uart_data->pclk);
@@ -1896,7 +1891,7 @@ static void cdns_uart_remove(struct platform_device *pdev)
reset_control_assert(cdns_uart_data->rstc); reset_control_assert(cdns_uart_data->rstc);
if (!--instances) if (!--instances)
uart_unregister_driver(cdns_uart_data->cdns_uart_driver); uart_unregister_driver(&cdns_uart_uart_driver);
} }
static struct platform_driver cdns_uart_platform_driver = { static struct platform_driver cdns_uart_platform_driver = {

View File

@@ -76,7 +76,7 @@ void tty_buffer_unlock_exclusive(struct tty_port *port)
mutex_unlock(&buf->lock); mutex_unlock(&buf->lock);
if (restart) if (restart)
queue_work(system_unbound_wq, &buf->work); queue_work(system_dfl_wq, &buf->work);
} }
EXPORT_SYMBOL_GPL(tty_buffer_unlock_exclusive); EXPORT_SYMBOL_GPL(tty_buffer_unlock_exclusive);
@@ -530,7 +530,7 @@ void tty_flip_buffer_push(struct tty_port *port)
struct tty_bufhead *buf = &port->buf; struct tty_bufhead *buf = &port->buf;
tty_flip_buffer_commit(buf->tail); tty_flip_buffer_commit(buf->tail);
queue_work(system_unbound_wq, &buf->work); queue_work(system_dfl_wq, &buf->work);
} }
EXPORT_SYMBOL(tty_flip_buffer_push); EXPORT_SYMBOL(tty_flip_buffer_push);
@@ -560,7 +560,7 @@ int tty_insert_flip_string_and_push_buffer(struct tty_port *port,
tty_flip_buffer_commit(buf->tail); tty_flip_buffer_commit(buf->tail);
spin_unlock_irqrestore(&port->lock, flags); spin_unlock_irqrestore(&port->lock, flags);
queue_work(system_unbound_wq, &buf->work); queue_work(system_dfl_wq, &buf->work);
return size; return size;
} }
@@ -613,7 +613,7 @@ void tty_buffer_set_lock_subclass(struct tty_port *port)
bool tty_buffer_restart_work(struct tty_port *port) bool tty_buffer_restart_work(struct tty_port *port)
{ {
return queue_work(system_unbound_wq, &port->buf.work); return queue_work(system_dfl_wq, &port->buf.work);
} }
bool tty_buffer_cancel_work(struct tty_port *port) bool tty_buffer_cancel_work(struct tty_port *port)

View File

@@ -424,8 +424,6 @@ static void do_compute_shiftstate(void)
/* We still have to export this method to vt.c */ /* We still have to export this method to vt.c */
void vt_set_leds_compute_shiftstate(void) void vt_set_leds_compute_shiftstate(void)
{ {
unsigned long flags;
/* /*
* When VT is switched, the keyboard led needs to be set once. * When VT is switched, the keyboard led needs to be set once.
* Ensure that after the switch is completed, the state of the * Ensure that after the switch is completed, the state of the
@@ -434,9 +432,8 @@ void vt_set_leds_compute_shiftstate(void)
vt_switch = true; vt_switch = true;
set_leds(); set_leds();
spin_lock_irqsave(&kbd_event_lock, flags); guard(spinlock_irqsave)(&kbd_event_lock);
do_compute_shiftstate(); do_compute_shiftstate();
spin_unlock_irqrestore(&kbd_event_lock, flags);
} }
/* /*
@@ -625,13 +622,12 @@ static void fn_compose(struct vc_data *vc)
static void fn_spawn_con(struct vc_data *vc) static void fn_spawn_con(struct vc_data *vc)
{ {
spin_lock(&vt_spawn_con.lock); guard(spinlock)(&vt_spawn_con.lock);
if (vt_spawn_con.pid) if (vt_spawn_con.pid)
if (kill_pid(vt_spawn_con.pid, vt_spawn_con.sig, 1)) { if (kill_pid(vt_spawn_con.pid, vt_spawn_con.sig, 1)) {
put_pid(vt_spawn_con.pid); put_pid(vt_spawn_con.pid);
vt_spawn_con.pid = NULL; vt_spawn_con.pid = NULL;
} }
spin_unlock(&vt_spawn_con.lock);
} }
static void fn_SAK(struct vc_data *vc) static void fn_SAK(struct vc_data *vc)
@@ -762,13 +758,9 @@ static void k_fn(struct vc_data *vc, unsigned char value, char up_flag)
return; return;
if ((unsigned)value < ARRAY_SIZE(func_table)) { if ((unsigned)value < ARRAY_SIZE(func_table)) {
unsigned long flags; guard(spinlock_irqsave)(&func_buf_lock);
spin_lock_irqsave(&func_buf_lock, flags);
if (func_table[value]) if (func_table[value])
puts_queue(vc, func_table[value]); puts_queue(vc, func_table[value]);
spin_unlock_irqrestore(&func_buf_lock, flags);
} else } else
pr_err("k_fn called with value=%d\n", value); pr_err("k_fn called with value=%d\n", value);
} }
@@ -1140,8 +1132,7 @@ static unsigned char getledstate(void)
void setledstate(struct kbd_struct *kb, unsigned int led) void setledstate(struct kbd_struct *kb, unsigned int led)
{ {
unsigned long flags; guard(spinlock_irqsave)(&led_lock);
spin_lock_irqsave(&led_lock, flags);
if (!(led & ~7)) { if (!(led & ~7)) {
ledioctl = led; ledioctl = led;
kb->ledmode = LED_SHOW_IOCTL; kb->ledmode = LED_SHOW_IOCTL;
@@ -1149,7 +1140,6 @@ void setledstate(struct kbd_struct *kb, unsigned int led)
kb->ledmode = LED_SHOW_FLAGS; kb->ledmode = LED_SHOW_FLAGS;
set_leds(); set_leds();
spin_unlock_irqrestore(&led_lock, flags);
} }
static inline unsigned char getleds(void) static inline unsigned char getleds(void)
@@ -1172,14 +1162,9 @@ static inline unsigned char getleds(void)
int vt_get_leds(unsigned int console, int flag) int vt_get_leds(unsigned int console, int flag)
{ {
struct kbd_struct *kb = &kbd_table[console]; struct kbd_struct *kb = &kbd_table[console];
int ret;
unsigned long flags;
spin_lock_irqsave(&led_lock, flags); guard(spinlock_irqsave)(&led_lock);
ret = vc_kbd_led(kb, flag); return vc_kbd_led(kb, flag);
spin_unlock_irqrestore(&led_lock, flags);
return ret;
} }
EXPORT_SYMBOL_GPL(vt_get_leds); EXPORT_SYMBOL_GPL(vt_get_leds);
@@ -1213,11 +1198,10 @@ void vt_set_led_state(unsigned int console, int leds)
void vt_kbd_con_start(unsigned int console) void vt_kbd_con_start(unsigned int console)
{ {
struct kbd_struct *kb = &kbd_table[console]; struct kbd_struct *kb = &kbd_table[console];
unsigned long flags;
spin_lock_irqsave(&led_lock, flags); guard(spinlock_irqsave)(&led_lock);
clr_vc_kbd_led(kb, VC_SCROLLOCK); clr_vc_kbd_led(kb, VC_SCROLLOCK);
set_leds(); set_leds();
spin_unlock_irqrestore(&led_lock, flags);
} }
/** /**
@@ -1230,11 +1214,10 @@ void vt_kbd_con_start(unsigned int console)
void vt_kbd_con_stop(unsigned int console) void vt_kbd_con_stop(unsigned int console)
{ {
struct kbd_struct *kb = &kbd_table[console]; struct kbd_struct *kb = &kbd_table[console];
unsigned long flags;
spin_lock_irqsave(&led_lock, flags); guard(spinlock_irqsave)(&led_lock);
set_vc_kbd_led(kb, VC_SCROLLOCK); set_vc_kbd_led(kb, VC_SCROLLOCK);
set_leds(); set_leds();
spin_unlock_irqrestore(&led_lock, flags);
} }
/* /*
@@ -1246,12 +1229,11 @@ void vt_kbd_con_stop(unsigned int console)
static void kbd_bh(struct tasklet_struct *unused) static void kbd_bh(struct tasklet_struct *unused)
{ {
unsigned int leds; unsigned int leds;
unsigned long flags;
spin_lock_irqsave(&led_lock, flags); scoped_guard(spinlock_irqsave, &led_lock) {
leds = getleds(); leds = getleds();
leds |= (unsigned int)kbd->lockstate << 8; leds |= (unsigned int)kbd->lockstate << 8;
spin_unlock_irqrestore(&led_lock, flags); }
if (vt_switch) { if (vt_switch) {
ledstate = ~leds; ledstate = ~leds;
@@ -1525,15 +1507,13 @@ static void kbd_event(struct input_handle *handle, unsigned int event_type,
unsigned int event_code, int value) unsigned int event_code, int value)
{ {
/* We are called with interrupts disabled, just take the lock */ /* We are called with interrupts disabled, just take the lock */
spin_lock(&kbd_event_lock); scoped_guard(spinlock, &kbd_event_lock) {
if (event_type == EV_MSC && event_code == MSC_RAW &&
if (event_type == EV_MSC && event_code == MSC_RAW && kbd_is_hw_raw(handle->dev))
kbd_is_hw_raw(handle->dev)) kbd_rawcode(value);
kbd_rawcode(value); if (event_type == EV_KEY && event_code <= KEY_MAX)
if (event_type == EV_KEY && event_code <= KEY_MAX) kbd_keycode(event_code, value, kbd_is_hw_raw(handle->dev));
kbd_keycode(event_code, value, kbd_is_hw_raw(handle->dev)); }
spin_unlock(&kbd_event_lock);
tasklet_schedule(&keyboard_tasklet); tasklet_schedule(&keyboard_tasklet);
do_poke_blanked_console = 1; do_poke_blanked_console = 1;
@@ -1566,10 +1546,9 @@ static bool kbd_match(struct input_handler *handler, struct input_dev *dev)
static int kbd_connect(struct input_handler *handler, struct input_dev *dev, static int kbd_connect(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id) const struct input_device_id *id)
{ {
struct input_handle *handle;
int error; int error;
handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL); struct input_handle __free(kfree) *handle = kzalloc(sizeof(*handle), GFP_KERNEL);
if (!handle) if (!handle)
return -ENOMEM; return -ENOMEM;
@@ -1579,18 +1558,18 @@ static int kbd_connect(struct input_handler *handler, struct input_dev *dev,
error = input_register_handle(handle); error = input_register_handle(handle);
if (error) if (error)
goto err_free_handle; return error;
error = input_open_device(handle); error = input_open_device(handle);
if (error) if (error)
goto err_unregister_handle; goto err_unregister_handle;
retain_and_null_ptr(handle);
return 0; return 0;
err_unregister_handle: err_unregister_handle:
input_unregister_handle(handle); input_unregister_handle(handle);
err_free_handle:
kfree(handle);
return error; return error;
} }
@@ -1681,77 +1660,64 @@ int __init kbd_init(void)
*/ */
int vt_do_diacrit(unsigned int cmd, void __user *udp, int perm) int vt_do_diacrit(unsigned int cmd, void __user *udp, int perm)
{ {
unsigned long flags;
int asize; int asize;
int ret = 0;
switch (cmd) { switch (cmd) {
case KDGKBDIACR: case KDGKBDIACR:
{ {
struct kbdiacrs __user *a = udp; struct kbdiacrs __user *a = udp;
struct kbdiacr *dia;
int i; int i;
dia = kmalloc_array(MAX_DIACR, sizeof(struct kbdiacr), struct kbdiacr __free(kfree) *dia = kmalloc_array(MAX_DIACR, sizeof(struct kbdiacr),
GFP_KERNEL); GFP_KERNEL);
if (!dia) if (!dia)
return -ENOMEM; return -ENOMEM;
/* Lock the diacriticals table, make a copy and then /* Lock the diacriticals table, make a copy and then
copy it after we unlock */ copy it after we unlock */
spin_lock_irqsave(&kbd_event_lock, flags); scoped_guard(spinlock_irqsave, &kbd_event_lock) {
asize = accent_table_size;
asize = accent_table_size; for (i = 0; i < asize; i++) {
for (i = 0; i < asize; i++) { dia[i].diacr = conv_uni_to_8bit(accent_table[i].diacr);
dia[i].diacr = conv_uni_to_8bit( dia[i].base = conv_uni_to_8bit(accent_table[i].base);
accent_table[i].diacr); dia[i].result = conv_uni_to_8bit(accent_table[i].result);
dia[i].base = conv_uni_to_8bit( }
accent_table[i].base);
dia[i].result = conv_uni_to_8bit(
accent_table[i].result);
} }
spin_unlock_irqrestore(&kbd_event_lock, flags);
if (put_user(asize, &a->kb_cnt)) if (put_user(asize, &a->kb_cnt))
ret = -EFAULT; return -EFAULT;
else if (copy_to_user(a->kbdiacr, dia, if (copy_to_user(a->kbdiacr, dia, asize * sizeof(struct kbdiacr)))
asize * sizeof(struct kbdiacr))) return -EFAULT;
ret = -EFAULT; return 0;
kfree(dia);
return ret;
} }
case KDGKBDIACRUC: case KDGKBDIACRUC:
{ {
struct kbdiacrsuc __user *a = udp; struct kbdiacrsuc __user *a = udp;
void *buf;
buf = kmalloc_array(MAX_DIACR, sizeof(struct kbdiacruc), void __free(kfree) *buf = kmalloc_array(MAX_DIACR, sizeof(struct kbdiacruc),
GFP_KERNEL); GFP_KERNEL);
if (buf == NULL) if (buf == NULL)
return -ENOMEM; return -ENOMEM;
/* Lock the diacriticals table, make a copy and then /* Lock the diacriticals table, make a copy and then
copy it after we unlock */ copy it after we unlock */
spin_lock_irqsave(&kbd_event_lock, flags); scoped_guard(spinlock_irqsave, &kbd_event_lock) {
asize = accent_table_size;
asize = accent_table_size; memcpy(buf, accent_table, asize * sizeof(struct kbdiacruc));
memcpy(buf, accent_table, asize * sizeof(struct kbdiacruc)); }
spin_unlock_irqrestore(&kbd_event_lock, flags);
if (put_user(asize, &a->kb_cnt)) if (put_user(asize, &a->kb_cnt))
ret = -EFAULT; return -EFAULT;
else if (copy_to_user(a->kbdiacruc, buf, if (copy_to_user(a->kbdiacruc, buf, asize * sizeof(struct kbdiacruc)))
asize*sizeof(struct kbdiacruc))) return -EFAULT;
ret = -EFAULT;
kfree(buf); return 0;
return ret;
} }
case KDSKBDIACR: case KDSKBDIACR:
{ {
struct kbdiacrs __user *a = udp; struct kbdiacrs __user *a = udp;
struct kbdiacr *dia = NULL; struct kbdiacr __free(kfree) *dia = NULL;
unsigned int ct; unsigned int ct;
int i; int i;
@@ -1769,7 +1735,7 @@ int vt_do_diacrit(unsigned int cmd, void __user *udp, int perm)
return PTR_ERR(dia); return PTR_ERR(dia);
} }
spin_lock_irqsave(&kbd_event_lock, flags); guard(spinlock_irqsave)(&kbd_event_lock);
accent_table_size = ct; accent_table_size = ct;
for (i = 0; i < ct; i++) { for (i = 0; i < ct; i++) {
accent_table[i].diacr = accent_table[i].diacr =
@@ -1779,8 +1745,7 @@ int vt_do_diacrit(unsigned int cmd, void __user *udp, int perm)
accent_table[i].result = accent_table[i].result =
conv_8bit_to_uni(dia[i].result); conv_8bit_to_uni(dia[i].result);
} }
spin_unlock_irqrestore(&kbd_event_lock, flags);
kfree(dia);
return 0; return 0;
} }
@@ -1788,7 +1753,7 @@ int vt_do_diacrit(unsigned int cmd, void __user *udp, int perm)
{ {
struct kbdiacrsuc __user *a = udp; struct kbdiacrsuc __user *a = udp;
unsigned int ct; unsigned int ct;
void *buf = NULL; void __free(kfree) *buf = NULL;
if (!perm) if (!perm)
return -EPERM; return -EPERM;
@@ -1804,18 +1769,16 @@ int vt_do_diacrit(unsigned int cmd, void __user *udp, int perm)
ct, sizeof(struct kbdiacruc)); ct, sizeof(struct kbdiacruc));
if (IS_ERR(buf)) if (IS_ERR(buf))
return PTR_ERR(buf); return PTR_ERR(buf);
} }
spin_lock_irqsave(&kbd_event_lock, flags); guard(spinlock_irqsave)(&kbd_event_lock);
if (ct) if (ct)
memcpy(accent_table, buf, memcpy(accent_table, buf,
ct * sizeof(struct kbdiacruc)); ct * sizeof(struct kbdiacruc));
accent_table_size = ct; accent_table_size = ct;
spin_unlock_irqrestore(&kbd_event_lock, flags);
kfree(buf);
return 0; return 0;
} }
} }
return ret; return 0;
} }
/** /**
@@ -1829,33 +1792,29 @@ int vt_do_diacrit(unsigned int cmd, void __user *udp, int perm)
int vt_do_kdskbmode(unsigned int console, unsigned int arg) int vt_do_kdskbmode(unsigned int console, unsigned int arg)
{ {
struct kbd_struct *kb = &kbd_table[console]; struct kbd_struct *kb = &kbd_table[console];
int ret = 0;
unsigned long flags;
spin_lock_irqsave(&kbd_event_lock, flags); guard(spinlock_irqsave)(&kbd_event_lock);
switch(arg) { switch(arg) {
case K_RAW: case K_RAW:
kb->kbdmode = VC_RAW; kb->kbdmode = VC_RAW;
break; return 0;
case K_MEDIUMRAW: case K_MEDIUMRAW:
kb->kbdmode = VC_MEDIUMRAW; kb->kbdmode = VC_MEDIUMRAW;
break; return 0;
case K_XLATE: case K_XLATE:
kb->kbdmode = VC_XLATE; kb->kbdmode = VC_XLATE;
do_compute_shiftstate(); do_compute_shiftstate();
break; return 0;
case K_UNICODE: case K_UNICODE:
kb->kbdmode = VC_UNICODE; kb->kbdmode = VC_UNICODE;
do_compute_shiftstate(); do_compute_shiftstate();
break; return 0;
case K_OFF: case K_OFF:
kb->kbdmode = VC_OFF; kb->kbdmode = VC_OFF;
break; return 0;
default: default:
ret = -EINVAL; return -EINVAL;
} }
spin_unlock_irqrestore(&kbd_event_lock, flags);
return ret;
} }
/** /**
@@ -1869,75 +1828,68 @@ int vt_do_kdskbmode(unsigned int console, unsigned int arg)
int vt_do_kdskbmeta(unsigned int console, unsigned int arg) int vt_do_kdskbmeta(unsigned int console, unsigned int arg)
{ {
struct kbd_struct *kb = &kbd_table[console]; struct kbd_struct *kb = &kbd_table[console];
int ret = 0;
unsigned long flags;
spin_lock_irqsave(&kbd_event_lock, flags); guard(spinlock_irqsave)(&kbd_event_lock);
switch(arg) { switch(arg) {
case K_METABIT: case K_METABIT:
clr_vc_kbd_mode(kb, VC_META); clr_vc_kbd_mode(kb, VC_META);
break; return 0;
case K_ESCPREFIX: case K_ESCPREFIX:
set_vc_kbd_mode(kb, VC_META); set_vc_kbd_mode(kb, VC_META);
break; return 0;
default: default:
ret = -EINVAL; return -EINVAL;
} }
spin_unlock_irqrestore(&kbd_event_lock, flags);
return ret;
} }
int vt_do_kbkeycode_ioctl(int cmd, struct kbkeycode __user *user_kbkc, int vt_do_kbkeycode_ioctl(int cmd, struct kbkeycode __user *user_kbkc, int perm)
int perm)
{ {
struct kbkeycode tmp; struct kbkeycode tmp;
int kc = 0; int kc;
if (copy_from_user(&tmp, user_kbkc, sizeof(struct kbkeycode))) if (copy_from_user(&tmp, user_kbkc, sizeof(struct kbkeycode)))
return -EFAULT; return -EFAULT;
switch (cmd) { switch (cmd) {
case KDGETKEYCODE: case KDGETKEYCODE:
kc = getkeycode(tmp.scancode); kc = getkeycode(tmp.scancode);
if (kc >= 0) if (kc < 0)
kc = put_user(kc, &user_kbkc->keycode); return kc;
break; return put_user(kc, &user_kbkc->keycode);
case KDSETKEYCODE: case KDSETKEYCODE:
if (!perm) if (!perm)
return -EPERM; return -EPERM;
kc = setkeycode(tmp.scancode, tmp.keycode); return setkeycode(tmp.scancode, tmp.keycode);
break;
} }
return kc;
return 0;
} }
static unsigned short vt_kdgkbent(unsigned char kbdmode, unsigned char idx, static unsigned short vt_kdgkbent(unsigned char kbdmode, unsigned char idx,
unsigned char map) unsigned char map)
{ {
unsigned short *key_map, val; unsigned short *key_map;
unsigned long flags;
/* Ensure another thread doesn't free it under us */ /* Ensure another thread doesn't free it under us */
spin_lock_irqsave(&kbd_event_lock, flags); guard(spinlock_irqsave)(&kbd_event_lock);
key_map = key_maps[map]; key_map = key_maps[map];
if (key_map) { if (key_map) {
val = U(key_map[idx]); unsigned short val = U(key_map[idx]);
if (kbdmode != VC_UNICODE && KTYP(val) >= NR_TYPES) if (kbdmode != VC_UNICODE && KTYP(val) >= NR_TYPES)
val = K_HOLE; return K_HOLE;
} else return val;
val = idx ? K_HOLE : K_NOSUCHMAP; }
spin_unlock_irqrestore(&kbd_event_lock, flags);
return val; return idx ? K_HOLE : K_NOSUCHMAP;
} }
static int vt_kdskbent(unsigned char kbdmode, unsigned char idx, static int vt_kdskbent(unsigned char kbdmode, unsigned char idx,
unsigned char map, unsigned short val) unsigned char map, unsigned short val)
{ {
unsigned long flags; unsigned short *key_map, oldval;
unsigned short *key_map, *new_map, oldval;
if (!idx && val == K_NOSUCHMAP) { if (!idx && val == K_NOSUCHMAP) {
spin_lock_irqsave(&kbd_event_lock, flags); guard(spinlock_irqsave)(&kbd_event_lock);
/* deallocate map */ /* deallocate map */
key_map = key_maps[map]; key_map = key_maps[map];
if (map && key_map) { if (map && key_map) {
@@ -1947,7 +1899,6 @@ static int vt_kdskbent(unsigned char kbdmode, unsigned char idx,
keymap_count--; keymap_count--;
} }
} }
spin_unlock_irqrestore(&kbd_event_lock, flags);
return 0; return 0;
} }
@@ -1965,45 +1916,36 @@ static int vt_kdskbent(unsigned char kbdmode, unsigned char idx,
return 0; return 0;
#endif #endif
new_map = kmalloc(sizeof(plain_map), GFP_KERNEL); unsigned short __free(kfree) *new_map = kmalloc(sizeof(plain_map), GFP_KERNEL);
if (!new_map) if (!new_map)
return -ENOMEM; return -ENOMEM;
spin_lock_irqsave(&kbd_event_lock, flags); guard(spinlock_irqsave)(&kbd_event_lock);
key_map = key_maps[map]; key_map = key_maps[map];
if (key_map == NULL) { if (key_map == NULL) {
int j; int j;
if (keymap_count >= MAX_NR_OF_USER_KEYMAPS && if (keymap_count >= MAX_NR_OF_USER_KEYMAPS && !capable(CAP_SYS_RESOURCE))
!capable(CAP_SYS_RESOURCE)) {
spin_unlock_irqrestore(&kbd_event_lock, flags);
kfree(new_map);
return -EPERM; return -EPERM;
}
key_maps[map] = new_map; key_map = key_maps[map] = no_free_ptr(new_map);
key_map = new_map;
key_map[0] = U(K_ALLOCATED); key_map[0] = U(K_ALLOCATED);
for (j = 1; j < NR_KEYS; j++) for (j = 1; j < NR_KEYS; j++)
key_map[j] = U(K_HOLE); key_map[j] = U(K_HOLE);
keymap_count++; keymap_count++;
} else }
kfree(new_map);
oldval = U(key_map[idx]); oldval = U(key_map[idx]);
if (val == oldval) if (val == oldval)
goto out; return 0;
/* Attention Key */ /* Attention Key */
if ((oldval == K_SAK || val == K_SAK) && !capable(CAP_SYS_ADMIN)) { if ((oldval == K_SAK || val == K_SAK) && !capable(CAP_SYS_ADMIN))
spin_unlock_irqrestore(&kbd_event_lock, flags);
return -EPERM; return -EPERM;
}
key_map[idx] = U(val); key_map[idx] = U(val);
if (!map && (KTYP(oldval) == KT_SHIFT || KTYP(val) == KT_SHIFT)) if (!map && (KTYP(oldval) == KT_SHIFT || KTYP(val) == KT_SHIFT))
do_compute_shiftstate(); do_compute_shiftstate();
out:
spin_unlock_irqrestore(&kbd_event_lock, flags);
return 0; return 0;
} }
@@ -2049,9 +1991,6 @@ static char *vt_kdskbsent(char *kbs, unsigned char cur)
int vt_do_kdgkb_ioctl(int cmd, struct kbsentry __user *user_kdgkb, int perm) int vt_do_kdgkb_ioctl(int cmd, struct kbsentry __user *user_kdgkb, int perm)
{ {
unsigned char kb_func; unsigned char kb_func;
unsigned long flags;
char *kbs;
int ret;
if (get_user(kb_func, &user_kdgkb->kb_func)) if (get_user(kb_func, &user_kdgkb->kb_func))
return -EFAULT; return -EFAULT;
@@ -2063,57 +2002,50 @@ int vt_do_kdgkb_ioctl(int cmd, struct kbsentry __user *user_kdgkb, int perm)
/* size should have been a struct member */ /* size should have been a struct member */
ssize_t len = sizeof(user_kdgkb->kb_string); ssize_t len = sizeof(user_kdgkb->kb_string);
kbs = kmalloc(len, GFP_KERNEL); char __free(kfree) *kbs = kmalloc(len, GFP_KERNEL);
if (!kbs) if (!kbs)
return -ENOMEM; return -ENOMEM;
spin_lock_irqsave(&func_buf_lock, flags); scoped_guard(spinlock_irqsave, &func_buf_lock)
len = strscpy(kbs, func_table[kb_func] ? : "", len); len = strscpy(kbs, func_table[kb_func] ? : "", len);
spin_unlock_irqrestore(&func_buf_lock, flags);
if (len < 0) { if (len < 0)
ret = -ENOSPC; return -ENOSPC;
break;
} if (copy_to_user(user_kdgkb->kb_string, kbs, len + 1))
ret = copy_to_user(user_kdgkb->kb_string, kbs, len + 1) ? return -EFAULT;
-EFAULT : 0;
break; return 0;
} }
case KDSKBSENT: case KDSKBSENT:
if (!perm || !capable(CAP_SYS_TTY_CONFIG)) if (!perm || !capable(CAP_SYS_TTY_CONFIG))
return -EPERM; return -EPERM;
kbs = strndup_user(user_kdgkb->kb_string, char __free(kfree) *kbs = strndup_user(user_kdgkb->kb_string,
sizeof(user_kdgkb->kb_string)); sizeof(user_kdgkb->kb_string));
if (IS_ERR(kbs)) if (IS_ERR(kbs))
return PTR_ERR(kbs); return PTR_ERR(kbs);
spin_lock_irqsave(&func_buf_lock, flags); guard(spinlock_irqsave)(&func_buf_lock);
kbs = vt_kdskbsent(kbs, kb_func); kbs = vt_kdskbsent(kbs, kb_func);
spin_unlock_irqrestore(&func_buf_lock, flags);
ret = 0; return 0;
break;
} }
kfree(kbs); return 0;
return ret;
} }
int vt_do_kdskled(unsigned int console, int cmd, unsigned long arg, int perm) int vt_do_kdskled(unsigned int console, int cmd, unsigned long arg, int perm)
{ {
struct kbd_struct *kb = &kbd_table[console]; struct kbd_struct *kb = &kbd_table[console];
unsigned long flags;
unsigned char ucval; unsigned char ucval;
switch(cmd) { switch(cmd) {
/* the ioctls below read/set the flags usually shown in the leds */ /* the ioctls below read/set the flags usually shown in the leds */
/* don't use them - they will go away without warning */ /* don't use them - they will go away without warning */
case KDGKBLED: case KDGKBLED:
spin_lock_irqsave(&kbd_event_lock, flags); scoped_guard(spinlock_irqsave, &kbd_event_lock)
ucval = kb->ledflagstate | (kb->default_ledflagstate << 4); ucval = kb->ledflagstate | (kb->default_ledflagstate << 4);
spin_unlock_irqrestore(&kbd_event_lock, flags);
return put_user(ucval, (char __user *)arg); return put_user(ucval, (char __user *)arg);
case KDSKBLED: case KDSKBLED:
@@ -2121,11 +2053,11 @@ int vt_do_kdskled(unsigned int console, int cmd, unsigned long arg, int perm)
return -EPERM; return -EPERM;
if (arg & ~0x77) if (arg & ~0x77)
return -EINVAL; return -EINVAL;
spin_lock_irqsave(&led_lock, flags); scoped_guard(spinlock_irqsave, &led_lock) {
kb->ledflagstate = (arg & 7); kb->ledflagstate = (arg & 7);
kb->default_ledflagstate = ((arg >> 4) & 7); kb->default_ledflagstate = ((arg >> 4) & 7);
set_leds(); set_leds();
spin_unlock_irqrestore(&led_lock, flags); }
return 0; return 0;
/* the ioctls below only set the lights, not the functions */ /* the ioctls below only set the lights, not the functions */
@@ -2182,11 +2114,8 @@ int vt_do_kdgkbmeta(unsigned int console)
*/ */
void vt_reset_unicode(unsigned int console) void vt_reset_unicode(unsigned int console)
{ {
unsigned long flags; guard(spinlock_irqsave)(&kbd_event_lock);
spin_lock_irqsave(&kbd_event_lock, flags);
kbd_table[console].kbdmode = default_utf8 ? VC_UNICODE : VC_XLATE; kbd_table[console].kbdmode = default_utf8 ? VC_UNICODE : VC_XLATE;
spin_unlock_irqrestore(&kbd_event_lock, flags);
} }
/** /**
@@ -2211,22 +2140,19 @@ int vt_get_shift_state(void)
void vt_reset_keyboard(unsigned int console) void vt_reset_keyboard(unsigned int console)
{ {
struct kbd_struct *kb = &kbd_table[console]; struct kbd_struct *kb = &kbd_table[console];
unsigned long flags;
spin_lock_irqsave(&kbd_event_lock, flags); guard(spinlock_irqsave)(&kbd_event_lock);
set_vc_kbd_mode(kb, VC_REPEAT); set_vc_kbd_mode(kb, VC_REPEAT);
clr_vc_kbd_mode(kb, VC_CKMODE); clr_vc_kbd_mode(kb, VC_CKMODE);
clr_vc_kbd_mode(kb, VC_APPLIC); clr_vc_kbd_mode(kb, VC_APPLIC);
clr_vc_kbd_mode(kb, VC_CRLF); clr_vc_kbd_mode(kb, VC_CRLF);
kb->lockstate = 0; kb->lockstate = 0;
kb->slockstate = 0; kb->slockstate = 0;
spin_lock(&led_lock); guard(spinlock)(&led_lock);
kb->ledmode = LED_SHOW_FLAGS; kb->ledmode = LED_SHOW_FLAGS;
kb->ledflagstate = kb->default_ledflagstate; kb->ledflagstate = kb->default_ledflagstate;
spin_unlock(&led_lock);
/* do not do set_leds here because this causes an endless tasklet loop /* do not do set_leds here because this causes an endless tasklet loop
when the keyboard hasn't been initialized yet */ when the keyboard hasn't been initialized yet */
spin_unlock_irqrestore(&kbd_event_lock, flags);
} }
/** /**
@@ -2256,11 +2182,9 @@ int vt_get_kbd_mode_bit(unsigned int console, int bit)
void vt_set_kbd_mode_bit(unsigned int console, int bit) void vt_set_kbd_mode_bit(unsigned int console, int bit)
{ {
struct kbd_struct *kb = &kbd_table[console]; struct kbd_struct *kb = &kbd_table[console];
unsigned long flags;
spin_lock_irqsave(&kbd_event_lock, flags); guard(spinlock_irqsave)(&kbd_event_lock);
set_vc_kbd_mode(kb, bit); set_vc_kbd_mode(kb, bit);
spin_unlock_irqrestore(&kbd_event_lock, flags);
} }
/** /**
@@ -2275,9 +2199,7 @@ void vt_set_kbd_mode_bit(unsigned int console, int bit)
void vt_clr_kbd_mode_bit(unsigned int console, int bit) void vt_clr_kbd_mode_bit(unsigned int console, int bit)
{ {
struct kbd_struct *kb = &kbd_table[console]; struct kbd_struct *kb = &kbd_table[console];
unsigned long flags;
spin_lock_irqsave(&kbd_event_lock, flags); guard(spinlock_irqsave)(&kbd_event_lock);
clr_vc_kbd_mode(kb, bit); clr_vc_kbd_mode(kb, bit);
spin_unlock_irqrestore(&kbd_event_lock, flags);
} }

View File

@@ -348,10 +348,11 @@ static int vc_selection(struct vc_data *vc, struct tiocl_selection *v,
return 0; return 0;
} }
v->xs = min_t(u16, v->xs - 1, vc->vc_cols - 1); /* Historically 0 => max value */
v->ys = min_t(u16, v->ys - 1, vc->vc_rows - 1); v->xs = umin(v->xs - 1, vc->vc_cols - 1);
v->xe = min_t(u16, v->xe - 1, vc->vc_cols - 1); v->ys = umin(v->ys - 1, vc->vc_rows - 1);
v->ye = min_t(u16, v->ye - 1, vc->vc_rows - 1); v->xe = umin(v->xe - 1, vc->vc_cols - 1);
v->ye = umin(v->ye - 1, vc->vc_rows - 1);
if (mouse_reporting() && (v->sel_mode & TIOCL_SELMOUSEREPORT)) { if (mouse_reporting() && (v->sel_mode & TIOCL_SELMOUSEREPORT)) {
mouse_report(tty, v->sel_mode & TIOCL_SELBUTTONMASK, v->xs, mouse_report(tty, v->sel_mode & TIOCL_SELBUTTONMASK, v->xs,

View File

@@ -4862,7 +4862,7 @@ static int con_font_get(struct vc_data *vc, struct console_font_op *op)
return ret; return ret;
} }
c = (font.width+7)/8 * vpitch * font.charcount; c = DIV_ROUND_UP(font.width, 8) * vpitch * font.charcount;
if (op->data && font.charcount > op->charcount) if (op->data && font.charcount > op->charcount)
return -ENOSPC; return -ENOSPC;
@@ -4894,7 +4894,7 @@ static int con_font_set(struct vc_data *vc, const struct console_font_op *op)
return -EINVAL; return -EINVAL;
if (vpitch < op->height) if (vpitch < op->height)
return -EINVAL; return -EINVAL;
size = (op->width+7)/8 * vpitch * op->charcount; size = DIV_ROUND_UP(op->width, 8) * vpitch * op->charcount;
if (size > max_font_size) if (size > max_font_size)
return -ENOSPC; return -ENOSPC;

View File

@@ -1,2 +1,3 @@
# SPDX-License-Identifier: GPL-2.0-only # SPDX-License-Identifier: GPL-2.0-only
tty_tiocsti_test
tty_tstamp_update tty_tstamp_update

View File

@@ -1,5 +1,9 @@
# SPDX-License-Identifier: GPL-2.0 # SPDX-License-Identifier: GPL-2.0
CFLAGS = -O2 -Wall CFLAGS = -O2 -Wall
TEST_GEN_PROGS := tty_tstamp_update TEST_GEN_PROGS := tty_tstamp_update tty_tiocsti_test
LDLIBS += -lcap
include ../lib.mk include ../lib.mk
# Add libcap for TIOCSTI test
$(OUTPUT)/tty_tiocsti_test: LDLIBS += -lcap

View File

@@ -0,0 +1 @@
CONFIG_LEGACY_TIOCSTI=y

View File

@@ -0,0 +1,650 @@
// SPDX-License-Identifier: GPL-2.0
/*
* TTY Tests - TIOCSTI
*
* Copyright © 2025 Abhinav Saxena <xandfury@gmail.com>
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <stdbool.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <pwd.h>
#include <termios.h>
#include <grp.h>
#include <sys/capability.h>
#include <sys/prctl.h>
#include <pty.h>
#include <utmp.h>
#include "../kselftest_harness.h"
enum test_type {
TEST_PTY_TIOCSTI_BASIC,
TEST_PTY_TIOCSTI_FD_PASSING,
/* other tests cases such as serial may be added. */
};
/*
* Test Strategy:
* - Basic tests: Use PTY with/without TIOCSCTTY (controlling terminal for
* current process)
* - FD passing tests: Child creates PTY, parent receives FD (demonstrates
* security issue)
*
* SECURITY VULNERABILITY DEMONSTRATION:
* FD passing tests show that TIOCSTI uses CURRENT process credentials, not
* opener credentials. This means privileged processes can be given FDs from
* unprivileged processes and successfully perform TIOCSTI operations that the
* unprivileged process couldn't do directly.
*
* Attack scenario:
* 1. Unprivileged process opens TTY (direct TIOCSTI fails due to lack of
* privileges)
* 2. Unprivileged process passes FD to privileged process via SCM_RIGHTS
* 3. Privileged process can use TIOCSTI on the FD (succeeds due to its
* privileges)
* 4. Result: Effective privilege escalation via file descriptor passing
*
* This matches the kernel logic in tiocsti():
* 1. if (!tty_legacy_tiocsti && !capable(CAP_SYS_ADMIN)) return -EIO;
* 2. if ((current->signal->tty != tty) && !capable(CAP_SYS_ADMIN))
* return -EPERM;
* Note: Both checks use capable() on CURRENT process, not FD opener!
*
* If the file credentials were also checked along with the capable() checks
* then the results for FD pass tests would be consistent with the basic tests.
*/
FIXTURE(tiocsti)
{
int pty_master_fd; /* PTY - for basic tests */
int pty_slave_fd;
bool has_pty;
bool initial_cap_sys_admin;
int original_legacy_tiocsti_setting;
bool can_modify_sysctl;
};
FIXTURE_VARIANT(tiocsti)
{
const enum test_type test_type;
const bool controlling_tty; /* true=current->signal->tty == tty */
const int legacy_tiocsti; /* 0=restricted, 1=permissive */
const bool requires_cap; /* true=with CAP_SYS_ADMIN, false=without */
const int expected_success; /* 0=success, -EIO/-EPERM=specific error */
};
/*
* Tests Controlling Terminal Variants (current->signal->tty == tty)
*
* TIOCSTI Test Matrix:
*
* | legacy_tiocsti | CAP_SYS_ADMIN | Expected Result | Error |
* |----------------|---------------|-----------------|-------|
* | 1 (permissive) | true | SUCCESS | - |
* | 1 (permissive) | false | SUCCESS | - |
* | 0 (restricted) | true | SUCCESS | - |
* | 0 (restricted) | false | FAILURE | -EIO |
*/
/* clang-format off */
FIXTURE_VARIANT_ADD(tiocsti, basic_pty_permissive_withcap) {
.test_type = TEST_PTY_TIOCSTI_BASIC,
.controlling_tty = true,
.legacy_tiocsti = 1,
.requires_cap = true,
.expected_success = 0,
};
FIXTURE_VARIANT_ADD(tiocsti, basic_pty_permissive_nocap) {
.test_type = TEST_PTY_TIOCSTI_BASIC,
.controlling_tty = true,
.legacy_tiocsti = 1,
.requires_cap = false,
.expected_success = 0,
};
FIXTURE_VARIANT_ADD(tiocsti, basic_pty_restricted_withcap) {
.test_type = TEST_PTY_TIOCSTI_BASIC,
.controlling_tty = true,
.legacy_tiocsti = 0,
.requires_cap = true,
.expected_success = 0,
};
FIXTURE_VARIANT_ADD(tiocsti, basic_pty_restricted_nocap) {
.test_type = TEST_PTY_TIOCSTI_BASIC,
.controlling_tty = true,
.legacy_tiocsti = 0,
.requires_cap = false,
.expected_success = -EIO, /* FAILURE: legacy restriction */
}; /* clang-format on */
/*
* Note for FD Passing Test Variants
* Since we're testing the scenario where an unprivileged process pass an FD
* to a privileged one, .requires_cap here means the caps of the child process.
* Not the parent; parent would always be privileged.
*/
/* clang-format off */
FIXTURE_VARIANT_ADD(tiocsti, fdpass_pty_permissive_withcap) {
.test_type = TEST_PTY_TIOCSTI_FD_PASSING,
.controlling_tty = true,
.legacy_tiocsti = 1,
.requires_cap = true,
.expected_success = 0,
};
FIXTURE_VARIANT_ADD(tiocsti, fdpass_pty_permissive_nocap) {
.test_type = TEST_PTY_TIOCSTI_FD_PASSING,
.controlling_tty = true,
.legacy_tiocsti = 1,
.requires_cap = false,
.expected_success = 0,
};
FIXTURE_VARIANT_ADD(tiocsti, fdpass_pty_restricted_withcap) {
.test_type = TEST_PTY_TIOCSTI_FD_PASSING,
.controlling_tty = true,
.legacy_tiocsti = 0,
.requires_cap = true,
.expected_success = 0,
};
FIXTURE_VARIANT_ADD(tiocsti, fdpass_pty_restricted_nocap) {
.test_type = TEST_PTY_TIOCSTI_FD_PASSING,
.controlling_tty = true,
.legacy_tiocsti = 0,
.requires_cap = false,
.expected_success = -EIO,
}; /* clang-format on */
/*
* Non-Controlling Terminal Variants (current->signal->tty != tty)
*
* TIOCSTI Test Matrix:
*
* | legacy_tiocsti | CAP_SYS_ADMIN | Expected Result | Error |
* |----------------|---------------|-----------------|-------|
* | 1 (permissive) | true | SUCCESS | - |
* | 1 (permissive) | false | FAILURE | -EPERM|
* | 0 (restricted) | true | SUCCESS | - |
* | 0 (restricted) | false | FAILURE | -EIO |
*/
/* clang-format off */
FIXTURE_VARIANT_ADD(tiocsti, basic_nopty_permissive_withcap) {
.test_type = TEST_PTY_TIOCSTI_BASIC,
.controlling_tty = false,
.legacy_tiocsti = 1,
.requires_cap = true,
.expected_success = 0,
};
FIXTURE_VARIANT_ADD(tiocsti, basic_nopty_permissive_nocap) {
.test_type = TEST_PTY_TIOCSTI_BASIC,
.controlling_tty = false,
.legacy_tiocsti = 1,
.requires_cap = false,
.expected_success = -EPERM,
};
FIXTURE_VARIANT_ADD(tiocsti, basic_nopty_restricted_withcap) {
.test_type = TEST_PTY_TIOCSTI_BASIC,
.controlling_tty = false,
.legacy_tiocsti = 0,
.requires_cap = true,
.expected_success = 0,
};
FIXTURE_VARIANT_ADD(tiocsti, basic_nopty_restricted_nocap) {
.test_type = TEST_PTY_TIOCSTI_BASIC,
.controlling_tty = false,
.legacy_tiocsti = 0,
.requires_cap = false,
.expected_success = -EIO,
};
FIXTURE_VARIANT_ADD(tiocsti, fdpass_nopty_permissive_withcap) {
.test_type = TEST_PTY_TIOCSTI_FD_PASSING,
.controlling_tty = false,
.legacy_tiocsti = 1,
.requires_cap = true,
.expected_success = 0,
};
FIXTURE_VARIANT_ADD(tiocsti, fdpass_nopty_permissive_nocap) {
.test_type = TEST_PTY_TIOCSTI_FD_PASSING,
.controlling_tty = false,
.legacy_tiocsti = 1,
.requires_cap = false,
.expected_success = -EPERM,
};
FIXTURE_VARIANT_ADD(tiocsti, fdpass_nopty_restricted_withcap) {
.test_type = TEST_PTY_TIOCSTI_FD_PASSING,
.controlling_tty = false,
.legacy_tiocsti = 0,
.requires_cap = true,
.expected_success = 0,
};
FIXTURE_VARIANT_ADD(tiocsti, fdpass_nopty_restricted_nocap) {
.test_type = TEST_PTY_TIOCSTI_FD_PASSING,
.controlling_tty = false,
.legacy_tiocsti = 0,
.requires_cap = false,
.expected_success = -EIO,
}; /* clang-format on */
/* Helper function to send FD via SCM_RIGHTS */
static int send_fd_via_socket(int socket_fd, int fd_to_send)
{
struct msghdr msg = { 0 };
struct cmsghdr *cmsg;
char cmsg_buf[CMSG_SPACE(sizeof(int))];
char dummy_data = 'F';
struct iovec iov = { .iov_base = &dummy_data, .iov_len = 1 };
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = cmsg_buf;
msg.msg_controllen = sizeof(cmsg_buf);
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
memcpy(CMSG_DATA(cmsg), &fd_to_send, sizeof(int));
return sendmsg(socket_fd, &msg, 0) < 0 ? -1 : 0;
}
/* Helper function to receive FD via SCM_RIGHTS */
static int recv_fd_via_socket(int socket_fd)
{
struct msghdr msg = { 0 };
struct cmsghdr *cmsg;
char cmsg_buf[CMSG_SPACE(sizeof(int))];
char dummy_data;
struct iovec iov = { .iov_base = &dummy_data, .iov_len = 1 };
int received_fd = -1;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = cmsg_buf;
msg.msg_controllen = sizeof(cmsg_buf);
if (recvmsg(socket_fd, &msg, 0) < 0)
return -1;
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
if (cmsg->cmsg_level == SOL_SOCKET &&
cmsg->cmsg_type == SCM_RIGHTS) {
memcpy(&received_fd, CMSG_DATA(cmsg), sizeof(int));
break;
}
}
return received_fd;
}
static inline bool has_cap_sys_admin(void)
{
cap_t caps = cap_get_proc();
if (!caps)
return false;
cap_flag_value_t cap_val;
bool has_cap = (cap_get_flag(caps, CAP_SYS_ADMIN, CAP_EFFECTIVE,
&cap_val) == 0) &&
(cap_val == CAP_SET);
cap_free(caps);
return has_cap;
}
/*
* Switch to non-root user and clear all capabilities
*/
static inline bool drop_all_privs(struct __test_metadata *_metadata)
{
/* Drop supplementary groups */
ASSERT_EQ(setgroups(0, NULL), 0);
/* Switch to non-root user */
ASSERT_EQ(setgid(1000), 0);
ASSERT_EQ(setuid(1000), 0);
/* Clear all capabilities */
cap_t empty = cap_init();
ASSERT_NE(empty, NULL);
ASSERT_EQ(cap_set_proc(empty), 0);
cap_free(empty);
/* Prevent privilege regain */
ASSERT_EQ(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0), 0);
/* Verify privilege drop */
ASSERT_FALSE(has_cap_sys_admin());
return true;
}
static inline int get_legacy_tiocsti_setting(struct __test_metadata *_metadata)
{
FILE *fp;
int value = -1;
fp = fopen("/proc/sys/dev/tty/legacy_tiocsti", "r");
if (!fp) {
/* legacy_tiocsti sysctl not available (kernel < 6.2) */
return -1;
}
if (fscanf(fp, "%d", &value) == 1 && fclose(fp) == 0) {
if (value < 0 || value > 1)
value = -1; /* Invalid value */
} else {
value = -1; /* Failed to parse */
}
return value;
}
static inline bool set_legacy_tiocsti_setting(struct __test_metadata *_metadata,
int value)
{
FILE *fp;
bool success = false;
/* Sanity-check the value */
ASSERT_GE(value, 0);
ASSERT_LE(value, 1);
/*
* Try to open for writing; if we lack permission, return false so
* the test harness will skip variants that need to change it
*/
fp = fopen("/proc/sys/dev/tty/legacy_tiocsti", "w");
if (!fp)
return false;
/* Write the new setting */
if (fprintf(fp, "%d\n", value) > 0 && fclose(fp) == 0)
success = true;
else
TH_LOG("Failed to write legacy_tiocsti: %s", strerror(errno));
return success;
}
/*
* TIOCSTI injection test function
* @tty_fd: TTY slave file descriptor to test TIOCSTI on
* Returns: 0 on success, -errno on failure
*/
static inline int test_tiocsti_injection(struct __test_metadata *_metadata,
int tty_fd)
{
int ret;
char inject_char = 'V';
errno = 0;
ret = ioctl(tty_fd, TIOCSTI, &inject_char);
return ret == 0 ? 0 : -errno;
}
/*
* Child process: test TIOCSTI directly with capability/controlling
* terminal setup
*/
static void run_basic_tiocsti_test(struct __test_metadata *_metadata,
FIXTURE_DATA(tiocsti) * self,
const FIXTURE_VARIANT(tiocsti) * variant)
{
/* Handle capability requirements */
if (self->initial_cap_sys_admin && !variant->requires_cap)
ASSERT_TRUE(drop_all_privs(_metadata));
if (variant->controlling_tty) {
/*
* Create new session and set PTY as
* controlling terminal
*/
pid_t sid = setsid();
ASSERT_GE(sid, 0);
ASSERT_EQ(ioctl(self->pty_slave_fd, TIOCSCTTY, 0), 0);
}
/*
* Validate test environment setup and verify final
* capability state matches expectation
* after potential drop.
*/
ASSERT_TRUE(self->has_pty);
ASSERT_EQ(has_cap_sys_admin(), variant->requires_cap);
/* Test TIOCSTI and validate result */
int result = test_tiocsti_injection(_metadata, self->pty_slave_fd);
/* Check against expected result from variant */
EXPECT_EQ(result, variant->expected_success);
_exit(0);
}
/*
* Child process: create PTY and then pass FD to parent via SCM_RIGHTS
*/
static void run_fdpass_tiocsti_test(struct __test_metadata *_metadata,
const FIXTURE_VARIANT(tiocsti) * variant,
int sockfd)
{
signal(SIGHUP, SIG_IGN);
/* Handle privilege dropping */
if (!variant->requires_cap && has_cap_sys_admin())
ASSERT_TRUE(drop_all_privs(_metadata));
/* Create child's PTY */
int child_master_fd, child_slave_fd;
ASSERT_EQ(openpty(&child_master_fd, &child_slave_fd, NULL, NULL, NULL),
0);
if (variant->controlling_tty) {
pid_t sid = setsid();
ASSERT_GE(sid, 0);
ASSERT_EQ(ioctl(child_slave_fd, TIOCSCTTY, 0), 0);
}
/* Test child's direct TIOCSTI for reference */
int direct_result = test_tiocsti_injection(_metadata, child_slave_fd);
EXPECT_EQ(direct_result, variant->expected_success);
/* Send FD to parent */
ASSERT_EQ(send_fd_via_socket(sockfd, child_slave_fd), 0);
/* Wait for parent completion signal */
char sync_byte;
ssize_t bytes_read = read(sockfd, &sync_byte, 1);
ASSERT_EQ(bytes_read, 1);
close(child_master_fd);
close(child_slave_fd);
close(sockfd);
_exit(0);
}
FIXTURE_SETUP(tiocsti)
{
/* Create PTY pair for basic tests */
self->has_pty = (openpty(&self->pty_master_fd, &self->pty_slave_fd,
NULL, NULL, NULL) == 0);
if (!self->has_pty) {
self->pty_master_fd = -1;
self->pty_slave_fd = -1;
}
self->initial_cap_sys_admin = has_cap_sys_admin();
self->original_legacy_tiocsti_setting =
get_legacy_tiocsti_setting(_metadata);
if (self->original_legacy_tiocsti_setting < 0)
SKIP(return,
"legacy_tiocsti sysctl not available (kernel < 6.2)");
/* Common skip conditions */
if (variant->test_type == TEST_PTY_TIOCSTI_BASIC && !self->has_pty)
SKIP(return, "PTY not available for controlling terminal test");
if (variant->test_type == TEST_PTY_TIOCSTI_FD_PASSING &&
!self->initial_cap_sys_admin)
SKIP(return, "FD Pass tests require CAP_SYS_ADMIN");
if (variant->requires_cap && !self->initial_cap_sys_admin)
SKIP(return, "Test requires initial CAP_SYS_ADMIN");
/* Test if we can modify the sysctl (requires appropriate privileges) */
self->can_modify_sysctl = set_legacy_tiocsti_setting(
_metadata, self->original_legacy_tiocsti_setting);
/* Sysctl setup based on variant */
if (self->can_modify_sysctl &&
self->original_legacy_tiocsti_setting != variant->legacy_tiocsti) {
if (!set_legacy_tiocsti_setting(_metadata,
variant->legacy_tiocsti))
SKIP(return, "Failed to set legacy_tiocsti sysctl");
} else if (!self->can_modify_sysctl &&
self->original_legacy_tiocsti_setting !=
variant->legacy_tiocsti)
SKIP(return, "legacy_tiocsti setting mismatch");
}
FIXTURE_TEARDOWN(tiocsti)
{
/*
* Backup restoration -
* each test should restore its own sysctl changes
*/
if (self->can_modify_sysctl) {
int current_value = get_legacy_tiocsti_setting(_metadata);
if (current_value != self->original_legacy_tiocsti_setting) {
TH_LOG("Backup: Restoring legacy_tiocsti from %d to %d",
current_value,
self->original_legacy_tiocsti_setting);
set_legacy_tiocsti_setting(
_metadata,
self->original_legacy_tiocsti_setting);
}
}
if (self->has_pty) {
if (self->pty_master_fd >= 0)
close(self->pty_master_fd);
if (self->pty_slave_fd >= 0)
close(self->pty_slave_fd);
}
}
TEST_F(tiocsti, test)
{
int status;
pid_t child_pid;
if (variant->test_type == TEST_PTY_TIOCSTI_BASIC) {
/* ===== BASIC TIOCSTI TEST ===== */
child_pid = fork();
ASSERT_GE(child_pid, 0);
/* Perform the actual test in the child process */
if (child_pid == 0)
run_basic_tiocsti_test(_metadata, self, variant);
} else {
/* ===== FD PASSING SECURITY TEST ===== */
int sockpair[2];
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sockpair), 0);
child_pid = fork();
ASSERT_GE(child_pid, 0);
if (child_pid == 0) {
/* Child process - create PTY and send FD */
close(sockpair[0]);
run_fdpass_tiocsti_test(_metadata, variant,
sockpair[1]);
}
/* Parent process - receive FD and test TIOCSTI */
close(sockpair[1]);
int received_fd = recv_fd_via_socket(sockpair[0]);
ASSERT_GE(received_fd, 0);
bool parent_has_cap = self->initial_cap_sys_admin;
TH_LOG("=== TIOCSTI FD Passing Test Context ===");
TH_LOG("legacy_tiocsti: %d, Parent CAP_SYS_ADMIN: %s, Child: %s",
variant->legacy_tiocsti, parent_has_cap ? "yes" : "no",
variant->requires_cap ? "kept" : "dropped");
/* SECURITY TEST: Try TIOCSTI with FD opened by child */
int result = test_tiocsti_injection(_metadata, received_fd);
/* Log security concern if demonstrated */
if (result == 0 && !variant->requires_cap) {
TH_LOG("*** SECURITY CONCERN DEMONSTRATED ***");
TH_LOG("Privileged parent can use TIOCSTI on FD from unprivileged child");
TH_LOG("This shows current process credentials are used, not opener credentials");
}
EXPECT_EQ(result, variant->expected_success)
{
TH_LOG("FD passing: expected error %d, got %d",
variant->expected_success, result);
}
/* Signal child completion */
char sync_byte = 'D';
ssize_t bytes_written = write(sockpair[0], &sync_byte, 1);
ASSERT_EQ(bytes_written, 1);
close(received_fd);
close(sockpair[0]);
}
/* Common child process cleanup for both test types */
ASSERT_EQ(waitpid(child_pid, &status, 0), child_pid);
if (WIFSIGNALED(status)) {
TH_LOG("Child terminated by signal %d", WTERMSIG(status));
ASSERT_FALSE(WIFSIGNALED(status))
{
TH_LOG("Child process failed assertion");
}
} else {
EXPECT_EQ(WEXITSTATUS(status), 0);
}
}
TEST_HARNESS_MAIN