Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions Documentation/mainboard/emulation/qemu-q35-s3-verify.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
qemu-q35 ACPI S3 smoke (Dasharo/dasharo-issues#902)
==============================================

Host: QEMU emulator version 8.2.2 (Debian 1:8.2.2+ds-0ubuntu1.18)
ROM: configs/config.emulation_qemu_x86_q35_s3_smoke
coreboot 64d6c7396a (x86_32, loglevel 8, PAYLOAD_NONE + injected stub)
Command:
make olddefconfig && make -j$(nproc)
src/mainboard/emulation/qemu-q35/s3-verify.sh build/coreboot.rom

s3-verify.sh result
-------------------
PAYLOAD: S3-PAYLOAD: programmed
COLD: Q35 S3: PM1_STS=0x0001 PM1_CNT=0x00000000 s3resume=0
STATUS before S3: running
HMP o 0x605 0x24 -> event SUSPEND
STATUS after S3 write: suspended
WAKE: Q35 S3: PM1_STS=0x8101 PM1_CNT=0x00000000 s3resume=1
PASS: QEMU q35 S3 detect, TSEG cache resume, and FACS waking-vector jump

Serial excerpts (ANSI stripped)
------------------------------
Cold:
Q35 S3: PM1_STS=0x0001 PM1_CNT=0x00000000 s3resume=0
SMM_SIZE: 8 MiB
Subregion 1: 0x3fe00000 0x200000
S3-PAYLOAD: looking for FACS
S3-PAYLOAD: programmed

Wake:
Q35 S3: PM1_STS=0x8101 PM1_CNT=0x00000000 s3resume=1
S3 Resume
Jumping to image.
Trying to find the wakeup vector...
RSDP found at 0x000f0000
RSDT found at 0x3f64234e ends at 0x3f64238a
FADT found at 0x3f642146
FACS found at 0x3f640040
OS waking vector is 0x00001000
Wake vector 0x00001000 bytes fa ba f8 03
Q35VEC

Test matrix
-----------
1 Cold boot s3resume=0 PASS (this run)
2 Firmware HMP S3 + system_wakeup PASS (this run)
3 Guest Linux systemctl suspend NOT RUN (no guest disk in this environment)
4 Guest S5 / QEMU typ=0 poweroff NOT RUN (no guest OS)
5 ICH9-LPC.disable_s3=on NOT RUN (PM path is independent of fw_cfg)
6 CBMEM lost NOT RUN (destructive)
7 TSEG cache too small covered by prior commit 45fee7c607
8 TSEG 8MiB + SMM_RESERVED_SIZE=0x200000 PASS (Jumping to image, no postcar cache invalid)
9 ACPI 1.0 RSDT + firmware_ctrl PASS (FADT/FACS found; not "No FADT found")
10 FACS waking vector 0x1000 + Q35VEC PASS (this run)

Notes
-----
QEMU ACPI PM SLP_TYP is 1 for S3 and 0 for S5 (not ICH9 5/7).
Wake is RESET_TYPE_WAKEUP; WAK_STS|PWRBTN_STS|TMROF_STS = 0x8101.
qemu-q35 CAR is DRAM at 0x10000-0xa0000; bootblock zeros only allocated
CAR through _car_unallocated_start so a waking vector in unused
conventional memory (here 0x1000) survives the S3 reset.
65 changes: 65 additions & 0 deletions Documentation/mainboard/emulation/qemu-q35.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,71 @@ and firmware.
qemu-system-x86_64 -bios build/coreboot.rom -serial stdio -M q35
```

## ACPI S3 resume (QEMU ACPI PM, not ICH9)

QEMU's ACPI PM (`hw/acpi/core.c`) does **not** implement ICH9 sleep types:

| Sleep | QEMU `SLP_TYP` | ICH9 `SLP_TYP` | QEMU action |
| ----- | ------------- | -------------- | ----------- |
| S3 | **1** | 5 | `qemu_system_suspend_request()` |
| S5 | **0** | 7 | `qemu_system_shutdown_request()` |

Intel `sleepstates.asl` (`_S3=5`, `_S5=7`) must not be included on this board: Linux would write typ=5, which QEMU ignores (default branch; not S3).

Wake path (QEMU 8.2+ `pc_machine_wakeup()`):

1. Guest writes `PM1_CNT` with `SLP_TYP=1 | SLP_EN`, or QMP `system_suspend`.
2. `system_wakeup` runs `RESET_TYPE_WAKEUP` (reset vector, RAM preserved).
3. `acpi_notify_wakeup()` sets `PM1_STS.WAK_STS` (and `PWRBTN_STS` for `QEMU_WAKEUP_REASON_OTHER`).
4. `PM1_CNT` is typically 0 after wakeup reset, so `SLP_TYP` is no longer 1.
5. coreboot `acpi_get_sleep_type()` treats `WAK_STS` (and leftover QEMU typ=1) as `ACPI_S3`.
6. romstage recovers CBMEM and `romstage_handoff_init(true)`; ramstage jumps to the FACS 32-bit `firmware_waking_vector` if the OS stored one.

`ICH9-LPC.disable_s3` only changes fw_cfg `etc/system-states`. It does not change PM1_CNT behavior. This DSDT always advertises `_S3={1,1,0,0}`.

### Test matrix

| # | Case | Expected |
| - | ---- | -------- |
| 1 | Cold boot (`qemu-system-x86_64 -M q35 -bios coreboot.rom`) | Serial: `Q35 S3: ... s3resume=0`. No FACS jump. |
| 2 | Firmware-only wake: HMP `o 0x605 0x24` (`SLP_TYP=1\|SLP_EN`) then `system_wakeup`. QMP `system_suspend` without a guest OS often does not enter S3. | Second pass: `Q35 S3: ... s3resume=1` and `S3 Resume`. If FACS vector is 0, continue to payload (not a failure). |
| 3 | Guest S3 (Linux `systemctl suspend`) with waking vector | Detect S3, jump to OS vector, userspace resumes. |
| 4 | Guest S5 (`systemctl poweroff`) | QEMU exits (typ=0). Next VM start is cold (`s3resume=0`). |
| 5 | `ICH9-LPC.disable_s3=on` | Guest may still enter S3 via PM1; this port still detects `WAK_STS`. |
| 6 | CBMEM lost on wake | `S3 resume: CBMEM recovery failed, cold boot` then payload. |
| 7 | TSEG stage cache too small (`SMM_RESERVED_SIZE=0`) | Detect `s3resume=1`, then `Can't find 57a9e002 metadata in imd` / `postcar cache invalid` / `board_reset`. Next pass is a cold boot (`s3resume=0`) because the reset clears `WAK_STS`. |
| 8 | TSEG 8MiB + `SMM_RESERVED_SIZE=0x200000` (defaults with `CPU_QEMU_X86_TSEG_SMM`) | Cold boot stashes postcar/ramstage. Wake prints `S3 Resume` and continues; no `postcar cache invalid`. |
| 9 | QEMU ACPI 1.0 RSDP (`XSDT=0`) + FADT `x_firmware_ctl=0` | Resume must walk **RSDT** and `firmware_ctrl`, then print `FADT found` / `FACS found`. XSDT-only lookup prints `No FADT found` and never jumps. |
| 10 | Payload programs `FACS.firmware_waking_vector` at `0x1000` then S3 | Serial: `OS waking vector is 0x00001000` then stub `Q35VEC` on COM1. Stub must be below qemu CAR (`DCACHE_RAM_BASE=0x10000`); bootblock only zeros allocated CAR, not 64KiB–640KiB unused DRAM. |

On default `qemu-system-x86_64 -M q35`, coreboot loads QEMU's fw_cfg ACPI tables first and does **not** install the CBFS DSDT. Linux then sees QEMU's DSDT, which already uses `SLP_TYP 1` for S3 when `ICH9-LPC.disable_s3` is off. The CBFS `_S3={1,1,0,0}` still matters if ACPI build is off (`-machine acpi=off`) so coreboot's own DSDT is used. Firmware wake detection always uses `WAK_STS`, not the DSDT. The waking-vector jump uses the low-memory RSDP copy (often QEMU's ACPI 1.0 RSDP).

