Skip to the content.

seabios 中也会校准时间

基本流程如下:

   platform_hardware_setup()                     src/post.c:139
    ├─ qemu_platform_setup()                     src/fw/paravirt.c:285
    │   ├─ kvmclock_init()  (内联)               paravirt.c:105
    │   │   └─ tsctimer_setfreq(2994*1000,"kvmclock")  paravirt.c:134  ★断点1 命中
    │   └─ pci_setup()                           pciinit.c:1240
    │       └─ pci_bios_init_devices()           pciinit.c:429
    │           └─ pci_bios_init_device()        pciinit.c:414
    │               └─ pci_init_device(表分发, 0xd7685 是 `jmp *%ecx` 尾调用)
    │                   └─ piix4_pm_setup()      pciinit.c:266   (i440fx 的 PIIX4 PM 0x7113)
    │                       └─ pmtimer_setup(acpi_pm_base+0x08) pciinit.c:272 ★断点2 命中(early return)
    ├─ timer_setup()                             post.c:153   ← TimerPort 已是 0,直接 return
    └─ clock_setup() → pit_setup()               clock.c:36   (PIT 中断初始化)

一个持续 5 年的错误

seabios

这是 seabios 的源码:

// Calibrate the CPU time-stamp-counter
static void
tsctimer_setup(void)
{
    // Setup "timer2"
    u8 orig = inb(PORT_PS2_CTRLB);
    outb((orig & ~PPCB_SPKR) | PPCB_T2GATE, PORT_PS2_CTRLB);
    /* binary, mode 0, LSB/MSB, Ch 2 */
    outb(PM_SEL_TIMER2|PM_ACCESS_WORD|PM_MODE0|PM_CNT_BINARY, PORT_PIT_MODE);
    /* LSB of ticks */
    outb(CALIBRATE_COUNT & 0xFF, PORT_PIT_COUNTER2);
    /* MSB of ticks */
    outb(CALIBRATE_COUNT >> 8, PORT_PIT_COUNTER2);
    // ...
}

从这里看,似乎和 PPCB_SPKR 有关,也就是 speaker 有关的

内核中对应的代码

static unsigned long quick_pit_calibrate(void)
{
  int i;
  u64 tsc, delta;
  unsigned long d1, d2;

  /* Set the Gate high, disable speaker */
  outb((inb(0x61) & ~0x02) | 0x01, 0x61);

QEMU 中观察

在 monitor 中 info mtree -f 可以知道

FlatView #1
 AS "I/O", root: io
 Root memory region: io
  0000000000000000-0000000000000007 (prio 0, i/o): dma-chan
  0000000000000008-000000000000000f (prio 0, i/o): dma-cont
  0000000000000010-000000000000001f (prio 0, container): io @0000000000000010
  0000000000000020-0000000000000021 (prio 0, i/o): kvm-pic
  0000000000000022-000000000000003f (prio 0, container): io @0000000000000022
  0000000000000040-0000000000000043 (prio 0, i/o): kvm-pit
  0000000000000044-000000000000005f (prio 0, container): io @0000000000000044
  0000000000000060-0000000000000060 (prio 0, i/o): i8042-data
  0000000000000061-0000000000000061 (prio 0, i/o): pcspk

分析 /hw/audio/pcspk.c isa_register_soundhw 中注册的初始化代码有效果的前提是当 QEMU 提供了 soundhw 的。

static void pcspk_register(void)
{
    type_register_static(&pcspk_info);
    isa_register_soundhw("pcspk", "PC speaker", pcspk_audio_init);
}

错误的结论

由此,我得出结论,始终校准和 speaker 有关,这显然是一个很扯的结论,不过我当时的组会上提到过这个事情, 我的导师认为不对,不过这是一个细枝末节的问题,我们再也没深究了。 2026-09-21 我用 deepseek flash v4.1 分析,确认结论如下:

本质就是在用 PIT —— 具体是 PIT 的通道 2(channel 2)。但”用 speaker 来校准 TSC”这个说法要纠正一下: 这里被当作秒表的是 8253/8254 PIT 通道 2 的计数器,speaker 只是通道 2 输出信号的下游消费者之一,而我们恰恰把 PPCB_SPKR 清掉了,就是让它别出声。

数据通路

   14.31818 MHz 晶振 ──/12──> 1.193182 MHz
                                  │
                                  ▼
                          ┌───────────────┐
                          │ PIT ch2 计数  │  ← 装入 0x800 (2048) 个计数
                          └───────┬───────┘
                                  │ OUT (计数到 0 时翻转)
                                  ├──────────────> 0x61 bit5 (T2OUT 状态, 只读)
                                  │                    ▲
                                  │                    └── 代码在这里忙等,这就是"秒表读数"
                                  ▼
                          AND( gate(bit0) , SPKR(bit1) )
                                  │
                                  ▼
                              喇叭放大器  ← 被 ~PPCB_SPKR 静音,不参与校准

所以:

换句话说:不是用 speaker 校准,而是”顺手把 speaker 关掉”再借用它背后的那个定时器。

本站所有文章转发 CSDN 将按侵权追究法律责任,其它情况随意。