diff --git a/loader/arch/x86/bios/CMakeLists.txt b/loader/arch/x86/bios/CMakeLists.txt index e80fc1c..2d6623a 100644 --- a/loader/arch/x86/bios/CMakeLists.txt +++ b/loader/arch/x86/bios/CMakeLists.txt @@ -29,6 +29,7 @@ target_sources( target_sources( ${LOADER_EXECUTABLE} PRIVATE + apm.c bios_disk_services.c bios_entry.c bios_find.c diff --git a/loader/arch/x86/bios/apm.c b/loader/arch/x86/bios/apm.c new file mode 100644 index 0000000..35de9fd --- /dev/null +++ b/loader/arch/x86/bios/apm.c @@ -0,0 +1,98 @@ +#define MSG_FMT(msg) "BIOS-APM: " msg + +#include "common/format.h" +#include "common/log.h" +#include "bios_call.h" +#include "services_impl.h" +#include "apm.h" + +#define APM_SIGNATURE 0x504D +#define APM_POWER_DEVICE_ID_APM_BIOS 0x0000 + +#define APM_FLAG_32BIT_INTERFACE_SUPPORTED (1 << 1) + +#define APM_INT 0x15 +#define APM_CMD 0x53 +#define MAKE_APM_CMD(cmd) ((APM_CMD << 8) | (cmd)) + +#define APM_INSTALLATION_CHECK MAKE_APM_CMD(0x00) +#define APM_PM32_INTERFACE_CONNECT MAKE_APM_CMD(0x03) +#define APM_INTERFACE_DISCONNECT MAKE_APM_CMD(0x04) + +static bool check_apm_call( + const struct real_mode_regs *in_regs, + const struct real_mode_regs *out_regs +) +{ + if (is_carry_set(out_regs)) { + print_warn( + "APM call 0x%04X failed: %d\n", + in_regs->eax, (out_regs->eax & 0xFFFF) >> 8 + ); + return false; + } + + if (in_regs->eax == APM_INSTALLATION_CHECK) { + u16 signature = out_regs->ebx & 0xFFFF; + + if (unlikely(signature != APM_SIGNATURE)) { + print_warn("bad APM signature 0x%04X\n", signature); + return false; + } + } + + return true; +} + +bool services_setup_apm(struct apm_info *out_info) +{ + struct real_mode_regs out_regs, in_regs = { 0 }; + + // All queries will be for the APM BIOS "device" + in_regs.ebx = APM_POWER_DEVICE_ID_APM_BIOS; + + // 1. Check if APM exists at all + in_regs.eax = APM_INSTALLATION_CHECK; + bios_call(APM_INT, &in_regs, &out_regs); + if (!check_apm_call(&in_regs, &out_regs)) + return false; + + if (!(out_regs.ecx & APM_FLAG_32BIT_INTERFACE_SUPPORTED)) { + print_warn("APM doesn't support 32-bit interface\n"); + return false; + } + + // 2. Disconnect if anything was connected previously, ignore return value + in_regs.eax = APM_INTERFACE_DISCONNECT; + bios_call(APM_INT, &in_regs, &out_regs); + + // 3. Connect the 32-bit interface + in_regs.eax = APM_PM32_INTERFACE_CONNECT; + bios_call(APM_INT, &in_regs, &out_regs); + if (!check_apm_call(&in_regs, &out_regs)) + return false; + + print_info("32-bit PM interface connected\n"); + out_info->pm_code_segment = out_regs.eax & 0xFFFF; + out_info->pm_code_segment_length = out_regs.esi & 0xFFFF; + out_info->pm_offset = out_regs.ebx; + + out_info->rm_code_segment = out_regs.ecx & 0xFFFF; + out_info->rm_code_segment_length = out_regs.esi >> 16; + + out_info->data_segment = out_regs.edx & 0xFFFF; + out_info->data_segment_length = out_regs.edi & 0xFFFF; + + // 4. Recheck flags, as they might change after 32-bit interface install + in_regs.eax = APM_INSTALLATION_CHECK; + bios_call(APM_INT, &in_regs, &out_regs); + if (unlikely(!check_apm_call(&in_regs, &out_regs))) { + in_regs.eax = APM_INTERFACE_DISCONNECT; + bios_call(APM_INT, &in_regs, &out_regs); + return false; + } + + out_info->version = out_regs.eax & 0xFFFF; + out_info->flags = out_regs.ecx & 0xFFFF; + return true; +} diff --git a/loader/boot_protocol/ultra.c b/loader/boot_protocol/ultra.c index 8a8494c..29f843e 100644 --- a/loader/boot_protocol/ultra.c +++ b/loader/boot_protocol/ultra.c @@ -458,10 +458,28 @@ static bool set_video_mode(struct config *cfg, struct loadable_entry *entry, return true; } +static bool apm_setup(struct config *cfg, struct loadable_entry *le, + struct apm_info *out_info) +{ + bool wants_apm = false; + + cfg_get_bool(cfg, le, SV("setup-apm"), &wants_apm); + if (!wants_apm) + return false; + + if (services_get_provider() != SERVICE_PROVIDER_BIOS) { + print_info("ignoring request to set up APM on UEFI\n"); + return false; + } + + return services_setup_apm(out_info); +} + struct attribute_array_spec { bool higher_half_pointers; bool fb_present; bool cmdline_present; + bool apm_info_present; uint8_t page_table_depth; struct ultra_framebuffer fb; @@ -471,6 +489,8 @@ struct attribute_array_spec { struct dynamic_buffer module_buf; + struct apm_info apm_info; + ptr_t acpi_rsdp_address; ptr_t dtb_address; ptr_t smbios_address; @@ -595,6 +615,18 @@ static void *write_framebuffer(struct ultra_framebuffer_attribute *fb_attr, return ++fb_attr; } +static void *write_apm_info(struct ultra_apm_attribute *apm_attr, + const struct attribute_array_spec *spec) +{ + apm_attr->header.type = ULTRA_ATTRIBUTE_APM_INFO; + apm_attr->header.size = sizeof(struct ultra_apm_attribute); + + BUILD_BUG_ON(sizeof(apm_attr->info) != sizeof(struct apm_info)); + memcpy(&apm_attr->info, &spec->apm_info, sizeof(struct apm_info)); + + return ++apm_attr; +} + static void *write_memory_map(void *attr_ptr, size_t entry_count) { struct ultra_memory_map_attribute *mm = attr_ptr; @@ -651,6 +683,8 @@ static ptr_t build_attribute_array(const struct attribute_array_spec *spec, bytes_needed += cmdline_aligned_length; bytes_needed += spec->fb_present * sizeof(struct ultra_framebuffer_attribute); + bytes_needed += spec->apm_info_present * + sizeof(struct ultra_apm_attribute); bytes_needed += sizeof(struct ultra_memory_map_attribute); // Add 2 to give some leeway for memory map growth after the next allocation @@ -725,6 +759,11 @@ static ptr_t build_attribute_array(const struct attribute_array_spec *spec, *attr_count += 1; } + if (spec->apm_info_present) { + attr_ptr = write_apm_info(attr_ptr, spec); + *attr_count += 1; + } + attr_ptr = write_memory_map(attr_ptr, mm_entry_count); *attr_count += 1; return ret; @@ -1140,6 +1179,8 @@ static void ultra_protocol_boot(struct config *cfg, struct loadable_entry *le) spec.dtb_address = services_find_dtb(); spec.smbios_address = services_find_smbios(); + spec.apm_info_present = apm_setup(cfg, le, &spec.apm_info); + /* * Attempt to set video mode last, as we're not going to be able to use * legacy tty logging after that. diff --git a/loader/boot_protocol/ultra_protocol b/loader/boot_protocol/ultra_protocol index 69235d3..edaaf53 160000 --- a/loader/boot_protocol/ultra_protocol +++ b/loader/boot_protocol/ultra_protocol @@ -1 +1 @@ -Subproject commit 69235d363c077a090a9b7a313ed8c6f73e4a4807 +Subproject commit edaaf53b976fea867b9ae953c6f2be1ab0a827d4 diff --git a/loader/include/apm.h b/loader/include/apm.h new file mode 100644 index 0000000..f25dbff --- /dev/null +++ b/loader/include/apm.h @@ -0,0 +1,18 @@ +#pragma once + +#include "common/types.h" + +struct apm_info { + u16 version; + u16 flags; + + u16 pm_code_segment; + u16 pm_code_segment_length; + u32 pm_offset; + + u16 rm_code_segment; + u16 rm_code_segment_length; + + u16 data_segment; + u16 data_segment_length; +}; diff --git a/loader/include/services.h b/loader/include/services.h index 9d0bf85..8cbe762 100644 --- a/loader/include/services.h +++ b/loader/include/services.h @@ -2,6 +2,7 @@ #include "common/types.h" #include "common/attributes.h" +#include "apm.h" enum service_provider { SERVICE_PROVIDER_INVALID, @@ -28,6 +29,12 @@ ptr_t services_find_dtb(void); */ ptr_t services_find_smbios(void); +/* + * Attempts to setup the 32-bit protected-mode interface for APM if it exists. + * Returns true if the interface was successfully installed, false otherwise. + */ +bool services_setup_apm(struct apm_info *out_info); + /* * Aborts the loader execution in a platform-specific manner. * Must be used for unrecoverable errors. diff --git a/loader/uefi/CMakeLists.txt b/loader/uefi/CMakeLists.txt index c6f20b3..b7a499f 100644 --- a/loader/uefi/CMakeLists.txt +++ b/loader/uefi/CMakeLists.txt @@ -34,4 +34,5 @@ target_sources( uefi_memory_services.c uefi_video_services.c relocator.c + stubs.c ) diff --git a/loader/uefi/stubs.c b/loader/uefi/stubs.c new file mode 100644 index 0000000..328bcbe --- /dev/null +++ b/loader/uefi/stubs.c @@ -0,0 +1,13 @@ +#define MSG_FMT(msg) "UEFI-STUBS: " msg + +#include "common/log.h" +#include "common/types.h" +#include "apm.h" + +bool services_setup_apm(struct apm_info *out_info) +{ + UNUSED(out_info); + + print_warn("APM setup is unsupported!\n"); + return false; +} diff --git a/tests/disk_image.py b/tests/disk_image.py index b5e3d91..41010a3 100644 --- a/tests/disk_image.py +++ b/tests/disk_image.py @@ -11,6 +11,7 @@ [i686_lower_half] protocol=ultra +setup-apm = true cmdline = {cmdline} binary = "/boot/kernel_i686_lower_half" @@ -37,6 +38,7 @@ [i686_higher_half] protocol=ultra +setup-apm = true cmdline = {cmdline} binary: @@ -68,6 +70,7 @@ [amd64_lower_half] protocol=ultra +setup-apm = true cmdline = {cmdline} binary = "/boot/kernel_amd64_lower_half" @@ -94,6 +97,7 @@ [amd64_higher_half] protocol=ultra +setup-apm = true cmdline = {cmdline} binary: @@ -109,6 +113,7 @@ [amd64_higher_half_5lvl] protocol=ultra +setup-apm = true cmdline = {cmdline} binary: diff --git a/tests/kernel/kernel.c b/tests/kernel/kernel.c index f6f35eb..970b594 100644 --- a/tests/kernel/kernel.c +++ b/tests/kernel/kernel.c @@ -306,6 +306,24 @@ static void validate_modules(struct ultra_module_info_attribute *mi, print("modules OK\n"); } +static void validate_apm_info(struct ultra_apm_attribute *apm) +{ + struct ultra_apm_info *info = &apm->info; + + if (info->version < 0x0100) + test_fail("bogus APM version 0x%04X\n", info->version); + if (!(info->flags & 2)) + test_fail("bogus APM flags: %d\n", info->flags); + if (info->pm_code_segment_length == 0) + test_fail("bogus pm code segment length %d\n", info->pm_code_segment_length); + if (info->rm_code_segment_length == 0) + test_fail("bogus rm code segment length %d\n", info->rm_code_segment_length); + if (info->data_segment_length == 0) + test_fail("bogus data segment length %d\n", info->data_segment_length); + + print("APM info OK\n"); +} + static void validate_platform_info(struct ultra_platform_info_attribute *pi, struct ultra_kernel_info_attribute *ki) { @@ -363,6 +381,7 @@ static void attribute_array_verify(struct ultra_boot_context *bctx) struct ultra_command_line_attribute *cl = NULL; struct ultra_framebuffer_attribute *fb = NULL; struct ultra_memory_map_attribute *mm = NULL; + struct ultra_apm_attribute *apm_info = NULL; struct ultra_module_info_attribute *modules_begin = NULL; size_t i, module_count = 0; bool modules_eof = false; @@ -435,6 +454,10 @@ static void attribute_array_verify(struct ultra_boot_context *bctx) break; + case ULTRA_ATTRIBUTE_APM_INFO: + apm_info = cursor; + break; + default: test_fail("invalid attribute type %u\n", hdr->type); } @@ -454,6 +477,9 @@ static void attribute_array_verify(struct ultra_boot_context *bctx) validate_platform_info(pi, ki); validate_modules(modules_begin, module_count, mm, pi); + if (apm_info) + validate_apm_info(apm_info); + print("\nLoader info: %s (version %d.%d) on %s\n", pi->loader_name, pi->loader_major, pi->loader_minor, platform_to_string(pi->platform_type));