Skip to content

Commit 539979b

Browse files
ardbiesheuvelwilldeacon
authored andcommitted
ACPI/IORT: work around num_ids ambiguity
The ID mapping table structure of the IORT table describes the size of a range using a num_ids field carrying the number of IDs in the region minus one. This has been misinterpreted in the past in the parsing code, and firmware is known to have shipped where this results in an ambiguity, where regions that should be adjacent have an overlap of one value. So let's work around this by detecting this case specifically: when resolving an ID translation, allow one that matches right at the end of a multi-ID region to be superseded by a subsequent one. To prevent potential regressions on broken firmware that happened to work before, only take the subsequent match into account if it occurs at the start of a mapping region. Signed-off-by: Ard Biesheuvel <[email protected]> Reviewed-by: Lorenzo Pieralisi <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Will Deacon <[email protected]>
1 parent 6d3b29d commit 539979b

File tree

1 file changed

+34
-6
lines changed

1 file changed

+34
-6
lines changed

drivers/acpi/arm64/iort.c

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ static acpi_status iort_match_node_callback(struct acpi_iort_node *node,
300300
}
301301

302302
static int iort_id_map(struct acpi_iort_id_mapping *map, u8 type, u32 rid_in,
303-
u32 *rid_out)
303+
u32 *rid_out, bool check_overlap)
304304
{
305305
/* Single mapping does not care for input id */
306306
if (map->flags & ACPI_IORT_ID_SINGLE_MAPPING) {
@@ -316,10 +316,34 @@ static int iort_id_map(struct acpi_iort_id_mapping *map, u8 type, u32 rid_in,
316316
}
317317

318318
if (rid_in < map->input_base ||
319-
(rid_in >= map->input_base + map->id_count))
319+
(rid_in > map->input_base + map->id_count))
320320
return -ENXIO;
321321

322+
if (check_overlap) {
323+
/*
324+
* We already found a mapping for this input ID at the end of
325+
* another region. If it coincides with the start of this
326+
* region, we assume the prior match was due to the off-by-1
327+
* issue mentioned below, and allow it to be superseded.
328+
* Otherwise, things are *really* broken, and we just disregard
329+
* duplicate matches entirely to retain compatibility.
330+
*/
331+
pr_err(FW_BUG "[map %p] conflicting mapping for input ID 0x%x\n",
332+
map, rid_in);
333+
if (rid_in != map->input_base)
334+
return -ENXIO;
335+
}
336+
322337
*rid_out = map->output_base + (rid_in - map->input_base);
338+
339+
/*
340+
* Due to confusion regarding the meaning of the id_count field (which
341+
* carries the number of IDs *minus 1*), we may have to disregard this
342+
* match if it is at the end of the range, and overlaps with the start
343+
* of another one.
344+
*/
345+
if (map->id_count > 0 && rid_in == map->input_base + map->id_count)
346+
return -EAGAIN;
323347
return 0;
324348
}
325349

@@ -404,7 +428,8 @@ static struct acpi_iort_node *iort_node_map_id(struct acpi_iort_node *node,
404428
/* Parse the ID mapping tree to find specified node type */
405429
while (node) {
406430
struct acpi_iort_id_mapping *map;
407-
int i, index;
431+
int i, index, rc = 0;
432+
u32 out_ref = 0, map_id = id;
408433

409434
if (IORT_TYPE_MASK(node->type) & type_mask) {
410435
if (id_out)
@@ -438,15 +463,18 @@ static struct acpi_iort_node *iort_node_map_id(struct acpi_iort_node *node,
438463
if (i == index)
439464
continue;
440465

441-
if (!iort_id_map(map, node->type, id, &id))
466+
rc = iort_id_map(map, node->type, map_id, &id, out_ref);
467+
if (!rc)
442468
break;
469+
if (rc == -EAGAIN)
470+
out_ref = map->output_reference;
443471
}
444472

445-
if (i == node->mapping_count)
473+
if (i == node->mapping_count && !out_ref)
446474
goto fail_map;
447475

448476
node = ACPI_ADD_PTR(struct acpi_iort_node, iort_table,
449-
map->output_reference);
477+
rc ? out_ref : map->output_reference);
450478
}
451479

452480
fail_map:

0 commit comments

Comments
 (0)