libflashprog: Introduce new probing API flashprog_flash_probe_chip()

This new probing API acts about the same as the old one, except that
it accepts a `struct flashprog_chip *` handle instead of a chip name.
Under the hood, however, it uses our new bus probing directly.

We introduce a new function, flashprog_flash_prepare_context(), that
creates a flash context from a probed chip, and the bus it was found
on. This will be the single place that creates flash contexts in the
future.

Change-Id: I1a344aedf573d92f8f297e6eed8daadb511ebc10
Signed-off-by: Nico Huber <nico.h@gmx.de>
Reviewed-on: https://review.sourcearcade.org/c/flashprog/+/484
diff --git a/include/chipdrivers/probing.h b/include/chipdrivers/probing.h
index 090f2ab..2e081d1 100644
--- a/include/chipdrivers/probing.h
+++ b/include/chipdrivers/probing.h
@@ -88,4 +88,10 @@
 void flashprog_bus_probe(struct registered_master *, const struct flashchip *);
 bool flashprog_chip_match(struct registered_master *, const struct flashchip *);
 
+struct flashprog_chips;
+struct flashprog_flashctx;
+struct flashprog_programmer;
+const struct master_common *flashprog_chip_probe(const struct flashprog_programmer *, const struct flashchip *);
+int flashprog_flash_prepare_context(struct flashprog_flashctx **, const struct flashprog_programmer *, const struct master_common *, const struct flashchip *);
+
 #endif /* !__PROBING_H__ */
diff --git a/include/flash.h b/include/flash.h
index 3ad9d1f..31b4f0c 100644
--- a/include/flash.h
+++ b/include/flash.h
@@ -440,10 +440,10 @@
 	uintptr_t physical_registers;
 	chipaddr virtual_registers;
 	union {
-		struct master_common *common;
-		struct par_master *par;
-		struct spi_master *spi;
-		struct opaque_master *opaque;
+		const struct master_common *common;
+		const struct par_master *par;
+		const struct spi_master *spi;
+		const struct opaque_master *opaque;
 	} mst;
 	const struct flashprog_layout *layout;
 	struct flashprog_layout *default_layout;
diff --git a/include/libflashprog.h b/include/libflashprog.h
index 8afd8d5..6f4193b 100644
--- a/include/libflashprog.h
+++ b/include/libflashprog.h
@@ -122,6 +122,8 @@
 
 struct flashprog_flashctx;
 int flashprog_flash_probe(struct flashprog_flashctx **, const struct flashprog_programmer *, const char *chip_name);
+__attribute__((nonnull))
+int flashprog_flash_probe_chip(struct flashprog_flashctx **, const struct flashprog_programmer *, const struct flashprog_chip *);
 size_t flashprog_flash_getsize(const struct flashprog_flashctx *);
 int flashprog_flash_erase(struct flashprog_flashctx *);
 void flashprog_flash_release(struct flashprog_flashctx *);
diff --git a/libflashprog.c b/libflashprog.c
index 6cdb135..d32f9bd 100644
--- a/libflashprog.c
+++ b/libflashprog.c
@@ -4,6 +4,8 @@
  * Copyright (C) 2012, 2016 secunet Security Networks AG
  * (Written by Nico Huber <nico.huber@secunet.com> for secunet)
  *
+ * Copyright (C) 2026 Nico Huber <nico.h@gmx.de>
+ *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
@@ -25,12 +27,15 @@
 #include <string.h>
 #include <stdarg.h>
 
+#define flashprog_chip flashchip	/* For now, we use direct pointers   */
+#include "libflashprog.h"		/* to the internal struct flashchip. */
+
 #include "flash.h"
 #include "fmap.h"
 #include "programmer.h"
 #include "layout.h"
+#include "hwaccess_physmap.h"
 #include "ich_descriptors.h"
