diff --git a/man/systemd-analyze.xml b/man/systemd-analyze.xml
index b3975d5f03..e61c557fe9 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 01e49965f8..6d2c74345f 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 0b029c5f01..f6a19f306f 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 959b8a1b85..1ab018e071 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);
diff --git a/src/basic/string-util.h b/src/basic/string-util.h
index 03bff1d5fc..e00a5f5977 100644
--- a/src/basic/string-util.h
+++ b/src/basic/string-util.h
@@ -9,23 +9,6 @@
#include "macro.h"
#include "string-util-fundamental.h"
-/* What is interpreted as whitespace? */
-#define WHITESPACE " \t\n\r"
-#define NEWLINE "\n\r"
-#define QUOTES "\"\'"
-#define COMMENTS "#;"
-#define GLOB_CHARS "*?["
-#define DIGITS "0123456789"
-#define LOWERCASE_LETTERS "abcdefghijklmnopqrstuvwxyz"
-#define UPPERCASE_LETTERS "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
-#define LETTERS LOWERCASE_LETTERS UPPERCASE_LETTERS
-#define ALPHANUMERICAL LETTERS DIGITS
-#define HEXDIGITS DIGITS "abcdefABCDEF"
-#define LOWERCASE_HEXDIGITS DIGITS "abcdef"
-#define URI_RESERVED ":/?#[]@!$&'()*+;=" /* [RFC3986] */
-#define URI_UNRESERVED ALPHANUMERICAL "-._~" /* [RFC3986] */
-#define URI_VALID URI_RESERVED URI_UNRESERVED /* [RFC3986] */
-
static inline char* strstr_ptr(const char *haystack, const char *needle) {
if (!haystack || !needle)
return NULL;
diff --git a/src/boot/chid.c b/src/boot/chid.c
index 69e68e7d01..24739e956c 100644
--- a/src/boot/chid.c
+++ b/src/boot/chid.c
@@ -16,6 +16,7 @@
#include "chid.h"
#include "chid-fundamental.h"
+#include "edid.h"
#include "efi.h"
#include "sha1-fundamental.h"
#include "smbios.h"
@@ -72,6 +73,8 @@ static void smbios_info_populate(SmbiosInfo *ret_info) {
ret_info->smbios_fields[CHID_SMBIOS_FAMILY] = smbios_to_hashable_string(raw.family);
ret_info->smbios_fields[CHID_SMBIOS_BASEBOARD_PRODUCT] = smbios_to_hashable_string(raw.baseboard_product);
ret_info->smbios_fields[CHID_SMBIOS_BASEBOARD_MANUFACTURER] = smbios_to_hashable_string(raw.baseboard_manufacturer);
+
+ edid_get_discovered_panel_id(&ret_info->smbios_fields[CHID_EDID_PANEL]);
}
static void smbios_info_done(SmbiosInfo *info) {
@@ -100,7 +103,7 @@ EFI_STATUS chid_match(const void *hwid_buffer, size_t hwid_length, uint32_t matc
const Device *devices = ASSERT_PTR(hwid_buffer);
EFI_GUID chids[CHID_TYPES_MAX] = {};
- static const size_t priority[] = { 3, 6, 8, 10, 4, 5, 7, 9, 11 }; /* From most to least specific. */
+ static const size_t priority[] = { 17, 16, 15, 3, 6, 8, 10, 4, 5, 7, 9, 11 }; /* From most to least specific. */
status = populate_board_chids(chids);
if (EFI_STATUS_IS_ERROR(status))
diff --git a/src/boot/edid.c b/src/boot/edid.c
new file mode 100644
index 0000000000..62e338353e
--- /dev/null
+++ b/src/boot/edid.c
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "edid.h"
+#include "edid-fundamental.h"
+#include "log.h"
+#include "proto/edid-discovered.h"
+#include "util.h"
+
+EFI_STATUS edid_get_discovered_panel_id(char16_t **ret_panel) {
+ assert(ret_panel);
+
+ EFI_EDID_DISCOVERED_PROTOCOL *edid_discovered = NULL;
+ EFI_STATUS status = BS->LocateProtocol(MAKE_GUID_PTR(EFI_EDID_DISCOVERED_PROTOCOL), NULL, (void **) &edid_discovered);
+ if (EFI_STATUS_IS_ERROR(status))
+ return status;
+
+ if (!edid_discovered)
+ return EFI_UNSUPPORTED;
+ if (!edid_discovered->Edid)
+ return EFI_UNSUPPORTED;
+ if (edid_discovered->SizeOfEdid == 0)
+ return EFI_UNSUPPORTED;
+
+ EdidHeader header;
+ if (edid_parse_blob(edid_discovered->Edid, edid_discovered->SizeOfEdid, &header) < 0)
+ return EFI_INCOMPATIBLE_VERSION;
+
+ _cleanup_free_ char16_t *panel = xnew0(char16_t, 8);
+ if (edid_get_panel_id(&header, panel) < 0)
+ return EFI_INVALID_PARAMETER;
+
+ *ret_panel = TAKE_PTR(panel);
+ return EFI_SUCCESS;
+}
diff --git a/src/boot/edid.h b/src/boot/edid.h
new file mode 100644
index 0000000000..9832dadaab
--- /dev/null
+++ b/src/boot/edid.h
@@ -0,0 +1,6 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "efi.h"
+
+EFI_STATUS edid_get_discovered_panel_id(char16_t **ret_panel);
diff --git a/src/boot/hwids/device1.json b/src/boot/hwids/device1.json
index 6649a76491..5fa718c029 100644
--- a/src/boot/hwids/device1.json
+++ b/src/boot/hwids/device1.json
@@ -3,7 +3,6 @@
"name": "Device 1",
"compatible": "test,device-1",
"hwids": [
- "2bdcafa9-727c-5e8b-b13b-236f1391c25a",
- "c87886da-8026-5149-8c95-6d2504c1b7f9"
+ "75caebc1-5b41-5d33-a262-154720304180"
]
}
diff --git a/src/boot/meson.build b/src/boot/meson.build
index d4bd202f6f..c2a894d323 100644
--- a/src/boot/meson.build
+++ b/src/boot/meson.build
@@ -292,6 +292,7 @@ libefi_sources = files(
'device-path-util.c',
'devicetree.c',
'drivers.c',
+ 'edid.c',
'efi-firmware.c',
'efi-string.c',
'efivars.c',
diff --git a/src/boot/proto/edid-discovered.h b/src/boot/proto/edid-discovered.h
new file mode 100644
index 0000000000..7c83fcdcaf
--- /dev/null
+++ b/src/boot/proto/edid-discovered.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "efi.h"
+
+#define EFI_EDID_DISCOVERED_PROTOCOL_GUID \
+ GUID_DEF(0x1c0c34f6, 0xd380, 0x41fa, 0xa0, 0x49, 0x8a, 0xd0, 0x6c, 0x1a, 0x66, 0xaa)
+
+typedef struct {
+ uint32_t SizeOfEdid;
+ uint8_t *Edid;
+} EFI_EDID_DISCOVERED_PROTOCOL;
diff --git a/src/boot/test-chid-match.c b/src/boot/test-chid-match.c
index 415681f201..5a22db70e3 100644
--- a/src/boot/test-chid-match.c
+++ b/src/boot/test-chid-match.c
@@ -4,6 +4,8 @@
#include
#include "chid.h"
+#include "edid.h"
+#include "efi-string.h"
#include "smbios.h"
#include "tests.h"
@@ -12,6 +14,7 @@ extern size_t hwids_section_len;
static struct {
const RawSmbiosInfo smbios_info;
+ const char16_t *panel_id;
uint32_t device_type;
} info[] = {
{
@@ -23,6 +26,7 @@ static struct {
.baseboard_product = "FODM1",
.baseboard_manufacturer = "First ODM",
},
+ .panel_id = u"TST42",
.device_type = DEVICE_TYPE_DEVICETREE,
},
{
@@ -79,9 +83,21 @@ void smbios_raw_info_get_cached(RawSmbiosInfo *ret_info) {
*ret_info = current_info;
}
+static const char16_t *current_panel = NULL;
+EFI_STATUS edid_get_discovered_panel_id(char16_t **ret_panel) {
+ assert(ret_panel);
+
+ if (!current_panel)
+ return EFI_UNSUPPORTED;
+
+ *ret_panel = xstrdup16(current_panel);
+ return EFI_SUCCESS;
+}
+
TEST(chid_match) {
for (size_t i = 0; i < ELEMENTSOF(info); i++) {
current_info = info[i].smbios_info;
+ current_panel = info[i].panel_id;
const Device *dev = NULL;
/* Match and check */
ASSERT_EQ(chid_match(hwids_section_data, hwids_section_len, info[i].device_type, &dev), EFI_SUCCESS);
diff --git a/src/boot/util.h b/src/boot/util.h
index 115258d5ee..2145b6e258 100644
--- a/src/boot/util.h
+++ b/src/boot/util.h
@@ -233,7 +233,10 @@ void *find_configuration_table(const EFI_GUID *guid);
char16_t *get_extra_dir(const EFI_DEVICE_PATH *file_path);
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+# define be16toh(x) __builtin_bswap16(x)
# define be32toh(x) __builtin_bswap32(x)
+# define le16toh(x) (x)
+# define le32toh(x) (x)
#else
# error "Unexpected byte order in EFI mode?"
#endif
diff --git a/src/fundamental/chid-fundamental.c b/src/fundamental/chid-fundamental.c
index a99d0dae64..474aab7484 100644
--- a/src/fundamental/chid-fundamental.c
+++ b/src/fundamental/chid-fundamental.c
@@ -148,6 +148,21 @@ const uint32_t chid_smbios_table[CHID_TYPES_MAX] = {
(UINT32_C(1) << CHID_SMBIOS_BASEBOARD_PRODUCT),
[14] = (UINT32_C(1) << CHID_SMBIOS_MANUFACTURER),
+
+ /* Extra non-standard CHIDs */
+
+ [EXTRA_CHID_BASE + 0] = (UINT32_C(1) << CHID_SMBIOS_MANUFACTURER) |
+ (UINT32_C(1) << CHID_SMBIOS_FAMILY) |
+ (UINT32_C(1) << CHID_SMBIOS_PRODUCT_NAME)|
+ (UINT32_C(1) << CHID_EDID_PANEL),
+
+ [EXTRA_CHID_BASE + 1] = (UINT32_C(1) << CHID_SMBIOS_MANUFACTURER) |
+ (UINT32_C(1) << CHID_SMBIOS_FAMILY) |
+ (UINT32_C(1) << CHID_EDID_PANEL),
+
+ [EXTRA_CHID_BASE + 2] = (UINT32_C(1) << CHID_SMBIOS_MANUFACTURER) |
+ (UINT32_C(1) << CHID_SMBIOS_PRODUCT_SKU) |
+ (UINT32_C(1) << CHID_EDID_PANEL),
};
void chid_calculate(const char16_t *const smbios_fields[static _CHID_SMBIOS_FIELDS_MAX], EFI_GUID ret_chids[static CHID_TYPES_MAX]) {
diff --git a/src/fundamental/chid-fundamental.h b/src/fundamental/chid-fundamental.h
index 81c40e087f..5c210e006a 100644
--- a/src/fundamental/chid-fundamental.h
+++ b/src/fundamental/chid-fundamental.h
@@ -11,7 +11,9 @@
#include "efi-fundamental.h"
#include "string-util-fundamental.h"
-#define CHID_TYPES_MAX 15
+#define CHID_TYPES_MAX 18
+/* Any chids starting from EXTRA_CHID_BASE are non-standard and are subject to change and renumeration at any time */
+#define EXTRA_CHID_BASE 15
typedef enum ChidSmbiosFields {
CHID_SMBIOS_MANUFACTURER,
@@ -25,6 +27,7 @@ typedef enum ChidSmbiosFields {
CHID_SMBIOS_BIOS_MAJOR,
CHID_SMBIOS_BIOS_MINOR,
CHID_SMBIOS_ENCLOSURE_TYPE,
+ CHID_EDID_PANEL,
_CHID_SMBIOS_FIELDS_MAX,
} ChidSmbiosFields;
diff --git a/src/fundamental/edid-fundamental.c b/src/fundamental/edid-fundamental.c
new file mode 100644
index 0000000000..9657d96704
--- /dev/null
+++ b/src/fundamental/edid-fundamental.c
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "edid-fundamental.h"
+#include "efivars-fundamental.h"
+
+#define EDID_FIXED_HEADER_PATTERN "\x00\xFF\xFF\xFF\xFF\xFF\xFF"
+assert_cc(sizeof_field(EdidHeader, pattern) == sizeof(EDID_FIXED_HEADER_PATTERN));
+
+int edid_parse_blob(const void *blob, size_t blob_size, EdidHeader *ret_header) {
+ assert(ret_header);
+
+ /* EDID size is at least 128 as per the specification */
+ if (blob_size < 128)
+ return -EINVAL;
+
+ const EdidHeader *edid_header = ASSERT_PTR(blob);
+ if (memcmp(edid_header->pattern, EDID_FIXED_HEADER_PATTERN, sizeof(EDID_FIXED_HEADER_PATTERN)) != 0)
+ return -EINVAL;
+
+ *ret_header = (EdidHeader) {
+ .pattern = EDID_FIXED_HEADER_PATTERN,
+ .manufacturer_id = be16toh(edid_header->manufacturer_id),
+ .manufacturer_product_code = le16toh(edid_header->manufacturer_product_code),
+ .serial_number = le32toh(edid_header->serial_number),
+ .week_of_manufacture = edid_header->week_of_manufacture,
+ .year_of_manufacture = edid_header->year_of_manufacture,
+ .edid_version = edid_header->edid_version,
+ .edid_revision = edid_header->edid_revision,
+ };
+ return 0;
+}
+
+int edid_get_panel_id(const EdidHeader *edid_header, char16_t ret_panel[static 8]) {
+ assert(edid_header);
+ assert(ret_panel);
+
+ for (size_t i = 0; i < 3; i++) {
+ uint8_t letter = (edid_header->manufacturer_id >> (5 * i)) & 0b11111;
+ if (letter > 0b11010)
+ return -EINVAL;
+ ret_panel[2 - i] = letter + 'A' - 1;
+ }
+ ret_panel[3] = LOWERCASE_HEXDIGITS[(edid_header->manufacturer_product_code >> 12) & 0x0F];
+ ret_panel[4] = LOWERCASE_HEXDIGITS[(edid_header->manufacturer_product_code >> 8) & 0x0F];
+ ret_panel[5] = LOWERCASE_HEXDIGITS[(edid_header->manufacturer_product_code >> 4) & 0x0F];
+ ret_panel[6] = LOWERCASE_HEXDIGITS[(edid_header->manufacturer_product_code >> 0) & 0x0F];
+ ret_panel[7] = L'\0';
+ return 0;
+}
diff --git a/src/fundamental/edid-fundamental.h b/src/fundamental/edid-fundamental.h
new file mode 100644
index 0000000000..52473784d9
--- /dev/null
+++ b/src/fundamental/edid-fundamental.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#if SD_BOOT
+# include "efi-string.h"
+# include "util.h"
+#else
+# include
+# include
+# include
+# include
+#endif
+
+#include "string-util-fundamental.h"
+
+/* EDID structure, version 1.4 */
+typedef struct EdidHeader {
+ uint8_t pattern[8]; /* fixed pattern */
+ uint16_t manufacturer_id; /* big-endian 3-letter code */
+ uint16_t manufacturer_product_code; /* little-endian */
+ uint32_t serial_number; /* little-endian */
+ uint8_t week_of_manufacture; /* week or model year flag (0xFF) */
+ uint8_t year_of_manufacture; /* year or model if flag is set (0 is 1990) */
+ uint8_t edid_version; /* 0x01 for 1.3 and 1.4 */
+ uint8_t edid_revision; /* 0x03 for 1.3, 0x04 for 1.4 */
+} _packed_ EdidHeader;
+
+int edid_parse_blob(const void *blob, size_t blob_size, EdidHeader *ret_header);
+int edid_get_panel_id(const EdidHeader *edid_header, char16_t ret_panel[static 8]);
diff --git a/src/fundamental/meson.build b/src/fundamental/meson.build
index 7b72372e83..5536be6e4c 100644
--- a/src/fundamental/meson.build
+++ b/src/fundamental/meson.build
@@ -5,6 +5,7 @@ fundamental_include = include_directories('.')
fundamental_sources = files(
'bootspec-fundamental.c',
'chid-fundamental.c',
+ 'edid-fundamental.c',
'efivars-fundamental.c',
'iovec-util-fundamental.h',
'sha1-fundamental.c',
diff --git a/src/fundamental/string-util-fundamental.h b/src/fundamental/string-util-fundamental.h
index a9638b4d0e..b523b6c8bf 100644
--- a/src/fundamental/string-util-fundamental.h
+++ b/src/fundamental/string-util-fundamental.h
@@ -11,6 +11,23 @@
#include "assert-fundamental.h"
#include "macro-fundamental.h"
+/* What is interpreted as whitespace? */
+#define WHITESPACE " \t\n\r"
+#define NEWLINE "\n\r"
+#define QUOTES "\"\'"
+#define COMMENTS "#;"
+#define GLOB_CHARS "*?["
+#define DIGITS "0123456789"
+#define LOWERCASE_LETTERS "abcdefghijklmnopqrstuvwxyz"
+#define UPPERCASE_LETTERS "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+#define LETTERS LOWERCASE_LETTERS UPPERCASE_LETTERS
+#define ALPHANUMERICAL LETTERS DIGITS
+#define HEXDIGITS DIGITS "abcdefABCDEF"
+#define LOWERCASE_HEXDIGITS DIGITS "abcdef"
+#define URI_RESERVED ":/?#[]@!$&'()*+;=" /* [RFC3986] */
+#define URI_UNRESERVED ALPHANUMERICAL "-._~" /* [RFC3986] */
+#define URI_VALID URI_RESERVED URI_UNRESERVED /* [RFC3986] */
+
#if SD_BOOT
# define strlen strlen16
# define strcmp strcmp16
diff --git a/src/libsystemd/libsystemd.sym b/src/libsystemd/libsystemd.sym
index 370e3b7c92..4543fb0e60 100644
--- a/src/libsystemd/libsystemd.sym
+++ b/src/libsystemd/libsystemd.sym
@@ -1064,6 +1064,7 @@ LIBSYSTEMD_258 {
global:
sd_bus_message_dump_json;
sd_device_enumerator_add_all_parents;
+ sd_device_get_sysattr_value_with_size;
sd_json_variant_type_from_string;
sd_json_variant_type_to_string;
sd_varlink_get_current_method;
diff --git a/src/libsystemd/sd-device/sd-device.c b/src/libsystemd/sd-device/sd-device.c
index 98a10d5b1f..12763f53b5 100644
--- a/src/libsystemd/sd-device/sd-device.c
+++ b/src/libsystemd/sd-device/sd-device.c
@@ -2333,6 +2333,8 @@ void device_clear_sysattr_cache(sd_device *device) {
typedef struct SysAttrCacheEntry {
char *key;
char *value;
+ char *value_stripped;
+ size_t size;
int error;
} SysAttrCacheEntry;
@@ -2342,6 +2344,7 @@ static SysAttrCacheEntry* sysattr_cache_entry_free(SysAttrCacheEntry *p) {
free(p->key);
free(p->value);
+ free(p->value_stripped);
return mfree(p);
}
@@ -2350,7 +2353,7 @@ DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
char, path_hash_func, path_compare,
SysAttrCacheEntry, sysattr_cache_entry_free);
-static int device_cache_sysattr_value_full(sd_device *device, char *key, char *value, int error, bool ignore_uevent) {
+static int device_cache_sysattr_value_full(sd_device *device, char *key, char *value, size_t size, int error, bool ignore_uevent) {
int r;
assert(device);
@@ -2375,9 +2378,20 @@ static int device_cache_sysattr_value_full(sd_device *device, char *key, char *v
if (!entry)
return -ENOMEM;
+ _cleanup_free_ char *value_stripped = NULL;
+
+ if (value) {
+ value_stripped = memdup_suffix0(value, size);
+ if (!value_stripped)
+ return -ENOMEM;
+ delete_trailing_chars(value_stripped, NEWLINE);
+ }
+
*entry = (SysAttrCacheEntry) {
.key = key,
.value = value,
+ .value_stripped = value_stripped,
+ .size = size,
.error = error,
};
@@ -2386,14 +2400,15 @@ static int device_cache_sysattr_value_full(sd_device *device, char *key, char *v
return r;
TAKE_PTR(entry);
+ TAKE_PTR(value_stripped);
return 1; /* cached */
}
int device_cache_sysattr_value(sd_device *device, char *key, char *value, int error) {
- return device_cache_sysattr_value_full(device, key, value, error, /* ignore_uevent = */ true);
+ return device_cache_sysattr_value_full(device, key, value, strlen(value), error, /* ignore_uevent = */ true);
}
-static int device_get_cached_sysattr_value(sd_device *device, const char *key, const char **ret_value) {
+static int device_get_cached_sysattr_value(sd_device *device, const char *key, const char **ret_value, size_t *ret_size) {
SysAttrCacheEntry *entry;
assert(device);
@@ -2408,7 +2423,9 @@ static int device_get_cached_sysattr_value(sd_device *device, const char *key, c
return -entry->error;
}
if (ret_value)
- *ret_value = entry->value;
+ *ret_value = ret_size ? entry->value : entry->value_stripped;
+ if (ret_size)
+ *ret_size = entry->size;
return 0;
}
@@ -2459,16 +2476,17 @@ int device_chase(sd_device *device, const char *path, ChaseFlags flags, char **r
return 0;
}
-_public_ int sd_device_get_sysattr_value(sd_device *device, const char *sysattr, const char **ret_value) {
+_public_ int sd_device_get_sysattr_value_with_size(sd_device *device, const char *sysattr, const char **ret_value, size_t *ret_size) {
_cleanup_free_ char *resolved = NULL, *value = NULL;
_cleanup_close_ int fd = -EBADF;
+ size_t size = 0;
int r;
assert_return(device, -EINVAL);
assert_return(sysattr, -EINVAL);
/* Look for possibly already cached result. */
- r = device_get_cached_sysattr_value(device, sysattr, ret_value);
+ r = device_get_cached_sysattr_value(device, sysattr, ret_value, ret_size);
if (r != -ENOANO)
return r;
@@ -2487,6 +2505,9 @@ _public_ int sd_device_get_sysattr_value(sd_device *device, const char *sysattr,
return -ENOMEM;
r = readlink_value(prefixed, &value);
+ if (r >= 0)
+ size = strlen(value);
+
if (r != -EINVAL) /* -EINVAL means the path is not a symlink. */
goto cache_result;
}
@@ -2496,18 +2517,16 @@ _public_ int sd_device_get_sysattr_value(sd_device *device, const char *sysattr,
goto cache_result;
/* Look for cached result again with the resolved path. */
- r = device_get_cached_sysattr_value(device, resolved, ret_value);
+ r = device_get_cached_sysattr_value(device, resolved, ret_value, ret_size);
if (r != -ENOANO)
return r;
/* Read attribute value, Some attributes contain embedded '\0'. So, it is necessary to also get the
* size of the result. See issue #20025. */
- size_t size;
r = read_virtual_file_fd(fd, SIZE_MAX, &value, &size);
if (r < 0)
goto cache_result;
- delete_trailing_chars(value, NEWLINE);
r = 0;
cache_result:
@@ -2521,7 +2540,7 @@ cache_result:
return RET_GATHER(r, -ENOMEM);
}
- int k = device_cache_sysattr_value_full(device, resolved, value, -r, /* ignore_uevent = */ false);
+ int k = device_cache_sysattr_value_full(device, resolved, value, size, -r, /* ignore_uevent = */ false);
if (k < 0) {
if (r < 0)
log_device_debug_errno(device, k,
@@ -2541,13 +2560,18 @@ cache_result:
}
assert(k > 0);
- if (ret_value && r >= 0)
- *ret_value = value;
-
/* device_cache_sysattr_value_full() takes 'resolved' and 'value' on success. */
- TAKE_PTR(resolved);
+ sysattr = TAKE_PTR(resolved);
TAKE_PTR(value);
- return r;
+
+ if (r < 0)
+ return r;
+
+ return device_get_cached_sysattr_value(device, sysattr, ret_value, ret_size);
+}
+
+_public_ int sd_device_get_sysattr_value(sd_device *device, const char *sysattr, const char **ret_value) {
+ return sd_device_get_sysattr_value_with_size(device, sysattr, ret_value, NULL);
}
int device_get_sysattr_int(sd_device *device, const char *sysattr, int *ret_value) {
diff --git a/src/libsystemd/sd-device/test-sd-device.c b/src/libsystemd/sd-device/test-sd-device.c
index 9758e07330..2db4f42c74 100644
--- a/src/libsystemd/sd-device/test-sd-device.c
+++ b/src/libsystemd/sd-device/test-sd-device.c
@@ -133,6 +133,18 @@ static void test_sd_device_one(sd_device *d) {
ASSERT_ERROR(r, ENOENT);
}
+ if (streq(subsystem, "drm")) {
+ const char *edid_content;
+ size_t edid_size = 0;
+
+ r = sd_device_get_sysattr_value_with_size(d, "edid", &edid_content, &edid_size);
+ if (r < 0)
+ ASSERT_ERROR(r, ENOENT);
+
+ /* at least 128 if monitor is connected, otherwise 0 */
+ ASSERT_TRUE(edid_size == 0 || edid_size >= 128);
+ }
+
is_block = streq_ptr(subsystem, "block");
r = sd_device_get_devname(d, &devname);
diff --git a/src/systemd/sd-device.h b/src/systemd/sd-device.h
index 8f6141b3df..71d8c89983 100644
--- a/src/systemd/sd-device.h
+++ b/src/systemd/sd-device.h
@@ -108,7 +108,8 @@ int sd_device_has_tag(sd_device *device, const char *tag);
int sd_device_has_current_tag(sd_device *device, const char *tag);
int sd_device_get_property_value(sd_device *device, const char *key, const char **value);
int sd_device_get_trigger_uuid(sd_device *device, sd_id128_t *ret);
-int sd_device_get_sysattr_value(sd_device *device, const char *sysattr, const char **_value);
+int sd_device_get_sysattr_value_with_size(sd_device *device, const char *sysattr, const char **ret_value, size_t *ret_size);
+int sd_device_get_sysattr_value(sd_device *device, const char *sysattr, const char **ret_value);
int sd_device_set_sysattr_value(sd_device *device, const char *sysattr, const char *value);
int sd_device_set_sysattr_valuef(sd_device *device, const char *sysattr, const char *format, ...) _sd_printf_(3, 4);