Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion include/qdl.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ enum qdl_skipblock_mode {
QDL_SKIPBLOCK_SHA256,
};

enum qdl_reset_mode {
QDL_RESET_NORMAL,
QDL_RESET_TO_EDL,
/* Just powers the device off. */
QDL_RESET_POWER_OFF,
};

struct qdl_device {
enum QDL_DEVICE_TYPE dev_type;
int fd;
Expand Down Expand Up @@ -191,7 +198,8 @@ int firehose_read_buf(struct qdl_device *qdl, struct firehose_op *read_op, void

/* Block-level entry points used by the nbdkit plugin */
int firehose_open(struct qdl_device *qdl, enum qdl_storage_type storage);
int firehose_reset(struct qdl_device *qdl);
int firehose_reset(struct qdl_device *qdl, enum qdl_reset_mode mode);
void firehose_drain(struct qdl_device *qdl, unsigned int timeout_ms);
int firehose_getsize(struct qdl_device *qdl, int lun, size_t *sector_size,
size_t *num_sectors);
ssize_t firehose_pread(struct qdl_device *qdl, int lun, size_t sector_offset,
Expand Down
40 changes: 35 additions & 5 deletions src/firehose.c
Original file line number Diff line number Diff line change
Expand Up @@ -1551,8 +1551,38 @@ static int firehose_set_bootable(struct qdl_device *qdl, int part)
return 0;
}

int firehose_reset(struct qdl_device *qdl)
/*
* Consume whatever the programmer has queued up, for callers that send a
* command as the first thing after the programmer starts. A programmer that
* is still printing its startup banner does not read its input endpoint, so
* the first write times out and is only retried a second later, once
* firehose_write() has drained the backlog itself.
*
* firehose_detect_and_configure() gets this for free from the configure
* exchange; callers that send nothing else need to drain explicitly.
*/
void firehose_drain(struct qdl_device *qdl, unsigned int timeout_ms)
{
firehose_read(qdl, timeout_ms, firehose_generic_parser, NULL);
}

static const char *firehose_reset_mode_str(enum qdl_reset_mode mode)
{
switch (mode) {
case QDL_RESET_NORMAL:
return "reset";
case QDL_RESET_TO_EDL:
return "reset_to_edl";
case QDL_RESET_POWER_OFF:
return "off";
}

return "reset";
}

int firehose_reset(struct qdl_device *qdl, enum qdl_reset_mode mode)
{
const char *mode_name = firehose_reset_mode_str(mode);
xmlNode *root;
xmlNode *node;
xmlDoc *doc;
Expand All @@ -1563,7 +1593,7 @@ int firehose_reset(struct qdl_device *qdl)
xmlDocSetRootElement(doc, root);

node = xmlNewChild(root, NULL, (xmlChar *)"power", NULL);
xml_setpropf(node, "value", "reset");
xml_setpropf(node, "value", mode_name);
xml_setpropf(node, "DelayInSeconds", "10"); // Add a delay to prevent reboot fail

ret = firehose_write(qdl, doc);
Expand All @@ -1573,7 +1603,7 @@ int firehose_reset(struct qdl_device *qdl)

ret = firehose_read(qdl, 5000, firehose_generic_parser, NULL);
if (ret < 0)
ux_err("failed to request device reset\n");
ux_err("failed to request device reset (mode=%s)\n", mode_name);
/* drain any remaining log messages for reset */
else
firehose_read(qdl, 1000, firehose_generic_parser, NULL);
Expand Down Expand Up @@ -1679,7 +1709,7 @@ int firehose_provision(struct qdl_device *qdl, struct ufs_provisioning *ufs, boo
ux_info("UFS provisioning failed\n");

if (!skip_reset)
firehose_reset(qdl);
firehose_reset(qdl, QDL_RESET_NORMAL);

return ret;

Expand Down Expand Up @@ -1785,7 +1815,7 @@ static int firehose_execute_ops(struct qdl_device *qdl, struct list_head *ops)
firehose_set_bootable(qdl, op->partition);
break;
case FIREHOSE_OP_RESET:
ret = firehose_reset(qdl);
ret = firehose_reset(qdl, QDL_RESET_NORMAL);
if (ret < 0)
return ret;
break;
Expand Down
2 changes: 1 addition & 1 deletion src/nbdkit-qdl-plugin.c
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ static void qdl_plugin_unload(void)
if (!dev)
return;

firehose_reset(dev);
firehose_reset(dev, QDL_RESET_NORMAL);
qdl_close(dev);
qdl_deinit(dev);
dev = NULL;
Expand Down
101 changes: 100 additions & 1 deletion src/qdl.c
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,7 @@ static void print_usage(FILE *out)
fprintf(out, " %s list\n", __progname);
fprintf(out, " %s chipinfo\n", __progname);
fprintf(out, " %s reset\n", __progname);
fprintf(out, " %s firehose-reset [<prog.mbn>] <reset-mode>\n", __progname);
fprintf(out, " %s ramdump [--debug] [-o <ramdump-path>] [<segment-filter>,...]\n", __progname);
fprintf(out, " %s ks [-p <sahara-dev-node> | --serial=T] -s <id:file-path>...\n", __progname);
fprintf(out, " %s flash (<flashmap>[::specifier] | <contents>[::<specifier>])\n", __progname);
Expand Down Expand Up @@ -507,6 +508,8 @@ static void print_usage(FILE *out)
fprintf(out, " \t\tnumber S, the number of sectors to follow L, or partition by \"name\"\n");
fprintf(out, " <ramdump-path>\t\tpath where ramdump should stored\n");
fprintf(out, " <segment-filter>\toptional glob-pattern to select which segments to ramdump\n");
fprintf(out, " <reset-mode>\t\tstate to leave the device in: <edl|off|system> (firehose-reset);\n");
fprintf(out, " \t\t<prog.mbn> may be omitted when a programmer is already running\n");
fprintf(out, " <sahara-dev-node>\tSahara device node, e.g. /dev/mhi0_QAIC_SAHARA (ks);\n");
fprintf(out, " \tomit to use the selected device backend (ks)\n");
fprintf(out, " <id:file-path>\t\tmap a Sahara image id to a host file, repeatable (ks)\n");
Expand Down Expand Up @@ -755,12 +758,106 @@ static int qdl_reset_run(struct qdl_device *qdl)
ret = sahara_device_reset(qdl);
if (ret == 1) {
ux_info("falling back to Firehose reset\n");
ret = firehose_reset(qdl);
ret = firehose_reset(qdl, QDL_RESET_NORMAL);
}

return ret;
}

static int decode_reset_mode(const char *name, enum qdl_reset_mode *out)
{
if (!strcmp(name, "system")) {
*out = QDL_RESET_NORMAL;
return 0;
}

if (!strcmp(name, "edl")) {
*out = QDL_RESET_TO_EDL;
return 0;
}

if (!strcmp(name, "off")) {
*out = QDL_RESET_POWER_OFF;
return 0;
}

return -1;
}

/*
* Firehose reset ("firehose-reset") subcommand.
*
* Sends a single Firehose <power> command, which the "reset" subcommand
* cannot express: that one resets the device from EDL over Sahara and only
* falls back to Firehose to perform a plain reset. Rebooting straight back
* into EDL, or powering the device off, is only available through Firehose.
*
* The command therefore needs a programmer running on the target. A device
* sitting in EDL has none, so the programmer is taken as an optional leading
* argument and uploaded exactly like a flashing run does; sahara_run() skips
* the upload by itself when the target answers in Firehose, so passing it is
* harmless when a previous --skip-reset run left one behind, and it can be
* omitted entirely when the programmer is known to be up.
*/
static int qdl_firehose_reset(int argc, char **argv)
{
struct sahara_image sahara_images[MAPPING_SZ] = {};
enum QDL_DEVICE_TYPE qdl_dev_type = QDL_DEVICE_AUTO;
enum qdl_reset_mode mode;
struct qdl_device *qdl;
char *programmer = NULL;
char *serial = NULL;
int ret;
int opt;

while ((opt = getopt_long(argc, argv, "dvS:h", qdl_common_options, NULL)) != -1) {
ret = qdl_common_opt(opt, &serial, &qdl_dev_type);
if (ret == QDL_OPT_EXIT_OK)
return 0;
if (ret == QDL_OPT_EXIT_FAIL)
return 1;
}

if (argc - optind == 2)
programmer = argv[optind++];

if (optind + 1 != argc) {
print_usage(stderr);
return 1;
}

if (decode_reset_mode(argv[optind], &mode) < 0)
errx(1, "unknown reset mode \"%s\" (expected edl|off|system)", argv[optind]);

ux_init();

if (programmer && decode_programmer(programmer, sahara_images) < 0)
return 1;

qdl = qdl_session_open(qdl_dev_type, serial);
if (!qdl) {
sahara_images_free(sahara_images, MAPPING_SZ);
return 1;
}

ret = sahara_run(qdl, sahara_images, NULL, NULL) < 0 ? 1 : 0;
if (!ret) {
/*
* <power> is the only command sent here, so nothing else
* consumes the banner of a programmer that just started.
*/
if (programmer)
firehose_drain(qdl, 1000);

ret = firehose_reset(qdl, mode) < 0 ? 1 : 0;
}

qdl_session_close(qdl);
sahara_images_free(sahara_images, MAPPING_SZ);

return ret;
}

/*
* Sahara kickstart ("ks") subcommand.
*
Expand Down Expand Up @@ -1570,6 +1667,8 @@ int main(int argc, char **argv)
return qdl_sahara_cmd(argc - i, argv + i, sahara_chipinfo);
if (!strcmp(argv[i], "reset"))
return qdl_sahara_cmd(argc - i, argv + i, qdl_reset_run);
if (!strcmp(argv[i], "firehose-reset"))
return qdl_firehose_reset(argc - i, argv + i);
if (!strcmp(argv[i], "ks"))
return qdl_ks(argc - i, argv + i);
if (!strcmp(argv[i], "create-zip"))
Expand Down
Loading