Skip to contentSkip to conversation

m68k: nfcon: don't call console_is_registered in nfcon_device

PATCH In review Andreas Schwab 4 participants 6 messages Aug 15 – Aug 20

Subsystems (4)
lore
Share & tools
lore
#0 lore raw

PATCH 1/1 m68k: nfcon: don't call console_is_registered in nfcon_device

Since 7c2af0f634f1 ("tty: tty_io: use console_list_lock for list synchronization") show_cons_active calls the device method under the console_list_lock, but console_is_registered tries to acquire console_list_lock as well, causing a deadlock. It should not be necessary to check console_is_registered here since the function should not be called in the fist place when the console is not registered.

Fixes 7c2af0f634f1 ("tty: tty_io: use console_list_lock for list synchronization")
---
 arch/m68k/emu/nfcon.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
arch/m68k/emu/nfcon.c +1 -1
index d41260672e24..f4657d9f4f15 100644
@@ -49,7 +49,7 @@ static void nfcon_write(struct console *con, const char *str,
 static struct tty_driver *nfcon_device(struct console *con, int *index)
 {
 	*index = 0;
-	return console_is_registered(con) ? nfcon_tty_driver : NULL;
+	return nfcon_tty_driver;
 }
 
 static struct console nf_console = {
Signature · 7 lines
2.55.0


-- 
Andreas Schwab, [email protected]
GPG Key fingerprint = 7578 EB47 D4E5 4D69 2510  2552 DF73 E780 A9DA AEC1
"And now for something completely different."
maintainer Reviewed-by on v1 1/1
#1 lore raw

Hi Andreas,

CC john, petr, serial

Thanks for your patch!

Quoted 6 lines · Andreas Schwab

Since 7c2af0f634f1 ("tty: tty_io: use console_list_lock for list synchronization") show_cons_active calls the device method under the console_list_lock, but console_is_registered tries to acquire console_list_lock as well, causing a deadlock. It should not be necessary to check console_is_registered here since the function should not be called in the fist place when the console is not registered.

Nice catch!

Quoted 2 lines
Fixes 7c2af0f634f1 ("tty: tty_io: use console_list_lock for list synchronization")

i.e. will queue in the m68k tree for v7.3.

arch/m68k/emu/nfcon.c static void nfcon_write(struct console *con, const char *str, · line 52

Quoted 7 lines
arch/m68k/emu/nfcon.c +0 -1
@@ -49,7 +49,7 @@ static void nfcon_write(struct console *con, const char *str,
 static struct tty_driver *nfcon_device(struct console *con, int *index)
 {
        *index = 0;
-       return console_is_registered(con) ? nfcon_tty_driver : NULL;

Interestingly, that call to console_is_registered() was added in commit de61a1a3a0830710 ("tty: nfcon: use console_is_registered()"), i.e. in the same series that added the extra locking...

arch/m68k/emu/nfcon.c static void nfcon_write(struct console *con, const char *str, · line 55

Quoted 4 lines
arch/m68k/emu/nfcon.c +1 -0
+       return nfcon_tty_driver;
 }
 
 static struct console nf_console = {

Gr{oetje,eeting}s,

Geert

Signature · 5 lines
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- [email protected]

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
                                -- Linus Torvalds
maintainer Reviewed-by
#2 lore raw
Quoted 8 lines · Geert Uytterhoeven
Quoted 6 lines · Andreas Schwab

Since 7c2af0f634f1 ("tty: tty_io: use console_list_lock for list synchronization") show_cons_active calls the device method under the console_list_lock, but console_is_registered tries to acquire console_list_lock as well, causing a deadlock. It should not be necessary to check console_is_registered here since the function should not be called in the fist place when the console is not registered.

Quoted 6 lines

Nice catch!

Quoted 2 lines
Fixes 7c2af0f634f1 ("tty: tty_io: use console_list_lock for list synchronization")

FWIW:

maintainer
#3 lore raw
In reply to Geert Uytterhoeven
Quoted 7 lines · Geert Uytterhoeven

Hi Andreas,

CC john, petr, serial

Thanks for your patch!

On Sat, 15 Aug 2026 at 09:33, Andreas Schwab <[email protected]> wrote:

Quoted 8 lines
Quoted 6 lines

Since 7c2af0f634f1 ("tty: tty_io: use console_list_lock for list synchronization") show_cons_active calls the device method under the console_list_lock, but console_is_registered tries to acquire console_list_lock as well, causing a deadlock. It should not be necessary to check console_is_registered here since the function should not be called in the fist place when the console is not registered.

Nice catch!

+1

Quoted 6 lines
Quoted 2 lines
Fixes 7c2af0f634f1 ("tty: tty_io: use console_list_lock for list synchronization")

i.e. will queue in the m68k tree for v7.3.

Quoted 11 lines

arch/m68k/emu/nfcon.c static void nfcon_write(struct console *con, const char *str, · line 52

Quoted 7 lines
arch/m68k/emu/nfcon.c +0 -1
@@ -49,7 +49,7 @@ static void nfcon_write(struct console *con, const char *str,
 static struct tty_driver *nfcon_device(struct console *con, int *index)
 {
        *index = 0;
-       return console_is_registered(con) ? nfcon_tty_driver : NULL;

Interestingly, that call to console_is_registered() was added in commit de61a1a3a0830710 ("tty: nfcon: use console_is_registered()"), i.e. in the same series that added the extra locking...

Sigh, we clearly did not check the callers of this function properly at that time :/

Quoted 1 line

arch/m68k/emu/nfcon.c static void nfcon_write(struct console *con, const char *str, · line 52

Quoted 1 line
arch/m68k/emu/nfcon.c +1 -0
+       return nfcon_tty_driver;

Honestly, I do not feel comfortable with removing the check completely.

The commit de61a1a3a083071 ("tty: nfcon: use console_is_registered()" used console_is_registered() instead of (con->flags & CON_ENABLED). And CON_ENABLED is historically cleared when the console is suspended, see console_suspend().

Is is OK to return a valid struct tty_driver for a non-registered or suspended console for all con->device() callers?

I have found only 4 callers:

  $> git grep "\->device("
  drivers/tty/serial/kgdboc.c:            if (cons->device && cons->device(cons, &idx) == p &&
  drivers/tty/tty_io.c:           struct tty_driver *drv = cs[i]->device(cs[i], &index);
  fs/proc/consoles.c:             driver = con->device(con, &index);
  kernel/printk/printk.c:         driver = c->device(c, index);

These are:

+ configure_kgdboc() in drivers/tty/serial/kgdboc.c
+ show_cons_active() in drivers/tty/tty_io.c
+ show_console_dev() in fs/proc/consoles.c
+ console_device() in kernel/printk/printk.c

, where

+ configure_kgdboc() is used during the system initialization when the console should not be suspended

+ show_cons_active() and show_console_dev() are questionable. Maybe, we should not advertise the device to the userspace when it is suspended.

+ console_device() is used in tty_kopen() -> tty_lookup_driver(). Also here we likely should not allow to open a suspended device.

Let's take a step back:

We should define the locking context for con->device() callback. It seems that it is called under console_lock() and console_srcu_read_lock() in:

+ configure_kgdboc()
+ console_device()

and under console_list_lock() and console_lock() in

+ show_console_dev()
+ show_cons_active()

I would put aside the console_lock(). We are trying to obsolete it. So it is down to

console_srcu_read_lock() vs console_list_lock()

Both of them look reasonable. So, I think about adding a variant of console_is_registered_srcu_read_locked() which would work with both locking. Something like:

arch/m68k/emu/nfcon.c +1 -1
index d41260672e24..d211b6c4da26 100644
@@ -49,7 +49,7 @@ static void nfcon_write(struct console *con, const char *str,
 static struct tty_driver *nfcon_device(struct console *con, int *index)
 {
 	*index = 0;
-	return console_is_registered(con) ? nfcon_tty_driver : NULL;
+	return console_is_registered_srcu_read_locked(con) ? nfcon_tty_driver : NULL;
 }
 
 static struct console nf_console = {
include/linux/console.h +19 -1
index d624200cfc17..b90527db4eca 100644
@@ -311,7 +311,7 @@ struct nbcon_write_context {
  * @name:		The name of the console driver
  * @write:		Legacy write callback to output messages (Optional)
  * @read:		Read callback for console input (Optional)
- * @device:		The underlying TTY device driver (Optional)
+ * @device:		The underlying TTY device driver (Optional) [1]
  * @unblank:		Callback to unblank the console (Optional)
  * @setup:		Callback for initializing the console (Optional)
  * @exit:		Callback for teardown of the console (Optional)
@@ -334,6 +334,9 @@ struct nbcon_write_context {
  * @kthread:		Printer kthread for this console
  * @rcuwait:		RCU-safe wait object for @kthread waking
  * @irq_work:		Defer @kthread waking to IRQ work context
+ *
+ * [1] The @device callback must be called under either console_list_lock()
+ *     or console_srcu_read_lock().
  */
 struct console {
 	char			name[16];
@@ -473,8 +476,13 @@ struct console {
 };
 
 #ifdef CONFIG_LOCKDEP
+extern bool console_list_lock_is_held(void);
 extern void lockdep_assert_console_list_lock_held(void);
 #else
+static inline bool console_list_lock_is_held(void)
+{
+	return 1;
+}
 static inline void lockdep_assert_console_list_lock_held(void)
 {
 }
@@ -555,6 +563,16 @@ static inline bool console_is_registered_locked(const struct console *con)
 	return !hlist_unhashed(&con->node);
 }
 
+/*
+ * Variant of console_is_registered() when either the console_srcu_read_lock
+ * or console_list_lock is held.
+*/
+static inline bool console_is_registered_srcu_read_locked(const struct console *con)
+{
+	lockdep_assert(console_srcu_read_lock_is_held() || console_list_lock_is_held());
+	return !hlist_unhashed(&con->node);
+}
+
 /*
  * console_is_registered - Check if the console is registered
  * @con:	struct console pointer of console to check
kernel/printk/printk.c +6 -0
index 2fe9a963c823..9b632b91f096 100644
@@ -109,6 +109,12 @@ static struct lockdep_map console_lock_dep_map = {
 	.name = "console_lock"
 };
 
+bool console_list_lock_is_held(void)
+{
+	return lockdep_is_held(&console_mutex);
+}
+EXPORT_SYMBOL(console_list_lock_is_held);
+
 void lockdep_assert_console_list_lock_held(void)
 {
 	lockdep_assert_held(&console_mutex);

Alternative solution would be to require the srcu locking. IMHO, it should be perfectly fine to use the srcu_read_lock in both show_console_dev() and show_cons_active().

What do you think, please?
John?

Best Regards,
Petr

maintainer
#4 lore raw
Quoted 17 lines · Petr Mladek
Quoted 11 lines

arch/m68k/emu/nfcon.c static void nfcon_write(struct console *con, const char *str, · line 52

Quoted 7 lines
arch/m68k/emu/nfcon.c +0 -1
@@ -49,7 +49,7 @@ static void nfcon_write(struct console *con, const char *str,
 static struct tty_driver *nfcon_device(struct console *con, int *index)
 {
        *index = 0;
-       return console_is_registered(con) ? nfcon_tty_driver : NULL;

Interestingly, that call to console_is_registered() was added in commit de61a1a3a0830710 ("tty: nfcon: use console_is_registered()"), i.e. in the same series that added the extra locking...

Sigh, we clearly did not check the callers of this function properly at that time :/

Quoted 1 line

arch/m68k/emu/nfcon.c static void nfcon_write(struct console *con, const char *str, · line 52

Quoted 1 line
arch/m68k/emu/nfcon.c +1 -0
+       return nfcon_tty_driver;
Quoted 7 lines

Honestly, I do not feel comfortable with removing the check completely.

The commit de61a1a3a083071 ("tty: nfcon: use console_is_registered()" used console_is_registered() instead of (con->flags & CON_ENABLED). And CON_ENABLED is historically cleared when the console is suspended, see console_suspend().

Since the NatFeat console driver never implemented suspend/resume callbacks, I assume it was intended to identify if the console was registered.

Quoted 23 lines

Is is OK to return a valid struct tty_driver for a non-registered or suspended console for all con->device() callers?

I have found only 4 callers:

  $> git grep "\->device("
  drivers/tty/serial/kgdboc.c:            if (cons->device && cons->device(cons, &idx) == p &&
  drivers/tty/tty_io.c:           struct tty_driver *drv = cs[i]->device(cs[i], &index);
  fs/proc/consoles.c:             driver = con->device(con, &index);
  kernel/printk/printk.c:         driver = c->device(c, index);

These are:

+ configure_kgdboc() in drivers/tty/serial/kgdboc.c
+ show_cons_active() in drivers/tty/tty_io.c
+ show_console_dev() in fs/proc/consoles.c
+ console_device() in kernel/printk/printk.c

, where

+ configure_kgdboc() is used during the system initialization when the console should not be suspended

Quoted 6 lines

+ show_cons_active() and show_console_dev() are questionable. Maybe, we should not advertise the device to the userspace when it is suspended.

+ console_device() is used in tty_kopen() -> tty_lookup_driver(). Also here we likely should not allow to open a suspended device.

All of these callbacks are done when the console is guaranteed to be registered (or not yet fully unregistered).

Quoted 109 lines

Let's take a step back:

We should define the locking context for con->device() callback. It seems that it is called under console_lock() and console_srcu_read_lock() in:

+ configure_kgdboc()
+ console_device()

and under console_list_lock() and console_lock() in

+ show_console_dev()
+ show_cons_active()

I would put aside the console_lock(). We are trying to obsolete it. So it is down to

console_srcu_read_lock() vs console_list_lock()

Both of them look reasonable. So, I think about adding a variant of console_is_registered_srcu_read_locked() which would work with both locking. Something like:

arch/m68k/emu/nfcon.c +1 -1
index d41260672e24..d211b6c4da26 100644
@@ -49,7 +49,7 @@ static void nfcon_write(struct console *con, const char *str,
 static struct tty_driver *nfcon_device(struct console *con, int *index)
 {
 	*index = 0;
-	return console_is_registered(con) ? nfcon_tty_driver : NULL;
+	return console_is_registered_srcu_read_locked(con) ? nfcon_tty_driver : NULL;
 }
 
 static struct console nf_console = {
include/linux/console.h +19 -1
index d624200cfc17..b90527db4eca 100644
@@ -311,7 +311,7 @@ struct nbcon_write_context {
  * @name:		The name of the console driver
  * @write:		Legacy write callback to output messages (Optional)
  * @read:		Read callback for console input (Optional)
- * @device:		The underlying TTY device driver (Optional)
+ * @device:		The underlying TTY device driver (Optional) [1]
  * @unblank:		Callback to unblank the console (Optional)
  * @setup:		Callback for initializing the console (Optional)
  * @exit:		Callback for teardown of the console (Optional)
@@ -334,6 +334,9 @@ struct nbcon_write_context {
  * @kthread:		Printer kthread for this console
  * @rcuwait:		RCU-safe wait object for @kthread waking
  * @irq_work:		Defer @kthread waking to IRQ work context
+ *
+ * [1] The @device callback must be called under either console_list_lock()
+ *     or console_srcu_read_lock().
  */
 struct console {
 	char			name[16];
@@ -473,8 +476,13 @@ struct console {
 };
 
 #ifdef CONFIG_LOCKDEP
+extern bool console_list_lock_is_held(void);
 extern void lockdep_assert_console_list_lock_held(void);
 #else
+static inline bool console_list_lock_is_held(void)
+{
+	return 1;
+}
 static inline void lockdep_assert_console_list_lock_held(void)
 {
 }
@@ -555,6 +563,16 @@ static inline bool console_is_registered_locked(const struct console *con)
 	return !hlist_unhashed(&con->node);
 }
 
+/*
+ * Variant of console_is_registered() when either the console_srcu_read_lock
+ * or console_list_lock is held.
+*/
+static inline bool console_is_registered_srcu_read_locked(const struct console *con)
+{
+	lockdep_assert(console_srcu_read_lock_is_held() || console_list_lock_is_held());
+	return !hlist_unhashed(&con->node);
+}
+
 /*
  * console_is_registered - Check if the console is registered
  * @con:	struct console pointer of console to check
kernel/printk/printk.c +6 -0
index 2fe9a963c823..9b632b91f096 100644
@@ -109,6 +109,12 @@ static struct lockdep_map console_lock_dep_map = {
 	.name = "console_lock"
 };
 
+bool console_list_lock_is_held(void)
+{
+	return lockdep_is_held(&console_mutex);
+}
+EXPORT_SYMBOL(console_list_lock_is_held);
+
 void lockdep_assert_console_list_lock_held(void)
 {
 	lockdep_assert_held(&console_mutex);
Quoted 6 lines

Alternative solution would be to require the srcu locking. IMHO, it should be perfectly fine to use the srcu_read_lock in both show_console_dev() and show_cons_active().

What do you think, please?
John?

I do not think any of that is necessary. I think it is enough to allow the ->device() callback to assume it is registered. I see no reason why the _callback_ should ever need to take the console_list_lock or the console_srcu_read_lock.

John

maintainer Reviewed-by
#5 lore raw
Quoted 21 lines · John Ogness
Quoted 20 lines · Petr Mladek
Quoted 11 lines

arch/m68k/emu/nfcon.c static void nfcon_write(struct console *con, const char *str, · line 52

Quoted 7 lines
arch/m68k/emu/nfcon.c +0 -1
@@ -49,7 +49,7 @@ static void nfcon_write(struct console *con, const char *str,
 static struct tty_driver *nfcon_device(struct console *con, int *index)
 {
        *index = 0;
-       return console_is_registered(con) ? nfcon_tty_driver : NULL;

Interestingly, that call to console_is_registered() was added in commit de61a1a3a0830710 ("tty: nfcon: use console_is_registered()"), i.e. in the same series that added the extra locking...

Sigh, we clearly did not check the callers of this function properly at that time :/

Quoted 1 line

arch/m68k/emu/nfcon.c static void nfcon_write(struct console *con, const char *str, · line 52

Quoted 1 line
arch/m68k/emu/nfcon.c +1 -0
+       return nfcon_tty_driver;

Honestly, I do not feel comfortable with removing the check completely.

Quoted 8 lines
Quoted 4 lines

The commit de61a1a3a083071 ("tty: nfcon: use console_is_registered()" used console_is_registered() instead of (con->flags & CON_ENABLED). And CON_ENABLED is historically cleared when the console is suspended, see console_suspend().

Since the NatFeat console driver never implemented suspend/resume callbacks, I assume it was intended to identify if the console was registered.

Good point!

Quoted 27 lines
Quoted 27 lines

Is is OK to return a valid struct tty_driver for a non-registered or suspended console for all con->device() callers?

I have found only 4 callers:

  $> git grep "\->device("
  drivers/tty/serial/kgdboc.c:            if (cons->device && cons->device(cons, &idx) == p &&
  drivers/tty/tty_io.c:           struct tty_driver *drv = cs[i]->device(cs[i], &index);
  fs/proc/consoles.c:             driver = con->device(con, &index);
  kernel/printk/printk.c:         driver = c->device(c, index);

These are:

+ configure_kgdboc() in drivers/tty/serial/kgdboc.c
+ show_cons_active() in drivers/tty/tty_io.c
+ show_console_dev() in fs/proc/consoles.c
+ console_device() in kernel/printk/printk.c

, where

+ configure_kgdboc() is used during the system initialization when the console should not be suspended

+ show_cons_active() and show_console_dev() are questionable. Maybe, we should not advertise the device to the userspace when it is suspended.

Quoted 5 lines
Quoted 2 lines

+ console_device() is used in tty_kopen() -> tty_lookup_driver(). Also here we likely should not allow to open a suspended device.

All of these callbacks are done when the console is guaranteed to be registered (or not yet fully unregistered).

Ditto.

Quoted 113 lines
Quoted 113 lines

Let's take a step back:

We should define the locking context for con->device() callback. It seems that it is called under console_lock() and console_srcu_read_lock() in:

+ configure_kgdboc()
+ console_device()

and under console_list_lock() and console_lock() in

+ show_console_dev()
+ show_cons_active()

I would put aside the console_lock(). We are trying to obsolete it. So it is down to

console_srcu_read_lock() vs console_list_lock()

Both of them look reasonable. So, I think about adding a variant of console_is_registered_srcu_read_locked() which would work with both locking. Something like:

arch/m68k/emu/nfcon.c +1 -1
index d41260672e24..d211b6c4da26 100644
@@ -49,7 +49,7 @@ static void nfcon_write(struct console *con, const char *str,
 static struct tty_driver *nfcon_device(struct console *con, int *index)
 {
 	*index = 0;
-	return console_is_registered(con) ? nfcon_tty_driver : NULL;
+	return console_is_registered_srcu_read_locked(con) ? nfcon_tty_driver : NULL;
 }
 
 static struct console nf_console = {
include/linux/console.h +19 -1
index d624200cfc17..b90527db4eca 100644
@@ -311,7 +311,7 @@ struct nbcon_write_context {
  * @name:		The name of the console driver
  * @write:		Legacy write callback to output messages (Optional)
  * @read:		Read callback for console input (Optional)
- * @device:		The underlying TTY device driver (Optional)
+ * @device:		The underlying TTY device driver (Optional) [1]
  * @unblank:		Callback to unblank the console (Optional)
  * @setup:		Callback for initializing the console (Optional)
  * @exit:		Callback for teardown of the console (Optional)
@@ -334,6 +334,9 @@ struct nbcon_write_context {
  * @kthread:		Printer kthread for this console
  * @rcuwait:		RCU-safe wait object for @kthread waking
  * @irq_work:		Defer @kthread waking to IRQ work context
+ *
+ * [1] The @device callback must be called under either console_list_lock()
+ *     or console_srcu_read_lock().
  */
 struct console {
 	char			name[16];
@@ -473,8 +476,13 @@ struct console {
 };
 
 #ifdef CONFIG_LOCKDEP
+extern bool console_list_lock_is_held(void);
 extern void lockdep_assert_console_list_lock_held(void);
 #else
+static inline bool console_list_lock_is_held(void)
+{
+	return 1;
+}
 static inline void lockdep_assert_console_list_lock_held(void)
 {
 }
@@ -555,6 +563,16 @@ static inline bool console_is_registered_locked(const struct console *con)
 	return !hlist_unhashed(&con->node);
 }
 
+/*
+ * Variant of console_is_registered() when either the console_srcu_read_lock
+ * or console_list_lock is held.
+*/
+static inline bool console_is_registered_srcu_read_locked(const struct console *con)
+{
+	lockdep_assert(console_srcu_read_lock_is_held() || console_list_lock_is_held());
+	return !hlist_unhashed(&con->node);
+}
+
 /*
  * console_is_registered - Check if the console is registered
  * @con:	struct console pointer of console to check
kernel/printk/printk.c +6 -0
index 2fe9a963c823..9b632b91f096 100644
@@ -109,6 +109,12 @@ static struct lockdep_map console_lock_dep_map = {
 	.name = "console_lock"
 };
 
+bool console_list_lock_is_held(void)
+{
+	return lockdep_is_held(&console_mutex);
+}
+EXPORT_SYMBOL(console_list_lock_is_held);
+
 void lockdep_assert_console_list_lock_held(void)
 {
 	lockdep_assert_held(&console_mutex);

Alternative solution would be to require the srcu locking. IMHO, it should be perfectly fine to use the srcu_read_lock in both show_console_dev() and show_cons_active().

Quoted 7 lines
Quoted 2 lines

What do you think, please?
John?

I do not think any of that is necessary. I think it is enough to allow the ->device() callback to assume it is registered. I see no reason why the _callback_ should ever need to take the console_list_lock or the console_srcu_read_lock.

Fair enough. I see that other con->device() callbacks just return the pointer without checking anything.

So, the fix looks good after all.

Best Regards,
Petr