From 9fed4ec252ee6b4fce8a778222f93bdb3eb35395 Mon Sep 17 00:00:00 2001 From: anonymix007 <48598263+anonymix007@users.noreply.github.com> Date: Mon, 31 Mar 2025 20:41:15 +0300 Subject: [PATCH] analyze-chid: Support EDID CHIDs --- man/systemd-analyze.xml | 9 +++ src/analyze/analyze-chid.c | 143 +++++++++++++++++++++++++++++++++++-- src/analyze/analyze.c | 15 ++++ src/analyze/analyze.h | 1 + 4 files changed, 161 insertions(+), 7 deletions(-) diff --git a/man/systemd-analyze.xml b/man/systemd-analyze.xml index b3975d5f03e..e61c557fe9b 100644 --- a/man/systemd-analyze.xml +++ b/man/systemd-analyze.xml @@ -1713,6 +1713,15 @@ LEGEND: M → sys_vendor (LENOVO) ┄ F → product_family (ThinkPad X1 Carbon G + + + + When provided with the chid command, use this sysfs path to a DRM + device to fetch EDID from. Example: /sys/class/drm/card1-HDMI-A-1/ + + + + diff --git a/src/analyze/analyze-chid.c b/src/analyze/analyze-chid.c index 01e49965f8e..6d2c74345f0 100644 --- a/src/analyze/analyze-chid.c +++ b/src/analyze/analyze-chid.c @@ -1,9 +1,14 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "sd-device.h" + #include "alloc-util.h" #include "analyze.h" #include "analyze-chid.h" #include "chid-fundamental.h" +#include "device-util.h" +#include "dirent-util.h" +#include "edid-fundamental.h" #include "efi-api.h" #include "escape.h" #include "fd-util.h" @@ -15,16 +20,27 @@ #include "virt.h" static int parse_chid_type(const char *s, size_t *ret) { + char *e; unsigned u; int r; assert(s); - r = safe_atou(s, &u); - if (r < 0) - return r; - if (u >= CHID_TYPES_MAX) - return -ERANGE; + if ((e = startswith(s, "ext"))) { + r = safe_atou(e, &u); + if (r < 0) + return r; + if (u >= CHID_TYPES_MAX - EXTRA_CHID_BASE) + return -ERANGE; + u += EXTRA_CHID_BASE; + } else { + r = safe_atou(s, &u); + if (r < 0) + return r; + if (u >= EXTRA_CHID_BASE) + return -ERANGE; + } + if (ret) *ret = u; @@ -44,6 +60,7 @@ static const char *const chid_smbios_friendly[_CHID_SMBIOS_FIELDS_MAX] = { [CHID_SMBIOS_BIOS_MAJOR] = "bios-major", [CHID_SMBIOS_BIOS_MINOR] = "bios-minor", [CHID_SMBIOS_ENCLOSURE_TYPE] = "enclosure-type", + [CHID_EDID_PANEL] = "edid-panel", }; static const char chid_smbios_fields_char[_CHID_SMBIOS_FIELDS_MAX] = { @@ -58,6 +75,7 @@ static const char chid_smbios_fields_char[_CHID_SMBIOS_FIELDS_MAX] = { [CHID_SMBIOS_BIOS_MAJOR] = 'R', [CHID_SMBIOS_BIOS_MINOR] = 'r', [CHID_SMBIOS_ENCLOSURE_TYPE] = 'e', + [CHID_EDID_PANEL] = 'E', }; static char *chid_smbios_fields_string(uint32_t combination) { @@ -91,8 +109,14 @@ static int add_chid(Table *table, const EFI_GUID guids[static CHID_TYPES_MAX], s if (!flags) return log_oom(); + if (t < EXTRA_CHID_BASE) + r = table_add_many(table, TABLE_UINT, (unsigned) t); + else + r = table_add_cell_stringf(table, NULL, "ext%zu", t - EXTRA_CHID_BASE); + if (r < 0) + return table_log_add_error(r); + r = table_add_many(table, - TABLE_UINT, (unsigned) t, TABLE_STRING, flags, TABLE_UUID, id); if (r < 0) @@ -224,6 +248,93 @@ static int smbios_fields_acquire(char16_t *fields[static _CHID_SMBIOS_FIELDS_MAX return 0; } +static int edid_parse(sd_device *drm_dev, char16_t **ret_panel) { + const char *edid_content; + size_t edid_size; + int r; + + assert(drm_dev); + assert(ret_panel); + + r = sd_device_get_sysattr_value_with_size(drm_dev, "edid", &edid_content, &edid_size); + if (r < 0) + return r; + if (edid_size == 0) + return -ENXIO; + + EdidHeader header; + if (edid_parse_blob(edid_content, edid_size, &header) < 0) + return -EBADMSG; + + _cleanup_free_ char16_t *panel_id = new0(char16_t, 8); + if (!panel_id) + return -ENOMEM; + + if (edid_get_panel_id(&header, panel_id) < 0) + return -EBADMSG; + + *ret_panel = TAKE_PTR(panel_id); + return 0; +} + +static int edid_search(char16_t **ret_panel) { + _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; + _cleanup_strv_free_ char **drm_paths = NULL; + _cleanup_free_ char16_t *unique_panel = NULL; + size_t n = 0; + int r; + + assert(ret_panel); + + r = sd_device_enumerator_new(&e); + if (r < 0) + return log_error_errno(r, "Failed to create device enumerator: %m"); + + r = sd_device_enumerator_allow_uninitialized(e); + if (r < 0) + return log_error_errno(r, "Failed to allow uninitialized device enumerator: %m"); + + r = sd_device_enumerator_add_match_subsystem(e, "drm", true); + if (r < 0) + return log_error_errno(r, "Failed to add drm match subsystem to device enumerator: %m"); + + FOREACH_DEVICE(e, d) { + _cleanup_free_ char16_t *panel = NULL; + const char *drm_path; + + r = sd_device_get_syspath(d, &drm_path); + if (r < 0) + return log_device_error_errno(d, r, "Failed to get syspath from device: %m"); + + r = edid_parse(d, &panel); + if (ERRNO_IS_DEVICE_ABSENT(r)) + continue; + if (r < 0) { + log_device_debug_errno(d, r, "Failed to parse EDID from DRM device, skipping: %m"); + continue; + } + + if (!unique_panel) + unique_panel = TAKE_PTR(panel); + + if (strv_extend_with_size(&drm_paths, &n, drm_path) < 0) + return log_oom(); + } + + if (n == 1) { + *ret_panel = TAKE_PTR(unique_panel); + return 0; + } + if (n == 0) + return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "No monitors detected, skipping EDID CHID extensions."); + + log_notice("Multiple monitors detected, skipping EDID CHID extensions."); + STRV_FOREACH(s, drm_paths) + log_info("Hint: use --drm-device=%s", *s); + + return -ENOTUNIQ; +} + int verb_chid(int argc, char *argv[], void *userdata) { _cleanup_(table_unrefp) Table *table = NULL; @@ -244,6 +355,24 @@ int verb_chid(int argc, char *argv[], void *userdata) { if (r < 0) return r; + if (arg_drm_device_path) { + _cleanup_(sd_device_unrefp) sd_device *drm_dev = NULL; + r = sd_device_new_from_path(&drm_dev, arg_drm_device_path); + if (r < 0) + return log_error_errno(r, "Failed to open device %s: %m", arg_drm_device_path); + + if (!device_in_subsystem(drm_dev, "drm")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot read EDID from a non-DRM device '%s'", arg_drm_device_path); + + r = edid_parse(drm_dev, &smbios_fields[CHID_EDID_PANEL]); + if (r < 0) + return log_error_errno(r, "Failed to parse EDID for device %s: %m", arg_drm_device_path); + } else { + r = edid_search(&smbios_fields[CHID_EDID_PANEL]); + if (r < 0 && !IN_SET(r, -ENOTUNIQ, -ENODEV)) + return r; + } + EFI_GUID chids[CHID_TYPES_MAX] = {}; chid_calculate((const char16_t* const*) smbios_fields, chids); @@ -258,7 +387,7 @@ int verb_chid(int argc, char *argv[], void *userdata) { size_t t; r = parse_chid_type(*as, &t); if (r < 0) - return log_error_errno(r, "Failed to pare CHID type: %s", *as); + return log_error_errno(r, "Failed to parse CHID type: %s", *as); r = add_chid(table, chids, t); if (r < 0) diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c index 0b029c5f017..f6a19f306fa 100644 --- a/src/analyze/analyze.c +++ b/src/analyze/analyze.c @@ -121,12 +121,14 @@ char *arg_profile = NULL; bool arg_legend = true; bool arg_table = false; ImagePolicy *arg_image_policy = NULL; +char *arg_drm_device_path = NULL; STATIC_DESTRUCTOR_REGISTER(arg_dot_from_patterns, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_dot_to_patterns, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_root, freep); STATIC_DESTRUCTOR_REGISTER(arg_image, freep); STATIC_DESTRUCTOR_REGISTER(arg_security_policy, freep); +STATIC_DESTRUCTOR_REGISTER(arg_drm_device_path, freep); STATIC_DESTRUCTOR_REGISTER(arg_unit, freep); STATIC_DESTRUCTOR_REGISTER(arg_profile, freep); STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); @@ -287,6 +289,8 @@ static int help(int argc, char *argv[], void *userdata) { " --image=PATH Operate on disk image as filesystem root\n" " --image-policy=POLICY Specify disk image dissection policy\n" " -m --mask Parse parameter as numeric capability mask\n" + " --drm-device=PATH Use this DRM device sysfs path to get EDID\n" + "\nSee the %2$s for details.\n", program_invocation_short_name, link, @@ -333,6 +337,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_TLDR, ARG_SCALE_FACTOR_SVG, ARG_DETAILED_SVG, + ARG_DRM_DEVICE_PATH, }; static const struct option options[] = { @@ -371,6 +376,7 @@ static int parse_argv(int argc, char *argv[]) { { "mask", no_argument, NULL, 'm' }, { "scale-svg", required_argument, NULL, ARG_SCALE_FACTOR_SVG }, { "detailed", no_argument, NULL, ARG_DETAILED_SVG }, + { "drm-device", required_argument, NULL, ARG_DRM_DEVICE_PATH }, {} }; @@ -580,6 +586,12 @@ static int parse_argv(int argc, char *argv[]) { arg_detailed_svg = true; break; + case ARG_DRM_DEVICE_PATH: + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_drm_device_path); + if (r < 0) + return r; + break; + case '?': return -EINVAL; @@ -638,6 +650,9 @@ static int parse_argv(int argc, char *argv[]) { if (arg_capability != CAPABILITY_LITERAL && !streq_ptr(argv[optind], "capability")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --mask is only supported for capability."); + if (arg_drm_device_path && !streq_ptr(argv[optind], "chid")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --drm-device is only supported for chid right now."); + return 1; /* work to do */ } diff --git a/src/analyze/analyze.h b/src/analyze/analyze.h index 959b8a1b850..1ab018e0711 100644 --- a/src/analyze/analyze.h +++ b/src/analyze/analyze.h @@ -51,6 +51,7 @@ extern char *arg_profile; extern bool arg_legend; extern bool arg_table; extern ImagePolicy *arg_image_policy; +extern char *arg_drm_device_path; int acquire_bus(sd_bus **bus, bool *use_full_bus); -- 2.47.3