Improve FDT parsing compatibility

Newer versions of dtc agressively pack the string table, using *any* matching
suffix rather than whole strings. This is specification compliant, but breaks
if you use the offset as a hash key as a [premature] optimization.

Just use the string table as a flat block of memory rather than pre-parsing it.

This fixes reading the system FDT on new OpenBMC builds.
This commit is contained in:
Richard Hughes 2023-01-13 10:06:07 +00:00
parent 2b0f92506b
commit eb30233b35

View File

@ -169,7 +169,7 @@ fu_fdt_firmware_get_image_by_path(FuFdtFirmware *self, const gchar *path, GError
} }
static gboolean static gboolean
fu_fdt_firmware_parse_dt_struct(FuFdtFirmware *self, GBytes *fw, GHashTable *strtab, GError **error) fu_fdt_firmware_parse_dt_struct(FuFdtFirmware *self, GBytes *fw, GBytes *strtab, GError **error)
{ {
gsize bufsz = 0; gsize bufsz = 0;
gsize offset = 0; gsize offset = 0;
@ -258,8 +258,8 @@ fu_fdt_firmware_parse_dt_struct(FuFdtFirmware *self, GBytes *fw, GHashTable *str
if (token == FDT_PROP) { if (token == FDT_PROP) {
guint32 prop_len = 0; guint32 prop_len = 0;
guint32 prop_nameoff = 0; guint32 prop_nameoff = 0;
gpointer value = NULL;
g_autoptr(GBytes) blob = NULL; g_autoptr(GBytes) blob = NULL;
g_autoptr(GString) str = NULL;
/* sanity check */ /* sanity check */
if (firmware_current == FU_FIRMWARE(self)) { if (firmware_current == FU_FIRMWARE(self)) {
@ -288,23 +288,18 @@ fu_fdt_firmware_parse_dt_struct(FuFdtFirmware *self, GBytes *fw, GHashTable *str
offset += sizeof(FuFdtProp); offset += sizeof(FuFdtProp);
/* add property */ /* add property */
if (!g_hash_table_lookup_extended(strtab, str = fu_string_new_safe(g_bytes_get_data(strtab, NULL),
GINT_TO_POINTER(prop_nameoff), g_bytes_get_size(strtab),
NULL, prop_nameoff,
&value)) { error);
g_set_error(error, if (str == NULL) {
G_IO_ERROR, g_prefix_error(error, "invalid strtab offset 0x%x: ", prop_nameoff);
G_IO_ERROR_INVALID_DATA,
"invalid strtab offset 0x%x",
prop_nameoff);
return FALSE; return FALSE;
} }
blob = fu_bytes_new_offset(fw, offset, prop_len, error); blob = fu_bytes_new_offset(fw, offset, prop_len, error);
if (blob == NULL) if (blob == NULL)
return FALSE; return FALSE;
fu_fdt_image_set_attr(FU_FDT_IMAGE(firmware_current), fu_fdt_image_set_attr(FU_FDT_IMAGE(firmware_current), str->str, blob);
(const gchar *)value,
blob);
offset += prop_len; offset += prop_len;
continue; continue;
} }
@ -332,43 +327,6 @@ fu_fdt_firmware_parse_dt_struct(FuFdtFirmware *self, GBytes *fw, GHashTable *str
return TRUE; return TRUE;
} }
static gboolean
fu_fdt_firmware_parse_dt_strings(FuFdtFirmware *self,
GBytes *fw,
GHashTable *strtab,
GError **error)
{
gsize bufsz = 0;
const guint8 *buf = g_bytes_get_data(fw, &bufsz);
/* debug */
if (g_getenv("FU_FDT_FIRMWARE_VERBOSE") != NULL)
fu_dump_bytes(G_LOG_DOMAIN, "dt_strings", fw);
/* parse */
for (gsize offset = 0; offset < bufsz; offset++) {
g_autoptr(GString) str = NULL;
str = fu_string_new_safe(buf, bufsz, offset, error);
if (str == NULL)
return FALSE;
if (str->len == 0) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"strtab invalid @0x%x",
(guint)offset);
return FALSE;
}
if (g_getenv("FU_FDT_FIRMWARE_VERBOSE") != NULL)
g_debug("strtab: %s", str->str);
g_hash_table_insert(strtab, GINT_TO_POINTER(offset), g_strdup(str->str));
offset += str->len;
}
/* success */
return TRUE;
}
static gboolean static gboolean
fu_fdt_firmware_parse_mem_rsvmap(FuFdtFirmware *self, fu_fdt_firmware_parse_mem_rsvmap(FuFdtFirmware *self,
const guint8 *buf, const guint8 *buf,
@ -451,7 +409,6 @@ fu_fdt_firmware_parse(FuFirmware *firmware,
guint32 size_dt_strings = 0; guint32 size_dt_strings = 0;
guint32 size_dt_struct = 0; guint32 size_dt_struct = 0;
const guint8 *buf = g_bytes_get_data(fw, &bufsz); const guint8 *buf = g_bytes_get_data(fw, &bufsz);
g_autoptr(GHashTable) strtab = NULL; /* uint:str */
/* sanity check */ /* sanity check */
if (!fu_memread_uint32_safe(buf, if (!fu_memread_uint32_safe(buf,
@ -472,8 +429,7 @@ fu_fdt_firmware_parse(FuFirmware *firmware,
} }
fu_firmware_set_size(firmware, totalsize); fu_firmware_set_size(firmware, totalsize);
/* read string table */ /* read header */
strtab = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free);
if (!fu_memread_uint32_safe(buf, if (!fu_memread_uint32_safe(buf,
bufsz, bufsz,
offset + G_STRUCT_OFFSET(FuFdtHeader, off_dt_strings), offset + G_STRUCT_OFFSET(FuFdtHeader, off_dt_strings),
@ -488,17 +444,6 @@ fu_fdt_firmware_parse(FuFirmware *firmware,
G_BIG_ENDIAN, G_BIG_ENDIAN,
error)) error))
return FALSE; return FALSE;
if (size_dt_strings != 0x0) {
g_autoptr(GBytes) dt_strings = NULL;
dt_strings =
fu_bytes_new_offset(fw, offset + off_dt_strings, size_dt_strings, error);
if (dt_strings == NULL)
return FALSE;
if (!fu_fdt_firmware_parse_dt_strings(self, dt_strings, strtab, error))
return FALSE;
}
/* read out DT struct */
if (!fu_memread_uint32_safe(buf, if (!fu_memread_uint32_safe(buf,
bufsz, bufsz,
offset + G_STRUCT_OFFSET(FuFdtHeader, off_dt_struct), offset + G_STRUCT_OFFSET(FuFdtHeader, off_dt_struct),
@ -513,16 +458,6 @@ fu_fdt_firmware_parse(FuFirmware *firmware,
G_BIG_ENDIAN, G_BIG_ENDIAN,
error)) error))
return FALSE; return FALSE;
if (size_dt_struct != 0x0) {
g_autoptr(GBytes) dt_struct = NULL;
dt_struct = fu_bytes_new_offset(fw, offset + off_dt_struct, size_dt_struct, error);
if (dt_struct == NULL)
return FALSE;
if (!fu_fdt_firmware_parse_dt_struct(self, dt_struct, strtab, error))
return FALSE;
}
/* read out reserved map */
if (!fu_memread_uint32_safe(buf, if (!fu_memread_uint32_safe(buf,
bufsz, bufsz,
offset + G_STRUCT_OFFSET(FuFdtHeader, off_mem_rsvmap), offset + G_STRUCT_OFFSET(FuFdtHeader, off_mem_rsvmap),
@ -538,8 +473,6 @@ fu_fdt_firmware_parse(FuFirmware *firmware,
error)) error))
return FALSE; return FALSE;
} }
/* read in CPUID */
if (!fu_memread_uint32_safe(buf, if (!fu_memread_uint32_safe(buf,
bufsz, bufsz,
offset + G_STRUCT_OFFSET(FuFdtHeader, boot_cpuid_phys), offset + G_STRUCT_OFFSET(FuFdtHeader, boot_cpuid_phys),
@ -547,8 +480,6 @@ fu_fdt_firmware_parse(FuFirmware *firmware,
G_BIG_ENDIAN, G_BIG_ENDIAN,
error)) error))
return FALSE; return FALSE;
/* header version */
if (!fu_memread_uint32_safe(buf, if (!fu_memread_uint32_safe(buf,
bufsz, bufsz,
offset + G_STRUCT_OFFSET(FuFdtHeader, last_comp_version), offset + G_STRUCT_OFFSET(FuFdtHeader, last_comp_version),
@ -573,6 +504,23 @@ fu_fdt_firmware_parse(FuFirmware *firmware,
error)) error))
return FALSE; return FALSE;
fu_firmware_set_version_raw(firmware, version); fu_firmware_set_version_raw(firmware, version);
/* parse device tree struct */
if (size_dt_struct != 0x0 && size_dt_strings != 0x0) {
g_autoptr(GBytes) dt_strings = NULL;
g_autoptr(GBytes) dt_struct = NULL;
dt_strings =
fu_bytes_new_offset(fw, offset + off_dt_strings, size_dt_strings, error);
if (dt_strings == NULL)
return FALSE;
dt_struct = fu_bytes_new_offset(fw, offset + off_dt_struct, size_dt_struct, error);
if (dt_struct == NULL)
return FALSE;
if (!fu_fdt_firmware_parse_dt_struct(self, dt_struct, dt_strings, error))
return FALSE;
}
/* success */
return TRUE; return TRUE;
} }