Fix FTDI/libFTDI SPI drivers: batch writes, macOS support, bug fixes - #1502
Fix FTDI/libFTDI SPI drivers: batch writes, macOS support, bug fixes#1502nurikk wants to merge 3 commits into
Conversation
13041b5 to
0035f77
Compare
57b4a2a to
b528cb8
Compare
| } | ||
|
|
||
| // data payload | ||
| command.insert(command.end(), data, data + size); |
There was a problem hiding this comment.
Is there any reason to justify allocating the large buffer memory a second time? At this stage, the data buffer is quite large already as it contains the over-sampled bitstream for the Neopixel protocol.
There was a problem hiding this comment.
Good point. The per-frame allocation has now been removed in fab82f7: both the D2XX and libFTDI providers keep an instance-owned command buffer and reuse its capacity. Allocation now happens only on the first frame or if a later frame requires more capacity.
The second buffer is still needed to coalesce CS-low, the MPSSE header, the oversampled payload, and CS-high into one contiguous FTDI write. For a concrete 240-pixel RGBW SK6812 setup:
- oversampled LED buffer:
240 * 4 channels * 4 SPI bytes + 4 latch bytes = 3,844 bytes - framed FTDI command buffer:
3,844 + 9 = 3,853 bytes - both buffers together:
7,697 bytes, or about7.52 KiB
So the memory footprint itself is negligible; the worthwhile part was removing allocator churn on every frame while retaining the single batched write.
There was a problem hiding this comment.
Also confirmed with the updated webOS build on real hardware: FTDI output to the 240-pixel RGBW SK6812 strip is working.
For clarity, this is not static storage: it is a provider-instance std::vector that dynamically allocates on the first frame (or when a larger frame appears) and then reuses that capacity. The reusable framed buffer is 3,853 bytes (~3.76 KiB) for this setup; including the existing oversampled LED buffer, both buffers total 7,697 bytes (~7.52 KiB).
The extra contiguous buffer lets us submit CS-low + MPSSE command header + payload + CS-high through one FTDI API write. The USB stack may still packetize it physically, but we avoid multiple driver submissions per frame.
- Batch MPSSE command + data into a single USB write instead of multiple fragmented writes, reducing overhead and preventing partial-frame issues - Add macOS dylib support for libftdi1 library loading - Add input validation for writeBytes size parameter - Fix device discovery bounds check to prevent OOB access - Fix copy-paste error in ftdi_write_data_set_chunksize error message - Fix "initilize" typo in both drivers - Remove unused includes in ProviderSpiLibFtdi
The output field now accepts both numeric bus:addr locations (backward compatible) and libftdi string identifiers like "s:0x0403:0x6014:usb_c" for stable device identification that survives reboots. Device discovery now reports serial numbers and returns stable string identifiers as the default value when a serial is available.
The SPI provider selection only routed numeric outputs to libFTDI, sending string identifiers like "s:0x0403:0x6014:serial" to the generic SPI provider which then failed with "No such file or directory". Now recognizes ftdi_usb_open_string prefixes (s:, i:, d:) and routes them to the FTDI provider.
b528cb8 to
fab82f7
Compare
Summary
ftdi_usb_open_stringidentifiers (e.g.s:0x0403:0x6014:usb_c) in addition to numeric bus:device addresses. This fixes the issue where the FTDI device location changes on every reboot, breaking the connection. String identifiers use the device's serial number which is stable across reboots.ProviderSpinow correctly routes string identifiers withs:,i:, ord:prefixes to the FTDI provider instead of falling through toProviderSpiGeneric.ProviderSpiFtdi(Windows) andProviderSpiLibFtdi(Linux/macOS) now assemble the full MPSSE command (CS low + write command + data payload + CS high) into a single buffer and perform one USB write, instead of 3-4 fragmented writes per frame. Reduces USB round-trip overhead and eliminates partial-frame failure modes.#ifdef __APPLE__to loadlibftdi1.dylib/libftdi1.1.dylibinstead of.sovariants.writeBytessize parameter (0 and >65536 rejected)std::minwith array size)ftdi_write_data_set_chunksize(was copy-paste of "ftdi_usb_reset")ProviderSpiLibFtdi.cppProblem
FTDI USB devices get assigned a dynamic bus:device address on each boot. HyperHDR stored this address (e.g.
1794= bus 7, device 2) in the config and usedftdi_usb_open_bus_addr()exclusively. After a reboot, the address changes (e.g. to1797= bus 7, device 5) and HyperHDR can't find the device.Solution
ftdi_usb_open_string()support toProviderSpiLibFtdi— if output is a number, uses the existing bus:addr path (backward compatible); if it's a string, usesftdi_usb_open_string()which supports formats like:s:<vendor>:<product>:<serial>— open by serial number (recommended, stable)i:<vendor>:<product>— open by vendor/product IDd:<device_node>— open by device pathftdi_usb_get_strings()to discovery so it reports serial numbersProviderSpi::init()to route string identifiers to the FTDI providerTested on
usb_c)s:0x0403:0x6014:usb_cworks regardless of USB enumeration orderTest plan