diff options
-rw-r--r-- | kexec/Makefile | 1 | ||||
-rw-r--r-- | kexec/arch/i386/kexec-x86.c | 124 | ||||
-rw-r--r-- | kexec/firmware_memmap.c | 278 | ||||
-rw-r--r-- | kexec/firmware_memmap.h | 63 |
4 files changed, 463 insertions, 3 deletions
diff --git a/kexec/Makefile b/kexec/Makefile index a80b940..3a00114 100644 --- a/kexec/Makefile +++ b/kexec/Makefile @@ -18,6 +18,7 @@ KEXEC_SRCS += kexec/kexec-elf-core.c KEXEC_SRCS += kexec/kexec-elf-rel.c KEXEC_SRCS += kexec/kexec-elf-boot.c KEXEC_SRCS += kexec/kexec-iomem.c +KEXEC_SRCS += kexec/firmware_memmap.c KEXEC_SRCS += kexec/crashdump.c KEXEC_SRCS += kexec/crashdump-xen.c KEXEC_SRCS += kexec/phys_arch.c diff --git a/kexec/arch/i386/kexec-x86.c b/kexec/arch/i386/kexec-x86.c index 8c8ba7f..554c154 100644 --- a/kexec/arch/i386/kexec-x86.c +++ b/kexec/arch/i386/kexec-x86.c @@ -28,15 +28,26 @@ #include "../../kexec.h" #include "../../kexec-elf.h" #include "../../kexec-syscall.h" +#include "../../firmware_memmap.h" #include "kexec-x86.h" #include "crashdump-x86.h" #include <arch/options.h> static struct memory_range memory_range[MAX_MEMORY_RANGES]; -/* Return a sorted list of memory ranges. */ -int get_memory_ranges(struct memory_range **range, int *ranges, - unsigned long kexec_flags) +/** + * The old /proc/iomem parsing code. + * + * @param[out] range pointer that will be set to an array that holds the + * memory ranges + * @param[out] ranges number of ranges valid in @p range + * @param[in] kexec_flags the kexec_flags to determine if we load a normal + * or a crashdump kernel + * + * @return 0 on success, any other value on failure. + */ +static int get_memory_ranges_proc_iomem(struct memory_range **range, int *ranges, + unsigned long kexec_flags) { const char *iomem= proc_iomem(); int memory_ranges = 0; @@ -114,6 +125,113 @@ int get_memory_ranges(struct memory_range **range, int *ranges, return 0; } +/** + * Calls the architecture independent get_firmware_memmap_ranges() to parse + * /sys/firmware/memmap and then do some x86 only modifications. + * + * @param[out] range pointer that will be set to an array that holds the + * memory ranges + * @param[out] ranges number of ranges valid in @p range + * @param[in] kexec_flags the kexec_flags to determine if we load a normal + * or a crashdump kernel + * + * @return 0 on success, any other value on failure. + */ +static int get_memory_ranges_sysfs(struct memory_range **range, int *ranges, + unsigned long kexec_flags) +{ + int ret; + size_t i; + size_t range_number = MAX_MEMORY_RANGES; + unsigned long long start, end; + + ret = get_firmware_memmap_ranges(memory_range, &range_number); + if (ret != 0) { + fprintf(stderr, "Parsing the /sys/firmware memory map failed. " + "Falling back to /proc/iomem.\n"); + return get_memory_ranges_proc_iomem(range, ranges, kexec_flags); + } + + /* Don't report the interrupt table as ram */ + for (i = 0; i < range_number; i++) { + if (memory_range[i].type == RANGE_RAM && + (memory_range[i].start < 0x100)) { + memory_range[i].start = 0x100; + break; + } + } + + /* + * Redefine the memory region boundaries if kernel + * exports the limits and if it is panic kernel. + * Override user values only if kernel exported values are + * subset of user defined values. + */ + if (kexec_flags & KEXEC_ON_CRASH) { + ret = parse_iomem_single("Crash kernel\n", &start, &end); + if (ret != 0) { + fprintf(stderr, "parse_iomem_single failed.\n"); + return -1; + } + + if (start > mem_min) + mem_min = start; + if (end < mem_max) + mem_max = end; + } + + *range = memory_range; + *ranges = range_number; + + return 0; +} + +/** + * Return a sorted list of memory ranges. + * + * If we have the /sys/firmware/memmap interface, then use that. If not, + * or if parsing of that fails, use /proc/iomem as fallback. + * + * @param[out] range pointer that will be set to an array that holds the + * memory ranges + * @param[out] ranges number of ranges valid in @p range + * @param[in] kexec_flags the kexec_flags to determine if we load a normal + * or a crashdump kernel + * + * @return 0 on success, any other value on failure. + */ +int get_memory_ranges(struct memory_range **range, int *ranges, + unsigned long kexec_flags) +{ + int ret; + + if (have_sys_firmware_memmap()) + ret = get_memory_ranges_sysfs(range, ranges,kexec_flags); + else + ret = get_memory_ranges_proc_iomem(range, ranges, kexec_flags); + + /* + * both get_memory_ranges_sysfs() and get_memory_ranges_proc_iomem() + * have already printed an error message, so fail silently here + */ + if (ret != 0) + return ret; + + /* just set 0 to 1 to enable printing for debugging */ +#if 0 + { + int i; + printf("MEMORY RANGES\n"); + for (i = 0; i < *ranges; i++) { + printf("%016Lx-%016Lx (%d)\n", (*range)[i].start, + (*range)[i].end, (*range)[i].type); + } + } +#endif + + return ret; +} + struct file_type file_type[] = { { "multiboot-x86", multiboot_x86_probe, multiboot_x86_load, multiboot_x86_usage }, diff --git a/kexec/firmware_memmap.c b/kexec/firmware_memmap.c new file mode 100644 index 0000000..b0d4f62 --- /dev/null +++ b/kexec/firmware_memmap.c @@ -0,0 +1,278 @@ +/* + * firmware_memmap.c: Read /sys/firmware/memmap + * + * Created by: Bernhard Walle (bwalle@suse.de) + * Copyright (C) SUSE LINUX Products GmbH, 2008. All rights reserved + * + * This program 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 (version 2 of the License). + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#define _GNU_SOURCE /* for ULLONG_MAX without C99 */ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <dirent.h> +#include <unistd.h> +#include <limits.h> +#include <errno.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "firmware_memmap.h" +#include "kexec.h" + +/* + * If the system is too old for ULLONG_MAX or LLONG_MAX, define it here. + */ +#ifndef ULLONG_MAX +# define ULLONG_MAX (~0ULL) +#endif /* ULLONG_MAX */ + +#ifndef LLONG_MAX +# define LLONG_MAX (~0ULL >> 1) +#endif /* LLONG_MAX */ + + +/** + * The full path to the sysfs interface that provides the memory map. + */ +#define FIRMWARE_MEMMAP_DIR "/sys/firmware/memmap" + +/** + * Parses a file that only contains one number. Typical for sysfs files. + * + * @param[in] filename the name of the file that should be parsed + * @return the value that has been read or ULLONG_MAX on error. + */ +static unsigned long long parse_numeric_sysfs(const char *filename) +{ + FILE *fp; + char linebuffer[BUFSIZ]; + unsigned long long retval; + + fp = fopen(filename, "r"); + if (!fp) { + fprintf(stderr, "Opening \"%s\" failed: %s\n", + filename, strerror(errno)); + return ULLONG_MAX; + } + + fgets(linebuffer, BUFSIZ, fp); + linebuffer[BUFSIZ-1] = 0; + + /* let strtoll() detect the base */ + retval = strtoll(linebuffer, NULL, 0); + + fclose(fp); + + return retval; +} + +/** + * Reads the contents of a one-line sysfs file to buffer. (This function is + * not threadsafe.) + * + * @param[in] filename the name of the file that should be read + * + * @return NULL on failure, a pointer to a static buffer (that should be copied + * with strdup() if the caller plans to use it after next function call) + */ +static char *parse_string_sysfs(const char *filename) +{ + FILE *fp; + static char linebuffer[BUFSIZ]; + char *end; + + fp = fopen(filename, "r"); + if (!fp) { + fprintf(stderr, "Opening \"%s\" failed: %s\n", + filename, strerror(errno)); + return NULL; + } + + fgets(linebuffer, BUFSIZ, fp); + linebuffer[BUFSIZ-1] = 0; + + /* truncate trailing newline(s) */ + end = linebuffer + strlen(linebuffer) - 1; + while (*end == '\n') + *end-- = 0; + + fclose(fp); + + return linebuffer; + +} + +static int parse_memmap_entry(const char *entry, struct memory_range *range) +{ + char filename[PATH_MAX]; + char *type; + + /* + * entry/start + */ + snprintf(filename, PATH_MAX, "%s/%s", entry, "start"); + filename[PATH_MAX-1] = 0; + + range->start = parse_numeric_sysfs(filename); + if (range->start == ULLONG_MAX) + return -1; + + /* + * entry/end + */ + snprintf(filename, PATH_MAX, "%s/%s", entry, "end"); + filename[PATH_MAX-1] = 0; + + range->end = parse_numeric_sysfs(filename); + if (range->end == ULLONG_MAX) + return -1; + range->end++; /* inclusive vs. exclusive ranges */ + + /* + * entry/type + */ + snprintf(filename, PATH_MAX, "%s/%s", entry, "type"); + filename[PATH_MAX-1] = 0; + + type = parse_string_sysfs(filename); + if (!type) + return -1; + + if (strcmp(type, "System RAM") == 0) + range->type = RANGE_RAM; + else if (strcmp(type, "ACPI Tables") == 0) + range->type = RANGE_ACPI; + else if (strcmp(type, "reserved") == 0) + range->type = RANGE_RESERVED; + else if (strcmp(type, "ACPI Non-volatile Storage") == 0) + range->type = RANGE_ACPI_NVS; + else { + fprintf(stderr, "Unknown type (%s) while parsing %s. Please " + "report this as bug. Using RANGE_RESERVED now.\n", + type, filename); + range->type = RANGE_RESERVED; + } + + return 0; +} + +/** + * Compares two memory ranges according to their start address. This function + * can be used with qsort() as @c compar function. + * + * @param[in] first a pointer to the first memory range + * @param[in] second a pointer to the second memory range + * @return 0 if @p first and @p second have the same start address, + * a value less then 0 if the start address of @p first is less than + * the start address of @p second, and a value greater than 0 if + * the opposite is in case. + */ +static int compare_ranges(const void *first, const void *second) +{ + const struct memory_range *first_range = first; + const struct memory_range *second_range = second; + + /* + * don't use the "first_range->start - second_range->start" + * notation because unsigned long long might overflow + */ + if (first_range->start > second_range->start) + return 1; + else if (first_range->start < second_range->start) + return -1; + else /* first_range->start == second_range->start */ + return 0; +} + +/* documentation: firmware_memmap.h */ +int have_sys_firmware_memmap(void) +{ + int ret; + struct stat mystat; + + ret = stat(FIRMWARE_MEMMAP_DIR, &mystat); + if (ret != 0) + return 0; + + return S_ISDIR(mystat.st_mode); +} + +/* documentation: firmware_memmap.h */ +int get_firmware_memmap_ranges(struct memory_range *range, size_t *ranges) +{ + DIR *firmware_memmap_dir = NULL; + struct dirent *dirent; + int i = 0; + + /* argument checking */ + if (!range || !ranges) { + fprintf(stderr, "%s: Invalid arguments.\n", __FUNCTION__); + return -1; + } + + /* open the directory */ + firmware_memmap_dir = opendir(FIRMWARE_MEMMAP_DIR); + if (!firmware_memmap_dir) { + perror("Could not open \"" FIRMWARE_MEMMAP_DIR "\""); + goto error; + } + + /* parse the entries */ + while ((dirent = readdir(firmware_memmap_dir)) != NULL) { + int ret; + char full_path[PATH_MAX]; + + /* array overflow check */ + if ((size_t)i >= *ranges) { + fprintf(stderr, "The firmware provides more entries " + "allowed (%d). Please report that as bug.\n", + *ranges); + goto error; + } + + /* exclude '.' and '..' */ + if (dirent->d_name[0] && dirent->d_name[0] == '.') { + continue; + } + + snprintf(full_path, PATH_MAX, "%s/%s", FIRMWARE_MEMMAP_DIR, + dirent->d_name); + full_path[PATH_MAX-1] = 0; + ret = parse_memmap_entry(full_path, &range[i]); + if (ret < 0) { + goto error; + } + + i++; + } + + /* close the dir as we don't need it any more */ + closedir(firmware_memmap_dir); + + /* update the number of ranges for the caller */ + *ranges = i; + + /* and finally sort the entries with qsort */ + qsort(range, *ranges, sizeof(struct memory_range), compare_ranges); + + return 0; + +error: + if (firmware_memmap_dir) { + closedir(firmware_memmap_dir); + } + return -1; +} + diff --git a/kexec/firmware_memmap.h b/kexec/firmware_memmap.h new file mode 100644 index 0000000..41c3b3f --- /dev/null +++ b/kexec/firmware_memmap.h @@ -0,0 +1,63 @@ +/* + * firmware_memmap.c: Read /sys/firmware/memmap + * + * Created by: Bernhard Walle (bwalle@suse.de) + * Copyright (C) SUSE LINUX Products GmbH, 2008. All rights reserved + * + * This program 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 (version 2 of the License). + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#ifndef FIRMWARE_MEMMAP_H +#define FIRMWARE_MEMMAP_H + +#include "kexec.h" + +/** + * Reads the /sys/firmware/memmap interface, documented in + * Documentation/ABI/testing/sysfs-firmware-memmap (kernel tree). + * + * The difference between /proc/iomem and /sys/firmware/memmap is that + * /sys/firmware/memmap provides the raw memory map, provided by the + * firmware of the system. That memory map should be passed to a kexec'd + * kernel because the behaviour should be the same as a normal booted kernel, + * so any limitation (e.g. by the user providing the mem command line option) + * should not be passed to the kexec'd kernel. + * + * The parsing of the code is independent of the architecture. However, the + * actual architecture-specific code might postprocess the code a bit, like + * x86 does. + */ + +/** + * Checks if the kernel provides the /sys/firmware/memmap interface. + * It makes sense to use that function in advance before calling + * get_firmware_memmap_ranges() because the latter function prints an error + * if it cannot open the directory. If have_sys_firmware_memmap() returns + * false, then one can use the old /proc/iomem interface (for older kernels). + */ +int have_sys_firmware_memmap(void); + +/** + * Parses the /sys/firmware/memmap memory map. + * + * @param[out] range a pointer to an array of type struct memory_range with + * at least *range entries + * @param[in,out] ranges a pointer to an integer that holds the number of + * entries which range contains (at least). After successful + * return, the number of actual entries will be written. + * @return 0 on success, -1 on failure. + */ +int get_firmware_memmap_ranges(struct memory_range *range, size_t *ranges); + + +#endif /* FIRMWARE_MEMMAP_H */ |