Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

printk: nbcon: Allow unsafe write_atomic() for panic

There may be console drivers that have not yet figured out a way
to implement safe atomic printing (->write_atomic() callback).
These drivers could choose to only implement threaded printing
(->write_thread() callback), but then it is guaranteed that _no_
output will be printed during panic. Not even attempted.

As a result, developers may be tempted to implement unsafe
->write_atomic() callbacks and/or implement some sort of custom
deferred printing trickery to try to make it work. This goes
against the principle intention of the nbcon API as well as
endangers other nbcon drivers that are doing things correctly
(safely).

As a compromise, allow nbcon drivers to implement unsafe
->write_atomic() callbacks by providing a new console flag
CON_NBCON_ATOMIC_UNSAFE. When specified, the ->write_atomic()
callback for that console will _only_ be called during the
final "hope and pray" flush attempt at the end of a panic:
nbcon_atomic_flush_unsafe().

Signed-off-by: John Ogness <john.ogness@linutronix.de>
Link: https://lore.kernel.org/lkml/b2qps3uywhmjaym4mht2wpxul4yqtuuayeoq4iv4k3zf5wdgh3@tocu6c7mj4lt
Reviewed-by: Petr Mladek <pmladek@suse.com>
Link: https://lore.kernel.org/all/swdpckuwwlv3uiessmtnf2jwlx3jusw6u7fpk5iggqo4t2vdws@7rpjso4gr7qp/ [1]
Link: https://lore.kernel.org/all/20251103-fix_netpoll_aa-v4-1-4cfecdf6da7c@debian.org/ [2]
Link: https://patch.msgid.link/20251027161212.334219-2-john.ogness@linutronix.de
[pmladek@suse.com: Fix build with rework/nbcon-in-kdb branch.]
Signed-off-by: Petr Mladek <pmladek@suse.com>

authored by

John Ogness and committed by
Petr Mladek
187de7c2 62627bf0