Stock Dasharo `configs/config.emulation_qemu_x86_q35_uefi` uses `CONFIG_DEFAULT_CONSOLE_LOGLEVEL_0`. S3 detect is printed at `BIOS_EMERG` so it still appears. For PM1 dumps on cold boot, use loglevel 6+ or `configs/config.emulation_qemu_x86_q35_s3_smoke`.

The board selects `DASHARO_PREFER_S3_SLEEP` so the EDK2 payload is built with S3 as the default sleep (otherwise `PAYLOAD_EDK2` defaults `DASHARO_PREFER_S3_SLEEP` off / S0ix).

`HAVE_ACPI_RESUME` plus `SMM_TSEG` selects `TSEG_STAGE_CACHE`. qemu-q35 must set `SMM_RESERVED_SIZE` (2MiB) and a larger TSEG (8MiB): `CBMEM_STAGE_CACHE` is unavailable while TSEG SMM is on, and a zero-sized TSEG subregion makes detect-then-reset look like a failed S3.

On QEMU, cache-as-RAM is ordinary DRAM at `DCACHE_RAM_BASE` (`0x10000`) for `DCACHE_RAM_SIZE` (up to VGA at `0xa0000`). Bootblock zeros only allocated CAR objects (`_car_region_start` through `_car_unallocated_start`), not the unused tail. Wiping 64KiB–640KiB on every reset would erase a guest `firmware_waking_vector` before ramstage jumped to it.

