[printk,v3,6/6] printk: introduce console_prepend_dropped() for dropped messages

Message ID 20221221202704.857925-7-john.ogness@linutronix.de
State New
Headers
Series printk: cleanup buffer handling |

Commit Message

John Ogness Dec. 21, 2022, 8:27 p.m. UTC
  Currently "dropped messages" are separately printed immediately
before printing the printk message. Since normal consoles are
now using an output buffer that is much larger than previously,
the "dropped message" could be prepended to the printk message
and output in a single call.

Introduce a helper function console_prepend_dropped() to prepend
an existing message with a "dropped message". This simplifies
the code by allowing all message formatting to be handled
together and then only requires a single write() call to output
the full message. And since this helper does not require any
locking, it can be used in the future for other console printing
contexts as well.

Signed-off-by: John Ogness <john.ogness@linutronix.de>
---
 kernel/printk/internal.h |  4 --
 kernel/printk/printk.c   | 84 ++++++++++++++++++++++++++--------------
 2 files changed, 55 insertions(+), 33 deletions(-)
  

Comments

Petr Mladek Jan. 2, 2023, 4:19 p.m. UTC | #1
On Wed 2022-12-21 21:33:04, John Ogness wrote:
> Currently "dropped messages" are separately printed immediately
> before printing the printk message. Since normal consoles are
> now using an output buffer that is much larger than previously,
> the "dropped message" could be prepended to the printk message
> and output in a single call.
> 
> Introduce a helper function console_prepend_dropped() to prepend
> an existing message with a "dropped message". This simplifies
> the code by allowing all message formatting to be handled
> together and then only requires a single write() call to output
> the full message. And since this helper does not require any
> locking, it can be used in the future for other console printing
> contexts as well.
> 
> Signed-off-by: John Ogness <john.ogness@linutronix.de>

> --- a/kernel/printk/printk.c
> +++ b/kernel/printk/printk.c
> @@ -2724,6 +2699,50 @@ static void __console_unlock(void)
>  	up_console_sem();
>  }
>  
> +/*
> + * Prepend the message in @cmsg->cbufs->outbuf with a "dropped message". This
> + * is achieved by shifting the existing message over and inserting the dropped
> + * message.
> + *
> + * @cmsg is the console message to prepend.
> + *
> + * @dropped is the dropped count to report in the dropped message.
> + *
> + * If the message text in @cmsg->cbufs->outbuf does not have enough space for
> + * the dropped message, the message text will be sufficiently truncated.
> + *
> + * If @cmsg->cbufs->outbuf is modified, @cmsg->outbuf_len is updated.
> + */
> +static void console_prepend_dropped(struct console_message *cmsg, unsigned long dropped)
> +{
> +	struct console_buffers *cbufs = cmsg->cbufs;
> +	const size_t scratchbuf_sz = sizeof(cbufs->scratchbuf);
> +	const size_t outbuf_sz = sizeof(cbufs->outbuf);
> +	char *scratchbuf = &cbufs->scratchbuf[0];
> +	char *outbuf = &cbufs->outbuf[0];
> +	size_t len;
> +
> +	len = snprintf(scratchbuf, scratchbuf_sz,
> +		       "** %lu printk messages dropped **\n", dropped);
> +
> +	/*
> +	 * Make sure outbuf is sufficiently large before prepending. Space
> +	 * for a terminator is also counted in case truncation occurs.
> +	 */
> +	if (WARN_ON_ONCE(len + 1 >= outbuf_sz))
> +		return;

Strictly speaking, this should be:

	if (WARN_ON_ONCE(len >= outbuf_sz))
		return;

The trailing '\0' will fit into the buffer when len < outbuf_sz.

That said, the consoles would be almost unusable when we are anywhere close this
limit. Most messages would be truncated.

> +
> +	if (cmsg->outbuf_len + len >= outbuf_sz) {
> +		/* Truncate the message, but keep it terminated. */
> +		cmsg->outbuf_len = outbuf_sz - (len + 1);
> +		outbuf[cmsg->outbuf_len] = 0;
> +	}
> +
> +	memmove(outbuf + len, outbuf, cmsg->outbuf_len + 1);
> +	memcxpy(outbuf, scratchbuf, len);
> +	cmsg->outbuf_len += len;
> +}
> +
>  /*
>   * Read and format the specified record (or a later record if the specified
>   * record is not available).

Otherwise, the change looks fine:

Reviewed-by: Petr Mladek <pmladek@suse.com>

Best Regards,
Petr
  
John Ogness Jan. 3, 2023, 10:20 a.m. UTC | #2
On 2023-01-02, Petr Mladek <pmladek@suse.com> wrote:
>> +static void console_prepend_dropped(struct console_message *cmsg, unsigned long dropped)
>> +{
>> +	struct console_buffers *cbufs = cmsg->cbufs;
>> +	const size_t scratchbuf_sz = sizeof(cbufs->scratchbuf);
>> +	const size_t outbuf_sz = sizeof(cbufs->outbuf);
>> +	char *scratchbuf = &cbufs->scratchbuf[0];
>> +	char *outbuf = &cbufs->outbuf[0];
>> +	size_t len;
>> +
>> +	len = snprintf(scratchbuf, scratchbuf_sz,
>> +		       "** %lu printk messages dropped **\n", dropped);
>> +
>> +	/*
>> +	 * Make sure outbuf is sufficiently large before prepending. Space
>> +	 * for a terminator is also counted in case truncation occurs.
>> +	 */
>> +	if (WARN_ON_ONCE(len + 1 >= outbuf_sz))
>> +		return;
>
> Strictly speaking, this should be:
>
> 	if (WARN_ON_ONCE(len >= outbuf_sz))
> 		return;
>
> The trailing '\0' will fit into the buffer when len < outbuf_sz.

