sahara: add a device reset helper

The Sahara protocol has carried a reset command since version 1.0 and
qdl already uses it internally on error paths and after ramdump
collection, but there is no way to just reset a device sitting in EDL
or crash mode without uploading a Firehose programmer first.

Add sahara_device_reset(), which acknowledges the device's HELLO with
whatever mode the device asked for - fresh EDL announces image
transfer, a crashed device announces memory debug - and then issues
the reset command. When the first exchange reveals a running Firehose
programmer instead of Sahara, report that to the caller so it can
fall back to a Firehose power reset. The missing-HELLO case is
handled like sahara_chipinfo() does, by prodding the device with an
unsolicited HELLO response, since the QUD driver eats the HELLO on
many targets.

Signed-off-by: Igor Opaniuk <igor.opaniuk@oss.qualcomm.com>
This commit is contained in:
Igor Opaniuk committed 2026-08-19 11:27:09 +02:00
1 parent 3a77f13339
commit 6354672439
2 files changed
+151

No files matched your search

+1
View File
@@ -200,6 +200,7 @@ int sahara_run(struct qdl_device *qdl, const struct sahara_image *images,
const char *ramdump_path,
const char *ramdump_filter);
int sahara_chipinfo(struct qdl_device *qdl);
int sahara_device_reset(struct qdl_device *qdl);
int load_sahara_image(struct qdl_zip *zip, const char *filename, struct sahara_image *image);
void sahara_images_free(struct sahara_image *images, size_t count);
void print_hex_dump(const char *prefix, const void *buf, size_t len);
+150
View File
@@ -109,6 +109,8 @@ typedef struct {
#define DEBUG_BLOCK_SIZE (512u * 1024u)
#define SAHARA_CMD_TIMEOUT_MS 1000
#define SAHARA_RESET_RETRIES 5
#define SAHARA_RESET_VERIFY_TIMEOUT_MS 3000
struct sahara_pkt {
uint32_t cmd;
@@ -1131,6 +1133,154 @@ int sahara_chipinfo(struct qdl_device *qdl)
return ret;
}
/*
* Reset the device over the Sahara protocol, from whichever Sahara state
* it booted into: fresh EDL (image transfer pending) or crash mode
* (memory debug). The HELLO is acknowledged with the mode the device
* asked for, so no state transition is forced before the reset command.
*
* Return: 0 when the reset was issued over Sahara, 1 when a Firehose
* programmer is already running (the caller should reset via Firehose
* instead), -1 on failure.
*/
int sahara_device_reset(struct qdl_device *qdl)
{
struct sahara_pkt *pkt;
char buf[4096];
int n;
int i;
if (qdl->dev_type == QDL_DEVICE_SIM)
return 0;
n = qdl_read(qdl, buf, sizeof(buf), SAHARA_CMD_TIMEOUT_MS);
/* A Firehose programmer is already running; there is no Sahara here */
if (n >= 5 && !memcmp(buf, "<?xml", 5))
goto firehose;
if (n < 0) {
if (n != -ETIMEDOUT) {
ux_err("failed to read Sahara HELLO from device\n");
return -1;
}
/*
* No HELLO: the QUD driver eats it on many targets, and a
* previous session may have consumed it. Prod the device with
* an unsolicited HELLO response: Sahara accepts it, while a
* running Firehose programmer answers the non-XML input with
* an error response, identifying itself for the fallback.
*/
sahara_send_hello_resp(qdl, SAHARA_VERSION,
SAHARA_MODE_IMAGE_TX_PENDING);
n = qdl_read(qdl, buf, sizeof(buf), SAHARA_CMD_TIMEOUT_MS);
if (n >= 5 && !memcmp(buf, "<?xml", 5))
goto firehose;
} else {
pkt = (struct sahara_pkt *)buf;
if ((uint32_t)n == pkt->length && pkt->cmd == SAHARA_HELLO_CMD) {
ux_debug("Sahara HELLO version %u mode %u\n",
pkt->hello_req.version, pkt->hello_req.mode);
/*
* The target services a reset only in states where it
* is valid: in image-transfer-pending state the PBL
* rejects it with an end-of-image error, while command
* mode accepts it. Crash-mode devices announce memory
* debug and take the reset directly, as the ramdump
* teardown has always relied on.
*/
if (pkt->hello_req.mode == SAHARA_MODE_MEMORY_DEBUG) {
sahara_send_hello_resp(qdl, pkt->hello_req.version,
SAHARA_MODE_MEMORY_DEBUG);
} else {
sahara_send_hello_resp(qdl, pkt->hello_req.version,
SAHARA_MODE_COMMAND);
n = qdl_read(qdl, buf, sizeof(buf),
SAHARA_CMD_TIMEOUT_MS);
if (n < 0 || pkt->cmd != SAHARA_CMD_READY_CMD)
ux_debug("no CMD_READY after command mode request\n");
}
}
/* On any other packet, proceed straight to the reset */
}
/*
* The reset response is the target's acknowledgment that it accepted
* the request, sent just before it resets; the protocol directs the
* host to resend the reset until the response arrives. The resend
* matters in practice: after the HELLO exchange the target
* immediately issues its first READ_DATA, which races the reset
* request, so the first answer read back is often not the response.
*/
for (i = 0; i < SAHARA_RESET_RETRIES; i++) {
sahara_send_reset(qdl);
n = qdl_read(qdl, buf, sizeof(buf), SAHARA_CMD_TIMEOUT_MS);
if (n < 0) {
/*
* A transport error other than a timeout means the
* device already dropped off the bus resetting.
*/
if (n != -ETIMEDOUT)
break;
continue;
}
if ((size_t)n < sizeof(uint32_t) * 2)
continue;
pkt = (struct sahara_pkt *)buf;
if (pkt->cmd == SAHARA_RESET_RESP_CMD) {
ux_debug("Sahara reset acknowledged\n");
break;
}
/*
* An invalid reset request is rejected with an end-of-image
* transfer packet carrying the error status.
*/
if (pkt->cmd == SAHARA_END_OF_IMAGE_CMD) {
ux_err("device rejected the reset request (end-of-image status %u)\n",
pkt->eoi.status);
return -1;
}
/* Some other packet crossed the reset request; resend */
}
if (i == SAHARA_RESET_RETRIES) {
ux_err("no reset response from device\n");
return -1;
}
/*
* The response only acknowledges the request; verify that the device
* actually went down. A PBL can tear down its Sahara session on the
* reset request without resetting the SoC (observed on QRB2210),
* which leaves the transport alive but the session dead. Only the
* transport dropping - the handle dying with the re-enumeration -
* proves the reset happened.
*/
n = qdl_read(qdl, buf, sizeof(buf), SAHARA_RESET_VERIFY_TIMEOUT_MS);
if (n >= 0 || n == -ETIMEDOUT) {
ux_err("device acknowledged the reset but did not go down; power cycle it or reset through a Firehose programmer: qdl <programmer> reset\n");
return -1;
}
ux_info("device reset via Sahara\n");
return 0;
firehose:
ux_info("Firehose programmer is running\n");
return 1;
}
int sahara_run(struct qdl_device *qdl, const struct sahara_image *images,
const char *ramdump_path,
const char *ramdump_filter)