Re: [PATCH v3] printk: fix zero-valued printk timestamps in early boot

Petr Mladek <[email protected]> Wed, 11 Mar 2026 16:45:51 +0100
Newsgroups org.kernel.vger.linux-embedded,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
On Tue 2026-03-10 18:54:22, Bird, Tim wrote:
> > From: Petr Mladek <[email protected]>
> > There are three stages:
> > 
> >    1. Early messages where the cycles are stored.
> > 
> >       The serial console shows zero time stamp because
> >       it reads the messages _before the calibration_, e.g.
> > 
> > 	[    0.000000] Linux version 6.19.0-rc7-default+ (pmladek@pathway) (gcc (SUSE Linux) 15.2.1 20251006, GNU ld (GNU Binutils;
> > openSUSE Tumbleweed) 2.45.0.20251103-2) #521 SMP PREEMPT_DYNAMIC Mon Feb  2 16:36:53 CET 2026
> > 	[    0.000000] Command line: BOOT_IMAGE=/boot/vmlinuz-6.19.0-rc7-default+ root=UUID=587ae802-e330-4059-9b48-
> > d5b845e1075a resume=/dev/disk/by-uuid/369c7453-3d16-409d-88b2-5de027891a12 mitigations=auto nosplash
> > earlycon=uart8250,io,0x3f8,115200 console=ttyS0,115200 console=ttynull console=tty0 debug_non_panic_cpus=1 panic=10 ignore_loglevel
> > log_buf_len=1M
> > 	[    0.000000] BIOS-provided physical RAM map:
> > 	[    0.000000] BIOS-e820: [mem 0x0000000000000000-0x000000000009fbff] usable
> > 	[    0.000000] BIOS-e820: [mem 0x000000000009fc00-0x000000000009ffff] reserved
> > 
> >       But "dmesg" shows some numbers because it reads the messages
> >       _after the calibration_:
> > 
> > 	[    8.853613] Linux version 6.19.0-rc7-default+ (pmladek@pathway) (gcc (SUSE Linux) 15.2.1 20251006, GNU ld (GNU Binutils;
> > openSUSE Tumbleweed) 2.45.0.20251103-2) #521 SMP PREEMPT_DYNAMIC Mon Feb  2 16:36:53 CET 2026
> > 	[    8.853617] Command line: BOOT_IMAGE=/boot/vmlinuz-6.19.0-rc7-default+ root=UUID=587ae802-e330-4059-9b48-
> > d5b845e1075a resume=/dev/disk/by-uuid/369c7453-3d16-409d-88b2-5de027891a12 mitigations=auto nosplash
> > earlycon=uart8250,io,0x3f8,115200 console=ttyS0,115200 console=ttynull console=tty0 debug_non_panic_cpus=1 panic=10 ignore_loglevel
> > log_buf_len=1M
> > 	[    8.865086] BIOS-provided physical RAM map:
> > 	[    8.865087] BIOS-e820: [mem 0x0000000000000000-0x000000000009fbff] usable
> > 	[    8.865089] BIOS-e820: [mem 0x000000000009fc00-0x000000000009ffff] reserved
> > 
> > 
> >    2. Early messages added _after the timekeeping_ is initialized
> >       but _before the early cycles calibration_.
> > 
> >       They serial console prints them _without the offset_ because
> >       it reads them _before the calibration_, e.g.
> > 
> > 	[    3.288049][    T1] Write protecting the kernel read-only data: 36864k
> > 	[    3.298554][    T1] Freeing unused kernel image (text/rodata gap) memory: 1656K
> > 	[    3.318942][    T1] Freeing unused kernel image (rodata/data gap) memory: 1540K
> > 
> >        But "dmesg" prints them _with the offset_ because it reads them
> >        _after the calibration_, e.g.
> > 
> > 	[   12.179999] [    T1] Write protecting the kernel read-only data: 36864k
> > 	[   12.190505] [    T1] Freeing unused kernel image (text/rodata gap) memory: 1656K
> > 	[   12.210893] [    T1] Freeing unused kernel image (rodata/data gap) memory: 1540K
> > 
> > 
> >    3. Messages added after the calibration of the early cycles.
> > 
> >       They are printed with the offset by both serial console and
> >       dmesg, e.g.
> > 
> > 	[   12.230014][    T1] Early printk times: mult=38775352, shift=27, offset=8891950261 ns
> > 	[   12.246008][    T1] Run /init as init process
> > 	[   12.254944][    T1]   with arguments:
> > 	[   12.264341][    T1]     /init
> 
> This is correct.  I don't want to overwhelm users of this, but there are three time-gathering periods, and roughly
> 2 output times (before calibration and after calibration).
> 
> early boot = before time init, before cycles calibration and offset calculation
> mid boot = after time init, before cycles calibration and offset calculation
> late boot = after time init, after cycles calibration and offset calculation
> All of these are before the start of user space processes.
>
> time of printk	output time	timestamp type	stored	timestamp output
> ------------------	----------------	-----------------------------	------------------------
> early boot	early boot	cycles			0
> mid boot	mid-boot	nanosecs		seconds, with offset from time_init
> late boot	late-boot	nanosecs		seconds with offset from cycle counter start