You are correct.

My concern was that if "@len = @outbuf_sz - 1", the entire message will
be truncated and you will only see the dropped messages text. I wanted
at least 1 character from the message to survive. ;-)

How about the commments:

	/*
	 * Make sure outbuf is sufficiently large before prepending. Space
	 * for a terminator and at least 1 byte of the message is also
	 * checked in case truncation is needed.
	 */

John
  
Petr Mladek Jan. 3, 2023, 1:29 p.m. UTC | #3
On Tue 2023-01-03 11:26:23, John Ogness wrote:
> On 2023-01-02, Petr Mladek <pmladek@suse.com> wrote:
> >> +static void console_prepend_dropped(struct console_message *cmsg, unsigned long dropped)
> >> +{
> >> +	struct console_buffers *cbufs = cmsg->cbufs;
> >> +	const size_t scratchbuf_sz = sizeof(cbufs->scratchbuf);
> >> +	const size_t outbuf_sz = sizeof(cbufs->outbuf);
> >> +	char *scratchbuf = &cbufs->scratchbuf[0];
> >> +	char *outbuf = &cbufs->outbuf[0];
> >> +	size_t len;
> >> +
> >> +	len = snprintf(scratchbuf, scratchbuf_sz,
> >> +		       "** %lu printk messages dropped **\n", dropped);
> >> +
> >> +	/*
> >> +	 * Make sure outbuf is sufficiently large before prepending. Space
> >> +	 * for a terminator is also counted in case truncation occurs.
> >> +	 */
> >> +	if (WARN_ON_ONCE(len + 1 >= outbuf_sz))
> >> +		return;
> >
> > Strictly speaking, this should be:
> >
> > 	if (WARN_ON_ONCE(len >= outbuf_sz))
> > 		return;
> >
> > The trailing '\0' will fit into the buffer when len < outbuf_sz.
> 
> You are correct.
> 
> My concern was that if "@len = @outbuf_sz - 1", the entire message will
> be truncated and you will only see the dropped messages text. I wanted
> at least 1 character from the message to survive. ;-)

