2004 lines · 60.4 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only// Copyright (C) 2022 Linutronix GmbH, John Ogness// Copyright (C) 2022 Intel, Thomas Gleixner #include <linux/atomic.h>#include <linux/bug.h>#include <linux/console.h>#include <linux/delay.h>#include <linux/errno.h>#include <linux/export.h>#include <linux/init.h>#include <linux/irqflags.h>#include <linux/kdb.h>#include <linux/kthread.h>#include <linux/minmax.h>#include <linux/panic.h>#include <linux/percpu.h>#include <linux/preempt.h>#include <linux/slab.h>#include <linux/smp.h>#include <linux/stddef.h>#include <linux/string.h>#include <linux/types.h>#include "internal.h"#include "printk_ringbuffer.h"/* * Printk console printing implementation for consoles which does not depend * on the legacy style console_lock mechanism. * * The state of the console is maintained in the "nbcon_state" atomic * variable. * * The console is locked when: * *   - The 'prio' field contains the priority of the context that owns the *     console. Only higher priority contexts are allowed to take over the *     lock. A value of 0 (NBCON_PRIO_NONE) means the console is not locked. * *   - The 'cpu' field denotes on which CPU the console is locked. It is used *     to prevent busy waiting on the same CPU. Also it informs the lock owner *     that it has lost the lock in a more complex scenario when the lock was *     taken over by a higher priority context, released, and taken on another *     CPU with the same priority as the interrupted owner. * * The acquire mechanism uses a few more fields: * *   - The 'req_prio' field is used by the handover approach to make the *     current owner aware that there is a context with a higher priority *     waiting for the friendly handover. * *   - The 'unsafe' field allows to take over the console in a safe way in the *     middle of emitting a message. The field is set only when accessing some *     shared resources or when the console device is manipulated. It can be *     cleared, for example, after emitting one character when the console *     device is in a consistent state. * *   - The 'unsafe_takeover' field is set when a hostile takeover took the *     console in an unsafe state. The console will stay in the unsafe state *     until re-initialized. * * The acquire mechanism uses three approaches: * *   1) Direct acquire when the console is not owned or is owned by a lower *      priority context and is in a safe state. * *   2) Friendly handover mechanism uses a request/grant handshake. It is used *      when the current owner has lower priority and the console is in an *      unsafe state. * *      The requesting context: * *        a) Sets its priority into the 'req_prio' field. * *        b) Waits (with a timeout) for the owning context to unlock the *           console. * *        c) Takes the lock and clears the 'req_prio' field. * *      The owning context: * *        a) Observes the 'req_prio' field set on exit from the unsafe *           console state. * *        b) Gives up console ownership by clearing the 'prio' field. * *   3) Unsafe hostile takeover allows to take over the lock even when the *      console is an unsafe state. It is used only in panic() by the final *      attempt to flush consoles in a try and hope mode. * *      Note that separate record buffers are used in panic(). As a result, *      the messages can be read and formatted without any risk even after *      using the hostile takeover in unsafe state. * * The release function simply clears the 'prio' field. * * All operations on @console::nbcon_state are atomic cmpxchg based to * handle concurrency. * * The acquire/release functions implement only minimal policies: * *   - Preference for higher priority contexts. *   - Protection of the panic CPU. * * All other policy decisions must be made at the call sites: * *   - What is marked as an unsafe section. *   - Whether to spin-wait if there is already an owner and the console is *     in an unsafe state. *   - Whether to attempt an unsafe hostile takeover. * * The design allows to implement the well known: * *     acquire() *     output_one_printk_record() *     release() * * The output of one printk record might be interrupted with a higher priority * context. The new owner is supposed to reprint the entire interrupted record * from scratch. */ /* Counter of active nbcon emergency contexts. */static atomic_t nbcon_cpu_emergency_cnt = ATOMIC_INIT(0); /** * nbcon_state_set - Helper function to set the console state * @con:	Console to update * @new:	The new state to write * * Only to be used when the console is not yet or no longer visible in the * system. Otherwise use nbcon_state_try_cmpxchg(). */static inline void nbcon_state_set(struct console *con, struct nbcon_state *new){	atomic_set(&ACCESS_PRIVATE(con, nbcon_state), new->atom);} /** * nbcon_state_read - Helper function to read the console state * @con:	Console to read * @state:	The state to store the result */static inline void nbcon_state_read(struct console *con, struct nbcon_state *state){	state->atom = atomic_read(&ACCESS_PRIVATE(con, nbcon_state));} /** * nbcon_state_try_cmpxchg() - Helper function for atomic_try_cmpxchg() on console state * @con:	Console to update * @cur:	Old/expected state * @new:	New state * * Return: True on success. False on fail and @cur is updated. */static inline bool nbcon_state_try_cmpxchg(struct console *con, struct nbcon_state *cur,					   struct nbcon_state *new){	return atomic_try_cmpxchg(&ACCESS_PRIVATE(con, nbcon_state), &cur->atom, new->atom);} /** * nbcon_seq_read - Read the current console sequence * @con:	Console to read the sequence of * * Return:	Sequence number of the next record to print on @con. */u64 nbcon_seq_read(struct console *con){	unsigned long nbcon_seq = atomic_long_read(&ACCESS_PRIVATE(con, nbcon_seq)); 	return __ulseq_to_u64seq(prb, nbcon_seq);} /** * nbcon_seq_force - Force console sequence to a specific value * @con:	Console to work on * @seq:	Sequence number value to set * * Only to be used during init (before registration) or in extreme situations * (such as panic with CONSOLE_REPLAY_ALL). */void nbcon_seq_force(struct console *con, u64 seq){	/*	 * If the specified record no longer exists, the oldest available record	 * is chosen. This is especially important on 32bit systems because only	 * the lower 32 bits of the sequence number are stored. The upper 32 bits	 * are derived from the sequence numbers available in the ringbuffer.	 */	u64 valid_seq = max_t(u64, seq, prb_first_valid_seq(prb)); 	atomic_long_set(&ACCESS_PRIVATE(con, nbcon_seq), __u64seq_to_ulseq(valid_seq));} /** * nbcon_seq_try_update - Try to update the console sequence number * @ctxt:	Pointer to an acquire context that contains *		all information about the acquire mode * @new_seq:	The new sequence number to set * * @ctxt->seq is updated to the new value of @con::nbcon_seq (expanded to * the 64bit value). This could be a different value than @new_seq if * nbcon_seq_force() was used or the current context no longer owns the * console. In the later case, it will stop printing anyway. */static void nbcon_seq_try_update(struct nbcon_context *ctxt, u64 new_seq){	unsigned long nbcon_seq = __u64seq_to_ulseq(ctxt->seq);	struct console *con = ctxt->console; 	if (atomic_long_try_cmpxchg(&ACCESS_PRIVATE(con, nbcon_seq), &nbcon_seq,				    __u64seq_to_ulseq(new_seq))) {		ctxt->seq = new_seq;	} else {		ctxt->seq = nbcon_seq_read(con);	}} /** * nbcon_context_try_acquire_direct - Try to acquire directly * @ctxt:		The context of the caller * @cur:		The current console state * @is_reacquire:	This acquire is a reacquire * * Acquire the console when it is released. Also acquire the console when * the current owner has a lower priority and the console is in a safe state. * * Return:	0 on success. Otherwise, an error code on failure. Also @cur *		is updated to the latest state when failed to modify it. * * Errors: * *	-EPERM:		A panic is in progress and this is neither the panic *			CPU nor is this a reacquire. Or the current owner or *			waiter has the same or higher priority. No acquire *			method can be successful in these cases. * *	-EBUSY:		The current owner has a lower priority but the console *			in an unsafe state. The caller should try using *			the handover acquire method. */static int nbcon_context_try_acquire_direct(struct nbcon_context *ctxt,					    struct nbcon_state *cur, bool is_reacquire){	unsigned int cpu = smp_processor_id();	struct console *con = ctxt->console;	struct nbcon_state new; 	do {		/*		 * Panic does not imply that the console is owned. However,		 * since all non-panic CPUs are stopped during panic(), it		 * is safer to have them avoid gaining console ownership.		 *		 * One exception is when kdb has locked for printing on this CPU.		 *		 * Second exception is a reacquire (and an unsafe takeover		 * has not previously occurred) then it is allowed to attempt		 * a direct acquire in panic. This gives console drivers an		 * opportunity to perform any necessary cleanup if they were		 * interrupted by the panic CPU while printing.		 */		if (panic_on_other_cpu() &&		    !kdb_printf_on_this_cpu() &&		    (!is_reacquire || cur->unsafe_takeover)) {			return -EPERM;		} 		if (ctxt->prio <= cur->prio || ctxt->prio <= cur->req_prio)			return -EPERM; 		if (cur->unsafe)			return -EBUSY; 		/*		 * The console should never be safe for a direct acquire		 * if an unsafe hostile takeover has ever happened.		 */		WARN_ON_ONCE(cur->unsafe_takeover); 		new.atom = cur->atom;		new.prio	= ctxt->prio;		new.req_prio	= NBCON_PRIO_NONE;		new.unsafe	= cur->unsafe_takeover;		new.cpu		= cpu; 	} while (!nbcon_state_try_cmpxchg(con, cur, &new)); 	return 0;} static bool nbcon_waiter_matches(struct nbcon_state *cur, int expected_prio){	/*	 * The request context is well defined by the @req_prio because:	 *	 * - Only a context with a priority higher than the owner can become	 *   a waiter.	 * - Only a context with a priority higher than the waiter can	 *   directly take over the request.	 * - There are only three priorities.	 * - Only one CPU is allowed to request PANIC priority.	 * - Lower priorities are ignored during panic() until reboot.	 *	 * As a result, the following scenario is *not* possible:	 *	 * 1. This context is currently a waiter.	 * 2. Another context with a higher priority than this context	 *    directly takes ownership.	 * 3. The higher priority context releases the ownership.	 * 4. Another lower priority context takes the ownership.	 * 5. Another context with the same priority as this context	 *    creates a request and starts waiting.	 *	 * Event #1 implies this context is EMERGENCY.	 * Event #2 implies the new context is PANIC.	 * Event #3 occurs when panic() has flushed the console.	 * Event #4 occurs when a non-panic CPU reacquires.	 * Event #5 is not possible due to the panic_on_other_cpu() check	 *          in nbcon_context_try_acquire_handover().	 */ 	return (cur->req_prio == expected_prio);} /** * nbcon_context_try_acquire_requested - Try to acquire after having *					 requested a handover * @ctxt:	The context of the caller * @cur:	The current console state * * This is a helper function for nbcon_context_try_acquire_handover(). * It is called when the console is in an unsafe state. The current * owner will release the console on exit from the unsafe region. * * Return:	0 on success and @cur is updated to the new console state. *		Otherwise an error code on failure. * * Errors: * *	-EPERM:		A panic is in progress and this is not the panic CPU *			or this context is no longer the waiter. * *	-EBUSY:		The console is still locked. The caller should *			continue waiting. * * Note: The caller must still remove the request when an error has occurred *       except when this context is no longer the waiter. */static int nbcon_context_try_acquire_requested(struct nbcon_context *ctxt,					       struct nbcon_state *cur){	unsigned int cpu = smp_processor_id();	struct console *con = ctxt->console;	struct nbcon_state new; 	/* Note that the caller must still remove the request! */	if (panic_on_other_cpu())		return -EPERM; 	/*	 * Note that the waiter will also change if there was an unsafe	 * hostile takeover.	 */	if (!nbcon_waiter_matches(cur, ctxt->prio))		return -EPERM; 	/* If still locked, caller should continue waiting. */	if (cur->prio != NBCON_PRIO_NONE)		return -EBUSY; 	/*	 * The previous owner should have never released ownership	 * in an unsafe region.	 */	WARN_ON_ONCE(cur->unsafe); 	new.atom = cur->atom;	new.prio	= ctxt->prio;	new.req_prio	= NBCON_PRIO_NONE;	new.unsafe	= cur->unsafe_takeover;	new.cpu		= cpu; 	if (!nbcon_state_try_cmpxchg(con, cur, &new)) {		/*		 * The acquire could fail only when it has been taken		 * over by a higher priority context.		 */		WARN_ON_ONCE(nbcon_waiter_matches(cur, ctxt->prio));		return -EPERM;	} 	/* Handover success. This context now owns the console. */	return 0;} /** * nbcon_context_try_acquire_handover - Try to acquire via handover * @ctxt:	The context of the caller * @cur:	The current console state * * The function must be called only when the context has higher priority * than the current owner and the console is in an unsafe state. * It is the case when nbcon_context_try_acquire_direct() returns -EBUSY. * * The function sets "req_prio" field to make the current owner aware of * the request. Then it waits until the current owner releases the console, * or an even higher context takes over the request, or timeout expires. * * The current owner checks the "req_prio" field on exit from the unsafe * region and releases the console. It does not touch the "req_prio" field * so that the console stays reserved for the waiter. * * Return:	0 on success. Otherwise, an error code on failure. Also @cur *		is updated to the latest state when failed to modify it. * * Errors: * *	-EPERM:		A panic is in progress and this is not the panic CPU. *			Or a higher priority context has taken over the *			console or the handover request. * *	-EBUSY:		The current owner is on the same CPU so that the hand *			shake could not work. Or the current owner is not *			willing to wait (zero timeout). Or the console does *			not enter the safe state before timeout passed. The *			caller might still use the unsafe hostile takeover *			when allowed. * *	-EAGAIN:	@cur has changed when creating the handover request. *			The caller should retry with direct acquire. */static int nbcon_context_try_acquire_handover(struct nbcon_context *ctxt,					      struct nbcon_state *cur){	unsigned int cpu = smp_processor_id();	struct console *con = ctxt->console;	struct nbcon_state new;	int timeout;	int request_err = -EBUSY; 	/*	 * Check that the handover is called when the direct acquire failed	 * with -EBUSY.	 */	WARN_ON_ONCE(ctxt->prio <= cur->prio || ctxt->prio <= cur->req_prio);	WARN_ON_ONCE(!cur->unsafe); 	/*	 * Panic does not imply that the console is owned. However, it	 * is critical that non-panic CPUs during panic are unable to	 * wait for a handover in order to satisfy the assumptions of	 * nbcon_waiter_matches(). In particular, the assumption that	 * lower priorities are ignored during panic.	 */	if (panic_on_other_cpu())		return -EPERM; 	/* Handover is not possible on the same CPU. */	if (cur->cpu == cpu)		return -EBUSY; 	/*	 * Console stays unsafe after an unsafe takeover until re-initialized.	 * Waiting is not going to help in this case.	 */	if (cur->unsafe_takeover)		return -EBUSY; 	/* Is the caller willing to wait? */	if (ctxt->spinwait_max_us == 0)		return -EBUSY; 	/*	 * Setup a request for the handover. The caller should try to acquire	 * the console directly when the current state has been modified.	 */	new.atom = cur->atom;	new.req_prio = ctxt->prio;	if (!nbcon_state_try_cmpxchg(con, cur, &new))		return -EAGAIN; 	cur->atom = new.atom; 	/* Wait until there is no owner and then acquire the console. */	for (timeout = ctxt->spinwait_max_us; timeout >= 0; timeout--) {		/* On successful acquire, this request is cleared. */		request_err = nbcon_context_try_acquire_requested(ctxt, cur);		if (!request_err)			return 0; 		/*		 * If the acquire should be aborted, it must be ensured		 * that the request is removed before returning to caller.		 */		if (request_err == -EPERM)			break; 		udelay(1);

Lines 1–500 of 2004.