Skip to content
Merged
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
45 changes: 30 additions & 15 deletions app/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@

using Action = std::function<int()>;

static std::vector<ftdi::DeviceInfo> scan() {
auto result = ftdi::Discovery::scan();
static std::vector<ftdi::DeviceInfo> scan(uint16_t pid = 0x6010) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine to default to the FT2232H/D, but should this default pid be behind a named constant?

auto result = ftdi::Discovery::scan(pid);
if (!result) {
std::cerr << "Error: Failed to communicate with FTDI driver." << std::endl;
exit(0);
Expand Down Expand Up @@ -61,11 +61,11 @@ new_flash_command(const std::string& name, const std::string& desc) {
}

static std::optional<ftdi::SpiHost>
handle_flash_command(std::unique_ptr<argparse::ArgumentParser>& cmd) {
handle_flash_command(std::unique_ptr<argparse::ArgumentParser>& cmd, uint16_t pid) {
auto ftdi = cmd->get<std::string>("--ftdi");
auto traces = cmd->get<bool>("--traces");
auto clock = cmd->get<size_t>("--clock");
auto devices = scan();
auto devices = scan(pid);
auto filtered = ftdi::DeviceInfo::filter_by_description(devices, ftdi);
if (filtered.empty()) {
std::print("No {} ftdi found.\n", ftdi);
Expand All @@ -81,9 +81,9 @@ handle_flash_command(std::unique_ptr<argparse::ArgumentParser>& cmd) {
}

static std::optional<ftdi::Gpio>
handle_gpio_command(std::unique_ptr<argparse::ArgumentParser>& cmd) {
handle_gpio_command(std::unique_ptr<argparse::ArgumentParser>& cmd, uint16_t pid) {
auto ftdi_filter = cmd->get<std::string>("--ftdi");
auto devices = scan();
auto devices = scan(pid);
auto filtered = ftdi::DeviceInfo::filter_by_description(devices, ftdi_filter);
if (filtered.empty()) {
std::print("No {} ftdi found.\n", ftdi_filter);
Expand All @@ -100,13 +100,20 @@ int main(int argc, char* argv[]) {
std::map<std::string, Action> commands;

argparse::ArgumentParser program("ftditool");
program.add_argument("--pid")
.default_value(std::uint16_t{0x6010})
.help(
"The usb product id, in case the ftdi_sio driver needs to be detached. Defaults to "
"FT2232H")
.scan<'x', std::uint16_t>();

argparse::ArgumentParser list_devices_cmd("list-devices");
list_devices_cmd.add_description("List FTDI devices.");
program.add_subparser(list_devices_cmd);
commands["list-devices"] = [&]() -> int {
auto pid = program.get<uint16_t>("--pid");
auto idx = 0;
for (auto device : scan()) {
for (auto device : scan(pid)) {
std::print("{}: {}\n", idx++, device);
}
return 0;
Expand All @@ -115,7 +122,8 @@ int main(int argc, char* argv[]) {
auto jedec_cmd = new_flash_command("jedec", "Read the JEDEC identifier.");
program.add_subparser(*jedec_cmd);
commands["jedec"] = [&]() -> int {
auto spih = handle_flash_command(jedec_cmd);
auto pid = program.get<uint16_t>("--pid");
auto spih = handle_flash_command(jedec_cmd, pid);
commands::ReadJedec(flash::Generic(*spih)).run();
spih->close();
return 0;
Expand All @@ -124,7 +132,8 @@ int main(int argc, char* argv[]) {
auto sfdp_cmd = new_flash_command("sfdp", "Read serial flash description.");
program.add_subparser(*sfdp_cmd);
commands["sfdp"] = [&]() -> int {
auto spih = handle_flash_command(sfdp_cmd);
auto pid = program.get<uint16_t>("--pid");
auto spih = handle_flash_command(sfdp_cmd, pid);
commands::ReadSfdp(flash::Generic(*spih)).run();
spih->close();
return 0;
Expand All @@ -134,8 +143,9 @@ int main(int argc, char* argv[]) {
read_page_cmd->add_argument("--addr").help("The page address").scan<'x', std::size_t>();
program.add_subparser(*read_page_cmd);
commands["read-page"] = [&]() -> int {
auto pid = program.get<uint16_t>("--pid");
auto addr = read_page_cmd->get<std::size_t>("--addr");
auto spih = handle_flash_command(read_page_cmd);
auto spih = handle_flash_command(read_page_cmd, pid);
commands::ReadPage(flash::Generic(*spih), addr).run();
spih->close();
return 0;
Expand All @@ -150,9 +160,10 @@ int main(int argc, char* argv[]) {
test_page_cmd->add_argument("--quad").help("Use qSPI").default_value(false).implicit_value(true);
program.add_subparser(*test_page_cmd);
commands["test-page"] = [&]() -> int {
auto pid = program.get<uint16_t>("--pid");
auto addr = test_page_cmd->get<std::size_t>("--addr");
auto quad = test_page_cmd->get<bool>("--quad");
auto spih = handle_flash_command(test_page_cmd);
auto spih = handle_flash_command(test_page_cmd, pid);
commands::TestPage(flash::Generic(*spih), addr, quad).run();

spih->close();
Expand All @@ -169,10 +180,11 @@ int main(int argc, char* argv[]) {
load_file_cmd->add_argument("--quad").help("Use qSPI").default_value(false).implicit_value(true);
program.add_subparser(*load_file_cmd);
commands["load-file"] = [&]() -> int {
auto pid = program.get<uint16_t>("--pid");
auto filename = load_file_cmd->get<std::string>("filename");
auto addr = load_file_cmd->get<std::size_t>("--addr");
auto quad = load_file_cmd->get<bool>("--quad");
auto spih = handle_flash_command(load_file_cmd);
auto spih = handle_flash_command(load_file_cmd, pid);
commands::LoadFile(flash::Generic(*spih), filename, addr, false, quad).run();

spih->close();
Expand All @@ -192,10 +204,11 @@ int main(int argc, char* argv[]) {
.implicit_value(true);
program.add_subparser(*verify_file_cmd);
commands["verify-file"] = [&]() -> int {
auto pid = program.get<uint16_t>("--pid");
auto filename = verify_file_cmd->get<std::string>("filename");
auto addr = verify_file_cmd->get<std::size_t>("--addr");
auto quad = verify_file_cmd->get<bool>("--quad");
auto spih = handle_flash_command(verify_file_cmd);
auto spih = handle_flash_command(verify_file_cmd, pid);
commands::VerifyFile(flash::Generic(*spih), filename, addr, quad).run();

spih->close();
Expand All @@ -212,10 +225,11 @@ int main(int argc, char* argv[]) {
bootstrap_cmd->add_argument("--quad").help("Use qSPI").default_value(false).implicit_value(true);
program.add_subparser(*bootstrap_cmd);
commands["bootstrap"] = [&]() -> int {
auto pid = program.get<uint16_t>("--pid");
auto filename = bootstrap_cmd->get<std::string>("filename");
auto addr = bootstrap_cmd->get<std::size_t>("--addr");
auto quad = bootstrap_cmd->get<bool>("--quad");
auto spih = handle_flash_command(bootstrap_cmd);
auto spih = handle_flash_command(bootstrap_cmd, pid);
commands::LoadFile(flash::Generic(*spih), filename, addr, true, quad).run();

spih->close();
Expand All @@ -227,9 +241,10 @@ int main(int argc, char* argv[]) {
gpio_write_cmd->add_argument("value").help("Value to write (0 or 1)").required().scan<'d', int>();
program.add_subparser(*gpio_write_cmd);
commands["gpio-write"] = [&]() -> int {
auto pid = program.get<uint16_t>("--pid");
auto pin = static_cast<uint8_t>(gpio_write_cmd->get<int>("pin"));
auto value = gpio_write_cmd->get<int>("value") != 0;
auto gpio = handle_gpio_command(gpio_write_cmd);
auto gpio = handle_gpio_command(gpio_write_cmd, pid);
commands::GpioWrite(*gpio, pin, value).run();
gpio->close();
return 0;
Expand Down
2 changes: 1 addition & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
ft4222
libmpsse
];
buildInputs = [ft4222 libmpsse ftd2xx];
buildInputs = with pkgs; [ft4222 libmpsse ftd2xx libusb1 systemd];
shellHook = ''
# Setting LD_LIBRARY_PATH for libftd2xx
export LD_LIBRARY_PATH="${ftd2xx}/lib:$LD_LIBRARY_PATH"
Expand Down
5 changes: 4 additions & 1 deletion lib/ftdi/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ target_sources(ftdipp
PRIVATE
spi_host.cc
gpio.cc
libusb.cc
PUBLIC
FILE_SET HEADERS
BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}
Expand All @@ -16,6 +17,7 @@ target_sources(ftdipp
spi_host.hh
gpio.hh
log.hh
libusb.hh
)

target_include_directories(ftdipp
Expand All @@ -25,7 +27,8 @@ target_include_directories(ftdipp
)

target_link_libraries(ftdipp
PUBLIC magic_enum::magic_enum embeddedpp mpsse ft4222
PUBLIC magic_enum::magic_enum embeddedpp mpsse ft4222
PRIVATE usb-1.0 udev
)
target_compile_options(ftdipp PRIVATE "-fno-exceptions")

8 changes: 6 additions & 2 deletions lib/ftdi/ftdi.hh
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
#pragma once

#include "ftd2xx.h"
#include <cstdint>
#include <optional>
#include <vector>
#include <iostream>
#include "device_info.hh"
#include "libusb.hh"

namespace ftdi {

Expand All @@ -19,7 +19,11 @@ class Discovery {
* @brief Scans for all connected FTDI devices.
* @return A vector of devices if successful, or std::nullopt if the driver call failed.
*/
static std::optional<std::vector<DeviceInfo>> scan() {

static std::optional<std::vector<DeviceInfo>> scan(uint16_t pid) {
// Detach ftdi_sio (if present) so D2XX can enumerate the device.
detach_ftdi_sio(pid);

DWORD num_devs = 0;

// 1. Get the number of devices
Expand Down
61 changes: 61 additions & 0 deletions lib/ftdi/libusb.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

#include "libusb.hh"

#include <libudev.h>
#include <libusb-1.0/libusb.h>

#include <iostream>

namespace ftdi {

enum { kFtdiVid = 0x0403 };

// Use libusb to temporarily detach ftdi_sio from every interface of
// every FTDI USB device. The kernel driver is re-attached automatically when
// the D2XX / libft4222 handle is closed.
void detach_ftdi_sio(uint16_t pid) {
libusb_context* ctx = nullptr;
if (libusb_init(&ctx) != LIBUSB_SUCCESS) {
std::cerr << "libusb_init failed\n";
return;
}

libusb_device** devs = nullptr;
ssize_t cnt = libusb_get_device_list(ctx, &devs);
if (cnt < 0) {
libusb_exit(ctx);
return;
}

for (ssize_t i = 0; i < cnt; i++) {
libusb_device_descriptor desc{};
if (libusb_get_device_descriptor(devs[i], &desc) != LIBUSB_SUCCESS) continue;
if (desc.idVendor != kFtdiVid || desc.idProduct != pid) continue;

libusb_device_handle* handle = nullptr;
if (libusb_open(devs[i], &handle) != LIBUSB_SUCCESS) continue;

libusb_config_descriptor* config = nullptr;
if (libusb_get_active_config_descriptor(devs[i], &config) == LIBUSB_SUCCESS) {
for (uint8_t j = 0; j < config->bNumInterfaces; j++) {
if (libusb_kernel_driver_active(handle, j) != 1) continue;
int ret = libusb_detach_kernel_driver(handle, j);
if (ret != LIBUSB_SUCCESS) {
std::cerr << "Failed to detach kernel driver on interface " << (int)j << ": "
<< libusb_error_name(ret) << "\n";
}
}
libusb_free_config_descriptor(config);
}

libusb_close(handle);
}

libusb_free_device_list(devs, 1);
libusb_exit(ctx);
}

} // namespace ftdi
19 changes: 19 additions & 0 deletions lib/ftdi/libusb.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include <cstdint>
#include <format>
#include <string>
#include <vector>

namespace ftdi {

// Detach ftdi_sio (or any kernel driver) from all FTDI USB interfaces
// using libusb. This allows the D2XX library to claim the device afterwards.
// Requires write access to the USB device files in the udev rules.
void detach_ftdi_sio(uint16_t pid);

} // namespace ftdi
4 changes: 4 additions & 0 deletions nix/ftditool.nix
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
argparse,
magic-enum,
picosha2,
libusb1,
systemd,
makeWrapper,
}:
stdenv.mkDerivation {
Expand All @@ -33,6 +35,8 @@ stdenv.mkDerivation {
magic-enum
picosha2
ftd2xx
libusb1
systemd
];

cmakeFlags = [
Expand Down