analyze-chid: Support EDID CHIDs

This commit is contained in:
anonymix007
2025-03-31 20:41:15 +03:00
parent 907328749f
commit 9fed4ec252
4 changed files with 161 additions and 7 deletions

View File

@@ -1713,6 +1713,15 @@ LEGEND: M → sys_vendor (LENOVO) ┄ F → product_family (ThinkPad X1 Carbon G
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--drm-device=<replaceable>PATH</replaceable></option></term>
<listitem><para>When provided with the <command>chid</command> command, use this sysfs path to a DRM
device to fetch EDID from. Example: <filename>/sys/class/drm/card1-HDMI-A-1/</filename></para>
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
<xi:include href="standard-options.xml" xpointer="no-pager" />

View File

@@ -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)

View File

@@ -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 */
}

View File

@@ -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);