Firmware-only script (no Ubuntu disk):

```bash
cp configs/config.emulation_qemu_x86_q35_s3_smoke .config
make olddefconfig && make -j$(nproc)
src/mainboard/emulation/qemu-q35/s3-verify.sh build/coreboot.rom
```

Captured smoke output from QEMU 8.2.2: ``Documentation/mainboard/emulation/qemu-q35-s3-verify.txt``.

Linux check after a real guest suspend/resume:

```text
dmesg | grep -E 'ACPI:.*S3|PM: suspend|thaw'
```

Do not include Intel `src/southbridge/intel/common/acpi/sleepstates.asl` on qemu-q35.

## Finding bugs
To test coreboot's x86 code it's recommended to run on a x86 host and enable KVM.
It will not only run faster, but is closer to real hardware. If you see the
Expand Down
13 changes: 13 additions & 0 deletions configs/config.emulation_qemu_x86_q35_s3_smoke
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Minimal qemu-q35 image for ACPI S3 wake detection (no EDK2).
# Use with: cp configs/config.emulation_qemu_x86_q35_s3_smoke .config && make olddefconfig && make
# Then: src/mainboard/emulation/qemu-q35/s3-verify.sh build/coreboot.rom
# TSEG 8MiB + SMM_RESERVED_SIZE=2MiB come from src/cpu/qemu-x86/Kconfig when TSEG SMM is on.
CONFIG_VENDOR_EMULATION=y
CONFIG_BOARD_EMULATION_QEMU_X86_Q35=y
CONFIG_CPU_QEMU_X86_TSEG_SMM=y
CONFIG_ANY_TOOLCHAIN=y
CONFIG_UDK_202005_BINDING=y
CONFIG_DEFAULT_CONSOLE_LOGLEVEL_8=y
CONFIG_PAYLOAD_NONE=y
CONFIG_CONSOLE_SERIAL=y
CONFIG_BOOTBLOCK_CONSOLE=y
82 changes: 65 additions & 17 deletions src/acpi/acpi.c
Original file line number Diff line number Diff line change
Expand Up @@ -1750,15 +1750,74 @@ static acpi_rsdp_t *valid_rsdp(acpi_rsdp_t *rsdp)
return rsdp;
}

static acpi_fadt_t *acpi_facp_from_table(void *table)
{
acpi_header_t *header = table;

if (header && strncmp(header->signature, "FACP", 4) == 0)
return table;
return NULL;
}

