diff options
Diffstat (limited to 'debian/patches-rt/0014-printk-nbcon-Implement-processing-in-port-lock-wrapp.patch')
-rw-r--r-- | debian/patches-rt/0014-printk-nbcon-Implement-processing-in-port-lock-wrapp.patch | 478 |
1 files changed, 478 insertions, 0 deletions
diff --git a/debian/patches-rt/0014-printk-nbcon-Implement-processing-in-port-lock-wrapp.patch b/debian/patches-rt/0014-printk-nbcon-Implement-processing-in-port-lock-wrapp.patch new file mode 100644 index 0000000000..edf62ea414 --- /dev/null +++ b/debian/patches-rt/0014-printk-nbcon-Implement-processing-in-port-lock-wrapp.patch @@ -0,0 +1,478 @@ +From: John Ogness <john.ogness@linutronix.de> +Date: Wed, 13 Sep 2023 08:35:23 +0000 +Subject: [PATCH 14/48] printk: nbcon: Implement processing in port->lock + wrapper +Origin: https://www.kernel.org/pub/linux/kernel/projects/rt/6.8/older/patches-6.8.2-rt11.tar.xz + +Currently the port->lock wrappers uart_port_lock(), +uart_port_unlock() (and their variants) only lock/unlock +the spin_lock. + +If the port is an nbcon console, the wrappers must also +acquire/release the console and mark the region as unsafe. This +allows general port->lock synchronization to be synchronized +with the nbcon console ownership. + +Introduce a new struct nbcon_drvdata within struct console that +provides the necessary components for the port lock wrappers to +acquire the nbcon console and track its ownership. + +Also introduce uart_port_set_cons() as a wrapper to set @cons +of a uart_port. The wrapper sets @cons under the port lock in +order to prevent @cons from disappearing while another context +owns the port lock via the port lock wrappers. + +Also cleanup the description of the console_srcu_read_flags() +function. It is used by the port lock wrappers to ensure a +console cannot be fully unregistered while another context +owns the port lock via the port lock wrappers. + +Signed-off-by: John Ogness <john.ogness@linutronix.de> +Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> +--- + drivers/tty/serial/8250/8250_core.c | 6 +- + drivers/tty/serial/amba-pl011.c | 2 + drivers/tty/serial/serial_core.c | 16 ++--- + include/linux/console.h | 57 ++++++++++++++++---- + include/linux/printk.h | 13 ++++ + include/linux/serial_core.h | 98 +++++++++++++++++++++++++++++++++++- + kernel/printk/nbcon.c | 52 +++++++++++++++++++ + 7 files changed, 219 insertions(+), 25 deletions(-) + +--- a/drivers/tty/serial/8250/8250_core.c ++++ b/drivers/tty/serial/8250/8250_core.c +@@ -627,11 +627,11 @@ static int univ8250_console_setup(struct + + port = &serial8250_ports[co->index].port; + /* link port to console */ +- port->cons = co; ++ uart_port_set_cons(port, co); + + retval = serial8250_console_setup(port, options, false); + if (retval != 0) +- port->cons = NULL; ++ uart_port_set_cons(port, NULL); + return retval; + } + +@@ -689,7 +689,7 @@ static int univ8250_console_match(struct + continue; + + co->index = i; +- port->cons = co; ++ uart_port_set_cons(port, co); + return serial8250_console_setup(port, options, true); + } + +--- a/drivers/tty/serial/amba-pl011.c ++++ b/drivers/tty/serial/amba-pl011.c +@@ -2488,7 +2488,7 @@ static int pl011_console_match(struct co + continue; + + co->index = i; +- port->cons = co; ++ uart_port_set_cons(port, co); + return pl011_console_setup(co, options); + } + +--- a/drivers/tty/serial/serial_core.c ++++ b/drivers/tty/serial/serial_core.c +@@ -3145,8 +3145,15 @@ static int serial_core_add_one_port(stru + state->uart_port = uport; + uport->state = state; + ++ /* ++ * If this port is in use as a console then the spinlock is already ++ * initialised. ++ */ ++ if (!uart_console_registered(uport)) ++ uart_port_spin_lock_init(uport); ++ + state->pm_state = UART_PM_STATE_UNDEFINED; +- uport->cons = drv->cons; ++ uart_port_set_cons(uport, drv->cons); + uport->minor = drv->tty_driver->minor_start + uport->line; + uport->name = kasprintf(GFP_KERNEL, "%s%d", drv->dev_name, + drv->tty_driver->name_base + uport->line); +@@ -3155,13 +3162,6 @@ static int serial_core_add_one_port(stru + goto out; + } + +- /* +- * If this port is in use as a console then the spinlock is already +- * initialised. +- */ +- if (!uart_console_registered(uport)) +- uart_port_spin_lock_init(uport); +- + if (uport->cons && uport->dev) + of_console_check(uport->dev->of_node, uport->cons->name, uport->line); + +--- a/include/linux/console.h ++++ b/include/linux/console.h +@@ -283,6 +283,25 @@ struct nbcon_write_context { + }; + + /** ++ * struct nbcon_drvdata - Data to allow nbcon acquire in non-print context ++ * @ctxt: The core console context ++ * @srcu_cookie: Storage for a console_srcu_lock cookie, if needed ++ * @owner_index: Storage for the owning console index, if needed ++ * @locked: Storage for the locked state, if needed ++ * ++ * All fields (except for @ctxt) are available exclusively to the driver to ++ * use as needed. They are not used by the printk subsystem. ++ */ ++struct nbcon_drvdata { ++ struct nbcon_context __private ctxt; ++ ++ /* reserved for driver use */ ++ int srcu_cookie; ++ short owner_index; ++ bool locked; ++}; ++ ++/** + * struct console - The console descriptor structure + * @name: The name of the console driver + * @write: Legacy write callback to output messages (Optional) +@@ -396,6 +415,21 @@ struct console { + + atomic_t __private nbcon_state; + atomic_long_t __private nbcon_seq; ++ ++ /** ++ * @nbcon_drvdata: ++ * ++ * Data for nbcon ownership tracking to allow acquiring nbcon consoles ++ * in non-printing contexts. ++ * ++ * Drivers may need to acquire nbcon consoles in non-printing ++ * contexts. This is achieved by providing a struct nbcon_drvdata. ++ * Then the driver can call nbcon_driver_acquire() and ++ * nbcon_driver_release(). The struct does not require any special ++ * initialization. ++ */ ++ struct nbcon_drvdata *nbcon_drvdata; ++ + struct printk_buffers *pbufs; + }; + +@@ -425,28 +459,29 @@ extern void console_list_unlock(void) __ + extern struct hlist_head console_list; + + /** +- * console_srcu_read_flags - Locklessly read the console flags ++ * console_srcu_read_flags - Locklessly read flags of a possibly registered ++ * console + * @con: struct console pointer of console to read flags from + * +- * This function provides the necessary READ_ONCE() and data_race() +- * notation for locklessly reading the console flags. The READ_ONCE() +- * in this function matches the WRITE_ONCE() when @flags are modified +- * for registered consoles with console_srcu_write_flags(). ++ * Locklessly reading @con->flags provides a consistent read value because ++ * there is at most one CPU modifying @con->flags and that CPU is using only ++ * read-modify-write operations to do so. + * +- * Only use this function to read console flags when locklessly +- * iterating the console list via srcu. ++ * Requires console_srcu_read_lock to be held, which implies that @con might ++ * be a registered console. If the caller is holding the console_list_lock or ++ * it is certain that the console is not registered, the caller may read ++ * @con->flags directly instead. + * + * Context: Any context. ++ * Return: The current value of the @con->flags field. + */ + static inline short console_srcu_read_flags(const struct console *con) + { + WARN_ON_ONCE(!console_srcu_read_lock_is_held()); + + /* +- * Locklessly reading console->flags provides a consistent +- * read value because there is at most one CPU modifying +- * console->flags and that CPU is using only read-modify-write +- * operations to do so. ++ * The READ_ONCE() matches the WRITE_ONCE() when @flags are modified ++ * for registered consoles with console_srcu_write_flags(). + */ + return data_race(READ_ONCE(con->flags)); + } +--- a/include/linux/printk.h ++++ b/include/linux/printk.h +@@ -9,6 +9,8 @@ + #include <linux/ratelimit_types.h> + #include <linux/once_lite.h> + ++struct console; ++ + extern const char linux_banner[]; + extern const char linux_proc_banner[]; + +@@ -193,6 +195,8 @@ void show_regs_print_info(const char *lo + extern asmlinkage void dump_stack_lvl(const char *log_lvl) __cold; + extern asmlinkage void dump_stack(void) __cold; + void printk_trigger_flush(void); ++extern void nbcon_driver_acquire(struct console *con); ++extern void nbcon_driver_release(struct console *con); + #else + static inline __printf(1, 0) + int vprintk(const char *s, va_list args) +@@ -272,6 +276,15 @@ static inline void dump_stack(void) + static inline void printk_trigger_flush(void) + { + } ++ ++static inline void nbcon_driver_acquire(struct console *con) ++{ ++} ++ ++static inline void nbcon_driver_release(struct console *con) ++{ ++} ++ + #endif + + bool this_cpu_in_panic(void); +--- a/include/linux/serial_core.h ++++ b/include/linux/serial_core.h +@@ -8,10 +8,13 @@ + #define LINUX_SERIAL_CORE_H + + #include <linux/bitops.h> ++#include <linux/bug.h> + #include <linux/compiler.h> + #include <linux/console.h> + #include <linux/interrupt.h> + #include <linux/circ_buf.h> ++#include <linux/lockdep.h> ++#include <linux/printk.h> + #include <linux/spinlock.h> + #include <linux/sched.h> + #include <linux/tty.h> +@@ -607,12 +610,90 @@ static inline void __uart_port_unlock_ir + } + + /** ++ * uart_port_set_cons - Safely set the @cons field for a uart ++ * @up: The uart port to set ++ * @con: The new console to set to ++ * ++ * This function must be used to set @up->cons. It uses the port lock to ++ * synchronize with the port lock wrappers in order to ensure that the console ++ * cannot change or disappear while another context is holding the port lock. ++ */ ++static inline void uart_port_set_cons(struct uart_port *up, struct console *con) ++{ ++ unsigned long flags; ++ ++ __uart_port_lock_irqsave(up, &flags); ++ up->cons = con; ++ __uart_port_unlock_irqrestore(up, flags); ++} ++ ++/* Only for internal port lock wrapper usage. */ ++static inline void __uart_port_nbcon_acquire(struct uart_port *up) ++{ ++ lockdep_assert_held_once(&up->lock); ++ ++ if (likely(!uart_console(up))) ++ return; ++ ++ if (up->cons->nbcon_drvdata) { ++ /* ++ * If @up->cons is registered, prevent it from fully ++ * unregistering until this context releases the nbcon. ++ */ ++ int cookie = console_srcu_read_lock(); ++ ++ /* Ensure console is registered and is an nbcon console. */ ++ if (!hlist_unhashed_lockless(&up->cons->node) && ++ (console_srcu_read_flags(up->cons) & CON_NBCON)) { ++ WARN_ON_ONCE(up->cons->nbcon_drvdata->locked); ++ ++ nbcon_driver_acquire(up->cons); ++ ++ /* ++ * Record @up->line to be used during release because ++ * @up->cons->index can change while the port and ++ * nbcon are locked. ++ */ ++ up->cons->nbcon_drvdata->owner_index = up->line; ++ up->cons->nbcon_drvdata->srcu_cookie = cookie; ++ up->cons->nbcon_drvdata->locked = true; ++ } else { ++ console_srcu_read_unlock(cookie); ++ } ++ } ++} ++ ++/* Only for internal port lock wrapper usage. */ ++static inline void __uart_port_nbcon_release(struct uart_port *up) ++{ ++ lockdep_assert_held_once(&up->lock); ++ ++ /* ++ * uart_console() cannot be used here because @up->cons->index might ++ * have changed. Check against @up->cons->nbcon_drvdata->owner_index ++ * instead. ++ */ ++ ++ if (unlikely(up->cons && ++ up->cons->nbcon_drvdata && ++ up->cons->nbcon_drvdata->locked && ++ up->cons->nbcon_drvdata->owner_index == up->line)) { ++ WARN_ON_ONCE(!up->cons->nbcon_drvdata->locked); ++ ++ up->cons->nbcon_drvdata->locked = false; ++ nbcon_driver_release(up->cons); ++ console_srcu_read_unlock(up->cons->nbcon_drvdata->srcu_cookie); ++ } ++} ++ ++/** + * uart_port_lock - Lock the UART port + * @up: Pointer to UART port structure + */ + static inline void uart_port_lock(struct uart_port *up) + { + spin_lock(&up->lock); ++ __uart_port_nbcon_acquire(up); + } + + /** +@@ -622,6 +703,7 @@ static inline void uart_port_lock(struct + static inline void uart_port_lock_irq(struct uart_port *up) + { + spin_lock_irq(&up->lock); ++ __uart_port_nbcon_acquire(up); + } + + /** +@@ -632,6 +714,7 @@ static inline void uart_port_lock_irq(st + static inline void uart_port_lock_irqsave(struct uart_port *up, unsigned long *flags) + { + spin_lock_irqsave(&up->lock, *flags); ++ __uart_port_nbcon_acquire(up); + } + + /** +@@ -642,7 +725,11 @@ static inline void uart_port_lock_irqsav + */ + static inline bool uart_port_trylock(struct uart_port *up) + { +- return spin_trylock(&up->lock); ++ if (!spin_trylock(&up->lock)) ++ return false; ++ ++ __uart_port_nbcon_acquire(up); ++ return true; + } + + /** +@@ -654,7 +741,11 @@ static inline bool uart_port_trylock(str + */ + static inline bool uart_port_trylock_irqsave(struct uart_port *up, unsigned long *flags) + { +- return spin_trylock_irqsave(&up->lock, *flags); ++ if (!spin_trylock_irqsave(&up->lock, *flags)) ++ return false; ++ ++ __uart_port_nbcon_acquire(up); ++ return true; + } + + /** +@@ -663,6 +754,7 @@ static inline bool uart_port_trylock_irq + */ + static inline void uart_port_unlock(struct uart_port *up) + { ++ __uart_port_nbcon_release(up); + spin_unlock(&up->lock); + } + +@@ -672,6 +764,7 @@ static inline void uart_port_unlock(stru + */ + static inline void uart_port_unlock_irq(struct uart_port *up) + { ++ __uart_port_nbcon_release(up); + spin_unlock_irq(&up->lock); + } + +@@ -682,6 +775,7 @@ static inline void uart_port_unlock_irq( + */ + static inline void uart_port_unlock_irqrestore(struct uart_port *up, unsigned long flags) + { ++ __uart_port_nbcon_release(up); + spin_unlock_irqrestore(&up->lock, flags); + } + +--- a/kernel/printk/nbcon.c ++++ b/kernel/printk/nbcon.c +@@ -3,9 +3,12 @@ + // Copyright (C) 2022 Intel, Thomas Gleixner + + #include <linux/kernel.h> ++#include <linux/bug.h> + #include <linux/console.h> + #include <linux/delay.h> ++#include <linux/export.h> + #include <linux/slab.h> ++#include <linux/string.h> + #include "internal.h" + /* + * Printk console printing implementation for consoles which does not depend +@@ -988,3 +991,52 @@ void nbcon_free(struct console *con) + + con->pbufs = NULL; + } ++ ++/** ++ * nbcon_driver_acquire - Acquire nbcon console and enter unsafe section ++ * @con: The nbcon console to acquire ++ * ++ * Context: Any context which could not be migrated to another CPU. ++ * ++ * Console drivers will usually use their own internal synchronization ++ * mechasism to synchronize between console printing and non-printing ++ * activities (such as setting baud rates). However, nbcon console drivers ++ * supporting atomic consoles may also want to mark unsafe sections when ++ * performing non-printing activities. ++ * ++ * This function acquires the nbcon console using priority NBCON_PRIO_NORMAL ++ * and marks it unsafe for handover/takeover. ++ * ++ * Console drivers using this function must have provided @nbcon_drvdata in ++ * their struct console, which is used to track ownership and state ++ * information. ++ */ ++void nbcon_driver_acquire(struct console *con) ++{ ++ struct nbcon_context *ctxt = &ACCESS_PRIVATE(con->nbcon_drvdata, ctxt); ++ ++ cant_migrate(); ++ ++ do { ++ do { ++ memset(ctxt, 0, sizeof(*ctxt)); ++ ctxt->console = con; ++ ctxt->prio = NBCON_PRIO_NORMAL; ++ } while (!nbcon_context_try_acquire(ctxt)); ++ ++ } while (!nbcon_context_enter_unsafe(ctxt)); ++} ++EXPORT_SYMBOL_GPL(nbcon_driver_acquire); ++ ++/** ++ * nbcon_driver_release - Exit unsafe section and release the nbcon console ++ * @con: The nbcon console acquired in nbcon_driver_acquire() ++ */ ++void nbcon_driver_release(struct console *con) ++{ ++ struct nbcon_context *ctxt = &ACCESS_PRIVATE(con->nbcon_drvdata, ctxt); ++ ++ if (nbcon_context_exit_unsafe(ctxt)) ++ nbcon_context_release(ctxt); ++} ++EXPORT_SYMBOL_GPL(nbcon_driver_release); |