diff --git a/include/qdl.h b/include/qdl.h index 3fb2438..3c20798 100644 --- a/include/qdl.h +++ b/include/qdl.h @@ -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; @@ -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, diff --git a/src/firehose.c b/src/firehose.c index 77ab377..77d89bd 100644 --- a/src/firehose.c +++ b/src/firehose.c @@ -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; @@ -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); @@ -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); @@ -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; @@ -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; diff --git a/src/nbdkit-qdl-plugin.c b/src/nbdkit-qdl-plugin.c index 533f436..805c910 100644 --- a/src/nbdkit-qdl-plugin.c +++ b/src/nbdkit-qdl-plugin.c @@ -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; diff --git a/src/qdl.c b/src/qdl.c index 18d7287..4aba279 100644 --- a/src/qdl.c +++ b/src/qdl.c @@ -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 [] \n", __progname); fprintf(out, " %s ramdump [--debug] [-o ] [,...]\n", __progname); fprintf(out, " %s ks [-p | --serial=T] -s ...\n", __progname); fprintf(out, " %s flash ([::specifier] | [::])\n", __progname); @@ -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, " \t\tpath where ramdump should stored\n"); fprintf(out, " \toptional glob-pattern to select which segments to ramdump\n"); + fprintf(out, " \t\tstate to leave the device in: (firehose-reset);\n"); + fprintf(out, " \t\t may be omitted when a programmer is already running\n"); fprintf(out, " \tSahara device node, e.g. /dev/mhi0_QAIC_SAHARA (ks);\n"); fprintf(out, " \tomit to use the selected device backend (ks)\n"); fprintf(out, " \t\tmap a Sahara image id to a host file, repeatable (ks)\n"); @@ -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 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) { + /* + * 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. * @@ -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"))