Skip to content

Commit

Permalink
Version 2.1
Browse files Browse the repository at this point in the history
  • Loading branch information
multiplemonomials committed Oct 25, 2023
1 parent a2bdce7 commit 432a7fe
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 69 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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
# ----------------------------------------------------------
Expand Down
20 changes: 14 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -155,26 +155,34 @@ This version of fxload allows you to specify the device to connect to in three d
2. You may specify `--device <vid>:<pid>` 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 <bus>.<port>` 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 <path/to/firmware.hex> -t FX2LP
$ fxload load_ram --ihex-path <path/to/firmware.hex> -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

**Warning: This process can soft-brick your device if you load invalid firmware. See the "unbricking" section below for more details.**

To load a hex file into EEPROM, use a command line:
```sh
$ fxload --ihex-path <path/to/firmware.hex> -t FX2LP --eeprom -c 0xC2
$ fxload load_eeprom --ihex-path <path/to/firmware.hex> -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

Expand Down
199 changes: 137 additions & 62 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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++) {
Expand All @@ -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;
}
}
}
}
Expand Down Expand Up @@ -106,10 +118,17 @@ libusb_device_handle *get_usb_device(struct device_spec *wanted) {
deviceDetailsString = "<failed to open device>";
#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<int>(nr - 1));
fflush(NULL);
Expand All @@ -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;
}

Expand All @@ -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);
Expand Down Expand Up @@ -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<uint16_t>(std::stoul(vid_string, nullptr, 16));
spec->pid = static_cast<uint16_t>(std::stoul(pid_string, nullptr, 16));
spec->searchByVidPid = true;
}
else if (dotIdx != std::string::npos)
{
Expand All @@ -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
Expand Down Expand Up @@ -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<uint16_t>(std::numeric_limits<uint8_t>::min()), static_cast<uint16_t>(std::numeric_limits<uint8_t>::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<uint8_t>::min(), std::numeric_limits<uint8_t>::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;
}

0 comments on commit 432a7fe

Please sign in to comment.