/*
* QEMU (and ACPI 1.0) publishes an RSDP with XSDT=0 and only RSDT.
* QEMU's FADT also leaves x_firmware_ctl at 0 and uses firmware_ctrl.
* Walking only XSDT / x_firmware_ctl then reports "No FADT" on S3 and
* never jumps to the OS waking vector.
*/
static acpi_fadt_t *acpi_find_fadt(acpi_rsdp_t *rsdp)
{
int i;
char *end;

if (rsdp->xsdt_address) {
acpi_xsdt_t *xsdt = (acpi_xsdt_t *)(uintptr_t)rsdp->xsdt_address;
end = (char *)xsdt + xsdt->header.length;
printk(BIOS_DEBUG, "XSDT found at %p ends at %p\n", xsdt, end);
for (i = 0; (char *)&xsdt->entry[i] < end; i++) {
acpi_fadt_t *fadt = acpi_facp_from_table(
(void *)(uintptr_t)xsdt->entry[i]);
if (fadt)
return fadt;
}
}

if (rsdp->rsdt_address) {
acpi_rsdt_t *rsdt = (acpi_rsdt_t *)(uintptr_t)rsdp->rsdt_address;
end = (char *)rsdt + rsdt->header.length;
printk(BIOS_DEBUG, "RSDT found at %p ends at %p\n", rsdt, end);
for (i = 0; (char *)&rsdt->entry[i] < end; i++) {
acpi_fadt_t *fadt = acpi_facp_from_table(
(void *)(uintptr_t)rsdt->entry[i]);
if (fadt)
return fadt;
}
}

return NULL;
}

static acpi_facs_t *acpi_facs_from_fadt(acpi_fadt_t *fadt)
{
uint64_t facs_addr;

facs_addr = ((uint64_t)fadt->x_firmware_ctl_h << 32) |
fadt->x_firmware_ctl_l;
if (!facs_addr)
facs_addr = fadt->firmware_ctrl;

if (!facs_addr)
return NULL;
return (acpi_facs_t *)(uintptr_t)facs_addr;
}

void *acpi_find_wakeup_vector(void)
{
char *p, *end;
acpi_xsdt_t *xsdt;
char *p;
acpi_facs_t *facs;
acpi_fadt_t *fadt = NULL;
acpi_fadt_t *fadt;
acpi_rsdp_t *rsdp = NULL;
void *wake_vec;
int i;

if (!acpi_is_wakeup_s3())
return NULL;
Expand All @@ -1779,17 +1838,7 @@ void *acpi_find_wakeup_vector(void)
}

printk(BIOS_DEBUG, "RSDP found at %p\n", rsdp);
xsdt = (acpi_xsdt_t *)(uintptr_t)rsdp->xsdt_address;

end = (char *)xsdt + xsdt->header.length;
printk(BIOS_DEBUG, "XSDT found at %p ends at %p\n", xsdt, end);

for (i = 0; ((char *)&xsdt->entry[i]) < end; i++) {
fadt = (acpi_fadt_t *)(uintptr_t)xsdt->entry[i];
if (strncmp((char *)fadt, "FACP", 4) == 0)
break;
fadt = NULL;
}
fadt = acpi_find_fadt(rsdp);

