| // Copyright 2021 The Fuchsia Authors |
| // |
| // Use of this source code is governed by a MIT-style |
| // license that can be found in the LICENSE file or at |
| // https://opensource.org/licenses/MIT |
| |
| #ifndef ZIRCON_KERNEL_ARCH_X86_PHYS_LINUXBOOT_H_ |
| #define ZIRCON_KERNEL_ARCH_X86_PHYS_LINUXBOOT_H_ |
| |
| #include <lib/zircon-internal/e820.h> |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| // This defines some layouts and constants for the Linux/x86 Boot Protocol, |
| // described in https://www.kernel.org/doc/html/latest/x86/boot.html; this |
| // file uses the struct and member names in that document, which reflect the |
| // <asm/bootparam.h> code in the Linux kernel's public headers, but placed in |
| // the `linuxboot` C++ namespace. Ony the subset needed by the shim code |
| // implemented for Zircon is included here. More can be added as necessary. |
| namespace linuxboot { |
| |
| // This is the primary protocol data structure that the boot loader reads and |
| // writes. Its placement in boot_params (below) puts the `jump` member at |
| // exactly 512 bytes from the start of boot_params, which is at the start of |
| // the whole bzImage file. The boot loader first loads the first 512-byte |
| // sector, so it can see just boot_params up through `hdr.boot_flag`. |
| // |
| // The boot loader then checks `header`, `version`, and `boot_flag` to validate |
| // the precise protocol it should be using. In the versions we support, then |
| // it consults `loadflags` and `syssize` for more details and those tell it |
| // which fixed load address to use for the main kernel image, and how big it is |
| // (in 16-byte units). As `loadflags` is past the first 512 bytes, the boot |
| // loader always reads at least one more sector. Whether it reads more than |
| // that depends on which booting path it's going to use. In either path, it |
| // loads the main kernel at the fixed load address indicated by the version and |
| // loadflags; for bzImage format it's always 1MiB. |
| // |
| // * Direct 32-bit entry ignores the rest of the setup area. It allocates a |
| // new boot_params struct somewhere and first zeros the whole thing. Then |
| // it copies just the setup_header region from the original image into that |
| // boot_params::hdr, and modifies various fields in setup_header and other |
| // parts of boot_params to pass information to the kernel. Finally it |
| // simply jumps to the fixed load address indicated by the setup_header |
| // flags, in 32-bit protected mode. The %esi register holds the physical |
| // address of the setup area (i.e. `boot_params*`). The %esp register |
| // points to some usable stack space. |
| // |
| // * 16-bit entry uses the rest of the setup area that 32-bit entry ignores. |
| // It looks at setup_header::setup_sects for a count of 512-byte sectors to |
| // read after the first. That whole "setup area" from the beginning of the |
| // image up through the total size `(hdr.setup_sects + 1) * 512` is loaded |
| // at some arbitrary 4KiB-aligned address, hence the moniker "zero page". |
| // Then it simply jumps to 512 bytes into the setup area, in 16-bit real |
| // mode. The %cs segment points to this directly, so the entry point is at |
| // %cs:0. The %ds and %es segments point to the start of the setup area, so |
| // %ds:0 (i.e. `%ds << 4`) is the boot_params object. The %ss:%sp points to |
| // some usable stack space. The boot loader fills in a few essential fields |
| // but not as much as the 32-bit entry protocol would. The 16-bit entry |
| // code is responsible for discovering more on its own and is expected to |
| // rely on the legacy PC 16-bit BIOS ABI. Traditionally, it allocates a new |
| // boot_params struct of its own and copies setup_header and other data into |
| // it to pass along to the 32-bit entry point so it looks like the direct |
| // 32-bit entry from a boot loader would. |
| // |
| struct [[gnu::packed]] setup_header { |
| enum LoadFlags : uint8_t { |
| kLoadedHigh = 1 << 0, // Load at 1MiB fixed address. |
| }; |
| |
| static constexpr uint16_t kBootFlag = 0xaa55; // boot_flag must match this. |
| |
| uint8_t setup_sects; |
| uint16_t root_flags; |
| uint32_t syssize; |
| uint16_t ram_size; |
| uint16_t vid_mode; |
| uint16_t root_dev; |
| uint16_t boot_flag; |
| uint16_t jump; |
| uint32_t header; |
| uint16_t version; |
| uint32_t realmode_swtch; |
| uint16_t start_sys_seg; |
| uint16_t kernel_version; |
| uint8_t type_of_loader; |
| uint8_t loadflags; |
| uint16_t setup_move_size; |
| uint32_t code32_start; |
| uint32_t ramdisk_image; |
| uint32_t ramdisk_size; |
| uint32_t bootsect_kludge; |
| uint16_t heap_end_ptr; |
| uint8_t ext_loader_ver; |
| uint8_t ext_loader_type; |
| uint32_t cmd_line_ptr; |
| uint32_t initrd_addr_max; |
| uint32_t kernel_alignment; |
| uint8_t relocatable_kernel; |
| uint8_t min_alignment; |
| uint16_t xloadflags; |
| uint32_t cmdline_size; |
| uint32_t hardware_subarch; |
| uint64_t hardware_subarch_data; |
| uint32_t payload_offset; |
| uint32_t payload_length; |
| uint64_t setup_data; |
| uint64_t pref_address; |
| uint32_t init_size; |
| uint32_t handover_offset; |
| uint32_t kernel_info_offset; |
| }; |
| |
| static constexpr uint32_t kLoadedHighAddress = 0x100000; |
| |
| // Many of these inner struct types are not actually consulted by shim code. |
| // But their layouts are complete here to get the overall boot_params layout. |
| |
| // This is `struct screen_info` in Linux, but `screen_info` is also used as a |
| // field name, which isn't good in C++. |
| struct [[gnu::packed]] screen_info_t { |
| uint8_t orig_x; |
| uint8_t orig_y; |
| uint16_t ext_mem_k; |
| uint16_t orig_video_page; |
| uint8_t orig_video_mode; |
| uint8_t orig_video_cols; |
| uint8_t flags; |
| uint8_t unused2; |
| uint16_t orig_video_ega_bx; |
| uint16_t unused3; |
| uint8_t orig_video_lines; |
| uint8_t orig_video_isVGA; |
| uint16_t orig_video_points; |
| uint16_t lfb_width; |
| uint16_t lfb_height; |
| uint16_t lfb_depth; |
| uint32_t lfb_base; |
| uint32_t lfb_size; |
| uint16_t cl_magic, cl_offset; |
| uint16_t lfb_linelength; |
| uint8_t red_size; |
| uint8_t red_pos; |
| uint8_t green_size; |
| uint8_t green_pos; |
| uint8_t blue_size; |
| uint8_t blue_pos; |
| uint8_t rsvd_size; |
| uint8_t rsvd_pos; |
| uint16_t vesapm_seg; |
| uint16_t vesapm_off; |
| uint16_t pages; |
| uint16_t vesa_attributes; |
| uint32_t capabilities; |
| uint32_t ext_lfb_base; |
| uint8_t reserved[2]; |
| }; |
| |
| // This is `struct apm_bios_info` in Linux, but `apm_bios_info` is also used as |
| // a field name, which isn't good in C++. |
| struct apm_bios_info_t { |
| uint16_t version; |
| uint16_t cseg; |
| uint32_t offset; |
| uint16_t cseg_16; |
| uint16_t dseg; |
| uint16_t flags; |
| uint16_t cseg_len; |
| uint16_t cseg_16_len; |
| uint16_t dseg_len; |
| }; |
| |
| // This is `struct ist_info` in Linux, but `ist_info` is also used as a field |
| // name, which isn't good in C++. |
| struct ist_info_t { |
| uint32_t signature; |
| uint32_t command; |
| uint32_t event; |
| uint32_t perf_level; |
| }; |
| |
| // This is `struct sys_desc_table` in Linux, but `sys_desc_table` is also used |
| // as a field name, which isn't good in C++. |
| struct sys_desc_table_t { |
| uint16_t length; |
| uint8_t table[14]; |
| }; |
| |
| // This is `struct olpc_ofw_header` in Linux, but `olpc_ofw_header` is also |
| // used as a field name, which isn't good in C++. |
| struct olpc_ofw_header_t { |
| uint32_t ofw_magic; /* OFW signature */ |
| uint32_t ofw_version; |
| uint32_t cif_handler; /* callback into OFW */ |
| uint32_t irq_desc_table; |
| }; |
| |
| // This is `struct edid_info` in Linux, but `edid_info` is also |
| // used as a field name, which isn't good in C++. |
| using edid_info_t = uint8_t[128]; |
| |
| // This is `struct efi_info` in Linux, but `efi_info` is also |
| // used as a field name, which isn't good in C++. |
| struct efi_info_t { |
| uint32_t efi_loader_signature; |
| uint32_t efi_systab; |
| uint32_t efi_memdesc_size; |
| uint32_t efi_memdesc_version; |
| uint32_t efi_memmap; |
| uint32_t efi_memmap_size; |
| uint32_t efi_systab_hi; |
| uint32_t efi_memmap_hi; |
| }; |
| |
| struct [[gnu::packed]] edd_device_params { |
| uint16_t length; |
| uint16_t info_flags; |
| uint32_t num_default_cylinders; |
| uint32_t num_default_heads; |
| uint32_t sectors_per_track; |
| uint64_t number_of_sectors; |
| uint16_t bytes_per_sector; |
| uint32_t dpte_ptr; /* 0xFFFFFFFF for our purposes */ |
| uint16_t key; /* = 0xBEDD */ |
| uint8_t device_path_info_length; /* = 44 */ |
| uint8_t reserved2; |
| uint16_t reserved3; |
| uint8_t host_bus_type[4]; |
| uint8_t interface_type[8]; |
| union { |
| struct [[gnu::packed]] { |
| uint16_t base_address; |
| uint16_t reserved1; |
| uint32_t reserved2; |
| } isa; |
| struct [[gnu::packed]] { |
| uint8_t bus; |
| uint8_t slot; |
| uint8_t function; |
| uint8_t channel; |
| uint32_t reserved; |
| } pci; |
| /* pcix is same as pci */ |
| struct [[gnu::packed]] { |
| uint64_t reserved; |
| } ibnd; |
| struct [[gnu::packed]] { |
| uint64_t reserved; |
| } xprs; |
| struct [[gnu::packed]] { |
| uint64_t reserved; |
| } htpt; |
| struct [[gnu::packed]] { |
| uint64_t reserved; |
| } unknown; |
| } interface_path; |
| union { |
| struct [[gnu::packed]] { |
| uint8_t device; |
| uint8_t reserved1; |
| uint16_t reserved2; |
| uint32_t reserved3; |
| uint64_t reserved4; |
| } ata; |
| struct [[gnu::packed]] { |
| uint8_t device; |
| uint8_t lun; |
| uint8_t reserved1; |
| uint8_t reserved2; |
| uint32_t reserved3; |
| uint64_t reserved4; |
| } atapi; |
| struct [[gnu::packed]] { |
| uint16_t id; |
| uint64_t lun; |
| uint16_t reserved1; |
| uint32_t reserved2; |
| } scsi; |
| struct [[gnu::packed]] { |
| uint64_t serial_number; |
| uint64_t reserved; |
| } usb; |
| struct [[gnu::packed]] { |
| uint64_t eui; |
| uint64_t reserved; |
| } i1394; |
| struct [[gnu::packed]] { |
| uint64_t wwid; |
| uint64_t lun; |
| } fibre; |
| struct [[gnu::packed]] { |
| uint64_t identity_tag; |
| uint64_t reserved; |
| } i2o; |
| struct [[gnu::packed]] { |
| uint32_t array_number; |
| uint32_t reserved1; |
| uint64_t reserved2; |
| } raid; |
| struct [[gnu::packed]] { |
| uint8_t device; |
| uint8_t reserved1; |
| uint16_t reserved2; |
| uint32_t reserved3; |
| uint64_t reserved4; |
| } sata; |
| struct [[gnu::packed]] { |
| uint64_t reserved1; |
| uint64_t reserved2; |
| } unknown; |
| } device_path; |
| uint8_t reserved4; |
| uint8_t checksum; |
| }; |
| |
| struct [[gnu::packed]] edd_info { |
| uint8_t device; |
| uint8_t version; |
| uint16_t interface_support; |
| uint16_t legacy_max_cylinder; |
| uint8_t legacy_max_head; |
| uint8_t legacy_sectors_per_track; |
| edd_device_params params; |
| }; |
| |
| static constexpr size_t kMaxEddNr = 6; |
| |
| static constexpr size_t kMaxEddMbrSig = 16; |
| |
| static constexpr size_t kMaxE820TableEntries = 128; |
| |
| // This is also known as "the zero page". This is the overall layout that |
| // starts the "bzImage" file format. The `hdr.setup_sects` value determines |
| // how much is actually loaded along with it. |
| struct [[gnu::packed]] boot_params { |
| screen_info_t screen_info; |
| apm_bios_info_t apm_bios_info; |
| uint8_t pad2[4]; |
| uint64_t tboot_addr; |
| ist_info_t ist_info; |
| uint64_t acpi_rsdp_addr; |
| uint8_t _pad3[8]; |
| uint8_t hd0_info[16]; |
| uint8_t hd1_info[16]; |
| sys_desc_table_t sys_desc_table; |
| olpc_ofw_header_t olpc_ofw_header; |
| uint32_t ext_ramdisk_image; |
| uint32_t ext_ramdisk_size; |
| uint32_t ext_cmd_line_ptr; |
| uint8_t _pad4[116]; |
| edid_info_t edid_info; |
| efi_info_t efi_info; |
| uint32_t alt_mem_k; |
| uint32_t scratch; |
| uint8_t e820_entries; |
| uint8_t eddbuf_entries; |
| uint8_t edd_mbr_sig_buf_entries; |
| uint8_t kbd_status; |
| uint8_t secure_boot; |
| uint8_t pad5[2]; |
| uint8_t sentinel; |
| uint8_t pad6[1]; |
| setup_header hdr; |
| uint8_t pad7[0x290 - 0x1f1 - sizeof(setup_header)]; |
| uint32_t edd_mbr_sig_buffer[kMaxEddMbrSig]; |
| E820Entry e820_table[kMaxE820TableEntries]; |
| uint8_t pad8[48]; |
| edd_info eddbuf[kMaxEddNr]; |
| uint8_t pad9[276]; |
| }; |
| |
| // This is not strictly part of the Linux protocol, but it is used in the |
| // 16-bit BIOS calls required to populate boot_params in the 16-bit entry path. |
| |
| constexpr uint32_t kE820Magic = 0x534d4150; // 'SMAP' |
| |
| } // namespace linuxboot |
| |
| #endif // ZIRCON_KERNEL_ARCH_X86_PHYS_LINUXBOOT_H_ |