I see.

> How about the commments:
> 
> 	/*
> 	 * Make sure outbuf is sufficiently large before prepending. Space
> 	 * for a terminator and at least 1 byte of the message is also
> 	 * checked in case truncation is needed.
> 	 */

It is better than the original.

Well, what about making sure that something more useful is always
printed. For example:

	/*
	 * Make sure outbuf is sufficiently large before prepending.
	 * Keep at least the prefix when the message has to be truncated.
	 * It is a rather theoretical problem when someone tries to
	 * use a minimalist buffer.
	 */
	if (WARN_ON_ONCE(len + PREFIX_MAX + 1 >= outbuf_sz))
		return;

If we want to use this way. It would probably make sense to
rename PREFIX_MAX to CONSOLE_PREFIX_MAX.

Best Regards,
Petr
  
John Ogness Jan. 3, 2023, 1:44 p.m. UTC | #4
On 2023-01-03, Petr Mladek <pmladek@suse.com> wrote:
> Well, what about making sure that something more useful is always
> printed. For example:
>
> 	/*
> 	 * Make sure outbuf is sufficiently large before prepending.
> 	 * Keep at least the prefix when the message has to be truncated.
> 	 * It is a rather theoretical problem when someone tries to
> 	 * use a minimalist buffer.
> 	 */
> 	if (WARN_ON_ONCE(len + PREFIX_MAX + 1 >= outbuf_sz))
> 		return;

I am fine with this. We won't see this warning anyway. Few lines would
ever be printed correctly if anyone ever tries to use a buffer so small.

> If we want to use this way. It would probably make sense to
> rename PREFIX_MAX to CONSOLE_PREFIX_MAX.

Actually, I would like to rename all of those limit macros to something
that makes more sense for the new code base:

CONSOLE_EXT_LOG_MAX -> CONSOLE_MESSAGE_MAX

CONSOLE_LOG_MAX     -> SYSLOG_MESSAGE_MAX

LOG_LINE_MAX        -> PRINTK_RECORD_MAX

PREFIX_MAX          -> CONSOLE_PREFIX_MAX

I have a patch to do this ready, but I did not want to post it until we
are finished with the thread/atomic work.

John
  
Petr Mladek Jan. 3, 2023, 2:16 p.m. UTC | #5
On Tue 2023-01-03 14:50:23, John Ogness wrote:
> On 2023-01-03, Petr Mladek <pmladek@suse.com> wrote:
> > Well, what about making sure that something more useful is always
> > printed. For example:
> >
> > 	/*
> > 	 * Make sure outbuf is sufficiently large before prepending.
> > 	 * Keep at least the prefix when the message has to be truncated.
> > 	 * It is a rather theoretical problem when someone tries to
> > 	 * use a minimalist buffer.
> > 	 */
> > 	if (WARN_ON_ONCE(len + PREFIX_MAX + 1 >= outbuf_sz))
> > 		return;
> 
> I am fine with this. We won't see this warning anyway. Few lines would
> ever be printed correctly if anyone ever tries to use a buffer so small.

Yup.

> > If we want to use this way. It would probably make sense to
> > rename PREFIX_MAX to CONSOLE_PREFIX_MAX.
> 
> Actually, I would like to rename all of those limit macros to something
> that makes more sense for the new code base:
> 
> CONSOLE_EXT_LOG_MAX -> CONSOLE_MESSAGE_MAX
> 
> CONSOLE_LOG_MAX     -> SYSLOG_MESSAGE_MAX

Heh, we actually do not need this. The size of @scratchbuf
might be LOG_LINE_MAX/PRINTK_RECORD_MAX. The scratch buffer
is newly used only to read the plain message. The prefixes
are added to @outbuf.

