On network boots grub_ieee1275_net_config() is used to determine the boot device but the path continues to be taken from the Open Firmware /chosen/bootpath property. This assumes the device node follows the generic IEEE 1275 syntax which is not always the case. Different drivers may extend or redefine the format and GRUB may then misinterpret the argument as a filename and set $prefix incorrectly. The generic Open Firmware device path format is: device-name[:device-argument] device-argument := [partition][,[filename]] For example, a bootpath such as: /vdevice/l-lan@30000002:speed=auto,duplex=auto,1.2.243.345,,9.8.76.543,1.2.34.5,5,5,255.255.255.0,512 does not follow this form. The section after the colon (the device-argument) contains driver-specific options and network parameters, not a valid filename. The GRUB interprets this string as a filename which results in $prefix being set to "/", effectively losing the intended boot directory. The firmware is not at fault here since interpretation of device nodes is driver-specific. Instead, GRUB should use the filename provided in the cached DHCP packet which is consistent and reliable. This is also the same mechanism already used on UEFI and legacy BIOS platforms. This patch updates grub_machine_get_bootlocation() to prefer the result from grub_ieee1275_net_config() when complete and only fall back to the firmware bootpath otherwise. Signed-off-by: Michael Chang <mchang@suse.com> Reviewed-by: Avnish Chouhan <avnish@linux.ibm.com> Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
1120 lines
33 KiB
C
1120 lines
33 KiB
C
/* init.c -- Initialize GRUB on the newworld mac (PPC). */
|
|
/*
|
|
* GRUB -- GRand Unified Bootloader
|
|
* Copyright (C) 2003,2004,2005,2007,2008,2009 Free Software Foundation, Inc.
|
|
*
|
|
* GRUB is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* GRUB is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stddef.h> /* offsetof() */
|
|
|
|
#include <grub/kernel.h>
|
|
#include <grub/dl.h>
|
|
#include <grub/disk.h>
|
|
#include <grub/mm.h>
|
|
#include <grub/partition.h>
|
|
#include <grub/normal.h>
|
|
#include <grub/fs.h>
|
|
#include <grub/setjmp.h>
|
|
#include <grub/env.h>
|
|
#include <grub/misc.h>
|
|
#include <grub/time.h>
|
|
#include <grub/ieee1275/console.h>
|
|
#include <grub/ieee1275/ofdisk.h>
|
|
#ifdef __sparc__
|
|
#include <grub/ieee1275/obdisk.h>
|
|
#endif
|
|
#include <grub/ieee1275/ieee1275.h>
|
|
#include <grub/net.h>
|
|
#include <grub/offsets.h>
|
|
#include <grub/memory.h>
|
|
#include <grub/loader.h>
|
|
#ifdef __i386__
|
|
#include <grub/cpu/tsc.h>
|
|
#endif
|
|
#ifdef __sparc__
|
|
#include <grub/machine/kernel.h>
|
|
#endif
|
|
#if defined(__powerpc__) || defined(__i386__)
|
|
#include <grub/ieee1275/alloc.h>
|
|
#endif
|
|
#if defined(__powerpc__)
|
|
#include <grub/lockdown.h>
|
|
#include <grub/powerpc/ieee1275/ieee1275.h>
|
|
#include <grub/powerpc/ieee1275/platform_keystore.h>
|
|
#endif
|
|
|
|
#ifdef __powerpc__
|
|
#define GRUB_SB_DISABLED ((grub_uint32_t) 0)
|
|
#define GRUB_SB_ENFORCE ((grub_uint32_t) 2)
|
|
#endif
|
|
|
|
/* The maximum heap size we're going to claim at boot. Not used by sparc. */
|
|
#ifdef __i386__
|
|
#define HEAP_MAX_SIZE (unsigned long) (64 * 1024 * 1024)
|
|
#else /* __powerpc__ */
|
|
#define HEAP_MAX_SIZE (unsigned long) (32 * 1024 * 1024)
|
|
#endif
|
|
|
|
/* RMO max. address at 768 MB */
|
|
#define RMO_ADDR_MAX (grub_uint64_t) (768 * 1024 * 1024)
|
|
|
|
/*
|
|
* The amount of OF space we will not claim here so as to leave space for
|
|
* the loader and linux to service early allocations.
|
|
*
|
|
* In 2021, Daniel Axtens claims that we should leave at least 128MB to
|
|
* ensure we can load a stock kernel and initrd on a pseries guest with
|
|
* a 512MB real memory area under PowerVM.
|
|
*/
|
|
#define RUNTIME_MIN_SPACE (128UL * 1024 * 1024)
|
|
|
|
extern char _start[];
|
|
extern char _end[];
|
|
|
|
#ifdef __sparc__
|
|
grub_addr_t grub_ieee1275_original_stack;
|
|
#endif
|
|
|
|
/* Options vector5 properties. */
|
|
|
|
#define LPAR 0x80
|
|
#define SPLPAR 0x40
|
|
#define DYN_RCON_MEM 0x20
|
|
#define LARGE_PAGES 0x10
|
|
#define DONATE_DCPU_CLS 0x02
|
|
#define PCI_EXP 0x01
|
|
#define BYTE2 (LPAR | SPLPAR | DYN_RCON_MEM | LARGE_PAGES | DONATE_DCPU_CLS | PCI_EXP)
|
|
|
|
#define CMOC 0x80
|
|
#define EXT_CMO 0x40
|
|
#define CMO (CMOC | EXT_CMO)
|
|
|
|
#define ASSOC_REF 0x80
|
|
#define AFFINITY 0x40
|
|
#define NUMA 0x20
|
|
#define ASSOCIATIVITY (ASSOC_REF | AFFINITY | NUMA)
|
|
|
|
#define HOTPLUG_INTRPT 0x04
|
|
#define HPT_RESIZE 0x01
|
|
#define BIN_OPTS (HOTPLUG_INTRPT | HPT_RESIZE)
|
|
|
|
#define MAX_CPU 256
|
|
|
|
#define PFO_HWRNG 0x80000000
|
|
#define PFO_HW_COMP 0x40000000
|
|
#define PFO_ENCRYPT 0x20000000
|
|
#define PLATFORM_FACILITIES (PFO_HWRNG | PFO_HW_COMP | PFO_ENCRYPT)
|
|
|
|
#define SUB_PROCESSORS 1
|
|
|
|
#define DY_MEM_V2 0x80
|
|
#define DRC_INFO 0x40
|
|
#define BYTE22 (DY_MEM_V2 | DRC_INFO)
|
|
|
|
/* For ibm,arch-vec-5-platform-support. */
|
|
#define XIVE_INDEX 0x17
|
|
#define MMU_INDEX 0x18
|
|
#define RADIX_GTSE_INDEX 0x1a
|
|
#define RADIX_ENABLED 0x40
|
|
#define XIVE_ENABLED 0x40
|
|
#define HASH_ENABLED 0x00
|
|
#define MAX_SUPPORTED 0xC0
|
|
#define RADIX_GTSE_ENABLED 0x40
|
|
|
|
void
|
|
grub_exit (void)
|
|
{
|
|
grub_ieee1275_exit ();
|
|
}
|
|
|
|
/* Translate an OF filesystem path (separated by backslashes), into a GRUB
|
|
path (separated by forward slashes). */
|
|
static void
|
|
grub_translate_ieee1275_path (char *filepath)
|
|
{
|
|
char *backslash;
|
|
|
|
backslash = grub_strchr (filepath, '\\');
|
|
while (backslash != 0)
|
|
{
|
|
*backslash = '/';
|
|
backslash = grub_strchr (filepath, '\\');
|
|
}
|
|
}
|
|
|
|
void (*grub_ieee1275_net_config) (const char *dev, char **device, char **path,
|
|
char *bootpath);
|
|
void
|
|
grub_machine_get_bootlocation (char **device, char **path)
|
|
{
|
|
char *bootpath;
|
|
char *filename;
|
|
char *type;
|
|
char *ret_device = NULL;
|
|
char *ret_path = NULL;
|
|
|
|
bootpath = grub_ieee1275_get_boot_dev ();
|
|
if (! bootpath)
|
|
return;
|
|
|
|
/* Transform an OF device path to a GRUB path. */
|
|
|
|
type = grub_ieee1275_get_device_type (bootpath);
|
|
if (type && grub_strcmp (type, "network") == 0)
|
|
{
|
|
char *dev, *canon;
|
|
char *ptr;
|
|
dev = grub_ieee1275_get_aliasdevname (bootpath);
|
|
canon = grub_ieee1275_canonicalise_devname (dev);
|
|
if (! canon)
|
|
goto done;
|
|
ptr = canon + grub_strlen (canon) - 1;
|
|
while (ptr > canon && (*ptr == ',' || *ptr == ':'))
|
|
ptr--;
|
|
ptr++;
|
|
*ptr = 0;
|
|
|
|
if (grub_ieee1275_net_config)
|
|
grub_ieee1275_net_config (canon, &ret_device, &ret_path, bootpath);
|
|
grub_free (dev);
|
|
grub_free (canon);
|
|
|
|
/* Use path from net config if it is provided by cached DHCP info */
|
|
if (ret_path != NULL)
|
|
goto done;
|
|
/* Fall through to use firmware bootpath */
|
|
}
|
|
else
|
|
ret_device = grub_ieee1275_encode_devname (bootpath);
|
|
|
|
filename = grub_ieee1275_get_filename (bootpath);
|
|
if (filename)
|
|
{
|
|
char *lastslash = grub_strrchr (filename, '\\');
|
|
|
|
/* Truncate at last directory. */
|
|
if (lastslash)
|
|
{
|
|
*lastslash = '\0';
|
|
grub_translate_ieee1275_path (filename);
|
|
|
|
ret_path = filename;
|
|
}
|
|
}
|
|
|
|
done:
|
|
grub_free (type);
|
|
grub_free (bootpath);
|
|
|
|
if (device != NULL)
|
|
*device = ret_device;
|
|
if (path != NULL)
|
|
*path = ret_path;
|
|
}
|
|
|
|
/* Claim some available memory in the first /memory node. */
|
|
#ifdef __sparc__
|
|
static void
|
|
grub_claim_heap (void)
|
|
{
|
|
grub_mm_init_region ((void *) (grub_modules_get_end ()
|
|
+ GRUB_KERNEL_MACHINE_STACK_SIZE), 0x200000);
|
|
}
|
|
#else
|
|
/* Helpers for mm on powerpc. */
|
|
|
|
/* ibm,kernel-dump data structures */
|
|
struct kd_section
|
|
{
|
|
grub_uint32_t flags;
|
|
grub_uint16_t src_datatype;
|
|
#define KD_SRC_DATATYPE_REAL_MODE_REGION 0x0011
|
|
grub_uint16_t error_flags;
|
|
grub_uint64_t src_address;
|
|
grub_uint64_t num_bytes;
|
|
grub_uint64_t act_bytes;
|
|
grub_uint64_t dst_address;
|
|
} GRUB_PACKED;
|
|
|
|
#define MAX_KD_SECTIONS 10
|
|
|
|
struct kernel_dump
|
|
{
|
|
grub_uint32_t format;
|
|
grub_uint16_t num_sections;
|
|
grub_uint16_t status_flags;
|
|
grub_uint32_t offset_1st_section;
|
|
grub_uint32_t num_blocks;
|
|
grub_uint64_t start_block;
|
|
grub_uint64_t num_blocks_avail;
|
|
grub_uint32_t offet_path_string;
|
|
grub_uint32_t max_time_allowed;
|
|
struct kd_section kds[MAX_KD_SECTIONS]; /* offset_1st_section should point to kds[0] */
|
|
} GRUB_PACKED;
|
|
|
|
/*
|
|
* Determine if a kernel dump exists and if it does, then determine the highest
|
|
* address that grub can use for memory allocations.
|
|
* The caller must have initialized *highest to rmo_top. *highest will not
|
|
* be modified if no kernel dump is found.
|
|
*/
|
|
static void
|
|
check_kernel_dump (grub_uint64_t *highest)
|
|
{
|
|
struct kernel_dump kernel_dump;
|
|
grub_ssize_t kernel_dump_size;
|
|
grub_ieee1275_phandle_t rtas;
|
|
struct kd_section *kds;
|
|
grub_size_t i;
|
|
|
|
/* If there's a kernel-dump it must have at least one section */
|
|
if (grub_ieee1275_finddevice ("/rtas", &rtas) ||
|
|
grub_ieee1275_get_property (rtas, "ibm,kernel-dump", &kernel_dump,
|
|
sizeof (kernel_dump), &kernel_dump_size) ||
|
|
kernel_dump_size <= (grub_ssize_t) offsetof (struct kernel_dump, kds[1]))
|
|
return;
|
|
|
|
kernel_dump_size = grub_min (kernel_dump_size, (grub_ssize_t) sizeof (kernel_dump));
|
|
|
|
if (grub_be_to_cpu32 (kernel_dump.format) != 1)
|
|
{
|
|
grub_printf (_("Error: ibm,kernel-dump has an unexpected format version '%u'\n"),
|
|
grub_be_to_cpu32 (kernel_dump.format));
|
|
return;
|
|
}
|
|
|
|
if (grub_be_to_cpu16 (kernel_dump.num_sections) > MAX_KD_SECTIONS)
|
|
{
|
|
grub_printf (_("Error: Too many kernel dump sections: %d\n"),
|
|
grub_be_to_cpu32 (kernel_dump.num_sections));
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < grub_be_to_cpu16 (kernel_dump.num_sections); i++)
|
|
{
|
|
kds = (struct kd_section *) ((grub_addr_t) &kernel_dump +
|
|
grub_be_to_cpu32 (kernel_dump.offset_1st_section) +
|
|
i * sizeof (struct kd_section));
|
|
/* sanity check the address is within the 'kernel_dump' struct */
|
|
if ((grub_addr_t) kds > (grub_addr_t) &kernel_dump + kernel_dump_size + sizeof (*kds))
|
|
{
|
|
grub_printf (_("Error: 'kds' address beyond last available section\n"));
|
|
return;
|
|
}
|
|
|
|
if ((grub_be_to_cpu16 (kds->src_datatype) == KD_SRC_DATATYPE_REAL_MODE_REGION) &&
|
|
(grub_be_to_cpu64 (kds->src_address) == 0))
|
|
{
|
|
*highest = grub_min (*highest, grub_be_to_cpu64 (kds->num_bytes));
|
|
break;
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* How much memory does OF believe exists in total?
|
|
*
|
|
* This isn't necessarily the true total. It can be the total memory
|
|
* accessible in real mode for a pseries guest, for example.
|
|
*/
|
|
static grub_uint64_t rmo_top;
|
|
|
|
static int
|
|
count_free (grub_uint64_t addr, grub_uint64_t len, grub_memory_type_t type,
|
|
void *data)
|
|
{
|
|
if (type != GRUB_MEMORY_AVAILABLE)
|
|
return 0;
|
|
|
|
/* Do not consider memory beyond 4GB */
|
|
if (addr > 0xffffffffULL)
|
|
return 0;
|
|
|
|
if (addr + len > 0xffffffffULL)
|
|
len = 0xffffffffULL - addr;
|
|
|
|
*(grub_uint32_t *) data += len;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
grub_regions_claim (grub_uint64_t addr, grub_uint64_t len,
|
|
grub_memory_type_t type, void *data)
|
|
{
|
|
struct regions_claim_request *rcr = data;
|
|
grub_uint64_t linux_rmo_save;
|
|
|
|
if (type != GRUB_MEMORY_AVAILABLE)
|
|
return 0;
|
|
|
|
/* Do not consider memory beyond 4GB */
|
|
if (addr > 0xffffffffULL)
|
|
return 0;
|
|
|
|
if (addr + len > 0xffffffffULL)
|
|
len = 0xffffffffULL - addr;
|
|
|
|
if (grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_NO_PRE1_5M_CLAIM))
|
|
{
|
|
if (addr + len <= 0x180000)
|
|
return 0;
|
|
|
|
if (addr < 0x180000)
|
|
{
|
|
len = addr + len - 0x180000;
|
|
addr = 0x180000;
|
|
}
|
|
}
|
|
|
|
/* In theory, firmware should already prevent this from happening by not
|
|
listing our own image in /memory/available. The check below is intended
|
|
as a safeguard in case that doesn't happen. However, it doesn't protect
|
|
us from corrupting our module area, which extends up to a
|
|
yet-undetermined region above _end. */
|
|
if ((addr < (grub_addr_t) _end) && ((addr + len) > (grub_addr_t) _start))
|
|
{
|
|
grub_printf ("Warning: attempt to claim over our own code!\n");
|
|
len = 0;
|
|
}
|
|
|
|
/*
|
|
* Linux likes to claim memory at min(RMO top, 768MB) and works down
|
|
* without reference to /memory/available. (See prom_init.c::alloc_down)
|
|
*
|
|
* If this block contains min(RMO top, 768MB), do not claim below that for
|
|
* at least a few MB (this is where RTAS, SML and potentially TCEs live).
|
|
*
|
|
* We also need to leave enough space for the DT in the RMA. (See
|
|
* prom_init.c::alloc_up)
|
|
*
|
|
* Finally, we also want to make sure that when grub loads the kernel,
|
|
* it isn't going to use up all the memory we're trying to reserve! So
|
|
* enforce our entire RUNTIME_MIN_SPACE here (no fadump):
|
|
*
|
|
* | Top of memory == upper_mem_limit -|
|
|
* | |
|
|
* | available |
|
|
* | |
|
|
* |---------- 768 MB ----------|
|
|
* | |
|
|
* | reserved |
|
|
* | |
|
|
* |--- 768 MB - runtime min space ---|
|
|
* | |
|
|
* | available |
|
|
* | |
|
|
* |---------- 0 MB ----------|
|
|
*
|
|
* In case fadump is used, we allow the following:
|
|
*
|
|
* |---------- Top of memory ----------|
|
|
* | |
|
|
* | unavailable |
|
|
* | (kernel dump area) |
|
|
* | |
|
|
* |--------- upper_mem_limit ---------|
|
|
* | |
|
|
* | available |
|
|
* | |
|
|
* |---------- 768 MB ----------|
|
|
* | |
|
|
* | reserved |
|
|
* | |
|
|
* |--- 768 MB - runtime min space ---|
|
|
* | |
|
|
* | available |
|
|
* | |
|
|
* |---------- 0 MB ----------|
|
|
*
|
|
* Edge cases:
|
|
*
|
|
* - Total memory less than RUNTIME_MIN_SPACE: only claim up to HEAP_MAX_SIZE.
|
|
* (enforced elsewhere)
|
|
*
|
|
* - Total memory between RUNTIME_MIN_SPACE and 768MB:
|
|
*
|
|
* |---------- Top of memory ----------|
|
|
* | |
|
|
* | reserved |
|
|
* | |
|
|
* |---- top - runtime min space ----|
|
|
* | |
|
|
* | available |
|
|
* | |
|
|
* |---------- 0 MB ----------|
|
|
*
|
|
* This by itself would not leave us with RUNTIME_MIN_SPACE of free bytes: if
|
|
* rmo_top < 768MB, we will almost certainly have FW claims in the reserved
|
|
* region. We try to address that elsewhere: grub_ieee1275_mm_add_region will
|
|
* not call us if the resulting free space would be less than RUNTIME_MIN_SPACE.
|
|
*/
|
|
linux_rmo_save = grub_min (RMO_ADDR_MAX, rmo_top) - RUNTIME_MIN_SPACE;
|
|
if (rmo_top > RUNTIME_MIN_SPACE)
|
|
{
|
|
if (rmo_top <= RMO_ADDR_MAX)
|
|
{
|
|
if (addr > linux_rmo_save)
|
|
{
|
|
grub_dprintf ("ieee1275", "rejecting region in RUNTIME_MIN_SPACE reservation (%llx)\n",
|
|
addr);
|
|
return 0;
|
|
}
|
|
else if (addr + len > linux_rmo_save)
|
|
{
|
|
grub_dprintf ("ieee1275", "capping region: (%llx -> %llx) -> (%llx -> %llx)\n",
|
|
addr, addr + len, addr, rmo_top - RUNTIME_MIN_SPACE);
|
|
len = linux_rmo_save - addr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
grub_uint64_t upper_mem_limit = rmo_top;
|
|
grub_uint64_t orig_addr = addr;
|
|
|
|
check_kernel_dump (&upper_mem_limit);
|
|
|
|
grub_dprintf ("ieee1275", "upper_mem_limit is at %llx (%lld MiB)\n",
|
|
upper_mem_limit, upper_mem_limit >> 20);
|
|
|
|
/*
|
|
* we order these cases to prefer higher addresses and avoid some
|
|
* splitting issues
|
|
* The following shows the order of variables:
|
|
* no kernel dump: linux_rmo_save < RMO_ADDR_MAX <= upper_mem_limit == rmo_top
|
|
* with kernel dump: liuxx_rmo_save < RMO_ADDR_MAX <= upper_mem_limit <= rmo_top
|
|
*/
|
|
if (addr < RMO_ADDR_MAX && (addr + len) > RMO_ADDR_MAX && upper_mem_limit >= RMO_ADDR_MAX)
|
|
{
|
|
grub_dprintf ("ieee1275",
|
|
"adjusting region for RUNTIME_MIN_SPACE: (%llx -> %llx) -> (%llx -> %llx)\n",
|
|
addr, addr + len, RMO_ADDR_MAX, addr + len);
|
|
len = (addr + len) - RMO_ADDR_MAX;
|
|
addr = RMO_ADDR_MAX;
|
|
|
|
/* We must not exceed the upper_mem_limit (assuming it's >= RMO_ADDR_MAX) */
|
|
if (addr + len > upper_mem_limit)
|
|
{
|
|
/* Take the bigger chunk from either below linux_rmo_save or above RMO_ADDR_MAX. */
|
|
len = upper_mem_limit - addr;
|
|
if (orig_addr < linux_rmo_save && linux_rmo_save - orig_addr > len)
|
|
{
|
|
/* lower part is bigger */
|
|
addr = orig_addr;
|
|
len = linux_rmo_save - addr;
|
|
}
|
|
|
|
grub_dprintf ("ieee1275", "re-adjusted region to: (%llx -> %llx)\n",
|
|
addr, addr + len);
|
|
|
|
if (len == 0)
|
|
return 0;
|
|
}
|
|
}
|
|
else if ((addr < linux_rmo_save) && ((addr + len) > linux_rmo_save))
|
|
{
|
|
grub_dprintf ("ieee1275", "capping region: (%llx -> %llx) -> (%llx -> %llx)\n",
|
|
addr, addr + len, addr, linux_rmo_save);
|
|
len = linux_rmo_save - addr;
|
|
}
|
|
else if (addr >= linux_rmo_save && (addr + len) <= RMO_ADDR_MAX)
|
|
{
|
|
grub_dprintf ("ieee1275", "rejecting region in RUNTIME_MIN_SPACE reservation (%llx)\n",
|
|
addr);
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Honor alignment restrictions on candidate addr */
|
|
if (rcr->align)
|
|
{
|
|
grub_uint64_t align_addr = ALIGN_UP (addr, rcr->align);
|
|
grub_uint64_t d = align_addr - addr;
|
|
|
|
if (d > len)
|
|
return 0;
|
|
|
|
len -= d;
|
|
addr = align_addr;
|
|
}
|
|
|
|
if (rcr->flags & GRUB_MM_ADD_REGION_CONSECUTIVE && len < rcr->total)
|
|
return 0;
|
|
|
|
if (len > rcr->total)
|
|
len = rcr->total;
|
|
|
|
if (len)
|
|
{
|
|
grub_err_t err;
|
|
/* Claim and use it. */
|
|
err = grub_claimmap (addr, len);
|
|
if (err)
|
|
return err;
|
|
if (rcr->init_region)
|
|
grub_mm_init_region ((void *) (grub_addr_t) addr, len);
|
|
rcr->total -= len;
|
|
|
|
rcr->addr = addr;
|
|
}
|
|
|
|
*(grub_uint32_t *) data = rcr->total;
|
|
|
|
if (rcr->total == 0)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
heap_init (grub_uint64_t addr, grub_uint64_t len, grub_memory_type_t type,
|
|
void *data)
|
|
{
|
|
struct regions_claim_request rcr = {
|
|
.flags = GRUB_MM_ADD_REGION_NONE,
|
|
.total = *(grub_uint32_t *) data,
|
|
.init_region = true,
|
|
};
|
|
int ret;
|
|
|
|
ret = grub_regions_claim (addr, len, type, &rcr);
|
|
|
|
*(grub_uint32_t *) data = rcr.total;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
region_claim (grub_uint64_t addr, grub_uint64_t len, grub_memory_type_t type,
|
|
void *data)
|
|
{
|
|
struct regions_claim_request rcr = {
|
|
.flags = GRUB_MM_ADD_REGION_CONSECUTIVE,
|
|
.total = *(grub_uint32_t *) data,
|
|
.init_region = true,
|
|
};
|
|
int ret;
|
|
|
|
ret = grub_regions_claim (addr, len, type, &rcr);
|
|
|
|
*(grub_uint32_t *) data = rcr.total;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static grub_err_t
|
|
grub_ieee1275_mm_add_region (grub_size_t size, unsigned int flags)
|
|
{
|
|
grub_uint32_t free_memory = 0;
|
|
grub_uint32_t avail = 0;
|
|
grub_uint32_t total;
|
|
|
|
grub_dprintf ("ieee1275", "mm requested region of size %x, flags %x\n",
|
|
size, flags);
|
|
|
|
/*
|
|
* Update free memory each time, which is a bit inefficient but guards us
|
|
* against a situation where some OF driver goes out to firmware for
|
|
* memory and we don't realise.
|
|
*/
|
|
grub_machine_mmap_iterate (count_free, &free_memory);
|
|
|
|
/* Ensure we leave enough space to boot. */
|
|
if (free_memory <= RUNTIME_MIN_SPACE + size)
|
|
{
|
|
grub_dprintf ("ieee1275", "Cannot satisfy allocation and retain minimum runtime space\n");
|
|
return GRUB_ERR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
if (free_memory > RUNTIME_MIN_SPACE)
|
|
avail = free_memory - RUNTIME_MIN_SPACE;
|
|
|
|
grub_dprintf ("ieee1275", "free = 0x%x available = 0x%x\n", free_memory, avail);
|
|
|
|
if (flags & GRUB_MM_ADD_REGION_CONSECUTIVE)
|
|
{
|
|
/* first try rounding up hard for the sake of speed */
|
|
total = grub_max (ALIGN_UP (size, 1024 * 1024) + 1024 * 1024, 32 * 1024 * 1024);
|
|
total = grub_min (avail, total);
|
|
|
|
grub_dprintf ("ieee1275", "looking for %x bytes of memory (%x requested)\n", total, size);
|
|
|
|
grub_machine_mmap_iterate (region_claim, &total);
|
|
grub_dprintf ("ieee1275", "get memory from fw %s\n", total == 0 ? "succeeded" : "failed");
|
|
|
|
if (total != 0)
|
|
{
|
|
total = grub_min (avail, size);
|
|
|
|
grub_dprintf ("ieee1275", "fallback for %x bytes of memory (%x requested)\n", total, size);
|
|
|
|
grub_machine_mmap_iterate (region_claim, &total);
|
|
grub_dprintf ("ieee1275", "fallback from fw %s\n", total == 0 ? "succeeded" : "failed");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* provide padding for a grub_mm_header_t and region */
|
|
total = grub_min (avail, size);
|
|
grub_machine_mmap_iterate (heap_init, &total);
|
|
grub_dprintf ("ieee1275", "get noncontig memory from fw %s\n", total == 0 ? "succeeded" : "failed");
|
|
}
|
|
|
|
if (total == 0)
|
|
return GRUB_ERR_NONE;
|
|
else
|
|
return GRUB_ERR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
/*
|
|
* How much memory does OF believe it has? (regardless of whether
|
|
* it's accessible or not)
|
|
*/
|
|
static grub_err_t
|
|
grub_ieee1275_total_mem (grub_uint64_t *total)
|
|
{
|
|
grub_ieee1275_phandle_t root;
|
|
grub_ieee1275_phandle_t memory;
|
|
grub_uint32_t reg[4];
|
|
grub_ssize_t reg_size;
|
|
grub_uint32_t address_cells = 1;
|
|
grub_uint32_t size_cells = 1;
|
|
grub_uint64_t size;
|
|
|
|
/* If we fail to get to the end, report 0. */
|
|
*total = 0;
|
|
|
|
/* Determine the format of each entry in `reg'. */
|
|
if (grub_ieee1275_finddevice ("/", &root))
|
|
return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "couldn't find / node");
|
|
if (grub_ieee1275_get_integer_property (root, "#address-cells", &address_cells,
|
|
sizeof (address_cells), 0))
|
|
return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "couldn't examine #address-cells");
|
|
if (grub_ieee1275_get_integer_property (root, "#size-cells", &size_cells,
|
|
sizeof (size_cells), 0))
|
|
return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "couldn't examine #size-cells");
|
|
|
|
if (size_cells > address_cells)
|
|
address_cells = size_cells;
|
|
|
|
/* Load `/memory/reg'. */
|
|
if (grub_ieee1275_finddevice ("/memory", &memory))
|
|
return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "couldn't find /memory node");
|
|
if (grub_ieee1275_get_integer_property (memory, "reg", reg,
|
|
sizeof (reg), ®_size))
|
|
return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "couldn't examine /memory/reg property");
|
|
if (reg_size < 0 || (grub_size_t) reg_size > sizeof (reg))
|
|
return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "/memory response buffer exceeded");
|
|
|
|
if (grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_BROKEN_ADDRESS_CELLS))
|
|
{
|
|
address_cells = 1;
|
|
size_cells = 1;
|
|
}
|
|
|
|
/* Decode only the size */
|
|
size = reg[address_cells];
|
|
if (size_cells == 2)
|
|
size = (size << 32) | reg[address_cells + 1];
|
|
|
|
*total = size;
|
|
|
|
return grub_errno;
|
|
}
|
|
|
|
#if defined(__powerpc__)
|
|
|
|
/* See PAPR or arch/powerpc/kernel/prom_init.c */
|
|
struct option_vector2
|
|
{
|
|
grub_uint8_t byte1;
|
|
grub_uint16_t reserved;
|
|
grub_uint32_t real_base;
|
|
grub_uint32_t real_size;
|
|
grub_uint32_t virt_base;
|
|
grub_uint32_t virt_size;
|
|
grub_uint32_t load_base;
|
|
grub_uint32_t min_rma;
|
|
grub_uint32_t min_load;
|
|
grub_uint8_t min_rma_percent;
|
|
grub_uint8_t max_pft_size;
|
|
} GRUB_PACKED;
|
|
|
|
struct option_vector5
|
|
{
|
|
grub_uint8_t byte1;
|
|
grub_uint8_t byte2;
|
|
grub_uint8_t byte3;
|
|
grub_uint8_t cmo;
|
|
grub_uint8_t associativity;
|
|
grub_uint8_t bin_opts;
|
|
grub_uint8_t micro_checkpoint;
|
|
grub_uint8_t reserved0;
|
|
grub_uint32_t max_cpus;
|
|
grub_uint16_t base_papr;
|
|
grub_uint16_t mem_reference;
|
|
grub_uint32_t platform_facilities;
|
|
grub_uint8_t sub_processors;
|
|
grub_uint8_t byte22;
|
|
grub_uint8_t xive;
|
|
grub_uint8_t mmu;
|
|
grub_uint8_t hpt_ext;
|
|
grub_uint8_t radix_gtse;
|
|
} GRUB_PACKED;
|
|
|
|
struct pvr_entry
|
|
{
|
|
grub_uint32_t mask;
|
|
grub_uint32_t entry;
|
|
};
|
|
|
|
struct cas_vector
|
|
{
|
|
struct
|
|
{
|
|
struct pvr_entry terminal;
|
|
} pvr_list;
|
|
grub_uint8_t num_vecs;
|
|
grub_uint8_t vec1_size;
|
|
grub_uint8_t vec1;
|
|
grub_uint8_t vec2_size;
|
|
struct option_vector2 vec2;
|
|
grub_uint8_t vec3_size;
|
|
grub_uint16_t vec3;
|
|
grub_uint8_t vec4_size;
|
|
grub_uint16_t vec4;
|
|
grub_uint8_t vec5_size;
|
|
struct option_vector5 vec5;
|
|
} GRUB_PACKED;
|
|
|
|
/*
|
|
* Call ibm,client-architecture-support to try to get more RMA.
|
|
* We ask for 768MB which should be enough to verify a distro kernel.
|
|
* We ignore most errors: if we don't succeed we'll proceed with whatever
|
|
* memory we have.
|
|
*/
|
|
static void
|
|
grub_ieee1275_ibm_cas (void)
|
|
{
|
|
int rc;
|
|
grub_ieee1275_ihandle_t root;
|
|
grub_uint8_t ibm_arch_platform_support[8];
|
|
grub_ssize_t actual;
|
|
grub_uint8_t xive_support = 0;
|
|
grub_uint8_t mmu_support = 0;
|
|
grub_uint8_t radix_gtse_support = 0;
|
|
int i = 0;
|
|
int prop_len = 8;
|
|
struct cas_args
|
|
{
|
|
struct grub_ieee1275_common_hdr common;
|
|
grub_ieee1275_cell_t method;
|
|
grub_ieee1275_ihandle_t ihandle;
|
|
grub_ieee1275_cell_t cas_addr;
|
|
grub_ieee1275_cell_t result;
|
|
} args;
|
|
|
|
grub_ieee1275_get_integer_property (grub_ieee1275_chosen,
|
|
"ibm,arch-vec-5-platform-support",
|
|
(grub_uint32_t *) ibm_arch_platform_support,
|
|
sizeof (ibm_arch_platform_support),
|
|
&actual);
|
|
|
|
for (i = 0; i < prop_len; i++)
|
|
{
|
|
switch (ibm_arch_platform_support[i])
|
|
{
|
|
case XIVE_INDEX:
|
|
if (ibm_arch_platform_support[i + 1] & MAX_SUPPORTED)
|
|
xive_support = XIVE_ENABLED;
|
|
else
|
|
xive_support = 0;
|
|
break;
|
|
|
|
case MMU_INDEX:
|
|
if (ibm_arch_platform_support[i + 1] & MAX_SUPPORTED)
|
|
mmu_support = RADIX_ENABLED;
|
|
else
|
|
mmu_support = HASH_ENABLED;
|
|
break;
|
|
|
|
case RADIX_GTSE_INDEX:
|
|
if (mmu_support == RADIX_ENABLED)
|
|
radix_gtse_support = ibm_arch_platform_support[i + 1] & RADIX_GTSE_ENABLED;
|
|
else
|
|
radix_gtse_support = 0;
|
|
break;
|
|
|
|
default:
|
|
/* Ignoring the other indexes of ibm,arch-vec-5-platform-support. */
|
|
break;
|
|
}
|
|
/* Skipping the property value. */
|
|
i++;
|
|
}
|
|
|
|
struct cas_vector vector =
|
|
{
|
|
.pvr_list = { { 0x00000000, 0xffffffff } }, /* any processor */
|
|
.num_vecs = 5 - 1,
|
|
.vec1_size = 0,
|
|
.vec1 = 0x80, /* ignore */
|
|
.vec2_size = 1 + sizeof (struct option_vector2) - 2,
|
|
.vec2 = {
|
|
0, 0, -1, -1, -1, -1, -1, 768, -1, 0, 48
|
|
},
|
|
.vec3_size = 2 - 1,
|
|
.vec3 = 0x00e0, /* ask for FP + VMX + DFP but don't halt if unsatisfied */
|
|
.vec4_size = 2 - 1,
|
|
.vec4 = 0x0001, /* set required minimum capacity % to the lowest value */
|
|
.vec5_size = 1 + sizeof (struct option_vector5) - 2,
|
|
.vec5 = {
|
|
0, BYTE2, 0, CMO, ASSOCIATIVITY, BIN_OPTS, 0, 0, MAX_CPU, 0, 0, PLATFORM_FACILITIES, SUB_PROCESSORS, BYTE22, xive_support, mmu_support, 0, radix_gtse_support
|
|
}
|
|
};
|
|
|
|
INIT_IEEE1275_COMMON (&args.common, "call-method", 3, 2);
|
|
args.method = (grub_ieee1275_cell_t) "ibm,client-architecture-support";
|
|
rc = grub_ieee1275_open ("/", &root);
|
|
if (rc)
|
|
{
|
|
grub_error (GRUB_ERR_IO, "could not open root when trying to call CAS");
|
|
return;
|
|
}
|
|
args.ihandle = root;
|
|
args.cas_addr = (grub_ieee1275_cell_t) &vector;
|
|
|
|
grub_printf ("Calling ibm,client-architecture-support from grub...");
|
|
IEEE1275_CALL_ENTRY_FN (&args);
|
|
grub_printf ("done\n");
|
|
|
|
grub_ieee1275_close (root);
|
|
}
|
|
|
|
#endif /* __powerpc__ */
|
|
|
|
static void
|
|
grub_claim_heap (void)
|
|
{
|
|
grub_err_t err;
|
|
grub_uint32_t total = HEAP_MAX_SIZE;
|
|
#if defined(__powerpc__)
|
|
grub_uint32_t ibm_ca_support_reboot = 0;
|
|
grub_ssize_t actual;
|
|
#endif
|
|
|
|
err = grub_ieee1275_total_mem (&rmo_top);
|
|
|
|
/*
|
|
* If we cannot size the available memory, we can't be sure we're leaving
|
|
* space for the kernel, initrd and things Linux loads early in boot. So only
|
|
* allow further allocations from firmware on success
|
|
*/
|
|
if (err == GRUB_ERR_NONE)
|
|
grub_mm_add_region_fn = grub_ieee1275_mm_add_region;
|
|
|
|
#if defined(__powerpc__)
|
|
/* Check if it's a CAS reboot with below property. If so, we will skip CAS call. */
|
|
if (grub_ieee1275_get_integer_property (grub_ieee1275_chosen,
|
|
"ibm,client-architecture-support-reboot",
|
|
&ibm_ca_support_reboot,
|
|
sizeof (ibm_ca_support_reboot),
|
|
&actual) >= 0)
|
|
grub_dprintf ("ieee1275", "ibm,client-architecture-support-reboot: %" PRIuGRUB_UINT32_T "\n",
|
|
ibm_ca_support_reboot);
|
|
|
|
if (grub_ieee1275_test_flag (GRUB_IEEE1275_FLAG_CAN_TRY_CAS_FOR_MORE_MEMORY))
|
|
{
|
|
/*
|
|
* If we have an error don't call CAS. Just hope for the best.
|
|
* Along with the above, if the rmo_top is 512 MB or above. We
|
|
* will skip the CAS call. However, if we call CAS, the rmo_top
|
|
* will be set to 768 MB via CAS Vector2. But we need to call
|
|
* CAS with rmo_top < 512 MB to avoid the issue on the older
|
|
* Linux kernel, which still uses rmo_top as 512 MB. If we call
|
|
* CAS with a condition rmo_top < 768 MB, it will result in an
|
|
* issue due to the IBM CAS reboot feature and we won't be able
|
|
* to boot the newer kernel. Whenever a reboot is detected as
|
|
* the CAS reboot by GRUB it will boot the machine with the
|
|
* last booted kernel by reading the variable boot-last-label
|
|
* which has the info related to the last boot and it's specific
|
|
* to IBM PowerPC. Due to this, the machine will boot with the
|
|
* last booted kernel which has rmo_top as 512 MB. Also, if the
|
|
* reboot is detected as a CAS reboot, the GRUB will skip the CAS
|
|
* call. As the CAS has already been called earlier, so it is
|
|
* not required to call CAS even if the other conditions are met.
|
|
* This condition will also prevent a scenario where the machine
|
|
* get stuck in the CAS reboot loop while booting a machine with
|
|
* an older kernel, having option_vector2 MIN_RMA as 512 MB in
|
|
* Linux prom_init.c and GRUB uses rmo_top < 768 MB condition
|
|
* for calling CAS. Due to their respective conditions, Linux
|
|
* CAS and GRUB CAS will keep doing the CAS calls and change
|
|
* the MIN_RMA from 768, changed by GRUB, to 512, changed by Linux,
|
|
* to 768, changed by GRUB, to 512, changed by Linux, and so on.
|
|
* The machine will stuck in this CAS reboot loop forever.
|
|
*
|
|
* IBM PAPR: https://openpower.foundation/specifications/linuxonpower/
|
|
*/
|
|
if (!ibm_ca_support_reboot && err == GRUB_ERR_NONE && rmo_top < (512 * 1024 * 1024))
|
|
grub_ieee1275_ibm_cas ();
|
|
}
|
|
#endif
|
|
|
|
grub_machine_mmap_iterate (heap_init, &total);
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
grub_parse_cmdline (void)
|
|
{
|
|
grub_ssize_t actual;
|
|
char args[256];
|
|
|
|
if (grub_ieee1275_get_property (grub_ieee1275_chosen, "bootargs", &args,
|
|
sizeof args, &actual) == 0
|
|
&& actual > 1)
|
|
{
|
|
int i = 0;
|
|
|
|
while (i < actual)
|
|
{
|
|
char *command = &args[i];
|
|
char *end;
|
|
char *val;
|
|
|
|
end = grub_strchr (command, ';');
|
|
if (end == 0)
|
|
i = actual; /* No more commands after this one. */
|
|
else
|
|
{
|
|
*end = '\0';
|
|
i += end - command + 1;
|
|
while (grub_isspace(args[i]))
|
|
i++;
|
|
}
|
|
|
|
/* Process command. */
|
|
val = grub_strchr (command, '=');
|
|
if (val)
|
|
{
|
|
*val = '\0';
|
|
grub_env_set (command, val + 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#ifdef __powerpc__
|
|
static void
|
|
grub_ieee1275_get_secure_boot (void)
|
|
{
|
|
grub_ieee1275_phandle_t root;
|
|
grub_uint32_t sb_mode = GRUB_SB_DISABLED;
|
|
grub_int32_t rc;
|
|
|
|
rc = grub_ieee1275_finddevice ("/", &root);
|
|
if (rc != 0)
|
|
{
|
|
grub_error (GRUB_ERR_UNKNOWN_DEVICE, "couldn't find / node");
|
|
return;
|
|
}
|
|
|
|
rc = grub_ieee1275_get_integer_property (root, "ibm,secure-boot", &sb_mode, sizeof (sb_mode), 0);
|
|
if (rc != 0)
|
|
{
|
|
grub_error (GRUB_ERR_UNKNOWN_DEVICE, "couldn't examine /ibm,secure-boot property");
|
|
return;
|
|
}
|
|
/*
|
|
* Secure Boot Mode:
|
|
* 0 - disabled
|
|
* No signature verification is performed. This is the default.
|
|
* 1 - audit
|
|
* Signature verification is performed and if signature verification
|
|
* fails, display the errors and allow the boot to continue.
|
|
* 2 - enforce
|
|
* Lockdown the GRUB. Signature verification is performed and If
|
|
* signature verification fails, display the errors and stop the boot.
|
|
*
|
|
* Now, only support disabled and enforce.
|
|
*/
|
|
if (sb_mode == GRUB_SB_ENFORCE)
|
|
{
|
|
grub_dprintf ("ieee1275", "Secure Boot Enabled\n");
|
|
grub_lockdown ();
|
|
}
|
|
else
|
|
grub_dprintf ("ieee1275", "Secure Boot Disabled\n");
|
|
|
|
grub_pks_keystore_init ();
|
|
}
|
|
#endif /* __powerpc__ */
|
|
grub_addr_t grub_modbase;
|
|
|
|
void
|
|
grub_machine_init (void)
|
|
{
|
|
grub_modbase = ALIGN_UP((grub_addr_t) _end
|
|
+ GRUB_KERNEL_MACHINE_MOD_GAP,
|
|
GRUB_KERNEL_MACHINE_MOD_ALIGN);
|
|
grub_ieee1275_init ();
|
|
|
|
grub_console_init_early ();
|
|
grub_claim_heap ();
|
|
grub_console_init_lately ();
|
|
#ifdef __sparc__
|
|
grub_obdisk_init ();
|
|
#else
|
|
grub_ofdisk_init ();
|
|
#endif
|
|
grub_parse_cmdline ();
|
|
|
|
#ifdef __i386__
|
|
grub_tsc_init ();
|
|
#else
|
|
grub_install_get_time_ms (grub_rtc_get_time_ms);
|
|
#endif
|
|
|
|
#ifdef __powerpc__
|
|
grub_ieee1275_get_secure_boot ();
|
|
#endif
|
|
}
|
|
|
|
void
|
|
grub_machine_fini (int flags)
|
|
{
|
|
if (flags & GRUB_LOADER_FLAG_NORETURN)
|
|
{
|
|
#ifdef __sparc__
|
|
grub_obdisk_fini ();
|
|
#else
|
|
grub_ofdisk_fini ();
|
|
#endif
|
|
grub_console_fini ();
|
|
}
|
|
}
|
|
|
|
grub_uint64_t
|
|
grub_rtc_get_time_ms (void)
|
|
{
|
|
grub_uint32_t msecs = 0;
|
|
|
|
grub_ieee1275_milliseconds (&msecs);
|
|
|
|
return msecs;
|
|
}
|