The S1 state is implemented simply by calling an ACPI-CA function after sending the device-sleeping request to the device driver. This ACPI-CA function uses the ACPI registers to stop the system. When the system wakes up from S1 sleep, it can immediately resume processing from where it was just before the sleep request was made.
The S2 and S3 states do not preserve the CPU context. So the FreeBSD kernel is required to do a CPU context save is required before entering the sleep state.
Since entering and exiting the S3 sleep state requires the use of real-mode, the kernel must place a ``resume handler'' somewhere in the first 1MB of memory so that it can be addressed from real mode. We use a perl script along with objcopy, hexdump, and nm to generate code. With this script, a real-mode executable object is turned into a header file with an array of chars and some definitions that point to the offset of the symbol in the object file. When the system boots the memory for the resume handler is allocated in the first 1MB as early as possible using a special memory allocator. The ACPI driver attach routine then copies and links in the resume handler code to the newly allocated memory area. Finally, the physical address of the resume handler is recorded in the ACPI driver software context.
When S3 is invoked, the physical address of the resume handler is installed in the ACPI firmware context via an ACPI-CA function. The memory mapping table of the current process is used to create an identity mapping where a process' virtual address maps directly to the hardware's physical address. This mapping is needed to restart paging when the system resumes. Next, the sleeping procedure saves all the registers. Special registers (segment selectors, GDTR, TR, LDTR, IDTR, CR2, CR3, CR4) are put into the appropriate places so they can be restored later by the real mode wake-up code. Other registers are saved in static variables in the kernel data area.
When the machine wakes up the firmware passes control, in real mode, to the resume handler. This is done by placing the top 16 bits of the physical address of the resume handler in the code segment register and placing the lower 4 bits in the instruction pointer. The handler sets the other segment registers to the same value as code segment registers. The CPU state can then be switched to protected mode by changing CR0. At this point the special registers are restored. Note that before restoring the task register, the task selector entry in GDT should be fixed up so that it does not become marked as a busy task selector.
After the special registers have been restored, then paging can be reenabled by setting the appropriate bit in CR0. After the paging has started, the instruction pointer is still pointing to an identity-mapped page. Next, the handler passes the control back to its calling routine which resides in normal kernel virtual memory. Finally, the stack pointer and remaining preserved registers are restored. Figure 5 shows memory mapping transition.
After returning to the sleep code, hardware devices should be restored. First, the interrupt controller must be reprogrammed because some device drivers and ACPI itself depend on functioning interrupts. To do this, we split the isa_defaultirq()@intr_machdep.c function into two. Now isa_defaultirq() installs a stray interrupt handler, and then calls init_i8259 to initialize the interrupt controller. We call the resumption function icu_reinit()@intr_machdep, and then we call the routine init_i8259(). We then check the interrupt handler and enable the interrupt if the handler is not a stray interrupt handler. After this call, we use the routine call DEVICE_RESUME method of root_bus to request that the device drivers resume the device, after which normal operation resumes.