| // Copyright (c) 2012 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /* Toad Utility -- The USB micro dev companion. |
| * |
| * If you just want an EC or AP serial console, you do not need this tool. |
| * The ftdi_sio module will expose the serial device which you can use normally. |
| * Press the button on the device to switch between EC and AP consoles. |
| * |
| * This utility lets you control the additional capabilites of Toad. |
| * You can: |
| * > Switch the console endpoint (EC or AP) via software and/or monitor it |
| * > Enable the EC boot mode and upload code (unless the RO screw is present) |
| * > Toggle the VBUS to the DUT to simulate connect/disconnect |
| * > Initialize Toad when it's fresh out of the factory |
| * |
| * Building is easy, just run "make", and optionally "sudo make install". |
| * i386, x86_64, and 32-bit ARM architectures should all work with this tool. |
| */ |
| |
| static const char *USAGE = |
| "command [options]\n" |
| "\n" |
| "Options:\n" |
| " -s SN - Specifies the Toad you want to communicate with, by\n" |
| " --serialname=SN serial. If unspecified, the tool will use the first\n" |
| " Toad found. If SN is left empty (long form only) or\n" |
| " set to \"all\", all connected Toads will be used in\n" |
| " batch operations.\n" |
| " -f - Forces a command. Specify multiple times to apply\n" |
| " more force. Currently only used with initialize.\n" |
| "\n" |
| "Commands:\n" |
| " list - Prints out the serial.\n" |
| " initialize - Initializes the Toad EEPROM.\n" |
| " Use -f to re-program programmed Toad devices.\n" |
| " Use -f twice to re-program devices that are not\n" |
| " recognized as a Toad. (Use with -s; dangerous.)\n" |
| " status - Prints the full state: VBUS, EC/AP, boot states.\n" |
| " setvbus STATE - Sets or toggles VBUS, where STATE can be one of\n" |
| " on, off, or toggle.\n" |
| " setecap STATE - Sets the EC/AP target mode, where STATE can be one\n" |
| " of ec, ap, or toggle.\n" |
| " setboot STATE - Sets the boot override mode, where STATE can be one\n" |
| " of on, off, or toggle.\n" |
| " getmode - Gets the current effective mode: off, ec, ap, boot.\n" |
| " setmode MODE - Sets the effective mode, where MODE can be one of\n" |
| " off, ec, ap, or boot.\n" |
| " boot [FILE] - Boots the EC using the specified binary.\n" |
| " Uses stdin if FILE is left unspecified.\n"; |
| static const char *CONSOLE = |
| " console - Opens a console to the DUT without switching modes.\n" |
| " ec - Switches to EC mode and opens a console.\n" |
| " ap - Switches to AP mode and opens a console.\n"; |
| static const char *ESCAPES = |
| "Console escapes: prefix with ^X (Ctrl-X)\n" |
| " h, H, ^H (Ctrl-H) - Print out supported escapes (this message).\n" |
| " ^X (Ctrl-X) - Send a literal ^X\n" |
| " ^C (Ctrl-C) - Close the console. Returns a failure exit code.\n" |
| " ^D (Ctrl-D) - Close the console.\n" |
| " ^Z (Ctrl-Z) - Suspend the console into the background.\n" |
| " e, E, ^E (Ctrl-E) - Switch to monitoring the EC output.\n" |
| " a, A, ^A (Ctrl-A) - Switch to monitoring the AP output.\n" |
| " p, P, ^P (Ctrl-P) - Switch to monitoring the AP output.\n"; |
| |
| |
| #include <errno.h> |
| #include <getopt.h> |
| #include <poll.h> |
| #include <signal.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <termios.h> |
| #include <unistd.h> |
| |
| #include "toad.h" |
| |
| |
| /* Options for passing into getopt_long */ |
| static const char *SHORT_OPTS = "s:f"; |
| static const struct option LONG_OPTS[] = { |
| { "serialname", required_argument, NULL, 's' }, |
| { NULL, 0, NULL, 0 } // end-of-list sentinel |
| }; |
| |
| /* Commands and their dispatch functions */ |
| static const struct cmd COMMAND_LIST[] = { |
| { "list", cmdList }, |
| { "init", cmdInitialize }, |
| { "initialize", cmdInitialize }, |
| { "st", cmdStatus }, |
| { "status", cmdStatus }, |
| { "vbus", cmdSetVbus }, |
| { "setvbus", cmdSetVbus }, |
| { "ecap", cmdSetEcAp }, |
| { "setecap", cmdSetEcAp }, |
| { "setboot", cmdSetBoot }, |
| { "getmode", cmdGetMode }, |
| { "setmode", cmdSetMode }, |
| { "mode", cmdMode }, |
| { "boot", cmdBoot }, |
| #if ENABLE_CONSOLE |
| { "console", cmdConsole }, |
| { "ec", cmdEc }, |
| { "ap", cmdAp }, |
| #endif |
| { NULL, NULL } // end-of-list sentinel |
| }; |
| |
| |
| /* Config data at start of FT230X EEPROM for programming */ |
| static const unsigned char TOAD_EEPROM_00[] = { |
| /* 00 */ 0x00, 0x00, // First two bytes must be zero |
| /* 02 */ TOAD_VID & 0xFF, TOAD_VID >> 8, |
| /* 04 */ TOAD_PID & 0xFF, TOAD_PID >> 8, |
| /* 06 */ 0x00, 0x10, // bcdDevice |
| /* 08 */ 0x80, // bus powered |
| /* 09 */ 0xFA, // 500 mA (2mA increments) |
| /* 0A */ 0x08, // Max packet size |
| /* 0B */ 0x00, 0x00, 0x00, // bDeviceClass/bDeviceSubClass/bDeviceProtocol |
| /* 0E */ TOAD_EEPROM_MANUFACTURER_START, sizeof(TOAD_MANUFACTURER) * 2, |
| /* 10 */ TOAD_EEPROM_DESC_START, sizeof(TOAD_DESC) * 2, |
| /* 12 */ TOAD_EEPROM_SERIAL_START, 0, // we don't know the size yet |
| /* 14 */ 0x00, 0x00, 0x00, // AC are normal skew, no schmitt, 4mA drive |
| /* 17 */ 0x00, 0x00, 0x00, // AD are normal skew, no schmitt, 4mA drive |
| /* 20 */ CBUSH_IOMODE, CBUSH_IOMODE, CBUSH_IOMODE, CBUSH_IOMODE, // Cbus0-3 |
| /* 24 */ CBUSH_TRISTATE, CBUSH_TRISTATE, CBUSH_TRISTATE, // Cbus4-6 |
| /* 27 */ 0, 0, 0, 0, // Do not invert TXD, RXD, RTS, CTS |
| /* 2B */ 0, 0, 0, 0, // Do not invert DTR, DSR, DCD, RI |
| /* 2F */ 0, 0, 0, // No battery charge detect, disable BCD pin, keep awake |
| /* 32 */ 0, 0, 0, // No I2C address, no device ID, no schmitt trigger |
| /* 35 */ 0, 0, 0, // Not an FT1248 (clock polarity, data endianness, flow) |
| /* 38 */ 0, 0, 0, // No RS485 echo suppression, no power save, D2XX driver |
| }; |
| |
| |
| /* Entry-point. |
| * Processes global options, determines the command, and hands off to the |
| * command dispatcher (runCmd). |
| * |
| * returns: zero on success, non-zero on error. |
| */ |
| int main(int argc, char **argv) { |
| const char *device = NULL, *command = NULL, *option = NULL; |
| const struct cmd *curCmd = NULL; |
| int force = 0; |
| int c; |
| |
| if (argc == 1) { |
| // Print usage and quit if there are no arguments. |
| if (ENABLE_CONSOLE) { |
| fprintf(stderr, "Usage: %s %s%s\n%s", |
| argv[0], USAGE, CONSOLE, ESCAPES); |
| } else { |
| fprintf(stderr, "Usage: %s %s", argv[0], USAGE); |
| } |
| return 1; |
| } |
| |
| while ((c = getopt_long(argc, argv, SHORT_OPTS, LONG_OPTS, NULL)) != -1) { |
| if (c == 's') { |
| if (device) { |
| ERR(1, "Only one serial may be specified at a time."); |
| } |
| // "all" is equivalent to an empty string. |
| device = strcmp("all", optarg) ? optarg : ""; |
| } else if (c == 'f') { |
| force++; |
| } else { |
| return 1; |
| } |
| } |
| |
| // Grab command |
| if (optind < argc) { |
| command = argv[optind++]; |
| } else { |
| ERR(1, "Please specify a command."); |
| } |
| |
| // Grab option |
| if (optind < argc) { |
| option = argv[optind++]; |
| } |
| |
| if (optind < argc) { |
| ERR(1, "Too many parameters."); |
| } |
| |
| // Hand off to the proper function. |
| for (curCmd = COMMAND_LIST; curCmd->name; ++curCmd) { |
| if (!strcmp(command, curCmd->name)) { |
| return runCmd(curCmd->func, device, option, force); |
| } |
| } |
| ERR(1, "Unrecognized command."); |
| } |
| |
| |
| /* Switches the TTY into raw mode and back (if it's a TTY) |
| * In raw mode, characters are passed through as they are typed (instead of |
| * line-based editing), and are not automatically echoed back out on the TTY. |
| * |
| * enable: non-zero to enable, zero to disable. |
| * returns: zero on success, non-zero on error, or if stdin is not a TTY. |
| */ |
| int ttyRawMode(int enable) { |
| static int rawmode = 0; |
| static struct termios orig; |
| if (!isatty(STDIN_FILENO)) { |
| return -1; |
| } |
| |
| if (enable == rawmode) { |
| return 0; |
| } |
| |
| if (enable) { |
| struct termios raw; |
| if (tcgetattr(STDIN_FILENO, &orig) < 0) { |
| return -1; |
| } |
| raw = orig; |
| cfmakeraw(&raw); |
| raw.c_cc[VMIN] = 0; |
| raw.c_cc[VTIME] = 0; |
| if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) < 0) { |
| return -1; |
| } |
| } else { |
| if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig) < 0) { |
| return -1; |
| } |
| // Ensure that the cursor is at a new line. |
| fputc('\n', stderr); |
| } |
| rawmode = enable; |
| return 0; |
| } |
| |
| |
| /* Configures UART settings, using the constants specified above. |
| * |
| * returns: zero on success; non-zero on error. */ |
| int ftdiConfigure(struct ftdi_context *ftdi) { |
| unsigned char latency; |
| |
| FTORDIE(ftdi_set_baudrate(ftdi, TOAD_BAUD)); |
| FTORDIE(ftdi_set_line_property(ftdi, TOAD_LINE_PROPERTY)); |
| FTORDIE(ftdi_setflowctrl(ftdi, TOAD_FLOW_CONTROL)); |
| |
| /* HACK: After the FTDI chip comes out of reset, ftdi_read_pins doesn't |
| * return sensible results until ftdi_set_bitmode has been called to set the |
| * mode to bit-bang. Since we don't want to wipe the boot/vbus state every |
| * time we run the tool, we store some state on the FT230X to see if we've |
| * initialized it or not. Unfortunately, the only volatile state accessible |
| * is the latency timer, which is initially 16ms. Thus we check the latency |
| * timer and call ftdi_set_bitmode if it is not our custom value. The custom |
| * value can be anything (we don't care too much about the buffer timeout |
| * latency), as long as it's not 16. |
| */ |
| FTORDIE(ftdi_get_latency_timer(ftdi, &latency)); |
| if (latency != TOAD_LATENCY_TIMER) { |
| FTORDIE(ftdi_set_bitmode(ftdi, 0x00, BITMODE_CBUS)); |
| FTORDIE(ftdi_set_latency_timer(ftdi, TOAD_LATENCY_TIMER)); |
| } |
| return 0; |
| } |
| |
| |
| /* Opens FT230X devices and runs the specified command. |
| * |
| * If no device is specified, opens the first device with the correct |
| * description; this is either the generic FT230X description (in the case of |
| * the initialize command), or Toad's unique description. |
| * |
| * If device is specified but blank, operates on all devices that match the |
| * appropriate description. |
| * |
| * If device is specified and non-empty, opens the first device with the |
| * matching serial. |
| * |
| * cmd: the function to call on each device opened. |
| * device: the device specified as a command line flag, or NULL if --device was |
| * not provided. |
| * option: an additional parameter if provided by the user. |
| * returns: zero on success, non-zero on error. |
| */ |
| int runCmd(cmd_func cmd, const char *device, const char *option, int force) { |
| int ret = 0; |
| struct ftdi_context *ftdi = ftdi_new(); |
| const char *desc = TOAD_DESC; |
| struct ftdi_device_list *list, *cur; |
| unsigned int i, count, num_valid = 0, num_failed_to_open = 0; |
| int all = 0, check_desc = 1; |
| |
| // If we're initializing (and there's no force), look for generic parts. |
| // If we are 2x forcing, don't even bother checking desc. |
| if (cmd == cmdInitialize) { |
| if (!force) { |
| desc = FTDI_DESC; |
| } else if (force >= 2) { |
| check_desc = 0; |
| } |
| } |
| |
| // If device is provided but blank, process all devices. |
| if (device && !*device) { |
| all = 1; |
| } |
| |
| // Get the list of devices |
| FTORDIE(count = ftdi_usb_find_all(ftdi, &list, TOAD_VID, TOAD_PID)); |
| |
| // Go through each one and operate on them individually. |
| for (i = 0, cur = list; i < count && cur; ++i, cur = cur->next) { |
| char cur_desc[64], cur_serial[64]; |
| int result; |
| // If we've specified a device, make sure it matches that. |
| // If the device doesn't match and we're operating on all, explain why. |
| if (ftdi_usb_get_strings(ftdi, cur->dev, NULL, 0, |
| cur_desc, sizeof(cur_desc), |
| cur_serial, sizeof(cur_serial)) < 0) { |
| if (all) { |
| prn_error("%u: unable to query device.", i); |
| } |
| } else if (check_desc && strcmp(cur_desc, desc)) { |
| if (all) { |
| if (!strcmp(cur_desc, FTDI_DESC)) { |
| prn_error("%u (%s): unprogrammed, or generic part.", |
| i, cur_serial); |
| } else if (!strcmp(cur_desc, TOAD_DESC)) { |
| prn_error("%u (%s): already programmed.", i, cur_serial); |
| } else { |
| prn_error("%u: incorrect description (\"%s\").", |
| i, cur_desc); |
| } |
| } |
| } else if (!all && device && strcmp(cur_serial, device)) { |
| // Didn't match the specified device serial. |
| } else if ((result = ftdi_usb_open_dev(ftdi, cur->dev)) < 0) { |
| num_failed_to_open++; |
| if (all) { |
| prn_error("%u (%s): %s", |
| i, cur_serial, ftdi_get_error_string(ftdi)); |
| } |
| } else { |
| // Matched! Do the proccessing. |
| num_valid++; |
| if (all) { |
| prn_info("%u (%s): processing...", i, cur_serial); |
| } |
| result = ftdiConfigure(ftdi); |
| if (!result) result = (*cmd)(ftdi, option, force); |
| ftdi_usb_close(ftdi); |
| if (all && !result) { |
| prn_info("%u (%s): success.", i, cur_serial); |
| } else if (!ret) { |
| ret = result; |
| } |
| if (!all) { |
| break; |
| } |
| } |
| } |
| // Clean up, and notify the user if there were no devices |
| ftdi_list_free(&list); |
| ftdi_free(ftdi); |
| if (!num_valid) { |
| if (!ret) ret = 2; |
| if (num_failed_to_open) { |
| prn_error("Failed to open %u devices.", num_failed_to_open); |
| } else { |
| prn_error("No valid devices found."); |
| } |
| } else if (all) { |
| prn_info("Processed %u devices.", num_valid); |
| } |
| |
| return ret; |
| } |
| |
| |
| /* Sets the CBUS pins to the specified values, keeping others the same. |
| * |
| * For boot_mode and vbus_en, the special value SET_CBUS_KEEP can be specified |
| * to keep the value at the same state that it presently is. |
| * WARNING: passing SET_CBUS_KEEP to mode_sw is unsafe (since the user may be |
| * pressing the button), so this is not allowed. |
| * |
| * boot_mode: set to 1 to enable EC boot mode. This has higher precedence than |
| * EC/AP mode, but lower precedence than disabling VBUS. |
| * vbus_en: set to 1 to enable VBUS to the DUT. If VBUS is disabled, all modes |
| * are overridden with "off". |
| * mode_sw: set to 1 to assert the mode switch button, toggling the EC/AP mode. |
| * This is the same line that the user uses (via a physical button), so |
| * be sure to de-assert it once the mode switches. |
| * returns: zero on success, non-zero on error. |
| */ |
| int setCbus(struct ftdi_context *ftdi, int boot_mode, |
| int vbus_en, int mode_sw) { |
| unsigned char mode, mask; |
| FTORDIE(ftdi_read_pins(ftdi, &mode)); |
| if (vbus_en == SET_CBUS_KEEP) { |
| vbus_en = ((mode & BIT_VBUS_EN_MASK) != 0); |
| } |
| if (boot_mode == SET_CBUS_KEEP) { |
| boot_mode = !(mode & BIT_BOOT_MODE_L_MASK); |
| } |
| if (mode_sw == SET_CBUS_KEEP) { |
| ERR(254, "mode_sw should never be set to SET_CBUS_KEEP in setCbus"); |
| } |
| mask = (vbus_en ? BIT_VBUS_EN_ASSERT : BIT_VBUS_EN_DEASSERT) |
| | (boot_mode ? BIT_BOOT_MODE_L_ASSERT : BIT_BOOT_MODE_L_DEASSERT) |
| | (mode_sw ? BIT_MODE_SW_L_ASSERT : BIT_MODE_SW_L_DEASSERT) |
| | BIT_AP_MODE_EC_MODE_L_INPUT; |
| FTORDIE(ftdi_set_bitmode(ftdi, mask, BITMODE_CBUS)); |
| return 0; |
| } |
| |
| |
| /* Sets the ec/ap mode by pressing the button and checking the result. |
| * Does not affect boot or vbus states. |
| * |
| * ec: set to 1 to change to EC mode, 0 to change to AP mode, or |
| * SET_EC_AP_TOGGLE to toggle the current mode. |
| * returns: zero on success, non-zero on error. |
| */ |
| int setEcAp(struct ftdi_context *ftdi, int ec) { |
| int ret; |
| unsigned char mode; |
| FTORDIE(ftdi_read_pins(ftdi, &mode)); |
| // Check if the button is held down. If so, make sure we're not the ones |
| // holding it down, then alert the user and wait for it to be released. |
| if (!(mode & BIT_MODE_SW_L_MASK)) { |
| ret = setCbus(ftdi, SET_CBUS_KEEP, SET_CBUS_KEEP, 0); |
| if (ret) return ret; |
| FTORDIE(ftdi_read_pins(ftdi, &mode)); |
| if (!(mode & BIT_MODE_SW_L_MASK)) { |
| prn_warn("The mode button is pressed. Please release it."); |
| while (!(mode & BIT_MODE_SW_L_MASK)) { |
| FTORDIE(ftdi_read_pins(ftdi, &mode)); |
| } |
| prn_info("The mode button has been released. Thank you."); |
| } |
| } |
| // If we requested a toggle, update ec to our target value. |
| if (ec == SET_EC_AP_TOGGLE) { |
| ec = ((mode & BIT_AP_MODE_EC_MODE_L_MASK) != 0); |
| } |
| // See if the mode even needs to be toggled. |
| if (!(mode & BIT_AP_MODE_EC_MODE_L_MASK) != ec) { |
| // Press the button and wait for the mode to switch. |
| ret = setCbus(ftdi, SET_CBUS_KEEP, SET_CBUS_KEEP, 1); |
| if (ret) return ret; |
| // Wait for mode to change. |
| while (!(mode & BIT_AP_MODE_EC_MODE_L_MASK) != ec) { |
| FTORDIE(ftdi_read_pins(ftdi, &mode)); |
| } |
| // Release the button. All done! |
| return setCbus(ftdi, SET_CBUS_KEEP, SET_CBUS_KEEP, 0); |
| } else { |
| // We're already in the right state. |
| return 0; |
| } |
| } |
| |
| |
| /* Rewrites the EEPROM of the FT230X with the settings for Toad. |
| * |
| * option: no options allowed. |
| * force: the user-specified force; non-zero forces operation by ignoring some |
| * sanity checks. >=1 allows rewriting an already-programmed Toad, and |
| * >=2 allows rewriting something totally unrecognized. |
| * returns: zero on success, non-zero on error. |
| */ |
| int cmdInitialize(struct ftdi_context *ftdi UNUSED_ON_OLD_LIBFTDI, |
| const char *option UNUSED_ON_OLD_LIBFTDI, |
| int force UNUSED_ON_OLD_LIBFTDI) { |
| #if NEW_LIBFTDI |
| struct libusb_device_descriptor desc; |
| int detected; |
| unsigned char serial[16]; |
| int serial_size; |
| unsigned char data[TOAD_EEPROM_SIZE]; |
| size_t i; |
| uint16_t checksum; |
| |
| NO_OPTIONS(option); |
| |
| // Determine device type; assume the device doesn't match |
| detected = 2; |
| FTORDIE(libusb_get_device_descriptor( |
| libusb_get_device(ftdi->usb_dev), &desc)); |
| if (desc.idVendor == TOAD_VID && desc.idProduct == TOAD_PID) { |
| unsigned char product[64]; |
| FTORDIE(libusb_get_string_descriptor_ascii( |
| ftdi->usb_dev, desc.iProduct, product, sizeof(product))); |
| if (!strcmp((char*)product, FTDI_DESC)) { |
| detected = 0; |
| } else if (!strcmp((char*)product, TOAD_DESC)) { |
| detected = 1; |
| } |
| } |
| |
| // Stop if we have insufficient force. |
| if (detected > force) { |
| if (detected == 1) { |
| ERR(2, "Avoiding re-programming part; specify -f to force."); |
| } else { |
| ERR(2, "Avoiding programming random device; specify -ff to force."); |
| } |
| } |
| |
| // Get serial and print it out |
| FTORDIE(serial_size = libusb_get_string_descriptor_ascii( |
| ftdi->usb_dev, desc.iSerialNumber, serial, sizeof(serial))); |
| // Make sure that our serial_size counts the null byte, like sizeof |
| if (serial[serial_size - 1]) |
| serial_size++; |
| // Update the manufacturer ID |
| serial[0] = TOAD_MANUFACTURER_ID[0]; |
| serial[1] = TOAD_MANUFACTURER_ID[1]; |
| prn_info("Initializing device %s", serial); |
| |
| // Initialize the EEPROM part of libftdi. We don't actually use the stuff it |
| // initializes, but without this initialization, ftdi_eeprom_write will not |
| // function. |
| FTORDIE(ftdi_eeprom_initdefaults(ftdi, "", "", "")); |
| |
| // Grab the EEPROM data |
| FTORDIE(ftdi_read_eeprom(ftdi)); |
| FTORDIE(ftdi_get_eeprom_buf(ftdi, data, sizeof(data))); |
| |
| // Overwrite the header information |
| memcpy(data, TOAD_EEPROM_00, sizeof(TOAD_EEPROM_00)); |
| |
| // Fix up the serial size |
| data[TOAD_EEPROM_00_SERIAL_SIZE_OFFSET] = serial_size * 2; |
| |
| // Rewrite the manufacturer descriptor |
| data[TOAD_EEPROM_MANUFACTURER_START + 0] = sizeof(TOAD_MANUFACTURER) * 2; |
| data[TOAD_EEPROM_MANUFACTURER_START + 1] = TOAD_EEPROM_STRING_DESCRIPTOR; |
| for (i = 1; i < sizeof(TOAD_MANUFACTURER); ++i) { |
| data[TOAD_EEPROM_MANUFACTURER_START + i*2 + 0] = TOAD_MANUFACTURER[i-1]; |
| data[TOAD_EEPROM_MANUFACTURER_START + i*2 + 1] = 0; |
| } |
| |
| // Rewrite the description descriptor |
| data[TOAD_EEPROM_DESC_START + 0] = sizeof(TOAD_DESC) * 2; |
| data[TOAD_EEPROM_DESC_START + 1] = TOAD_EEPROM_STRING_DESCRIPTOR; |
| for (i = 1; i < sizeof(TOAD_DESC); ++i) { |
| data[TOAD_EEPROM_DESC_START + i*2 + 0] = TOAD_DESC[i-1]; |
| data[TOAD_EEPROM_DESC_START + i*2 + 1] = 0; |
| } |
| |
| // Rewrite the serial descriptor |
| data[TOAD_EEPROM_SERIAL_START + 0] = serial_size * 2; |
| data[TOAD_EEPROM_SERIAL_START + 1] = TOAD_EEPROM_STRING_DESCRIPTOR; |
| for (i = 1; i < (size_t)serial_size; ++i) { |
| data[TOAD_EEPROM_SERIAL_START + i*2 + 0] = serial[i-1]; |
| data[TOAD_EEPROM_SERIAL_START + i*2 + 1] = 0; |
| } |
| |
| // Zero out everything afterwards |
| i = TOAD_EEPROM_SERIAL_START + serial_size * 2; |
| memset(&data[i], 0, sizeof(data) - i); |
| |
| // Calculate and fill in the checksum (algorithm from libftdi's ftdi.c) |
| checksum = TOAD_EEPROM_CHECKSUM_SEED; |
| for (i = 0; i < TOAD_EEPROM_CHECKSUM_OFFSET; i+=2) { |
| checksum = (data[i] | (data[i+1] << 8)) ^ checksum; |
| checksum = (checksum << 1) | (checksum >> 15); |
| } |
| data[TOAD_EEPROM_CHECKSUM_OFFSET + 0] = checksum & 0xFF; |
| data[TOAD_EEPROM_CHECKSUM_OFFSET + 1] = checksum >> 8; |
| |
| // Write back to the device |
| FTORDIE(ftdi_set_eeprom_buf(ftdi, data, sizeof(data))); |
| FTORDIE(ftdi_write_eeprom(ftdi)); |
| |
| // Reset the port to reload the configuration |
| libusb_reset_device(ftdi->usb_dev); |
| |
| return 0; |
| #else // if NEW_LIBFTDI |
| ERR(1, "initialize command is not supported with libftdi 0.x"); |
| #endif // if NEW_LIBFTDI |
| } |
| |
| |
| /* Prints out the serial on stdout, in a format compatible with "eval". |
| * |
| * option: no options allowed. |
| * force: not applicable. |
| * returns: zero on success, non-zero on error. |
| */ |
| int cmdList(struct ftdi_context *ftdi, const char *option, int force UNUSED) { |
| unsigned char serial[16]; |
| struct libusb_device_descriptor desc; |
| |
| NO_OPTIONS(option); |
| |
| FTORDIE(libusb_get_device_descriptor( |
| libusb_get_device(ftdi->usb_dev), &desc)); |
| FTORDIE(libusb_get_string_descriptor_ascii( |
| ftdi->usb_dev, desc.iSerialNumber, serial, sizeof(serial))); |
| fprintf(stdout, "serial='%s'\n", serial); |
| return 0; |
| } |
| |
| |
| /* Prints full device state, in a format compatible with "eval". |
| * |
| * option: no options allowed. |
| * force: not applicable. |
| * returns: zero on success, non-zero on error. |
| * */ |
| int cmdStatus(struct ftdi_context *ftdi, const char *option, int force UNUSED) { |
| unsigned char mode; |
| |
| NO_OPTIONS(option); |
| |
| FTORDIE(ftdi_read_pins(ftdi, &mode)); |
| fprintf(stdout, "vbus='%s'\necap='%s'\nboot='%s'\nmodesw='%s'\n", |
| (mode & BIT_VBUS_EN_MASK) ? "on" : "off", |
| (mode & BIT_AP_MODE_EC_MODE_L_MASK) ? "ap" : "ec", |
| (mode & BIT_BOOT_MODE_L_MASK) ? "off" : "on", |
| (mode & BIT_MODE_SW_L_MASK) ? "off" : "pushed"); |
| return 0; |
| } |
| |
| |
| /* Processes option parameter string for "on"/"off"/"toggle". |
| * Returns whether the new state should be asserted or deasserted. |
| * Properly handles inversion of the active-low inputs in toggle mode. |
| * |
| * option: the user-supplied option string, either "on", "off", or "toggle". |
| * mask: a single BIT_*_MASK value that denotes the option to be set. |
| * enable: returns the new value for the output. |
| * returns: zero on success, non-zero on error. |
| */ |
| int parseOnOffToggle(struct ftdi_context *ftdi, const char *option, |
| unsigned char mask, int *enable) { |
| NEEDS_OPTION(option); |
| if (!strcmp(option, "on")) { |
| *enable = 1; |
| } else if (!strcmp(option, "off")) { |
| *enable = 0; |
| } else if (!strcmp(option, "toggle")) { |
| unsigned char mode; |
| FTORDIE(ftdi_read_pins(ftdi, &mode)); |
| *enable = !(mode & mask); |
| // The other masks are active low, so invert. |
| if (mask != BIT_VBUS_EN_MASK) { |
| *enable = !*enable; |
| } |
| } else { |
| ERR(1, "Unrecognized option."); |
| } |
| return 0; |
| } |
| |
| |
| /* Sets or toggles vbus. |
| * |
| * option: the user-supplied option string, either "on", "off", or "toggle". |
| * force: not applicable. |
| * returns: zero on success, non-zero on error. |
| */ |
| int cmdSetVbus(struct ftdi_context *ftdi, const char *option, |
| int force UNUSED) { |
| int enable; |
| int ret = parseOnOffToggle(ftdi, option, BIT_VBUS_EN_MASK, &enable); |
| return ret ? ret : setCbus(ftdi, SET_CBUS_KEEP, enable, 0); |
| } |
| |
| |
| /* Sets or toggles the ec/ap mode. |
| * |
| * option: the user-supplied option string, either "ec", "ap", or "toggle". |
| * force: not applicable. |
| * returns: zero on success, non-zero on error. |
| */ |
| int cmdSetEcAp(struct ftdi_context *ftdi, const char *option, |
| int force UNUSED) { |
| NEEDS_OPTION(option); |
| if (!strcmp(option, "ec")) { |
| return setEcAp(ftdi, 1); |
| } else if (!strcmp(option, "ap")) { |
| return setEcAp(ftdi, 0); |
| } else if (!strcmp(option, "toggle")) { |
| return setEcAp(ftdi, SET_EC_AP_TOGGLE); |
| } else { |
| ERR(1, "Unrecognized option."); |
| } |
| } |
| |
| |
| /* Sets or toggles boot mode. |
| * |
| * option: the user-supplied option string, either "on", "off", or "toggle". |
| * force: not applicable. |
| * returns: zero on success, non-zero on error. |
| */ |
| int cmdSetBoot(struct ftdi_context *ftdi, const char *option, |
| int force UNUSED) { |
| int enable; |
| int ret = parseOnOffToggle(ftdi, option, BIT_BOOT_MODE_L_MASK, &enable); |
| return ret ? ret : setCbus(ftdi, enable, SET_CBUS_KEEP, 0); |
| } |
| |
| |
| /* Sets or gets the effective mode. |
| * |
| * option: if empty, outputs the current mode on stdout. Otherwise, it is the |
| * mode to change to; see cmdSetMode. |
| * force: passed to cmdSetMode/cmdGetMode. |
| * returns: zero on success, non-zero on error. |
| */ |
| int cmdMode(struct ftdi_context *ftdi, const char *option, int force) { |
| if (option && *option) { |
| return cmdSetMode(ftdi, option, force); |
| } else { |
| return cmdGetMode(ftdi, option, force); |
| } |
| } |
| |
| |
| /* Prints the effective mode to stdout, in a format compatible with "eval". |
| * |
| * option: no options allowed. |
| * force: not applicable. |
| * returns: zero on success, non-zero on error. |
| */ |
| int cmdGetMode(struct ftdi_context *ftdi, const char *option, |
| int force UNUSED) { |
| unsigned char mode; |
| const char *name; |
| |
| NO_OPTIONS(option); |
| |
| FTORDIE(ftdi_read_pins(ftdi, &mode)); |
| if (!(mode & BIT_VBUS_EN_MASK)) { |
| name = "off"; |
| } else if (!(mode & BIT_BOOT_MODE_L_MASK)) { |
| name = "boot"; |
| } else if (!(mode & BIT_AP_MODE_EC_MODE_L_MASK)) { |
| name = "ec"; |
| } else { |
| name = "ap"; |
| } |
| fprintf(stdout, "mode='%s'\n", name); |
| return 0; |
| } |
| |
| |
| /* Sets the effective mode explicitly. |
| * Changes VBUS_EN, BOOT_MODE, and toggles the MODE_SW as appropriate. |
| * |
| * option: the user-supplied option string, either "off", "boot", "ec", or "ap". |
| * force: not applicable. |
| * returns: zero on success, non-zero on error. |
| */ |
| int cmdSetMode(struct ftdi_context *ftdi, const char *option, |
| int force UNUSED) { |
| NEEDS_OPTION(option); |
| if (!strcmp(option, "off")) { |
| return setCbus(ftdi, SET_CBUS_KEEP, 0, 0); |
| } else if (!strcmp(option, "boot")) { |
| return setCbus(ftdi, 1, 1, 0); |
| } else if (!strcmp(option, "ec")) { |
| int ret = setCbus(ftdi, 0, 1, 0); |
| return ret ? ret : setEcAp(ftdi, 1); |
| } else if (!strcmp(option, "ap")) { |
| int ret = setCbus(ftdi, 0, 1, 0); |
| return ret ? ret : setEcAp(ftdi, 0); |
| } else { |
| ERR(1, "Unrecognized option."); |
| } |
| } |
| |
| |
| #if NEW_LIBFTDI |
| /* Grabs all available input from FTDI and outputs on stdout without blocking. |
| * We start a one-byte asynchronous transfer. libftdi's chunking system will |
| * make this efficient. If data's already there, completed will be set to 1, and |
| * we can immediately print it out and loop. Otherwise, it will start an |
| * asynchronous request that we can then wait on using poll elsewhere, or |
| * revisit when we want to. |
| * |
| * read_tc: a state pointer that gets updated with the current read transaction. |
| * returns: zero on success, non-zero on error. |
| */ |
| int printAvailableFtdiOutput(struct ftdi_context *ftdi, |
| struct ftdi_transfer_control **read_tc) { |
| unsigned char *buffer = NULL; |
| struct timeval zero = { 0, 0 }; |
| int written = 0; |
| // Make sure libusb has a chance to process events |
| libusb_handle_events_timeout(ftdi->usb_ctx, &zero); |
| // Loop until there's no data available and we've initiated a read |
| while (!*read_tc || (*read_tc)->completed) { |
| // There's data available if we've looped and there's a read |
| if (*read_tc) { |
| // Grab the buffer pointer, since ftdi_transfer_data_done frees |
| // the structure |
| buffer = (*read_tc)->buf; |
| if (ftdi_transfer_data_done(*read_tc) > 0) { |
| // Output our byte |
| if (fputc(*buffer, stdout) == EOF) { |
| free(buffer); |
| ttyRawMode(0); |
| ERR(2, "Failed write to stdout"); |
| } |
| written++; |
| } |
| *read_tc = NULL; |
| // We don't have to free buffer since we can immediately reuse it. |
| } |
| // If we didn't just finish a read, then we won't have a buffer. |
| // This will only happen once per ftdi_transfer_control* variable. |
| if (!buffer) { |
| buffer = malloc(1); |
| } |
| // Initialize the read |
| if (!(*read_tc = ftdi_read_data_submit(ftdi, buffer, 1))) { |
| free(buffer); |
| ttyRawMode(0); |
| ERR(2, "Failed to communicate with Toad"); |
| } |
| } |
| if (written) { |
| fflush(stdout); |
| } |
| return 0; |
| } |
| #else // NEW_LIBFTDI |
| /* We can't check how much data is available on the old libftdi, so no-op. |
| */ |
| int printAvailableFtdiOutput(struct ftdi_context *ftdi, |
| struct ftdi_transfer_control **read_tc) { |
| if (ftdi || read_tc) {} |
| return 0; |
| } |
| #endif // NEW_LIBFTDI |
| |
| |
| /* Writes the buffer to the FTDI, receiving FTDI output at each step. |
| * |
| * buffer: data to write to the FTDI. |
| * length: length of the data pointed to by buffer. |
| * read_tc: a state pointer that gets updated with the current read transaction. |
| * returns: zero on success, non-zero on error. |
| */ |
| int ftdiWrite(struct ftdi_context *ftdi, unsigned char *buffer, size_t length, |
| struct ftdi_transfer_control **read_tc) { |
| while (length) { |
| int written = ftdi_write_data(ftdi, buffer, length); |
| if (written < 0) { |
| ttyRawMode(0); |
| ERR(2, "Failed writing to Toad\n"); |
| } |
| buffer += written; |
| length -= written; |
| if (printAvailableFtdiOutput(ftdi, read_tc)) { |
| return 2; |
| } |
| } |
| return 0; |
| } |
| |
| |
| /* Sets the system to boot mode, dumps code, and resets the mode. |
| * Will also print any messages that appear over UART out to stdout. |
| * |
| * option: if NULL, reads from stdin. Otherwise, a path to the file to write. |
| * force: not applicable. |
| * returns: zero on success, non-zero on error. |
| */ |
| int cmdBoot(struct ftdi_context *ftdi, const char *option, int force UNUSED) { |
| struct ftdi_transfer_control *read_tc = NULL; |
| unsigned char mode; |
| FILE *file; |
| int ret; |
| unsigned char buffer[1024]; |
| size_t received; |
| |
| // Save the VBUS state |
| FTORDIE(ftdi_read_pins(ftdi, &mode)); |
| // Open the file |
| file = option ? fopen(option, "rb") : stdin; |
| if (!file) { |
| ERR(2, "Unable to open file for reading."); |
| } |
| // Set boot mode |
| ret = setCbus(ftdi, 1, 1, 0); |
| // Wait a second for EC to be ready |
| if (!ret) sleep(1); |
| // Dump the file |
| while (!ret) { |
| received = fread(buffer, 1, sizeof(buffer), file); |
| if (!received) break; |
| ret = ftdiWrite(ftdi, buffer, received, &read_tc); |
| } |
| // Return to non-boot mode |
| if (!ret) ret = setCbus(ftdi, 0, (mode & BIT_VBUS_EN_MASK) != 0, 0); |
| // Close the file. |
| if (option) fclose(file); |
| return ret; |
| } |
| |
| |
| /* Processes stdin input for console mode. Assumes there is input available. |
| * |
| * escaped: state variable for escapes, both read and written. |
| * Set to NULL if escapes are disabled (e.g. not a TTY). |
| * returns: zero if the user quit, 1 upon EOF, and anything else on error. |
| */ |
| int processConsoleInput(struct ftdi_context *ftdi, int *escaped, |
| struct ftdi_transfer_control **read_tc) { |
| // stdin input is available. Grab what's there |
| unsigned char buffer[1024]; |
| size_t available = read(STDIN_FILENO, buffer, sizeof(buffer)); |
| size_t start = 0, current; |
| if (available == 0) { |
| // EOF |
| return 1; |
| } else if (escaped != NULL) { |
| // Search for and process escape characters. |
| for (current = 0; current < available; ++current) { |
| if (*escaped) { |
| *escaped = 0; |
| start = current + 1; |
| switch (buffer[current]) { |
| case TOAD_CONSOLE_HELP1: |
| case TOAD_CONSOLE_HELP2: |
| case TOAD_CONSOLE_HELP3: |
| // Print out console help. |
| ttyRawMode(0); |
| fputs(ESCAPES, stderr); |
| ttyRawMode(1); |
| break; |
| case TOAD_CONSOLE_ESCAPE: |
| // Send the escape character. |
| start = current; |
| break; |
| case TOAD_CONSOLE_BREAK: |
| // Exit out, return failure. |
| return 2; |
| case TOAD_CONSOLE_EOF: |
| // Exit out, return success. |
| return 1; |
| case TOAD_CONSOLE_SUSPEND: |
| // Suspend by calling SIGTSTP on ourselves. |
| ttyRawMode(0); |
| kill(0, SIGTSTP); |
| ttyRawMode(1); |
| break; |
| case TOAD_CONSOLE_EC_SWITCH1: |
| case TOAD_CONSOLE_EC_SWITCH2: |
| case TOAD_CONSOLE_EC_SWITCH3: |
| // Switch to EC console |
| ttyRawMode(0); |
| if (cmdSetMode(ftdi, "ec", 0) == 0) { |
| puts("*** Switched to EC console ***"); |
| } else { |
| puts("*** FAILED to switch to EC console ***"); |
| } |
| ttyRawMode(1); |
| break; |
| case TOAD_CONSOLE_AP_SWITCH1: |
| case TOAD_CONSOLE_AP_SWITCH2: |
| case TOAD_CONSOLE_AP_SWITCH3: |
| case TOAD_CONSOLE_AP_SWITCH4: |
| case TOAD_CONSOLE_AP_SWITCH5: |
| case TOAD_CONSOLE_AP_SWITCH6: |
| // Switch to AP console |
| ttyRawMode(0); |
| if (cmdSetMode(ftdi, "ap", 0) == 0) { |
| puts("*** Switched to AP console ***"); |
| } else { |
| puts("*** FAILED to switch to AP console ***"); |
| } |
| ttyRawMode(1); |
| break; |
| default: |
| // Consume it |
| break; |
| } |
| } else if (buffer[current] == TOAD_CONSOLE_ESCAPE) { |
| *escaped = 1; |
| // Print out what we have so far |
| if (start < current) { |
| int ret = ftdiWrite(ftdi, &buffer[start], current - start, |
| read_tc); |
| if (ret) return ret; |
| } |
| // Don't spit out this and the next character |
| start = current + 2; |
| } |
| } |
| } else { |
| // Request to out everything |
| current = available; |
| } |
| // Print out what we have left |
| if (start < current) { |
| int ret = ftdiWrite(ftdi, &buffer[start], current - start, read_tc); |
| if (ret) return ret; |
| } |
| return 0; |
| } |
| |
| |
| #if ENABLE_CONSOLE |
| /* Monitors the current UART console. |
| * If stdin is a TTY, changes it to raw mode and provides an interactive |
| * console with escapes. |
| * |
| * option: no options allowed. |
| * force: not applicable. |
| * returns: zero on success, non-zero on error. |
| */ |
| int cmdConsole(struct ftdi_context *ftdi, const char *option, |
| int force UNUSED) { |
| int escaped = 0; |
| int *pescaped = NULL; |
| |
| // Monitor for STDIN input and USB events |
| int ret = 0; |
| const char *poll_err = NULL; |
| struct pollfd *fds = NULL; |
| int num_fds = 0; |
| struct ftdi_transfer_control *read_tc = NULL; |
| struct timeval tv; |
| |
| NO_OPTIONS(option); |
| |
| // Switch to raw mode, and set pescaped if we should handle escapes |
| if (!ttyRawMode(1)) { |
| pescaped = &escaped; |
| } |
| |
| // Main IO loop |
| while (1) { |
| const struct libusb_pollfd** libusb_fds; |
| int cur_fds, i; |
| |
| // Print any available FTDI output, and start input monitoring. |
| // NOTE: calls libusb_handle_events_timout for async stuff. |
| if ((ret = printAvailableFtdiOutput(ftdi, &read_tc))) |
| break; |
| |
| // Prepare the poll; lock libusb events |
| libusb_lock_events(ftdi->usb_ctx); |
| |
| // Make sure a transfer completion didn't sneak in on us |
| if (read_tc && read_tc->completed) { |
| // Whoops! Loop around so that the events are handled. |
| libusb_unlock_events(ftdi->usb_ctx); |
| continue; |
| } |
| |
| // Make a list of all the fds we should poll on |
| if (!(libusb_fds = libusb_get_pollfds(ftdi->usb_ctx))) { |
| poll_err = "Unable to query libusb file descriptors."; |
| break; |
| } |
| // Count the libusb fds |
| for (cur_fds = 0; libusb_fds[cur_fds]; ++cur_fds) { |
| } |
| // Add one for stdin |
| cur_fds += 1; |
| |
| // Make sure our structure is big enough for all the fds |
| if (cur_fds > num_fds) { |
| num_fds = cur_fds; |
| if (!(fds = realloc(fds, sizeof(struct pollfd) * num_fds))) { |
| poll_err = "Realloc failed when polling %d file descriptors."; |
| break; |
| } |
| } |
| |
| // Fill the structure with fds, starting with stdin |
| fds[0].fd = STDIN_FILENO; |
| fds[0].events = POLLIN; |
| // Poll all of the libusb fds |
| for (i = 1; i < cur_fds; ++i) { |
| fds[i].fd = libusb_fds[i-1]->fd; |
| fds[i].events = libusb_fds[i-1]->events; |
| } |
| |
| // We're done with the list |
| free(libusb_fds); |
| |
| // libusb might want a timeout, so query it |
| if (!libusb_get_next_timeout(ftdi->usb_ctx, &tv)) { |
| // No timeouts. Specifying a negative timeout waits forever. |
| tv.tv_sec = -1; |
| } |
| |
| // Poll |
| ret = poll(fds, cur_fds, tv.tv_usec / 1000 + tv.tv_sec * 1000); |
| if (ret < 0 && errno != EINTR) { |
| poll_err = "Call to poll() failed."; |
| break; |
| } |
| |
| // Allow for other threads to do event handling |
| libusb_unlock_events(ftdi->usb_ctx); |
| |
| // Handle user input if there is some available |
| if (fds[0].revents) { |
| if ((ret = processConsoleInput(ftdi, pescaped, &read_tc))) { |
| // ret == 1 means EOF, which is not an error. |
| if (ret == 1) |
| ret = 0; |
| break; |
| } |
| } |
| } |
| |
| // Clean up |
| ttyRawMode(0); |
| free(fds); |
| |
| // Print poll-related error messages AFTER ttyRawMode has been deactivated. |
| if (poll_err) { |
| ret = 2; |
| prn_error(poll_err, num_fds); |
| } |
| |
| return ret; |
| } |
| #endif // if ENABLE_CONSOLE |
| |
| |
| #if ENABLE_CONSOLE |
| /* Sets EC mode and monitors the UART console. |
| * |
| * option: no options allowed. |
| * force: passed to cmdSetMode and cmdConsole. |
| * returns: zero on success, non-zero on error. |
| */ |
| int cmdEc(struct ftdi_context *ftdi, const char *option, int force) { |
| int ret = cmdSetMode(ftdi, "ec", force); |
| return ret ? ret : cmdConsole(ftdi, option, force); |
| } |
| #endif // if ENABLE_CONSOLE |
| |
| |
| #if ENABLE_CONSOLE |
| /* Sets AP mode and monitors the UART console |
| * |
| * option: no options allowed. |
| * force: passed to cmdSetMode and cmdConsole. |
| * returns: zero on success, non-zero on error. |
| */ |
| int cmdAp(struct ftdi_context *ftdi, const char *option, int force) { |
| int ret = cmdSetMode(ftdi, "ap", force); |
| return ret ? ret : cmdConsole(ftdi, option, force); |
| } |
| #endif // if ENABLE_CONSOLE |