From c51d84dc09476d9c06b8aac726220bf3c7d62e8d Mon Sep 17 00:00:00 2001 From: Kay Sievers Date: Mon, 23 Sep 2013 17:23:35 -0500 Subject: [PATCH] support acpi firmware performance data (FPDT) Prefer firmware-provided performance data over loader-exported ones; if ACPI data is available, always use it, otherwise try to read the loader data. The firmware-provided variables start at the time the first EFI image is executed and end when the operating system exits the boot services; the (loader) time calculated in systemd-analyze increases. --- .gitignore | 2 +- Makefile.am | 15 +- src/boot/boot-efi.c | 2 +- src/core/manager.c | 4 +- src/efi-boot-generator/efi-boot-generator.c | 2 +- src/shared/acpi-fpdt.c | 155 ++++++++++++++++++++ src/shared/acpi-fpdt.h | 26 ++++ src/shared/boot-timestamps.c | 65 ++++++++ src/shared/boot-timestamps.h | 27 ++++ src/shared/efivars.c | 41 +----- src/shared/efivars.h | 5 +- src/test/test-boot-timestamps.c | 98 +++++++++++++ src/test/test-efivars.c | 47 ------ 13 files changed, 389 insertions(+), 100 deletions(-) create mode 100644 src/shared/acpi-fpdt.c create mode 100644 src/shared/acpi-fpdt.h create mode 100644 src/shared/boot-timestamps.c create mode 100644 src/shared/boot-timestamps.h create mode 100644 src/test/test-boot-timestamps.c delete mode 100644 src/test/test-efivars.c diff --git a/.gitignore b/.gitignore index 8115d4d0e..5b38c0b2e 100644 --- a/.gitignore +++ b/.gitignore @@ -85,6 +85,7 @@ /systemd-user-sessions /systemd-vconsole-setup /tags +/test-boot-timestamp /test-bus-chat /test-bus-kernel /test-bus-kernel-bloom @@ -102,7 +103,6 @@ /test-daemon /test-date /test-device-nodes -/test-efivars /test-engine /test-env-replace /test-fileio diff --git a/Makefile.am b/Makefile.am index 89a5c8635..0eee1d93f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -717,6 +717,10 @@ libsystemd_shared_la_SOURCES = \ src/shared/output-mode.h \ src/shared/MurmurHash3.c \ src/shared/MurmurHash3.h \ + src/shared/acpi-fpdt.h \ + src/shared/acpi-fpdt.c \ + src/shared/boot-timestamps.h \ + src/shared/boot-timestamps.c \ src/shared/refcnt.h #------------------------------------------------------------------------------- @@ -1155,9 +1159,6 @@ EXTRA_DIST += \ test_device_nodes_SOURCES = \ src/test/test-device-nodes.c -test_device_nodes_CFLAGS = \ - $(AM_CFLAGS) - test_device_nodes_LDADD = \ libsystemd-shared.la @@ -1209,12 +1210,12 @@ test_hostname_LDADD = \ if ENABLE_EFI manual_tests += \ - test-efivars + test-boot-timestamp -test_efivars_SOURCES = \ - src/test/test-efivars.c +test_boot_timestamp_SOURCES = \ + src/test/test-boot-timestamps.c -test_efivars_LDADD = \ +test_boot_timestamp_LDADD = \ libsystemd-shared.la endif diff --git a/src/boot/boot-efi.c b/src/boot/boot-efi.c index 9960c4d74..33840b686 100644 --- a/src/boot/boot-efi.c +++ b/src/boot/boot-efi.c @@ -174,7 +174,7 @@ int boot_info_query(struct boot_info *info) { efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderFirmwareInfo", &info->fw_info); efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderImageIdentifier", &info->loader_image_path); tilt_slashes(info->loader_image_path); - efi_get_loader_device_part_uuid(&info->loader_part_uuid); + efi_loader_get_device_part_uuid(&info->loader_part_uuid); boot_loader_read_entries(info); efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderEntrySelected", &loader_active); diff --git a/src/core/manager.c b/src/core/manager.c index 079db4157..f70ff0303 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -70,7 +70,7 @@ #include "cgroup-util.h" #include "path-util.h" #include "audit-fd.h" -#include "efivars.h" +#include "boot-timestamps.h" #include "env-util.h" /* As soon as 5s passed since a unit was added to our GC queue, make sure to run a gc sweep */ @@ -496,7 +496,7 @@ int manager_new(SystemdRunningAs running_as, bool reexecuting, Manager **_m) { #ifdef ENABLE_EFI if (detect_container(NULL) <= 0) - efi_get_boot_timestamps(&m->userspace_timestamp, &m->firmware_timestamp, &m->loader_timestamp); + boot_timestamps(&m->userspace_timestamp, &m->firmware_timestamp, &m->loader_timestamp); #endif m->running_as = running_as; diff --git a/src/efi-boot-generator/efi-boot-generator.c b/src/efi-boot-generator/efi-boot-generator.c index 4367c536b..05b95ed45 100644 --- a/src/efi-boot-generator/efi-boot-generator.c +++ b/src/efi-boot-generator/efi-boot-generator.c @@ -55,7 +55,7 @@ int main(int argc, char *argv[]) { if (dir_is_empty("/boot") <= 0) return EXIT_SUCCESS; - r = efi_get_loader_device_part_uuid(&id); + r = efi_loader_get_device_part_uuid(&id); if (r == -ENOENT) return EXIT_SUCCESS; if (r < 0) { diff --git a/src/shared/acpi-fpdt.c b/src/shared/acpi-fpdt.c new file mode 100644 index 000000000..b094f34a5 --- /dev/null +++ b/src/shared/acpi-fpdt.c @@ -0,0 +1,155 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Kay Sievers + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +struct acpi_table_header { + char signature[4]; + uint32_t length; + uint8_t revision; + uint8_t checksum; + char oem_id[6]; + char oem_table_id[8]; + uint32_t oem_revision; + char asl_compiler_id[4]; + uint32_t asl_compiler_revision; +}; + +enum { + ACPI_FPDT_TYPE_BOOT = 0, + ACPI_FPDT_TYPE_S3PERF = 1, +}; + +struct acpi_fpdt_header { + uint16_t type; + uint8_t length; + uint8_t revision; + uint8_t reserved[4]; + uint64_t ptr; +}; + +struct acpi_fpdt_boot_header { + char signature[4]; + uint32_t length; +}; + +enum { + ACPI_FPDT_S3PERF_RESUME_REC = 0, + ACPI_FPDT_S3PERF_SUSPEND_REC = 1, + ACPI_FPDT_BOOT_REC = 2, +}; + +struct acpi_fpdt_boot { + uint16_t type; + uint8_t length; + uint8_t revision; + uint8_t reserved[4]; + uint64_t reset_end; + uint64_t load_start; + uint64_t startup_start; + uint64_t exit_services_entry; + uint64_t exit_services_exit; +}; + +int acpi_get_boot_usec(usec_t *loader_start, usec_t *loader_exit) { + char *buf; + struct acpi_table_header *tbl; + size_t l; + struct acpi_fpdt_header *rec; + int r; + uint64_t ptr = 0; + _cleanup_close_ int fd = -1; + struct acpi_fpdt_boot_header hbrec; + struct acpi_fpdt_boot brec; + + r = read_full_file("/sys/firmware/acpi/tables/FPDT", &buf, &l); + if (r < 0) + return r; + + if (l < sizeof(struct acpi_table_header) + sizeof(struct acpi_fpdt_header)) + return -EINVAL; + + tbl = (struct acpi_table_header *)buf; + if (l != tbl->length) + return -EINVAL; + + if (memcmp(tbl->signature, "FPDT", 4) != 0) + return -EINVAL; + + /* find Firmware Basic Boot Performance Pointer Record */ + for (rec = (struct acpi_fpdt_header *)(buf + sizeof(struct acpi_table_header)); + (char *)rec < buf + l; + rec = (struct acpi_fpdt_header *)((char *)rec + rec->length)) { + if (rec->type != ACPI_FPDT_TYPE_BOOT) + continue; + if (rec->length != sizeof(struct acpi_fpdt_header)) + continue; + + ptr = rec->ptr; + break; + } + + if (ptr == 0) + return -EINVAL; + + /* read Firmware Basic Boot Performance Data Record */ + fd = open("/dev/mem", O_CLOEXEC|O_RDONLY); + if (fd < 0) + return -errno; + + l = pread(fd, &hbrec, sizeof(struct acpi_fpdt_boot_header), ptr); + if (l != sizeof(struct acpi_fpdt_boot_header)) + return -EINVAL; + + if (memcmp(hbrec.signature, "FBPT", 4) != 0) + return -EINVAL; + + if (hbrec.length < sizeof(struct acpi_fpdt_boot_header) + sizeof(struct acpi_fpdt_boot)) + return -EINVAL; + + l = pread(fd, &brec, sizeof(struct acpi_fpdt_boot), ptr + sizeof(struct acpi_fpdt_boot_header)); + if (l != sizeof(struct acpi_fpdt_boot)) + return -EINVAL; + + if (brec.length != sizeof(struct acpi_fpdt_boot)) + return -EINVAL; + + if (brec.type != ACPI_FPDT_BOOT_REC) + return -EINVAL; + + if (loader_start) + *loader_start = brec.startup_start / 1000; + if (loader_exit) + *loader_exit = brec.exit_services_exit / 1000; + + return 0; +} diff --git a/src/shared/acpi-fpdt.h b/src/shared/acpi-fpdt.h new file mode 100644 index 000000000..fc4fe6f10 --- /dev/null +++ b/src/shared/acpi-fpdt.h @@ -0,0 +1,26 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 Kay Sievers + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include + +int acpi_get_boot_usec(usec_t *loader_start, usec_t *loader_exit); diff --git a/src/shared/boot-timestamps.c b/src/shared/boot-timestamps.c new file mode 100644 index 000000000..944996582 --- /dev/null +++ b/src/shared/boot-timestamps.c @@ -0,0 +1,65 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2012 Lennart Poettering + Copyright 2013 Kay Sievers + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ +#include + +#include "boot-timestamps.h" +#include "acpi-fpdt.h" +#include "efivars.h" + +int boot_timestamps(const dual_timestamp *n, dual_timestamp *firmware, dual_timestamp *loader) { + usec_t x, y, a; + int r; + dual_timestamp _n; + + assert(firmware); + assert(loader); + + if (!n) { + dual_timestamp_get(&_n); + n = &_n; + } + + r = acpi_get_boot_usec(&x, &y); + if (r < 0) { + r = efi_loader_get_boot_usec(&x, &y); + if (r < 0) + return r; + } + + /* Let's convert this to timestamps where the firmware + * began/loader began working. To make this more confusing: + * since usec_t is unsigned and the kernel's monotonic clock + * begins at kernel initialization we'll actually initialize + * the monotonic timestamps here as negative of the actual + * value. */ + + firmware->monotonic = y; + loader->monotonic = y - x; + + a = n->monotonic + firmware->monotonic; + firmware->realtime = n->realtime > a ? n->realtime - a : 0; + + a = n->monotonic + loader->monotonic; + loader->realtime = n->realtime > a ? n->realtime - a : 0; + + return 0; +} diff --git a/src/shared/boot-timestamps.h b/src/shared/boot-timestamps.h new file mode 100644 index 000000000..a3d2405b5 --- /dev/null +++ b/src/shared/boot-timestamps.h @@ -0,0 +1,27 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 Lennart Poettering + Copyright 2013 Kay Sievers + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include + +int boot_timestamps(const dual_timestamp *n, dual_timestamp *firmware, dual_timestamp *loader); diff --git a/src/shared/efivars.c b/src/shared/efivars.c index 8d004bad3..1d5b6f9e7 100644 --- a/src/shared/efivars.c +++ b/src/shared/efivars.c @@ -24,6 +24,7 @@ #include #include +#include "acpi-fpdt.h" #include "util.h" #include "utf8.h" #include "efivars.h" @@ -413,7 +414,7 @@ static int read_usec(sd_id128_t vendor, const char *name, usec_t *u) { return 0; } -static int get_boot_usec(usec_t *firmware, usec_t *loader) { +int efi_loader_get_boot_usec(usec_t *firmware, usec_t *loader) { uint64_t x, y; int r; @@ -440,43 +441,7 @@ static int get_boot_usec(usec_t *firmware, usec_t *loader) { return 0; } -int efi_get_boot_timestamps(const dual_timestamp *n, dual_timestamp *firmware, dual_timestamp *loader) { - usec_t x, y, a; - int r; - dual_timestamp _n; - - assert(firmware); - assert(loader); - - if (!n) { - dual_timestamp_get(&_n); - n = &_n; - } - - r = get_boot_usec(&x, &y); - if (r < 0) - return r; - - /* Let's convert this to timestamps where the firmware - * began/loader began working. To make this more confusing: - * since usec_t is unsigned and the kernel's monotonic clock - * begins at kernel initialization we'll actually initialize - * the monotonic timestamps here as negative of the actual - * value. */ - - firmware->monotonic = y; - loader->monotonic = y - x; - - a = n->monotonic + firmware->monotonic; - firmware->realtime = n->realtime > a ? n->realtime - a : 0; - - a = n->monotonic + loader->monotonic; - loader->realtime = n->realtime > a ? n->realtime - a : 0; - - return 0; -} - -int efi_get_loader_device_part_uuid(sd_id128_t *u) { +int efi_loader_get_device_part_uuid(sd_id128_t *u) { _cleanup_free_ char *p = NULL; int r, parsed[16]; unsigned i; diff --git a/src/shared/efivars.h b/src/shared/efivars.h index 2b88c6075..7921bedc9 100644 --- a/src/shared/efivars.h +++ b/src/shared/efivars.h @@ -42,6 +42,5 @@ int efi_get_boot_option(uint16_t nr, char **title, sd_id128_t *partuuid, char ** int efi_get_boot_order(uint16_t **order); int efi_get_boot_options(uint16_t **options); -int efi_get_boot_timestamps(const dual_timestamp *n, dual_timestamp *firmware, dual_timestamp *loader); - -int efi_get_loader_device_part_uuid(sd_id128_t *u); +int efi_loader_get_device_part_uuid(sd_id128_t *u); +int efi_loader_get_boot_usec(usec_t *firmware, usec_t *loader); diff --git a/src/test/test-boot-timestamps.c b/src/test/test-boot-timestamps.c new file mode 100644 index 000000000..4ede318e3 --- /dev/null +++ b/src/test/test-boot-timestamps.c @@ -0,0 +1,98 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + Copyright 2013 Kay Sievers + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include "util.h" +#include "log.h" +#include "boot-timestamps.h" +#include "efivars.h" +#include "acpi-fpdt.h" + +static int test_acpi_fpdt(void) { + usec_t loader_start; + usec_t loader_exit; + char ts_start[FORMAT_TIMESPAN_MAX]; + char ts_exit[FORMAT_TIMESPAN_MAX]; + char ts_span[FORMAT_TIMESPAN_MAX]; + int r; + + r = acpi_get_boot_usec(&loader_start, &loader_exit); + if (r < 0) { + if (r != -ENOENT) + log_error("Failed to read ACPI FPDT: %s", strerror(-r)); + return r; + } + + log_info("ACPI FPDT: loader start=%s exit=%s duration=%s", + format_timespan(ts_start, sizeof(ts_start), loader_start, USEC_PER_MSEC), + format_timespan(ts_exit, sizeof(ts_exit), loader_exit, USEC_PER_MSEC), + format_timespan(ts_span, sizeof(ts_span), loader_exit - loader_start, USEC_PER_MSEC)); + + return 0; +} + +static int test_efi_loader(void) { + usec_t loader_start; + usec_t loader_exit; + char ts_start[FORMAT_TIMESPAN_MAX]; + char ts_exit[FORMAT_TIMESPAN_MAX]; + char ts_span[FORMAT_TIMESPAN_MAX]; + int r; + + r = efi_loader_get_boot_usec(&loader_start, &loader_exit); + if (r < 0) { + if (r != -ENOENT) + log_error("Failed to read EFI loader data: %s", strerror(-r)); + return r; + } + + log_info("EFI Loader: start=%s exit=%s duration=%s", + format_timespan(ts_start, sizeof(ts_start), loader_start, USEC_PER_MSEC), + format_timespan(ts_exit, sizeof(ts_exit), loader_exit, USEC_PER_MSEC), + format_timespan(ts_span, sizeof(ts_span), loader_exit - loader_start, USEC_PER_MSEC)); + + return 0; +} + +int main(int argc, char* argv[]) { + char s[MAX(FORMAT_TIMESPAN_MAX, FORMAT_TIMESTAMP_MAX)]; + int r; + dual_timestamp fw, l, k; + + test_acpi_fpdt(); + test_efi_loader(); + + dual_timestamp_from_monotonic(&k, 0); + + r = boot_timestamps(NULL, &fw, &l); + if (r < 0) { + log_error("Failed to read variables: %s", strerror(-r)); + return 1; + } + + log_info("Firmware began %s before kernel.", format_timespan(s, sizeof(s), fw.monotonic, 0)); + log_info("Loader began %s before kernel.", format_timespan(s, sizeof(s), l.monotonic, 0)); + log_info("Firmware began %s.", format_timestamp(s, sizeof(s), fw.realtime)); + log_info("Loader began %s.", format_timestamp(s, sizeof(s), l.realtime)); + log_info("Kernel began %s.", format_timestamp(s, sizeof(s), k.realtime)); + + return 0; +} diff --git a/src/test/test-efivars.c b/src/test/test-efivars.c deleted file mode 100644 index 43ea5917b..000000000 --- a/src/test/test-efivars.c +++ /dev/null @@ -1,47 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2013 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#include "util.h" -#include "log.h" -#include "efivars.h" - -int main(int argc, char* argv[]) { - char s[MAX(FORMAT_TIMESPAN_MAX, FORMAT_TIMESTAMP_MAX)]; - int r; - dual_timestamp fw, l, k; - - dual_timestamp_from_monotonic(&k, 0); - - r = efi_get_boot_timestamps(NULL, &fw, &l); - if (r < 0) { - log_error("Failed to read variables: %s", strerror(-r)); - return 1; - } - - log_info("Firmware began %s before kernel.", format_timespan(s, sizeof(s), fw.monotonic, 0)); - log_info("Loader began %s before kernel.", format_timespan(s, sizeof(s), l.monotonic, 0)); - - log_info("Firmware began %s.", format_timestamp(s, sizeof(s), fw.realtime)); - log_info("Loader began %s.", format_timestamp(s, sizeof(s), l.realtime)); - log_info("Kernel began %s.", format_timestamp(s, sizeof(s), k.realtime)); - - return 0; -} -- 2.30.2