> LOG_LINE_MAX        -> PRINTK_RECORD_MAX
> 
> PREFIX_MAX          -> CONSOLE_PREFIX_MAX

I like this.

> I have a patch to do this ready, but I did not want to post it until we
> are finished with the thread/atomic work.

It would make sense to do this as part of this patchset. But I do not
want to delay it too much. Feel free to do it later.

Best Regards,
Petr
  
John Ogness Jan. 3, 2023, 3 p.m. UTC | #6
On 2023-01-03, Petr Mladek <pmladek@suse.com> wrote:
>> Actually, I would like to rename all of those limit macros to something
>> that makes more sense for the new code base:
>> 
>> CONSOLE_EXT_LOG_MAX -> CONSOLE_MESSAGE_MAX
>> 
>> CONSOLE_LOG_MAX     -> SYSLOG_MESSAGE_MAX
>
> Heh, we actually do not need this. The size of @scratchbuf
> might be LOG_LINE_MAX/PRINTK_RECORD_MAX. The scratch buffer
> is newly used only to read the plain message. The prefixes
> are added to @outbuf.
>
>> LOG_LINE_MAX        -> PRINTK_RECORD_MAX

The scratch buffer would become PRINTK_RECORD_MAX, but we still need
SYSLOG_MESSAGE_MAX for the kmalloc's syslog buffers. Unless you think it
is OK to kmalloc 8KB instead of 1KB for the syslog calls. Then yes, we
do not need SYSLOG_MESSAGE_MAX.

John
  
Petr Mladek Jan. 3, 2023, 4:13 p.m. UTC | #7
On Tue 2023-01-03 16:06:03, John Ogness wrote:
> On 2023-01-03, Petr Mladek <pmladek@suse.com> wrote:
> >> Actually, I would like to rename all of those limit macros to something
> >> that makes more sense for the new code base:
> >> 
> >> CONSOLE_EXT_LOG_MAX -> CONSOLE_MESSAGE_MAX
> >> 
> >> CONSOLE_LOG_MAX     -> SYSLOG_MESSAGE_MAX
> >
> > Heh, we actually do not need this. The size of @scratchbuf
> > might be LOG_LINE_MAX/PRINTK_RECORD_MAX. The scratch buffer
> > is newly used only to read the plain message. The prefixes
> > are added to @outbuf.
> >
> >> LOG_LINE_MAX        -> PRINTK_RECORD_MAX
> 
> The scratch buffer would become PRINTK_RECORD_MAX, but we still need
> SYSLOG_MESSAGE_MAX for the kmalloc's syslog buffers.

I see.

> Unless you think it is OK to kmalloc 8KB instead of 1KB for the
> syslog calls. Then yes, we do not need SYSLOG_MESSAGE_MAX.

IMHO, it is acceptable and even correct. syslog uses the same
prefixes as console. It would make sense to use the same
buffers for formatting.

That said, 8kB looks non-necessary big to me.