-#include "libflashprog.h"
 #include "writeprotect.h"
 
 /**
@@ -239,6 +244,101 @@
 	return ret;
 }
 
+/** @private */
+int flashprog_flash_prepare_context(struct flashprog_flashctx **flashctx,
+				    const struct flashprog_programmer *flashprog,
+				    const struct master_common *bus,
+				    const struct flashchip *chip)
+{
+	if (bus->adapt_voltage) {
+		if (bus->adapt_voltage(bus, chip->voltage.min, chip->voltage.max))
+			return 4;
+	}
+
+	struct flashprog_flashctx *const flash = calloc(1, sizeof(*flash));
+	if (!flash) {
+		msg_gerr("Out of memory!\n");
+		return 1;
+	}
+
+	flash->chip = calloc(1, sizeof(*flash->chip));
+	if (!flash->chip) {
+		msg_gerr("Out of memory!\n");
+		free(flash);
+		return 1;
+	}
+
+	*flash->chip = *chip;
+	flash->mst.common = bus; /* `mst` is a union, so we need only one pointer */
+
+	if (chip->prepare_access && chip->prepare_access(flash, PREPARE_POST_PROBE))
+		goto free_flash;
+
+	/* Fill default layout covering the whole chip. */
+	if (flashprog_layout_new(&flash->default_layout) ||
+	    flashprog_layout_add_region(flash->default_layout,
+			0, flash->chip->total_size * 1024 - 1, "complete flash") ||
+	    flashprog_layout_include_region(flash->default_layout, "complete flash"))
+		goto free_flash;
+
+	char *const tmp = flashbuses_to_text(flash->chip->bustype);
+	msg_cinfo("Using %s flash chip \"%s\" (%d kB, %s) ",
+		  flash->chip->vendor, flash->chip->name, flash->chip->total_size, tmp);
+	free(tmp);
+	if (strcmp(flashprog->driver->name, "internal") == 0 && flash->physical_memory != 0)
+		msg_cinfo("mapped at physical address 0x%0*" PRIxPTR ".\n",
+			  PRIxPTR_WIDTH, flash->physical_memory);
+	else
+		msg_cinfo("on %s.\n", flashprog->driver->name);
+
+	if (flash->chip->printlock)
+		flash->chip->printlock(flash);
+
+	if (flash->chip->finish_access)
+		flash->chip->finish_access(flash);
+
+	*flashctx = flash;
+	return 0;
+
+free_flash:
+	if (flash->chip && flash->chip->finish_access)
+		flash->chip->finish_access(flash);
+	free(flash->chip);
+	free(flash);
+	return 4;
+}
+
+/**
+ * @brief Probe for a specific flash chip.
+ *
+ * Probes for a flash chip and returns a flash context, that can be used
+ * later with flash chip and @ref flashprog-ops "image operations".
+ *
+ * @param[out] flashctx Points to a pointer of type struct flashprog_flashctx
+ *                      that will be set. *flashctx has to be freed by the
+ *                      caller with @ref flashprog_flash_release.
+ * @param[in] flashprog The flash programmer used to access the chip.
+ * @param[in] chip A reference to a chip structure that was previously
+ *                 fetched by @ref flashprog-chips "enumeration". Its
+ *                 contents will be copied, so the underlying chip
+ *                 enumeration can be released directly after the call.
+ * @return 0 on success,
+ *         4 if the chip was detected, but preparation failed,
+ *         3 if multiple chips were found,
+ *         2 if no chip was detected,
+ *         or 1 on any other error.
+ */
+int flashprog_flash_probe_chip(struct flashprog_flashctx **flashctx,
+			       const struct flashprog_programmer *flashprog,
+			       const struct flashprog_chip *chip)
+{
+	const struct master_common *const bus = flashprog_chip_probe(flashprog, chip);
+	if (!bus)
+		return 2;
+
+	return flashprog_flash_prepare_context(flashctx, flashprog, bus, chip);
+}
+
 /**
  * @brief Returns the size of the specified flash chip in bytes.
  *
diff --git a/libflashprog.map b/libflashprog.map
index 033d5f8..42ebc86 100644
--- a/libflashprog.map
+++ b/libflashprog.map
@@ -18,6 +18,7 @@
     flashprog_flash_erase;
     flashprog_flash_getsize;
     flashprog_flash_probe;
+    flashprog_flash_probe_chip;
     flashprog_flash_release;
     flashprog_image_read;
     flashprog_image_verify;
diff --git a/libflashprog/chips.c b/libflashprog/chips.c
index 919d0eb..ac5cdc2 100644
--- a/libflashprog/chips.c
+++ b/libflashprog/chips.c
@@ -90,6 +90,39 @@
 	return 0;
 }
 
+/** @private */
+const struct master_common *flashprog_chip_probe(
+		const struct flashprog_programmer *flashprog,
+		const struct flashchip *chip)
+{
+	if (!chip_from_db(chip))
+		/* If not in the DB, it must be a probed `chip_entry`. */
+		return ((const struct chip_entry *)chip)->bus;
+
+	int i;
+	for (i = 0; i < registered_master_count; ++i) {
+		struct registered_master *const bus = &registered_masters[i];
+
+		if (!(bus->buses_supported & chip->bustype))
+			continue;
+
+		/* If it can't be probed, assume it's there. */
+		if (chip->id.type == ID_NONE)
+			return &bus->common;
+
+		/* We probe for a specific chip, so we can adapt the voltage early. */
+		if (bus->common.adapt_voltage &&
+		    bus->common.adapt_voltage(&bus->common, chip->voltage.min, chip->voltage.max))
+			return NULL;
+
+		flashprog_bus_probe(bus, chip);
+		if (flashprog_chip_match(bus, chip))
+			return &bus->common;
+	}
+
+	return NULL;
+}
+
 /**
  * @brief Probe for flash chips.
  *