Wednesday, June 5, 2024

[Tech] LimeSDR on ARM64 Windows (almost)

I recently got a LimeSDR Mini v2.0, a software-defined radio receiver-transmitter that's open-source, open-hardware, and generally awesome. (OK, that last bit is an opinion--I did back the CrowdSupply project, so I definitely thought it was Neat.)

Since ARM64 Windows is, shall we say, an intrinsic part of my day job, I naturally tried using the LimeSDR on the Lenovo ThinkPad X13s I use. It didn't work.

So, what's a software developer to do when an open-source project doesn't work?

Make it work and contribute back the changes, of course!

(This a hobby post and is neither part of my day job nor otherwise compensated, but part of why I'm finally doing it is because I helped staff the Windows on Arm booth at the recent Microsoft Build conference and got excited about it. I do not speak for my employer.)

I've helped a couple of projects with adding ARM64 Windows support before, including Chromium and OpenVPN('s driver), so I came into this expecting a pretty straightforward sequence of challenges for the main project and each of its dependencies:

  • The build system doesn't know what ARM64 Windows is
    • "not x86 (IA32) is x64 (x86_64)" is a common assumption which is now invalid
  • A dependency doesn't know what ARM64 Windows is
    • Putting the 'curse' in 'recurse'
  • The code needs to know what ARM64 Windows is
    • drivers
      • not just for hardware: diagnostics, anti-cheat/DRM, VPNs...
      • N.B. If emulation is sufficient for usermode, applications can use an ARM64 driver with an x64 app! OpenVPN, Wireshark, and others have worked this way in the past, and that's how Genshin Impact runs at the time of this writing. (Shout out to the Windows Gaming team for their work getting that to happen!)
    • other process architecture detection
    • assembly
    • installers

So, off to the races I went.

Just Try It

The best way I've found to start is to find the software's Getting Started page and, well, get started:

https://wiki.myriadrf.org/Getting_Started_with_the_LimeSDR

While prebuilt x64 PothosSDR with the LimeSuite installed from MyriadRF did, in fact, run, FTDI hadn't (yet!) published drivers for their USB3 chips on ARM64--so installing the driver was impossible and LimeSuite couldn't find the device.

Driver-Usermode Split?

So, the closed-source driver needed to be ported. Fortunately, I was one of several people that has asked FTDI for this driver over the last couple of years, and at the beginning of May they reached out to me and shared a beta version of the driver.

Great! As I pulled it apart and tried to use it with the existing x64 code, it slowly dawned on me that the existing code was inadequate: FTDI had moved from a custom driver to leveraging WinUSB, an in-box Windows driver, so the usermode lib they provide had changed drastically and had to be integrated afresh into the software.

So: Port

I decided to port the entire stack for two reasons:

  1. I'm a sucker for punishment and have been doing this for a while, so it sounded like fun.
    1. This is also the best thing for the ecosystem, as it shakes cobwebs loose from basically everything.
  2. I learned from the forums that Mini 2.0 support is only in tip-of-tree LimeSuite anyhow.

Happily, LimeSuite comes with 'install from source' instructions! Looking through the instructions, the dependency rabbit hole looks like this:

  1. LimeSuite's build depends on:
    1. wxWidgets
    2. SoapySDR
    3. FX3 SDK (x64 only last I checked)
    4. FTDI FCIB*
  2. PothosSDR consumes a plugin built as part of LimeSuite
    1. GNU Radio and more, I'm sure?

Fortunately, the bulk of this is either optional or open-source, so we can have some fun!

TIP: When you start on an unfamiliar project especially with an unfamiliar build system, build it for a supported platform first. This familiarizes you with the tools involved and shakes out any odd details or outdated instructions; in my case, this is how I figured out the CMake paths below.

*FCIB == Foreign Checked-In Binary. The original, a Dave-Cutler-ism, had a different initiating word.

wxWidgets

wxWidgets powers the independent LimeSuite tools like LimeUtil. Happily, it comes with build-from-source instructions:

https://github.com/wxWidgets/wxWidgets/blob/v3.2.5/docs/msw/install.md