It seems that it comes from devkmsg interface, see the commit
d43ff430f434d862db59582 ("printk: guard the amount written
per line by devkmsg_read()"). It was supposed to include
the message, the extended prefix and dictionary, where

   + message is limited by LOG_LINE_MAX
   + prefix includes few well defined fields (should be < 128B)
   + dictionary comes from dev_printk() => ( < 128B as well)

I believe that 2kB or 4kB would be perfectly fine.

Best Regards,
Petr
  
John Ogness Jan. 4, 2023, 9:06 a.m. UTC | #8
On 2023-01-03, Petr Mladek <pmladek@suse.com> wrote:
>> Unless you think it is OK to kmalloc 8KB instead of 1KB for the
>> syslog calls. Then yes, we do not need SYSLOG_MESSAGE_MAX.
>
> IMHO, it is acceptable and even correct. syslog uses the same
> prefixes as console. It would make sense to use the same
> buffers for formatting.
>
> That said, 8kB looks non-necessary big to me.
>
> It seems that it comes from devkmsg interface, see the commit
> d43ff430f434d862db59582 ("printk: guard the amount written
> per line by devkmsg_read()"). It was supposed to include
> the message, the extended prefix and dictionary, where
>
>    + message is limited by LOG_LINE_MAX
>    + prefix includes few well defined fields (should be < 128B)
>    + dictionary comes from dev_printk() => ( < 128B as well)
>
> I believe that 2kB or 4kB would be perfectly fine.

The main issue is multi-line records. Normal messages become _much_
larger than extended messages in this case because they add a prefix per
'\n', whereas extended messages just use "\x0a". Extended messages
really could only end up being significantly longer than normal messages
if there are many non-printable characters in the message. But AFAIK
non-printables are not really used in printk messages.

So IMHO it does not make sense that normal messages are limited to 1KB
but extended messages can use 8KB. I agree that a universal limit of 2KB
for normal/extended/syslog would be a nice compromise. Normal messages
will have more space available and it will reduce the overall static
buffer usage. It would mean that syslog calls will kmalloc 2KB instead
of 1KB, but I expect that should be acceptable since, generally
speaking, overall we are reducing memory usage.

John
  
Petr Mladek Jan. 4, 2023, 10:33 a.m. UTC | #9
On Wed 2023-01-04 10:12:01, John Ogness wrote:
> On 2023-01-03, Petr Mladek <pmladek@suse.com> wrote:
> >> Unless you think it is OK to kmalloc 8KB instead of 1KB for the
> >> syslog calls. Then yes, we do not need SYSLOG_MESSAGE_MAX.
> >
> > IMHO, it is acceptable and even correct. syslog uses the same
> > prefixes as console. It would make sense to use the same
> > buffers for formatting.
> >
> > That said, 8kB looks non-necessary big to me.
> >
> > It seems that it comes from devkmsg interface, see the commit
> > d43ff430f434d862db59582 ("printk: guard the amount written
> > per line by devkmsg_read()"). It was supposed to include
> > the message, the extended prefix and dictionary, where
> >
> >    + message is limited by LOG_LINE_MAX
> >    + prefix includes few well defined fields (should be < 128B)
> >    + dictionary comes from dev_printk() => ( < 128B as well)
> >
> > I believe that 2kB or 4kB would be perfectly fine.
> 
> The main issue is multi-line records. Normal messages become _much_
> larger than extended messages in this case because they add a prefix per
> '\n', whereas extended messages just use "\x0a". Extended messages
> really could only end up being significantly longer than normal messages
> if there are many non-printable characters in the message. But AFAIK
> non-printables are not really used in printk messages.

Right.

> So IMHO it does not make sense that normal messages are limited to 1KB
> but extended messages can use 8KB. I agree that a universal limit of 2KB
> for normal/extended/syslog would be a nice compromise. Normal messages
> will have more space available and it will reduce the overall static
> buffer usage. It would mean that syslog calls will kmalloc 2KB instead
> of 1KB, but I expect that should be acceptable since, generally
> speaking, overall we are reducing memory usage.

I agree that 2kB are a good compromise and the allocation should be acceptable.

Best Regards,
Petr
  
kernel test robot Jan. 5, 2023, 1:14 p.m. UTC | #10
Hi John,

I love your patch! Perhaps something to improve:

[auto build test WARNING on 6b2b0d839acaa84f05a77184370f793752e786e9]

url:    https://github.com/intel-lab-lkp/linux/commits/John-Ogness/printk-cleanup-buffer-handling/20221222-044216
base:   6b2b0d839acaa84f05a77184370f793752e786e9
patch link:    https://lore.kernel.org/r/20221221202704.857925-7-john.ogness%40linutronix.de
patch subject: [PATCH printk v3 6/6] printk: introduce console_prepend_dropped() for dropped messages
config: loongarch-randconfig-m031-20230103
compiler: loongarch64-linux-gcc (GCC) 12.1.0

If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <lkp@intel.com>

smatch warnings:
kernel/printk/printk.c:2735 console_prepend_dropped() warn: always true condition '(cmsg->outbuf_len + len >= outbuf_sz) => (0-u64max >= 0)'

vim +2735 kernel/printk/printk.c

  2701	
  2702	/*
  2703	 * Prepend the message in @cmsg->cbufs->outbuf with a "dropped message". This
  2704	 * is achieved by shifting the existing message over and inserting the dropped
  2705	 * message.
  2706	 *
  2707	 * @cmsg is the console message to prepend.
  2708	 *
  2709	 * @dropped is the dropped count to report in the dropped message.
  2710	 *
  2711	 * If the message text in @cmsg->cbufs->outbuf does not have enough space for
  2712	 * the dropped message, the message text will be sufficiently truncated.
  2713	 *
  2714	 * If @cmsg->cbufs->outbuf is modified, @cmsg->outbuf_len is updated.
  2715	 */
  2716	static void console_prepend_dropped(struct console_message *cmsg, unsigned long dropped)
  2717	{
  2718		struct console_buffers *cbufs = cmsg->cbufs;
  2719		const size_t scratchbuf_sz = sizeof(cbufs->scratchbuf);
  2720		const size_t outbuf_sz = sizeof(cbufs->outbuf);
  2721		char *scratchbuf = &cbufs->scratchbuf[0];
  2722		char *outbuf = &cbufs->outbuf[0];
  2723		size_t len;
  2724	
  2725		len = snprintf(scratchbuf, scratchbuf_sz,
  2726			       "** %lu printk messages dropped **\n", dropped);
  2727	
  2728		/*
  2729		 * Make sure outbuf is sufficiently large before prepending. Space
  2730		 * for a terminator is also counted in case truncation occurs.
  2731		 */
  2732		if (WARN_ON_ONCE(len + 1 >= outbuf_sz))
  2733			return;
  2734	
> 2735		if (cmsg->outbuf_len + len >= outbuf_sz) {
  2736			/* Truncate the message, but keep it terminated. */
  2737			cmsg->outbuf_len = outbuf_sz - (len + 1);
  2738			outbuf[cmsg->outbuf_len] = 0;
  2739		}
  2740	
  2741		memmove(outbuf + len, outbuf, cmsg->outbuf_len + 1);
  2742		memcpy(outbuf, scratchbuf, len);
  2743		cmsg->outbuf_len += len;
  2744	}
  2745
  
John Ogness Jan. 5, 2023, 1:55 p.m. UTC | #11
On 2023-01-05, kernel test robot <lkp@intel.com> wrote:
> smatch warnings:
> kernel/printk/printk.c:2735 console_prepend_dropped() warn: always true condition '(cmsg->outbuf_len + len >= outbuf_sz) => (0-u64max >= 0)'

Thank you kernel test robot, but actually this code will never be hit
when outbuf_sz is 0. Explanation below.

> vim +2735 kernel/printk/printk.c
>
>   2701	
>   2702	/*
>   2703	 * Prepend the message in @cmsg->cbufs->outbuf with a "dropped message". This
>   2704	 * is achieved by shifting the existing message over and inserting the dropped
>   2705	 * message.
>   2706	 *
>   2707	 * @cmsg is the console message to prepend.
>   2708	 *
>   2709	 * @dropped is the dropped count to report in the dropped message.
>   2710	 *
>   2711	 * If the message text in @cmsg->cbufs->outbuf does not have enough space for
>   2712	 * the dropped message, the message text will be sufficiently truncated.
>   2713	 *
>   2714	 * If @cmsg->cbufs->outbuf is modified, @cmsg->outbuf_len is updated.
>   2715	 */
>   2716	static void console_prepend_dropped(struct console_message *cmsg, unsigned long dropped)
>   2717	{
>   2718		struct console_buffers *cbufs = cmsg->cbufs;
>   2719		const size_t scratchbuf_sz = sizeof(cbufs->scratchbuf);
>   2720		const size_t outbuf_sz = sizeof(cbufs->outbuf);
>   2721		char *scratchbuf = &cbufs->scratchbuf[0];
>   2722		char *outbuf = &cbufs->outbuf[0];
>   2723		size_t len;
>   2724	
>   2725		len = snprintf(scratchbuf, scratchbuf_sz,
>   2726			       "** %lu printk messages dropped **\n", dropped);
>   2727	
>   2728		/*
>   2729		 * Make sure outbuf is sufficiently large before prepending. Space
>   2730		 * for a terminator is also counted in case truncation occurs.
>   2731		 */
>   2732		if (WARN_ON_ONCE(len + 1 >= outbuf_sz))
>   2733			return;

If outbuf_sz is 0, the above check will return. It is interesting that
smatch did not complain about this line instead. I suppose the
WARN_ON_ONCE confused it.

Note that if outbuf_sz is 0, then CONFIG_PRINTK is undefined. In that
case this function should not be called anyway. So the check and warning
are appropriate here.

>   2734	
>> 2735		if (cmsg->outbuf_len + len >= outbuf_sz) {

John Ogness
  

Patch

diff --git a/kernel/printk/internal.h b/kernel/printk/internal.h
index 4f2eb8c470bc..b0ca59e6edad 100644
--- a/kernel/printk/internal.h
+++ b/kernel/printk/internal.h
@@ -26,9 +26,6 @@  int devkmsg_sysctl_set_loglvl(struct ctl_table *table, int write,
 /* the maximum size of a formatted extended record */
 #define CONSOLE_EXT_LOG_MAX	8192
 
-/* the maximum size for a dropped text message */
-#define DROPPED_TEXT_MAX	64
-
 /* the maximum size allowed to be reserved for a record */
 #define LOG_LINE_MAX		(CONSOLE_LOG_MAX - PREFIX_MAX)
 
@@ -68,7 +65,6 @@  u16 printk_parse_prefix(const char *text, int *level,
 
 #define CONSOLE_LOG_MAX		0
 #define CONSOLE_EXT_LOG_MAX	0
-#define DROPPED_TEXT_MAX	0
 #define LOG_LINE_MAX		0
 
 /*
diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c
index 7cac636600f8..f0d9c88e434f 100644
--- a/kernel/printk/printk.c
+++ b/kernel/printk/printk.c
@@ -1995,27 +1995,6 @@  static int console_trylock_spinning(void)
 	return 1;
 }
 
-/*
- * Call the specified console driver, asking it to write out the specified
- * text and length. If @dropped_text is non-NULL and any records have been
- * dropped, a dropped message will be written out first.
- */
-static void call_console_driver(struct console *con, const char *text, size_t len,
-				char *dropped_text)
-{
-	size_t dropped_len;
-
-	if (con->dropped && dropped_text) {
-		dropped_len = snprintf(dropped_text, DROPPED_TEXT_MAX,
-				       "** %lu printk messages dropped **\n",
-				       con->dropped);
-		con->dropped = 0;
-		con->write(con, dropped_text, dropped_len);
-	}
-
-	con->write(con, text, len);
-}
-
 /*
  * Recursion is tracked separately on each CPU. If NMIs are supported, an
  * additional NMI context per CPU is also separately tracked. Until per-CPU
@@ -2395,10 +2374,6 @@  static ssize_t msg_print_ext_body(char *buf, size_t size,
 				  struct dev_printk_info *dev_info) { return 0; }
 static void console_lock_spinning_enable(void) { }
 static int console_lock_spinning_disable_and_check(int cookie) { return 0; }
-static void call_console_driver(struct console *con, const char *text, size_t len,
-				char *dropped_text)
-{
-}
 static bool suppress_message_printing(int level) { return false; }
 static bool pr_flush(int timeout_ms, bool reset_on_progress) { return true; }
 static bool __pr_flush(struct console *con, int timeout_ms, bool reset_on_progress) { return true; }
@@ -2724,6 +2699,50 @@  static void __console_unlock(void)
 	up_console_sem();
 }
 
+/*
+ * Prepend the message in @cmsg->cbufs->outbuf with a "dropped message". This
+ * is achieved by shifting the existing message over and inserting the dropped
+ * message.
+ *
+ * @cmsg is the console message to prepend.
+ *
+ * @dropped is the dropped count to report in the dropped message.
+ *
+ * If the message text in @cmsg->cbufs->outbuf does not have enough space for
+ * the dropped message, the message text will be sufficiently truncated.
+ *
+ * If @cmsg->cbufs->outbuf is modified, @cmsg->outbuf_len is updated.
+ */
+static void console_prepend_dropped(struct console_message *cmsg, unsigned long dropped)
+{
+	struct console_buffers *cbufs = cmsg->cbufs;
+	const size_t scratchbuf_sz = sizeof(cbufs->scratchbuf);
+	const size_t outbuf_sz = sizeof(cbufs->outbuf);
+	char *scratchbuf = &cbufs->scratchbuf[0];
+	char *outbuf = &cbufs->outbuf[0];
+	size_t len;
+
+	len = snprintf(scratchbuf, scratchbuf_sz,
+		       "** %lu printk messages dropped **\n", dropped);
+
+	/*
+	 * Make sure outbuf is sufficiently large before prepending. Space
+	 * for a terminator is also counted in case truncation occurs.
+	 */
+	if (WARN_ON_ONCE(len + 1 >= outbuf_sz))
+		return;
+
+	if (cmsg->outbuf_len + len >= outbuf_sz) {
+		/* Truncate the message, but keep it terminated. */
+		cmsg->outbuf_len = outbuf_sz - (len + 1);
+		outbuf[cmsg->outbuf_len] = 0;
+	}
+
+	memmove(outbuf + len, outbuf, cmsg->outbuf_len + 1);
+	memcpy(outbuf, scratchbuf, len);
+	cmsg->outbuf_len += len;
+}
+
 /*
  * Read and format the specified record (or a later record if the specified
  * record is not available).
@@ -2817,7 +2836,6 @@  static bool console_get_next_message(struct console_message *cmsg, u64 seq,
 static bool console_emit_next_record(struct console *con, bool *handover, int cookie)
 {
 	bool is_extended = console_srcu_read_flags(con) & CON_EXTENDED;
-	static char dropped_text[DROPPED_TEXT_MAX];
 	static struct console_buffers cbufs;
 	static struct console_message cmsg = {
 		.cbufs = &cbufs,
@@ -2838,6 +2856,11 @@  static bool console_emit_next_record(struct console *con, bool *handover, int co
 		goto skip;
 	}
 
+	if (con->dropped && !is_extended) {
+		console_prepend_dropped(&cmsg, con->dropped);
+		con->dropped = 0;
+	}
+
 	/*
 	 * While actively printing out messages, if another printk()
 	 * were to occur on another CPU, it may wait for this one to
@@ -2851,9 +2874,12 @@  static bool console_emit_next_record(struct console *con, bool *handover, int co
 	printk_safe_enter_irqsave(flags);
 	console_lock_spinning_enable();
 
-	stop_critical_timings();	/* don't trace print latency */
-	call_console_driver(con, outbuf, cmsg.outbuf_len,
-			    is_extended ? NULL : dropped_text);
+	/* Do not trace print latency. */
+	stop_critical_timings();
+
+	/* Write everything out to the hardware. */
+	con->write(con, outbuf, cmsg.outbuf_len);
+
 	start_critical_timings();
 
 	con->seq = cmsg.outbuf_seq + 1;