if (fadt == NULL) {
printk(BIOS_ALERT,
Expand All @@ -1798,8 +1847,7 @@ void *acpi_find_wakeup_vector(void)
}

printk(BIOS_DEBUG, "FADT found at %p\n", fadt);
facs = (acpi_facs_t *)(uintptr_t)((uint64_t)fadt->x_firmware_ctl_l
| (uint64_t)fadt->x_firmware_ctl_h << 32);
facs = acpi_facs_from_fadt(fadt);

if (facs == NULL) {
printk(BIOS_ALERT,
Expand Down
4 changes: 4 additions & 0 deletions src/arch/x86/acpi_s3.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ void __noreturn acpi_resume(void *wake_vec)
timestamp_add_now(TS_ACPI_WAKE_JUMP);

post_code(POSTCODE_OS_RESUME);
printk(BIOS_DEBUG, "Wake vector %p bytes %02x %02x %02x %02x\n",
wake_vec,
((unsigned char *)wake_vec)[0], ((unsigned char *)wake_vec)[1],
((unsigned char *)wake_vec)[2], ((unsigned char *)wake_vec)[3]);
acpi_do_wakeup((uintptr_t)wake_vec);

die("Failed the jump to wakeup vector\n");
Expand Down
15 changes: 15 additions & 0 deletions src/cpu/qemu-x86/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,22 @@ endchoice
config SMM_TSEG_SIZE
hex
depends on SMM_TSEG
default 0x800000 if BOARD_EMULATION_QEMU_X86_Q35
default 0x100000
help
qemu-q35 S3 resume stashes postcar and ramstage in TSEG
(TSEG_STAGE_CACHE). This must be larger than SMM_RESERVED_SIZE.
Romstage programs ICH9 ESMRAMC for 1, 2, or 8 MiB.

config SMM_RESERVED_SIZE
hex
depends on SMM_TSEG
default 0x200000 if BOARD_EMULATION_QEMU_X86_Q35
help
External stage-cache subregion at the top of TSEG. qemu-q35 did
not set this (0), so HAVE_ACPI_RESUME detected S3 and then
postcar_cache_invalid() reset. Size for postcar plus ramstage
minus HEAP_SIZE.

config MAX_CPUS
int
Expand Down
8 changes: 5 additions & 3 deletions src/cpu/qemu-x86/cache_as_ram_bootblock.S
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ cache_as_ram:
/*
* Nothing to do here on qemu, RAM works just fine without any
* initialization.
*
* DCACHE_RAM is real DRAM (q35: 0x10000-0xa0000). Zeroing the unused
* tail would wipe an OS firmware_waking_vector on S3 resume. Only
* clear allocated CAR objects (stack, console, timestamps, BSS).
*/

/* Clear the cache memory region. This will also clear CAR GLOBAL */
movl $_car_region_start, %edi
movl $_car_region_end, %ecx
movl $_car_unallocated_start, %ecx
sub %edi, %ecx
shr $2, %ecx
xorl %eax, %eax
Expand Down
7 changes: 6 additions & 1 deletion src/mainboard/emulation/qemu-q35/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ config BOARD_SPECIFIC_OPTIONS
select HAVE_OPTION_TABLE
# select HAVE_PIRQ_TABLE
select HAVE_ACPI_TABLES
select HAVE_ACPI_RESUME
# EDK2 defaults DASHARO_PREFER_S3_SLEEP off (S0ix). Force S3.
select DASHARO_PREFER_S3_SLEEP
select BOARD_ROMSIZE_KB_8192
select MAINBOARD_HAS_NATIVE_VGA_INIT
select MAINBOARD_FORCE_NATIVE_VGA_INIT if !CHROMEOS
Expand Down Expand Up @@ -79,7 +82,9 @@ config MAX_ACPI_TABLE_SIZE_KB
default 224

# Skip the first 64KiB as coreboot table pointer is installed
# at address 0
# at address 0. Bootblock only zeros allocated CAR (through
# _car_unallocated_start) so an OS waking vector in unused
# conventional memory survives S3.
config DCACHE_RAM_BASE
hex
default 0x10000
Expand Down
11 changes: 11 additions & 0 deletions src/mainboard/emulation/qemu-q35/dsdt.asl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ DefinitionBlock (
{
#include <acpi/dsdt_top.asl>

/*
* QEMU ACPI PM (hw/acpi/core.c) is not ICH9:
* SLP_TYP 1 = S3 (qemu_system_suspend_request)
* SLP_TYP 0 = S5 (qemu_system_shutdown_request)
* Intel sleepstates.asl uses 5/7 and would shut down or no-op.
*/
#if CONFIG(HAVE_ACPI_RESUME)
Name(\_S3, Package () { 0x01, 0x01, 0x00, 0x00 })
#endif
Name(\_S5, Package () { 0x00, 0x00, 0x00, 0x00 })

#include "../qemu-i440fx/acpi/dbug.asl"

Scope(\_SB) {
Expand Down
4 changes: 4 additions & 0 deletions src/mainboard/emulation/qemu-q35/q35.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
#include <device/pci_type.h>
#include <types.h>

#if CONFIG(SMM_TSEG) && (CONFIG_SMM_TSEG_SIZE <= CONFIG_SMM_RESERVED_SIZE)
#error "qemu-q35 TSEG must be larger than SMM_RESERVED_SIZE for S3 stage cache"
#endif

#define HOST_BRIDGE PCI_DEV(0, 0, 0)

#define EXT_TSEG_MBYTES 0x50
Expand Down
Loading