After clone (https://github.com/wxWidgets/wxWidgets.git), submodule init, and submodule update, it's time to a) update the build system and b) build the code.

Build System

Since wxWidgets supports Visual Studio 2022, it was trivial to simply add ARM64 as a Platform in the VS Solution and projects files. (NMake can do ARM64 too, but I avoid it when I can so I haven't tried.)

In particular, I installed Visual Studio 2022 Community, opened build/msw/wx_vc17.sln in the GUI, then used the Configuration Manager to add ARM64 as a Platform with values copied from x64.

Build

Both the GUI and MSBuild at the command line work.

Note: If you're on an ARM64 device and prefer the command line, start "ARM64 Native Tools Command Prompt for VS 2022 Preview" from the Start Menu or use vcvarsall.bat as usual; the "Developer Command Prompt" and PowerShell equivalent default to 32-bit tools which lack the virtual address space to link parts of wxWidgets. (Edit: I found another hack, which is to set the PreferredToolArchitecture as either a property (/p:PreferredToolArchitecture) or an env var to "arm64".)

The MSBuild invocation I used was:

msbuild /p:Platform=ARM64 /p:Configuration=Release .\wx_vc17.sln 

You can confirm the output binaries are ARM64 with Link.exe:

>link /dump /headers build\msw\ARM64\Release\wxmsw33u_gl.lib | findstr /i machine

            AA64 machine (ARM64)
            AA64 machine (ARM64)
            AA64 machine (ARM64)

Integrate

For the LimeSuite build, point wxWidgets_ROOT_DIR at the root of the Git repo and wxWidgets_LIB_DIR to the folder with all the LIB files in it; in my case it was in the Git repo as build\msw\ARM64\Release, but MSBuild, GUI, and NMake builds all produce different folder structures.

SoapySDR

This one is mostly easy because it's a pure CMake project with no dependencies and CMake fully supports targeting ARM64 Windows these days. The trick is to actually run the 'installation' target to prep the CMake file structure for LimeSuite to use:

  1. clone https://github.com/pothosware/SoapySDR.git
  2. CD to the repo
  3. make a CMake folder ('build')
  4. CD to the new folder
  5. Run CMake init with CMAKE_INSTALL_PREFIX set to a writable location.
    1. cmake .. -G "Visual Studio 17 2022" -A ARM64 -DCMAKE_INSTALL_PREFIX=%USERPROFILE%\source\repos\SoapySDR-inst
  6. Build with CMake
    1. cmake --build . --config Release
  7. Install
    1. cmake --build . --config Release --target install
  8. The path of the 'cmake' folder under CMAKE_INSTALL_PREFIX will be the value of SoapySDR_DIR in the LimeSuite build.

LimeSuite

We'll follow the instructions for installing from source, but with some changes. (As noted above, I figured most of this out by building it on x64 for x64 first so I'd know what 'working' looks like.)

Git repo: https://github.com/myriadrf/LimeSuite.git

Tweaked process:

  1. CD into the repo
  2. Make a folder for CMake to work in
  3. CD into that folder
  4. Make changes needed to support ARM64 Windows with the new FTDI driver
    1. Update the files under src/ConnectionFTDI/FTD3XXLibrary using files from the beta driver package. Don't forget to both update the header and add the ARM64 folder.
    2. Edit src/ConnectionFTDI/CMakeLists.txt to point to the ARM64 folder in the "pointer size is 8 bytes" case. This is ugly, but it looks like there isn't a good solution. This is the MSVC case, so maybe CMAKE_SYSTEM_PROCESSOR is actually useful instead of buried under years of compatibility assumptions.
  5. Run CMake to set up the build
    1. cmake .. -G "Visual Studio 17 2022" -A ARM64 -DwxWidgets_ROOT_DIR=%USERPROFILE%\source\repos\wxWidgets -DwxWidgets_LIB_DIR=%USERPROFILE%\source\repos\wxWidgets\build\msw\ARM64\Release -DSoapy
      SDR_DIR=%USERPROFILE%\source\repos\SoapySDR-inst\cmake
  6. Build with CMake
    1. cmake --build . --config Release
  7. Run LimeSuite independent tools
    1. pushd bin\Release
    2. ..\..\LimeUtil\Release\LimeUtil.exe --find

VS Code Config

As part of debugging to figure out the SoapySDR_DIR value, I put together this VS Code config file (.vscode/settings.json):

{
    "cmake.configureArgs": [
        "-DwxWidgets_ROOT_DIR=${env:USERPROFILE}/source/repos/wxWidgets",
        "-DwxWidgets_LIB_DIR=${env:USERPROFILE}/source/repos/wxWidgets/build/msw/ARM64/Release",
        "-DSoapySDR_DIR=${env:USERPROFILE}/source/repos/SoapySDR-inst/cmake"
    ]
}

Figured someone might like to have that handy. Note that this requires VS Code >1.79

Running (ha! no)

At this point, I made sure the driver (.INF) was installed. Contrary to what the Driver Installation instructions say, the new driver enumerates in Device Manager as a Universal Serial Bus Device (instead of Controller) named "LimeSDR Mini". (I haven't checked, but I suspect this name comes from the WinUSB descriptor.) The driver file itself is WinUSB.sys.

Then I ran LimeSuite.exe --find and was able to find the device, but --update failed with a 'board not supported' error, so it's down to debugging. I'll need to grok a lot of things before I know if the bug is in LimeSuite vs. FTDI's beta driver, though I'm leaning towards the latter at this point--after all, that's what Beta programs are for! [Update! After chatting with FTDI, I'm leaning towards an issue in LimeSuite--but it'll be a while before I get to digging that deep.]

No comments:

Post a Comment