diff --git a/CMakeLists.txt b/CMakeLists.txt index aa4010a..c25d82f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.8) cmake_policy(VERSION 3.8) -project(fxload VERSION 2.0) +project(fxload VERSION 2.1) # set compile flags and options # ---------------------------------------------------------- diff --git a/README.md b/README.md index f5d5614..0df01ed 100644 --- a/README.md +++ b/README.md @@ -32,10 +32,10 @@ In 2023, the Mbed CE project is now making an updated version of this library wh - Improved error messages (esp. for failing to open USB devices) - Improved device selection menu -## Code Installation +## Installing FXLoad ### Installer -On Windows, FXLoad can be installed via the Windows installer downloadable from the Releases page. (TODO) +On Windows, FXLoad can be installed via the Windows installer downloadable from the [Releases](https://github.com/mbed-ce/fxload/releases) page. ### Building from Source FXLoad can be built from source using CMake and a C/C++ compiler. The first step is to use Git to clone the project. Make sure to pass "--recursive" to get the submodules: @@ -155,15 +155,21 @@ This version of fxload allows you to specify the device to connect to in three d 2. You may specify `--device :` to select a device by its vendor ID and hardware ID (in hexadecimal). For example, to flash an unconfigured FX2LP, you would pass `--device 04b4:8613`. By default, this will select the first such device found, but you can change that by adding `@N` after the vid and pid to use the Nth device found (where N is the 0-indexed index of the device to use). 3. You may specify `--device .` to select a device by its bus and device number, specified as decimal numbers. You can get the bus and device numbers from `lsusb` on Linux, though I'm not aware of a utility to list them on Linux. +To list available devices, run the command +``` +fxload list +``` +This will list out all available USB devices on your system, so you can pick which device to use. + ### Loading a Hex File to RAM To just load a firmware file into RAM, use a command like: ```sh -$ fxload --ihex-path -t FX2LP +$ fxload load_ram --ihex-path -t FX2LP ``` (the -t argument may be changed to "FX2", "FX", or "AN21" as appropriate) -Sinc you are loading to RAM, this method of loading firmware will only last until the device is reset, which is useful for testing firmware builds! +Since you are loading to RAM, this method of loading firmware will only last until the device is reset, which is useful for testing firmware builds! ### Loading a Hex File to EEPROM @@ -171,10 +177,12 @@ Sinc you are loading to RAM, this method of loading firmware will only last unti To load a hex file into EEPROM, use a command line: ```sh -$ fxload --ihex-path -t FX2LP --eeprom -c 0xC2 +$ fxload load_eeprom --ihex-path -t FX2LP --control-byte 0xC2 ``` -The `-c` argument gives the value for the command byte (the first byte of the device EEPROM). The value to use here changes based on the device. For FX2LP, 0xC2 causes the device to boot from EEPROM, and 0xC0 causes the device to load the VID, PID, and DID from EEPROM. +The `--control-byte` argument gives the value for the command byte (the first byte of the device EEPROM). The value to use here changes based on the device. For FX2LP, 0xC2 causes the device to boot from EEPROM, and 0xC0 causes the device to load the VID, PID, and DID from the EEPROM. + +Note: You may need to reset the chip before the new firmware will load. ### Loading Only VID, PID, and DID values to EEPROM diff --git a/src/main.cpp b/src/main.cpp index 2dff794..d07b424 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -30,26 +30,25 @@ #include "fxload-version.h" #include "ApplicationPaths.h" -struct device_spec { int index; uint16_t vid, pid; int bus, port; }; +struct device_spec { int index; bool searchByVidPid; uint16_t vid, pid; int bus, port; }; /* * Finds the correct USB device to open based on the provided device spec. * If wanted is nullptr, all USB devices are printed to the console and the user * can select which to use. + * If listOnly is true, we just print the list of USB devices and then return without selecting one. */ -libusb_device_handle *get_usb_device(struct device_spec *wanted) { +libusb_device_handle * search_usb_devices(bool listOnly, struct device_spec *wanted) { libusb_device **list; libusb_device_handle *dev_h = NULL; libusb_init(NULL); - // If in double-verbose mode or above, set libusb to debug log mode. - libusb_set_option(nullptr, LIBUSB_OPTION_LOG_LEVEL, verbose >= 2 ? LIBUSB_LOG_LEVEL_DEBUG : LIBUSB_LOG_LEVEL_WARNING); libusb_device *found = NULL; int nr_found = 0; - bool search_all = wanted == nullptr; + bool search_all = wanted == nullptr || listOnly; ssize_t nr = libusb_get_device_list(NULL, &list); for (int i = 0; i < nr; i++) { @@ -65,11 +64,24 @@ libusb_device_handle *get_usb_device(struct device_spec *wanted) { if(!search_all) { - if ((bus == wanted->bus && (port == wanted->port || wanted->port == 0)) || - (vid == wanted->vid && ( pid == wanted->pid || wanted->pid == 0))) { - if (nr_found++ == wanted->index) { - found = dev; - break; + if(wanted->searchByVidPid) + { + if(vid == wanted->vid && ( pid == wanted->pid || wanted->pid == 0)) + { + if (nr_found++ == wanted->index) { + found = dev; + break; + } + } + } + else + { + if (bus == wanted->bus && (port == wanted->port || wanted->port == 0)) + { + if (nr_found++ == wanted->index) { + found = dev; + break; + } } } } @@ -106,10 +118,17 @@ libusb_device_handle *get_usb_device(struct device_spec *wanted) { deviceDetailsString = ""; #endif } - printf("%d: Bus %03d Device %03d: ID %04X:%04X %s\n", i, bus, port, vid, pid, deviceDetailsString.c_str()); + printf("%d: Bus %03d Device %03d: ID %04x:%04x %s\n", i, bus, port, vid, pid, deviceDetailsString.c_str()); } } + if(listOnly) + { + // We just wanted to list them, so we're done now. + libusb_free_device_list(list, 1); + return nullptr; + } + if (search_all) { printf("Please select device to configure [0-%d]: ", static_cast(nr - 1)); fflush(NULL); @@ -120,11 +139,13 @@ libusb_device_handle *get_usb_device(struct device_spec *wanted) { if(!std::cin) { logerror("Invalid input.\n"); + libusb_free_device_list(list, 1); return nullptr; } if( sel < 0 || sel >= nr) { logerror("device selection out of bound: %d\n", sel); + libusb_free_device_list(list, 1); return NULL; } @@ -133,8 +154,13 @@ libusb_device_handle *get_usb_device(struct device_spec *wanted) { if (! found) { logerror("device not selected\n"); + libusb_free_device_list(list, 1); return NULL; } + + // If in double-verbose mode or above, set libusb to debug log mode. + // This will help diagnose errors from opening the device. + libusb_set_option(nullptr, LIBUSB_OPTION_LOG_LEVEL, verbose >= 2 ? LIBUSB_LOG_LEVEL_DEBUG : LIBUSB_LOG_LEVEL_WARNING); int openRet = libusb_open(found, &dev_h); libusb_free_device_list(list, 1); @@ -165,6 +191,7 @@ parse_device_path(const std::string & device_path, struct device_spec *spec) { auto pid_string = device_path.substr(colonIdx + 1, (atIndex == std::string::npos ? std::string::npos : (atIndex - colonIdx - 1))); spec->vid = static_cast(std::stoul(vid_string, nullptr, 16)); spec->pid = static_cast(std::stoul(pid_string, nullptr, 16)); + spec->searchByVidPid = true; } else if (dotIdx != std::string::npos) { @@ -177,6 +204,7 @@ parse_device_path(const std::string & device_path, struct device_spec *spec) { auto port_string = device_path.substr(dotIdx + 1, (atIndex == std::string::npos ? std::string::npos : (atIndex - dotIdx - 1))); spec->bus = std::stoi(bus_string); spec->port = std::stoi(port_string); + spec->searchByVidPid = false; } // Look for optional "@index" suffix @@ -211,80 +239,127 @@ int main(int argc, char*argv[]) ezusb_chip_t type = NONE; int eeprom_first_byte = -1; bool load_to_eeprom = false; + bool printVersion = false; // Find resources directory std::string app_install_dir = AppPaths::getExecutableDir(); std::string stage1_loader = app_install_dir + AppPaths::PATH_SEP + ".." + AppPaths::PATH_SEP + "share" + AppPaths::PATH_SEP + "fxload" + AppPaths::PATH_SEP + "Vend_Ax.hex"; - // List of CLI options + // CLI options for fxload app.add_flag("-v,--verbose", verbose, "Verbose mode. May be supplied up to 3 times for more verbosity."); // note: CLI11 will count the occurrences of a flag when you pass an integer variable to add_flag() - app.add_option("-I,--ihex-path", ihex_path, "Hex file to program") + app.add_flag("-V,--version", printVersion, "Print version and exit."); + + // Subcommands + CLI::App * load_ram_subcommand = app.add_subcommand("load_ram", "Load a binary into file into the EZ-USB chip's RAM."); + CLI::App * load_eeprom_subcommand = app.add_subcommand("load_eeprom", "Load a binary into file into the EZ-USB chip's EEPROM."); + CLI::App * list_usb_subcommand = app.add_subcommand("list", "List all available USB devices and exit"); + + // load_ram options + load_ram_subcommand->add_option("-I,--ihex-path", ihex_path, "Hex file to program") + ->required() + ->check(CLI::ExistingFile); + load_ram_subcommand->add_option("-t,--type", type, "Select device type (from AN21|FX|FX2|FX2LP)") + ->required() + ->transform(CLI::CheckedTransformer(DeviceTypeNames, CLI::ignore_case).description("")); + load_ram_subcommand->add_option("-D,--device", device_spec_string, + "Select device by vid:pid(@index) or bus.port(@index). If not provided, all discovered USB devices will be displayed as options."); + + // load_eeprom options + load_eeprom_subcommand->add_option("-I,--ihex-path", ihex_path, "Hex file to program") ->required() ->check(CLI::ExistingFile); - app.add_option("-t,--type", type, "Select device type (from AN21|FX|FX2|FX2LP)") + load_eeprom_subcommand->add_option("-t,--type", type, "Select device type (from AN21|FX|FX2|FX2LP)") ->required() - ->transform(CLI::CheckedTransformer(DeviceTypeNames, CLI::ignore_case)); - app.add_option("-D,--device", device_spec_string, "Select device by vid:pid(@index) or bus.port(@index). If not provided, all discovered USB devices will be displayed as options."); - auto eeprom_first_byte_opt = app.add_option("-c,--eeprom-first-byte", eeprom_first_byte, "Value programmed to first byte of EEPROM to set chip behavior. e.g. for FX2LP this should be 0xC0 or 0xC2") - ->check(CLI::Range(static_cast(std::numeric_limits::min()), static_cast(std::numeric_limits::max()))); - auto eeprom_opt = app.add_flag("-e, --eeprom", load_to_eeprom, "Load the hex file to EEPROM via a 1st stage loader instead of directly to RAM") - ->needs(eeprom_first_byte_opt); - app.add_option("-s,--stage1", stage1_loader, "Path to the stage 1 loader file to use when flashing EEPROM. Default: " + stage1_loader) - ->needs(eeprom_opt) + ->transform(CLI::CheckedTransformer(DeviceTypeNames, CLI::ignore_case).description("")); + load_eeprom_subcommand->add_option("-D,--device", device_spec_string, + "Select device by vid:pid(@index) or bus.port(@index). If not provided, all discovered USB devices will be displayed as options."); + load_eeprom_subcommand->add_option("-c,--control-byte", eeprom_first_byte, "Value programmed to first byte of EEPROM to set chip behavior. e.g. for FX2LP this should be 0xC0 or 0xC2") + ->check(CLI::Range(std::numeric_limits::min(), std::numeric_limits::max()).description("")); + load_eeprom_subcommand->add_option("-s,--stage1", stage1_loader, "Path to the stage 1 loader file to use when flashing EEPROM. Default: " + stage1_loader) ->check(CLI::ExistingFile); CLI11_PARSE(app, argc, argv); - // Handle CLI options - struct device_spec spec = {0}; - if(!device_spec_string.empty()) + // handle -V + if(printVersion) { - int parseResult = parse_device_path(device_spec_string, &spec); - if(parseResult != 0) + printf("%s\n", FXLOAD_VERSION_STR); + return 0; + } + else + { + if(app.get_subcommands().size() == 0) { - return parseResult; + printf("Must specify a subcommand! Run with --help for more information.\n"); + return 1; } } - libusb_device_handle *device; - int status; + // Handle subcommands + if(list_usb_subcommand->parsed()) + { + search_usb_devices(true, nullptr); + return 0; + } + else // load_ram or load_eeprom (all commands which open a USB device) + { + // Find USB device to operate on + struct device_spec spec = {0}; + if(!device_spec_string.empty()) + { + int parseResult = parse_device_path(device_spec_string, &spec); + if(parseResult != 0) + { + return parseResult; + } + } - device = get_usb_device(device_spec_string.empty() ? nullptr : &spec); + libusb_device_handle *device; - if (device == NULL) { - logerror("No device to configure\n"); - return -1; - } + device = search_usb_devices(false, device_spec_string.empty() ? nullptr : &spec); - if (type == NONE) { - type = FX; /* an21-compatible for most purposes */ - } + if (device == NULL) { + logerror("Failed to select device\n"); + return -1; + } - if (load_to_eeprom) { - /* first stage: put loader into internal memory */ - if (verbose) - logerror("1st stage: load 2nd stage loader\n"); - status = ezusb_load_ram (device, stage1_loader.c_str(), type, 0); - if (status != 0) - return status; - - /* second stage ... write EEPROM */ - status = ezusb_load_eeprom (device, ihex_path.c_str(), type, eeprom_first_byte); - if (status != 0) - return status; - - } else { - /* single stage, put into internal memory */ - if (verbose) - logerror("single stage: load on-chip memory\n"); - status = ezusb_load_ram (device, ihex_path.c_str(), type, 0); - if (status != 0) - return status; - } + if(load_ram_subcommand->parsed()) + { + /* single stage, put into internal memory */ + if (verbose) + logerror("single stage: load on-chip memory\n"); + int status = ezusb_load_ram (device, ihex_path.c_str(), type, 0); + if(status != 0) + { + libusb_close(device); + return status; + } + + } + else if(load_eeprom_subcommand->parsed()) + { + /* first stage: put loader into internal memory */ + if (verbose) + logerror("1st stage: load 2nd stage loader\n"); + int status = ezusb_load_ram (device, stage1_loader.c_str(), type, 0); + if (status != 0) + { + libusb_close(device); + return status; + } - libusb_close(device); + /* second stage ... write EEPROM */ + status = ezusb_load_eeprom (device, ihex_path.c_str(), type, eeprom_first_byte); + if (status != 0) + { + libusb_close(device); + return status; + } + } - printf("Done.\n"); + libusb_close(device); + printf("Done.\n"); + } - exit(0); + return 0; } \ No newline at end of file