This is a bit confusing. I looks like the offset from time_init() is
not longer added.

> -----
> early boot	post-boot	(cycles already stored)	seconds with offset from cycle counter start
> mid boot	post-boot	(ns already stored)	seconds with offset from cycle counter start
> late boot	post-boot	(ns already stored)	seconds with offset from cycle counter start
> all others	post-boot	nanosecs		seconds with offset from cycle counter start			

> The confusing thing is messages that are output to the console before calibration and offset calculation.
> Reports from all user space tools (ie from dmesg or journalctl) should be correct and consistent.

Yup.

I like that table. I just wonder how to better distinguish the offset from
timekeeping and calibrated cycles. My variant:

<proposal>
The printk timestamps are stored and interpretted differently in
the following periods:

  - early boot: before timekeeping init, before cycles calibration
  - mid boot:   after timekeeping init, before cycles calibration
  - late boot:  after timekeeping init, after cycles calibration

Console output (immediately):

printk() time	stored value	immediate output(sec)
-----------------------------------------------------------------------------
early boot	cycles		0
mid boot	get_time()	get_time()
late boot	get_time()	get_time + calibrated(cycles offset)

User space tools and late registered consoles:

printk() time	stored value	output after calibration (sec)
-----------------------------------------------------------------------------
early boot	cycles		calibrated(cycles)
mid boot	get_time()	get_time() + calibrated(cycles offset)
late boot	get_time()	get_time() + calibrated(cycles offset)

I am not sure where to put this. One place might be
Documentation/core-api/printk-basics.rst. But it might be better
add a separate file either under core-api/ or under admin-guide/.

> > 
> > > An accessor can be implemented for (struct printk_info).ts_nsec, say
> > > get_timestamp(), which can be called from both the places.
> > 
> > Yeah, a helper function for reading the timestamp might be a cleaner solution.
> > 
> I consider adjust_early_ts() to be such an accessor function.  It's supposed
> to hide the details of the type of the timestamp (cycles or ns) and
> the offset. Maybe this could be renamed to something better,
> like: get_adjusted_ts()? Let me know what you think.

This a bike shedding area ;-)

I personally find

    u64 ts = get_printk_info_ts(info);

a bit cleaner than

    u64 ts = adjust_early_ts(info->ts_nsec);

because you might add a comment into struct printk_info
definition that nobody should read the timestamp directly.
They should use the helper intead.

The helper would do something like:

/*
 * The number of early cycles is stored before the timekeeping gets initialized.
 * The local_clock() value is stored later.
 *
 * Note that early_ts_offset, early_mult, and early_shift are 0
 * before the cycles get calibrated against the official time keeping.
 *
 * Any output after user space start (eg. from dmesg or journalctl)
 * will show consistent values with calibrated cycles and offset.
 */
static inline u64 get_printk_info_ts(const struct printk_info *info)
{
	if (likely(!(info->ts_cycles & EARLY_CYCLES_BIT)))
		/* if timestamp is not in cycles, just add offset */
		return info->ts_nsec + early_ts_offset;

	/* mask high bit and convert to nanoseconds */
	return mul_u64_u32_shr(info->ts_cycles & EARLY_CYCLES_MASK,
			       early_mult, early_shift);
}

Note that I have already used two names (ts_cycles and ts_nsec) which
would point to the same data via an union.

> I'll rebase this patch to resolve the #include conflicts, and address
> this feedback, and hopefully get a new version out this week.

Thanks a lot. v3 looked good enough to me. But v4 will be even better
after the feedback.

Take your time ;-)

Best Regards,
Petr