ftdi: replace proprietary libftd2xx with open-source libftdi1

The Olimex ARM-USB-OCD (and any FT2232 with a custom USB id) couldn't be
enumerated by libftd2xx and needed a manual ftdi_sio unbind. libftdi1
(libusb) opens any VID:PID and auto-detaches the kernel driver.

- rewrite drivers/ftdi_jtag on libftdi1: enumerate a known VID:PID list
  (incl. Olimex 15ba:0003/002b) with per-chip channel counts, open by
  bus/addr + interface, MPSSE via ftdi_write_data/ftdi_read_data (+
  SEND_IMMEDIATE for deterministic reads). MPSSE command building, pin
  map and clocking unchanged.
- CMake: link libftdi1 + libusb-1.0 (pkg-config), drop FTDILIB/FTD2XX
  defines and the libftd2xx.a link; remove the vendored src/libs/libftd2xx.
- registry: NXP LPC2103 (ARM7TDMI-S) entry, IDCODE 0x4F1F0F0F.
- docs updated (deps, layout, decision note, roadmap phase 8).

Validated on hardware: ARM-USB-OCD enumerates, jtag_scan reads the
LPC2103 IDCODE 0x4F1F0F0F, target_info -> [cpu, ARM7] prog: arm_flash.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-24 16:06:52 +02:00
parent 9ad776268e
commit 39963fd6d8
10 changed files with 621 additions and 2580 deletions

View File