+48 -18
+16 -3
include/linux/console.h
··· 186 186 * printing callbacks must not be called. 187 187 * @CON_NBCON: Console can operate outside of the legacy style console_lock 188 188 * constraints. 189 + * @CON_NBCON_ATOMIC_UNSAFE: The write_atomic() callback is not safe and is 190 + * therefore only used by nbcon_atomic_flush_unsafe(). 189 191 */ 190 192 enum cons_flags { 191 193 CON_PRINTBUFFER = BIT(0), ··· 199 197 CON_EXTENDED = BIT(6), 200 198 CON_SUSPENDED = BIT(7), 201 199 CON_NBCON = BIT(8), 200 + CON_NBCON_ATOMIC_UNSAFE = BIT(9), 202 201 }; 203 202 204 203 /** ··· 611 608 extern bool nbcon_enter_unsafe(struct nbcon_write_context *wctxt); 612 609 extern bool nbcon_exit_unsafe(struct nbcon_write_context *wctxt); 613 610 extern void nbcon_reacquire_nobuf(struct nbcon_write_context *wctxt); 611 + extern bool nbcon_allow_unsafe_takeover(void); 614 612 extern bool nbcon_kdb_try_acquire(struct console *con, 615 613 struct nbcon_write_context *wctxt); 616 614 extern void nbcon_kdb_release(struct nbcon_write_context *wctxt); ··· 631 627 return false; 632 628 633 629 if (flags & CON_NBCON) { 634 - /* The write_atomic() callback is optional. */ 635 - if (use_atomic && !con->write_atomic) 636 - return false; 630 + if (use_atomic) { 631 + /* The write_atomic() callback is optional. */ 632 + if (!con->write_atomic) 633 + return false; 634 + 635 + /* 636 + * An unsafe write_atomic() callback is only usable 637 + * when unsafe takeovers are allowed. 638 + */ 639 + if ((flags & CON_NBCON_ATOMIC_UNSAFE) && !nbcon_allow_unsafe_takeover()) 640 + return false; 641 + } 637 642 638 643 /* 639 644 * For the !use_atomic case, @printk_kthreads_running is not
+32 -15
kernel/printk/nbcon.c
··· 1408 1408 return NBCON_PRIO_NORMAL; 1409 1409 } 1410 1410 1411 + /* 1412 + * Track if it is allowed to perform unsafe hostile takeovers of console 1413 + * ownership. When true, console drivers might perform unsafe actions while 1414 + * printing. It is externally available via nbcon_allow_unsafe_takeover(). 1415 + */ 1416 + static bool panic_nbcon_allow_unsafe_takeover; 1417 + 1418 + /** 1419 + * nbcon_allow_unsafe_takeover - Check if unsafe console takeovers are allowed 1420 + * 1421 + * Return: True, when it is permitted to perform unsafe console printing 1422 + * 1423 + * This is also used by console_is_usable() to determine if it is allowed to 1424 + * call write_atomic() callbacks flagged as unsafe (CON_NBCON_ATOMIC_UNSAFE). 1425 + */ 1426 + bool nbcon_allow_unsafe_takeover(void) 1427 + { 1428 + return panic_on_this_cpu() && panic_nbcon_allow_unsafe_takeover; 1429 + } 1430 + 1411 1431 /** 1412 1432 * nbcon_legacy_emit_next_record - Print one record for an nbcon console 1413 1433 * in legacy contexts ··· 1498 1478 * write_atomic() callback 1499 1479 * @con: The nbcon console to flush 1500 1480 * @stop_seq: Flush up until this record 1501 - * @allow_unsafe_takeover: True, to allow unsafe hostile takeovers 1502 1481 * 1503 1482 * Return: 0 if @con was flushed up to @stop_seq Otherwise, error code on 1504 1483 * failure. ··· 1516 1497 * returned, it cannot be expected that the unfinalized record will become 1517 1498 * available. 1518 1499 */ 1519 - static int __nbcon_atomic_flush_pending_con(struct console *con, u64 stop_seq, 1520 - bool allow_unsafe_takeover) 1500 + static int __nbcon_atomic_flush_pending_con(struct console *con, u64 stop_seq) 1521 1501 { 1522 1502 struct nbcon_write_context wctxt = { }; 1523 1503 struct nbcon_context *ctxt = &ACCESS_PRIVATE(&wctxt, ctxt); ··· 1525 1507 ctxt->console = con; 1526 1508 ctxt->spinwait_max_us = 2000; 1527 1509 ctxt->prio = nbcon_get_default_prio(); 1528 - ctxt->allow_unsafe_takeover = allow_unsafe_takeover; 1510 + ctxt->allow_unsafe_takeover = nbcon_allow_unsafe_takeover(); 1529 1511 1530 1512 if (!nbcon_context_try_acquire(ctxt, false)) 1531 1513 return -EPERM; ··· 1556 1538 * write_atomic() callback 1557 1539 * @con: The nbcon console to flush 1558 1540 * @stop_seq: Flush up until this record 1559 - * @allow_unsafe_takeover: True, to allow unsafe hostile takeovers 1560 1541 * 1561 1542 * This will stop flushing before @stop_seq if another context has ownership. 1562 1543 * That context is then responsible for the flushing. Likewise, if new records 1563 1544 * are added while this context was flushing and there is no other context 1564 1545 * to handle the printing, this context must also flush those records. 1565 1546 */ 1566 - static void nbcon_atomic_flush_pending_con(struct console *con, u64 stop_seq, 1567 - bool allow_unsafe_takeover) 1547 + static void nbcon_atomic_flush_pending_con(struct console *con, u64 stop_seq) 1568 1548 { 1569 1549 struct console_flush_type ft; 1570 1550 unsigned long flags; ··· 1577 1561 */ 1578 1562 local_irq_save(flags); 1579 1563 1580 - err = __nbcon_atomic_flush_pending_con(con, stop_seq, allow_unsafe_takeover); 1564 + err = __nbcon_atomic_flush_pending_con(con, stop_seq); 1581 1565 1582 1566 local_irq_restore(flags); 1583 1567 ··· 1609 1593 * __nbcon_atomic_flush_pending - Flush all nbcon consoles using their 1610 1594 * write_atomic() callback 1611 1595 * @stop_seq: Flush up until this record 1612 - * @allow_unsafe_takeover: True, to allow unsafe hostile takeovers 1613 1596 */ 1614 - static void __nbcon_atomic_flush_pending(u64 stop_seq, bool allow_unsafe_takeover) 1597 + static void __nbcon_atomic_flush_pending(u64 stop_seq) 1615 1598 { 1616 1599 struct console *con; 1617 1600 int cookie; ··· 1628 1613 if (nbcon_seq_read(con) >= stop_seq) 1629 1614 continue; 1630 1615 1631 - nbcon_atomic_flush_pending_con(con, stop_seq, allow_unsafe_takeover); 1616 + nbcon_atomic_flush_pending_con(con, stop_seq); 1632 1617 } 1633 1618 console_srcu_read_unlock(cookie); 1634 1619 } ··· 1644 1629 */ 1645 1630 void nbcon_atomic_flush_pending(void) 1646 1631 { 1647 - __nbcon_atomic_flush_pending(prb_next_reserve_seq(prb), false); 1632 + __nbcon_atomic_flush_pending(prb_next_reserve_seq(prb)); 1648 1633 } 1649 1634 1650 1635 /** ··· 1656 1641 */ 1657 1642 void nbcon_atomic_flush_unsafe(void) 1658 1643 { 1659 - __nbcon_atomic_flush_pending(prb_next_reserve_seq(prb), true); 1644 + panic_nbcon_allow_unsafe_takeover = true; 1645 + __nbcon_atomic_flush_pending(prb_next_reserve_seq(prb)); 1646 + panic_nbcon_allow_unsafe_takeover = false; 1660 1647 } 1661 1648 1662 1649 /** ··· 1865 1848 * using the legacy loop. 1866 1849 */ 1867 1850 if (ft.nbcon_atomic) { 1868 - __nbcon_atomic_flush_pending_con(con, prb_next_reserve_seq(prb), false); 1851 + __nbcon_atomic_flush_pending_con(con, prb_next_reserve_seq(prb)); 1869 1852 } else if (ft.legacy_direct) { 1870 1853 if (console_trylock()) 1871 1854 console_unlock(); ··· 1935 1918 * The console was locked only when the write_atomic() callback 1936 1919 * was usable. 1937 1920 */ 1938 - __nbcon_atomic_flush_pending_con(ctxt->console, prb_next_reserve_seq(prb), false); 1921 + __nbcon_atomic_flush_pending_con(ctxt->console, prb_next_reserve_seq(prb)); 1939 1922 }