Introduce a robust semantic matcher for Open Firmware (OFW) device paths to
replace the previous prefix-based string matching logic. This change enables
SeaBIOS to accurately correlate bootorder entries with concrete hardware
devices by understanding the topological structure and addressing scheme of
IEEE 1275-compliant paths.
Key changes:
- Implement parse_hex to handle standard and relaxed unit addresses (e.g.,
treating "@1,0" and "@1" as equivalent for function 0).
- Implement fw_path_match to perform node-by-node comparisons, supporting
wildcards ('*') for both node names and unit addresses.
- Incorporate a generic name fallback mechanism where nodes match based solely
on unit addresses if names differ, essential for Broadcom/standard PCI
addressing compatibility.
- Update find_prio and boot_lchs_find to utilize the new semantic matcher,
ensuring consistent behavior across boot priority and disk geometry selection.
These improvements ensure that boot ordering remains predictable and flexible
even when host-side tools use generic labels or when controller driver names
vary within the guest.
Signed-off-by: Glenn Griffin <glenng(a)google.com>
---
src/boot.c | 142 +++++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 126 insertions(+), 16 deletions(-)
diff --git a/src/boot.c b/src/boot.c
index 5c37dafd..d60950c1 100644
--- a/src/boot.c
+++ b/src/boot.c
@@ -29,24 +29,134 @@
// See if 'str' starts with 'glob' - if glob contains an '*' character
// it will match any number of characters in str that aren't a '/' or
// the next glob character.
-static char *
-glob_prefix(const char *glob, const char *str)
+// Parse a hex number from a string.
+static int
+parse_hex(const char **ptr)
{
- for (;;) {
- if (!*glob && (!*str || *str == '/'))
- return (char*)str;
- if (*glob == '*') {
- if (!*str || *str == '/' || *str == glob[1])
- glob++;
- else
- str++;
+ const char *p = *ptr;
+ int val = 0;
+ while (1) {
+ char c = *p;
+ if (c >= '0' && c <= '9')
+ val = (val << 4) | (c - '0');
+ else if (c >= 'a' && c <= 'f')
+ val = (val << 4) | (c - 'a' + 10);
+ else if (c >= 'A' && c <= 'F')
+ val = (val << 4) | (c - 'A' + 10);
+ else
+ break;
+ p++;
+ }
+ *ptr = p;
+ return val;
+}
+
+// Perform semantic matching of an Open Firmware path glob against a
concrete path.
+// Supports:
+// - '*' wildcard for node names (e.g., /pci@* matches /pci@i0cf8)
+// - Generic fallback: if addresses match, node name is ignored (e.g.
/disk@3,0 matches /nvme@3)
+// - Relaxed unit address matching (e.g., @3,0 matches @3)
+static int
+fw_path_match(const char *glob, const char *path)
+{
+ while (*glob && *path) {
+ // match separators
+ if (*glob == '/') {
+ if (*path != '/')
+ return 0;
+ glob++;
+ path++;
continue;
}
- if (*glob != *str)
- return NULL;
- glob++;
- str++;
+
+ // parse node name
+ const char *glob_end = strchr(glob, '@');
+ const char *path_end = strchr(path, '@');
+ // Handle cases where @ is missing (malformed or root?) or
next char is /
+ const char *glob_slash = strchr(glob, '/');
+ const char *path_slash = strchr(path, '/');
+
+ if (!glob_end || (glob_slash && glob_slash < glob_end))
+ glob_end = glob_slash ? glob_slash : glob + strlen(glob);
+ if (!path_end || (path_slash && path_slash < path_end))
+ path_end = path_slash ? path_slash : path + strlen(path);
+
+ int glob_len = glob_end - glob;
+ int path_len = path_end - path;
+
+ // Check Name Match
+ int name_match = 0;
+ if (glob[0] == '*')
+ name_match = 1; // Wildcard matches anything
+ else if (glob_len == path_len && memcmp(glob, path, glob_len) == 0)
+ name_match = 1; // Exact match
+
+ glob = glob_end;
+ path = path_end;
+
+ // Check Address Match
+ int glob_has_addr = (*glob == '@');
+ int path_has_addr = (*path == '@');
+
+ if (glob_has_addr != path_has_addr)
+ return 0; // Structure mismatch
+
+ if (glob_has_addr) {
+ glob++;
+ path++;
+
+ // Check for wildcard in address
+ if (glob[0] == '*') {
+ // Wildcard address matches any path address
+ // But we still require structure match (both had @)
+ // We advance pointers past address
+ // Consume glob address until / or end
+ while (*glob && *glob != '/') glob++;
+ // Consume path address until / or end
+ while (*path && *path != '/') path++;
+
+ // If name matched (or was *) AND address is *, then match.
+ if (!name_match) return 0;
+ continue;
+ }
+
+ // Parse Address
+ // Format: N[,M]
+ int glob_val1 = parse_hex(&glob);
+ int path_val1 = parse_hex(&path);
+
+ if (glob_val1 != path_val1)
+ return 0;
+
+ int glob_val2 = 0;
+ int path_val2 = 0;
+
+ if (*glob == ',') {
+ glob++;
+ glob_val2 = parse_hex(&glob);
+ }
+ if (*path == ',') {
+ path++;
+ path_val2 = parse_hex(&path);
+ }
+
+ // Semantic Address Match: Treat missing secondary address as 0
+ // If addresses match, we consider the node a match even
if names differed!
+ // (Generic PCI fallback logic: /ethernet@3 == /scsi@3 if
address is 3)
+ if (glob_val2 != path_val2)
+ return 0;
+
+ // If we are here, addresses matched.
+ // We ignore 'name_match' result (allow mismatch/alias)
+ } else {
+ // No addresses. Strict name match required (or wildcard)
+ if (!name_match)
+ return 0;
+ }
}
+
+ // Both must end or have matched fully
+ return (*glob == '\0' && *path == '\0');
}
#define FW_PCI_DOMAIN "/pci@i0cf8"
@@ -177,7 +287,7 @@ boot_lchs_find(const char *glob)
dprintf(1, "Searching bios-geometry for: %s\n", glob);
int i;
for (i = 0; i < BiosGeometryCount; i++)
- if (glob_prefix(glob, BiosGeometry[i].name))
+ if (fw_path_match(glob, BiosGeometry[i].name))
return &BiosGeometry[i];
return NULL;
}
@@ -290,7 +400,7 @@ find_prio(const char *glob)
dprintf(1, "Searching bootorder for: %s\n", glob);
int i;
for (i = 0; i < BootorderCount; i++)
- if (glob_prefix(glob, Bootorder[i]))
+ if (fw_path_match(glob, Bootorder[i]))
return i+1;
return -1;
}
--
2.52.0.457.g6b5491de43-goog