@@ -25,9 +25,8 @@ sitting alongside the Viveris ones.
## Architecture ## Architecture
``` ```
src/ — code + libs — src/
├── bs/ Application (readline REPL, no business logic) ├── bs/ Application (readline REPL, no business logic)
├── libs/libftd2xx/ Vendored FTDI SDK
└── modules/ └── modules/
— Viveris's library (LGPL, unchanged) — — Viveris's library (LGPL, unchanged) —
├── jtag_core/ TAP state machine, IR/DR shifts ├── jtag_core/ TAP state machine, IR/DR shifts
@@ -75,6 +74,8 @@ Adding a feature usually means adding a new script command in
| 6 | `svf/` | **done** (subset, commit `c77d86e`) | SVF player + `svf_play`: SIR/SDR with masked TDO compare, RUNTEST, STATE — single-device. Validated on the IGLOO2 IDCODE. | | 6 | `svf/` | **done** (subset, commit `c77d86e`) | SVF player + `svf_play`: SIR/SDR with masked TDO compare, RUNTEST, STATE — single-device. Validated on the IGLOO2 IDCODE. |
| 7 | `target/` + `program/` + `arm_debug/` | **structure done; ARM impl TODO** | Generalized `fpga/` into a kind-aware `target/` registry (FPGA \| CPU). `program <dev> <file>` dispatches by `prog` (svf wired; proxy_spi points at the flash workflow). `arm_debug/` (EmbeddedICE) + `arm_flash` backend are declared but not implemented; `arm-usb-ocd` probe profile added. FPGA path re-validated on the IGLOO2. See the ARM-debug design note. | | 7 | `target/` + `program/` + `arm_debug/` | **structure done; ARM impl TODO** | Generalized `fpga/` into a kind-aware `target/` registry (FPGA \| CPU). `program <dev> <file>` dispatches by `prog` (svf wired; proxy_spi points at the flash workflow). `arm_debug/` (EmbeddedICE) + `arm_flash` backend are declared but not implemented; `arm-usb-ocd` probe profile added. FPGA path re-validated on the IGLOO2. See the ARM-debug design note. |
| 8 | FTDI driver → libftdi1 | **done** | Replaced the proprietary libftd2xx with open-source libftdi1 (libusb): any VID:PID + auto kernel-detach. Detected an NXP LPC2103 (ARM7TDMI-S, IDCODE 0x4F1F0F0F) over an Olimex ARM-USB-OCD — the probe the old lib couldn't enumerate. Vendored `src/libs/libftd2xx` removed. |
Move forward phase by phase: validate one with the user before starting Move forward phase by phase: validate one with the user before starting
the next. Don't break the validated path the next. Don't break the validated path
`jtag_open → jtag_autoinit → jtag_mode 0 EXTEST` while `jtag_open → jtag_autoinit → jtag_mode 0 EXTEST` while
@@ -119,6 +120,19 @@ with `$BS_TARGETS`, loaded lazily. Adding a target = one flat YAML entry
`proxy_spi`/`svf`/`arm_flash`/`none` (inferred when omitted). Enums in `proxy_spi`/`svf`/`arm_flash`/`none` (inferred when omitted). Enums in
`target.h`. `target.h`.
### FTDI driver on libftdi1, not the proprietary libftd2xx
The FTDI MPSSE driver (`drivers/ftdi_jtag/`) runs on open-source
**libftdi1** (libusb), not FTDI's vendored `libftd2xx.a`. Why: libftdi1
opens **any USB VID:PID** (so probes with custom ids — Olimex
ARM-USB-OCD `0x15ba:0x0003`, etc. — enumerate with no `FT_SetVIDPID`
tricks) and **auto-detaches the kernel `ftdi_sio`** driver. The MPSSE
command construction, pin map and clocking are unchanged; only the
transport (enumerate/open/read/write/bitmode) moved to `ftdi_*` calls.
No proprietary binary in the repo. Build dep: pkg-config `libftdi1` +
`libusb-1.0`. Validated by detecting an LPC2103 over an ARM-USB-OCD —
the probe the old lib couldn't even enumerate.
### Digilent SMT2 modules need libdjtg, not raw MPSSE ### Digilent SMT2 modules need libdjtg, not raw MPSSE
Several Xilinx dev boards (KCU105, ZCU102, …) embed a Digilent Several Xilinx dev boards (KCU105, ZCU102, …) embed a Digilent
@@ -372,7 +386,7 @@ the SVF player and the flash logic — is portable C, reused as-is.
implementations, little rework); implementations, little rework);
- runtime BSDL parsing → pre-baked tables (the registry already does - runtime BSDL parsing → pre-baked tables (the registry already does
this); `bsdl_parser` can be left out; this); `bsdl_parser` can be left out;
- `libftd2xx` / Digilent dlopen → gone (the MCU is the probe). - `libftdi1` / Digilent → gone (the MCU is the probe).
**The one real rework: stream, don't `malloc`.** `bscan_shift_dr` **The one real rework: stream, don't `malloc`.** `bscan_shift_dr`
currently allocates the whole shift buffer (`malloc(nbits)` — ~19 MB for currently allocates the whole shift buffer (`malloc(nbits)` — ~19 MB for
@@ -415,9 +429,11 @@ mkdir build && cd build && cmake .. && make
``` ```
Build needs **libyaml** (pkg-config `yaml-0.1`; Arch `libyaml`, Debian Build needs **libyaml** (pkg-config `yaml-0.1`; Arch `libyaml`, Debian
`libyaml-dev`) the FPGA registry is parsed from `data/targets.yaml` `libyaml-dev`) for the registry, and **libftdi1** + **libusb-1.0**
at runtime. Run `bs` from the repo root so it finds that file (and (pkg-config `libftdi1`; Arch `libftdi`, Debian `libftdi1-dev`) for the
`data/bsdl_files/`, `data/bscan_proxies/`), or point `$BS_TARGETS` at it. FTDI/Olimex driver. Run `bs` from the repo root so it finds
`data/targets.yaml` (and `data/bsdl_files/`, `data/bscan_proxies/`), or
point `$BS_TARGETS` at it.
The Digilent SMT2 backend is built by default on UNIX (disable with The Digilent SMT2 backend is built by default on UNIX (disable with
`-DBS_ENABLE_DIGILENT=OFF`). To actually use such a probe, install the `-DBS_ENABLE_DIGILENT=OFF`). To actually use such a probe, install the

View File

@@ -38,7 +38,10 @@ and Microsemi IGLOO2 M2GL010T (`m2gl010t-fg484.bsd`). Add more by dropping
- `readline` (Arch: `readline`, Debian/Ubuntu: `libreadline-dev`) - `readline` (Arch: `readline`, Debian/Ubuntu: `libreadline-dev`)
- `libyaml` for the FPGA registry, found via pkg-config `yaml-0.1` - `libyaml` for the FPGA registry, found via pkg-config `yaml-0.1`
(Arch: `libyaml`, Debian/Ubuntu: `libyaml-dev`) (Arch: `libyaml`, Debian/Ubuntu: `libyaml-dev`)
- `libftd2xx` for FTDI probes (vendored in `src/libs/libftd2xx/`) - `libftdi1` + `libusb-1.0` for FTDI/Olimex probes, via pkg-config
`libftdi1` (Arch: `libftdi`, Debian/Ubuntu: `libftdi1-dev`). Open
source — no vendored proprietary SDK; works with any VID:PID and
auto-detaches the kernel `ftdi_sio` driver.
- *To drive a Digilent SMT2/SMT2-NC probe:* the Digilent - *To drive a Digilent SMT2/SMT2-NC probe:* the Digilent
[Adept Runtime](https://digilent.com/shop/software/digilent-adept/) [Adept Runtime](https://digilent.com/shop/software/digilent-adept/)
installed system-wide (provides `libdjtg.so` + `libdmgr.so`). installed system-wide (provides `libdjtg.so` + `libdmgr.so`).
@@ -176,9 +179,8 @@ shows it).
## Repository layout ## Repository layout
``` ```
src/ — code + libs — src/
├── bs/ Application (readline REPL) ├── bs/ Application (readline REPL)
├── libs/libftd2xx/ Vendored FTDI SDK
└── modules/ └── modules/
├── jtag_core/ TAP state machine, IR/DR shifts ├── jtag_core/ TAP state machine, IR/DR shifts
├── bsdl_parser/ .bsd loader ├── bsdl_parser/ .bsd loader

View File

@@ -84,11 +84,13 @@ targets:
ir_length: 8 ir_length: 8
prog: svf prog: svf
# --- CPU example (ARM7TDMI-S, LPC2148-class) ---------------------- # --- CPU: NXP LPC2103 (ARM7TDMI-S) --------------------------------
# EXAMPLE entry: verify the IDCODE, IR length and RAM/flash regions # IDCODE 0x4F1F0F0F detected on hardware (Olimex ARM-USB-OCD). That's
# against your actual part before relying on it. The arm_flash backend # the generic ARM7TDMI-S debug-TAP id, shared by many ARM7 parts, so
# is not implemented yet (see src/modules/arm_debug/ and CLAUDE.md). # the masked match covers any ARM7TDMI-S — adjust ram/flash per part.
- name: "ARM7TDMI-S (example, e.g. NXP LPC2148)" # LPC2103: 32 KB flash @0x0, 8 KB SRAM @0x40000000. The arm_flash
# backend is not implemented yet (see src/modules/arm_debug/).
- name: "NXP LPC2103 (ARM7TDMI-S)"
kind: cpu kind: cpu
idcode: 0x4F1F0F0F idcode: 0x4F1F0F0F
idcode_mask: 0x0FFFFFFF idcode_mask: 0x0FFFFFFF
@@ -96,7 +98,7 @@ targets:
ir_length: 4 ir_length: 4
debug: embeddedice debug: embeddedice
ram_base: 0x40000000 ram_base: 0x40000000
ram_size: 0x8000 ram_size: 0x2000
flash_base: 0x0 flash_base: 0x0
flash_size: 0x7D000 flash_size: 0x8000
prog: arm_flash prog: arm_flash

View File

@@ -15,8 +15,8 @@ only the IDCODE and BSDL filename change.
## Prerequisites ## Prerequisites
- A JTAG probe physically wired to the target's TCK/TDI/TDO/TMS/TRST. - A JTAG probe physically wired to the target's TCK/TDI/TDO/TMS/TRST.
- `libftd2xx` reachable at runtime (already vendored under - `libftdi1` + `libusb-1.0` installed (Arch: `libftdi`; Debian:
`src/libs/libftd2xx/`). `libftdi1-dev`) — open source, drives FTDI/Olimex probes.
- The target's BSDL in `data/bsdl_files/` (KU15P: `xcku15p_ffve1517.bsd` is - The target's BSDL in `data/bsdl_files/` (KU15P: `xcku15p_ffve1517.bsd` is
bundled). bundled).
- An entry for the target in `data/targets.yaml` (KU15P is bundled). - An entry for the target in `data/targets.yaml` (KU15P is bundled).

View File

@@ -1,154 +0,0 @@
#ifndef __WINDOWS_TYPES__
#define __WINDOWS_TYPES__
#define WINAPI
typedef unsigned int DWORD;
typedef unsigned int ULONG;
typedef unsigned short USHORT;
typedef unsigned short SHORT;
typedef unsigned char UCHAR;
typedef unsigned short WORD;
typedef unsigned short WCHAR;
typedef unsigned char BYTE;
typedef BYTE *LPBYTE;
typedef unsigned int BOOL;
typedef unsigned char BOOLEAN;
typedef unsigned char CHAR;
typedef BOOL *LPBOOL;
typedef UCHAR *PUCHAR;
typedef const char *LPCSTR;
typedef char *PCHAR;
typedef void *PVOID;
typedef void *HANDLE;
typedef unsigned int LONG;
typedef int INT;
typedef unsigned int UINT;
typedef char *LPSTR;
typedef char *LPTSTR;
typedef const char *LPCTSTR;
typedef DWORD *LPDWORD;
typedef WORD *LPWORD;
typedef ULONG *PULONG;
typedef LONG *LPLONG;
typedef PVOID LPVOID;
typedef void VOID;
typedef USHORT *PUSHORT;
typedef unsigned long long int ULONGLONG;
typedef struct _OVERLAPPED {
DWORD Internal;
DWORD InternalHigh;
union {
struct{
DWORD Offset;
DWORD OffsetHigh;
};
PVOID Pointer;
};
HANDLE hEvent;
} OVERLAPPED, *LPOVERLAPPED;
typedef struct _SECURITY_ATTRIBUTES {
DWORD nLength;
LPVOID lpSecurityDescriptor;
BOOL bInheritHandle;
} SECURITY_ATTRIBUTES , *LPSECURITY_ATTRIBUTES;
#include <pthread.h>
// Substitute for HANDLE returned by Windows CreateEvent API.
// FT_SetEventNotification expects parameter 3 to be the address
// of one of these structures.
typedef struct _EVENT_HANDLE
{
pthread_cond_t eCondVar;
pthread_mutex_t eMutex;
int iVar;
} EVENT_HANDLE;
typedef struct timeval SYSTEMTIME;
typedef struct timeval FILETIME;
// WaitForSingleObject return values.
#define WAIT_ABANDONED 0x00000080L
#define WAIT_OBJECT_0 0x00000000L
#define WAIT_TIMEOUT 0x00000102L
#define WAIT_FAILED 0xFFFFFFFF
// Special value for WaitForSingleObject dwMilliseconds parameter
#define INFINITE 0xFFFFFFFF // Infinite timeout
#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif
#ifndef CONST
#define CONST const
#endif
//
// Modem Status Flags
//
#define MS_CTS_ON ((DWORD)0x0010)
#define MS_DSR_ON ((DWORD)0x0020)
#define MS_RING_ON ((DWORD)0x0040)
#define MS_RLSD_ON ((DWORD)0x0080)
//
// Error Flags
//
#define CE_RXOVER 0x0001 // Receive Queue overflow
#define CE_OVERRUN 0x0002 // Receive Overrun Error
#define CE_RXPARITY 0x0004 // Receive Parity Error
#define CE_FRAME 0x0008 // Receive Framing error
#define CE_BREAK 0x0010 // Break Detected
#define CE_TXFULL 0x0100 // TX Queue is full
#define CE_PTO 0x0200 // LPTx Timeout
#define CE_IOE 0x0400 // LPTx I/O Error
#define CE_DNS 0x0800 // LPTx Device not selected
#define CE_OOP 0x1000 // LPTx Out-Of-Paper
#define CE_MODE 0x8000 // Requested mode unsupported
//
// Events
//
#define EV_RXCHAR 0x0001 // Any Character received
#define EV_RXFLAG 0x0002 // Received certain character
#define EV_TXEMPTY 0x0004 // Transmit Queue Empty
#define EV_CTS 0x0008 // CTS changed state
#define EV_DSR 0x0010 // DSR changed state
#define EV_RLSD 0x0020 // RLSD changed state
#define EV_BREAK 0x0040 // BREAK received
#define EV_ERR 0x0080 // Line status error occurred
#define EV_RING 0x0100 // Ring signal detected
#define EV_PERR 0x0200 // Printer error occured
#define EV_RX80FULL 0x0400 // Receive buffer is 80 percent full
#define EV_EVENT1 0x0800 // Provider specific event 1
#define EV_EVENT2 0x1000 // Provider specific event 2
//
// Escape Functions
//
#define SETXOFF 1 // Simulate XOFF received
#define SETXON 2 // Simulate XON received
#define SETRTS 3 // Set RTS high
#define CLRRTS 4 // Set RTS low
#define SETDTR 5 // Set DTR high
#define CLRDTR 6 // Set DTR low
#define RESETDEV 7 // Reset device if possible
#define SETBREAK 8 // Set the device break line.
#define CLRBREAK 9 // Clear the device break line.
//
// PURGE function flags.
//
#define PURGE_TXABORT 0x0001 // Kill the pending/current writes to the comm port.
#define PURGE_RXABORT 0x0002 // Kill the pending/current reads to the comm port.
#define PURGE_TXCLEAR 0x0004 // Kill the transmit queue if there.
#define PURGE_RXCLEAR 0x0008 // Kill the typeahead buffer if there.
#ifndef INVALID_HANDLE_VALUE
#define INVALID_HANDLE_VALUE 0xFFFFFFFF
#endif
#endif /* __WINDOWS_TYPES__ */

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -12,13 +12,16 @@ if(BS_ENABLE_DIGILENT)
endif() endif()
include_directories(${DIR_MODULES}) include_directories(${DIR_MODULES})
include_directories(${DIR_LIBS})
add_compile_definitions(FTD2XX_STATIC)
add_compile_definitions(FTDILIB)
add_library(drivers ${MAIN_SOURCES} ${FTDI_SOURCES} ${GPIO_SOURCES} ${JLINK_SOURCES} ${DIGILENT_SOURCES}) add_library(drivers ${MAIN_SOURCES} ${FTDI_SOURCES} ${GPIO_SOURCES} ${JLINK_SOURCES} ${DIGILENT_SOURCES})
# FTDI MPSSE driver on the open-source libftdi1 (libusb) — no vendored,
# proprietary libftd2xx. PUBLIC so the executable links them transitively.
find_package(PkgConfig REQUIRED)
pkg_check_modules(LIBFTDI REQUIRED IMPORTED_TARGET libftdi1)
pkg_check_modules(LIBUSB REQUIRED IMPORTED_TARGET libusb-1.0)
target_link_libraries(drivers PUBLIC PkgConfig::LIBFTDI PkgConfig::LIBUSB)
if(BS_ENABLE_DIGILENT) if(BS_ENABLE_DIGILENT)
target_link_libraries(drivers PUBLIC ${CMAKE_DL_LIBS}) target_link_libraries(drivers PUBLIC ${CMAKE_DL_LIBS})
endif() endif()

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,5 @@
set(SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}") set(SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
set(CONFIG_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../config/") set(CONFIG_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../config/")
set(LIB_FTD2XXX "${CMAKE_CURRENT_SOURCE_DIR}/../../libs/libftd2xx/libftd2xx.a")
file(GLOB_RECURSE ALL_SOURCES "*.c") file(GLOB_RECURSE ALL_SOURCES "*.c")
@@ -19,9 +18,6 @@ add_custom_target(
) )
include_directories(${DIR_MODULES}) include_directories(${DIR_MODULES})
include_directories(${DIR_LIBS})
add_library(jtag_core ${ALL_SOURCES}) add_library(jtag_core ${ALL_SOURCES})
add_dependencies(jtag_core prebuild) add_dependencies(jtag_core prebuild)
target_link_libraries(jtag_core PRIVATE ${LIB_FTD2XXX})