MiniPlex and Linux

The folks at ShipModul (archive) who make the MiniPlex-3 currently provide software only for Windows and MacOS; though with the latest release of MPXConfig it’s only available for Windows. I happen to run Linux as my primary OS, though I do have access to Windows either via my work laptop or a virtual machine. That said, the work machine has restrictions on what can be connected to it, so I have a USB-C SSD (using Ventoy) that I boot from when I want to run personal software. I have run the configuration software from the work installation of Windows before, but would like to get away from doing that.

Serial communication

The MiniPlex-3 uses a FTDI chip to present a serial port over USB, running at 406,800 bits/second. Connect a serial terminal (like Tera Term or minicom) to the presented serial port, and NMEA sentences spew by pretty quickly.

A driver must be installed in Windows for this to work, and it registers different USB IDs than other equipment that uses a similar chip (my AIS equipment).

The solution

In case someone other than me is reading this, the short form is you load two registry files into Wine via regedit, ensure the USB serial to COM port name mapping is correct in Wine, and then start up MPXConfig3.exe. It can even do firmware upgrades.

You should probably use a WINEPREFIX to isolate this configuration from other uses of Wine.

If you’re curious about how I worked all of this out, read on.

Oracle VirtualBox

My first attempt at getting the configuration program to work was to install VirtualBox on my portable Linux installation, and run a Windows 10 guest VM. I know the MPXConfig3 program works on Windows 10; I’ve run it natively on the work installation before.

The USB connection to the MiniPlex does not show up as a USB device for VirtualBox. Instead, a serial port has to be enabled, connected to a Host Device at a particular path. In this case, /dev/ttyUSB0 (not ACM0, because it’s an actual UART-over-USB) (archive).

This configuration works in Tera Term once the connection speed is set correctly, but MPXConfig declares that it cannot find the MiniPlex-3. VirtualBox is about impossible to debug, so I changed tack and tried a different way of running the config software. It will turn out that I could probably have made it work in VirtualBox, based on my further experimentation, but post-fact rationalisation is “running a whole OS to run one program is a bit silly”.

Wine

Formerly known as WINE Is Not an Emulator, Wine enables Windows software to run on Linux by pretending to be something that resembles Windows. This is the basis of Steam’s Proton layer, which is making them a lot of money in the form of the Steam Deck.

Wine is able to launch MPXConfig3 just fine, but the configuration program does not see the USB port. This is where life gets a bit frustrating, because the ‘net is full of posts about Wine and serial ports, but they mostly fall in to two camps – symlinks in .wine/dosdevices/, or registry keys in HKEY_LOCAL_MACHINE\Software\Wine\Ports.

The manual symlink approach is stale news though; Wine gained automatic detection in May of 2017, and the registry keys are used to override mappings to different port names if needed.

I’ve cross-checked my .wine/dosdevices/ and registry keys, and Wine is definitely picking up the ttyUSB0 device and mapping it to COM1. My user is in the appropriate group to use ttyUSB0 (dialout).

Side note, I thought about a program I used to use for dial-up – Procomm Plus. Amazingly, a website for it still exists, though it may be a tiny bit out of date with the modern internet.

Tracing Registry and SetupAPI calls in Wine

Wine’s documentation on how to trace debug channels is not great. It assumes you’ve got source code to Wine (which I could get) to grep out all of the debug channel names.

Why do I want the debug channel names? Because of this post (archive), which talks about turning on the traces and working out what a piece of software was doing. That post was invaluable to getting me on the right path to solve this problem.

It turns out the incantation I need is WINEDEBUG=trace+reg,trace+setupapi wine MPXConfig3.exe 2>&1 | tee logfile. To discern when the trace probably has activity from the configuration program (and nothing else, because this isn’t a whole Windows machine with background activity), searching the resulting log file for ‘MPXC’ is enough to find when the registry key for Image File Execution Options is being read – that should be read right before the executable starts to run, and any registry keys etc accessed after that will either be Wine starting the program, or the program itself doing registry work.

My hope was to find accesses to serial port registry keys. Alas, if the right keys aren’t in place, a trace doesn’t give any useful information, because the MiniPlex configuration program is using the Windows enumeration APIs to find where the FTDI driver has been installed.

grep -E '4d36e978|VID_0403' -A 5 notworking.log | sed -e 's/0024:trace://'
reg:NtOpenKeyEx (0xa4,L"{4d36e978-e325-11ce-bfc1-08002be10318}",f003f,0x8efa78)
reg:NtOpenKeyEx <- 0xa8
reg:RegQueryValueExW (000000A8,L"Class",00000000,00000000,008EFAD0,008EFA7C=512)
reg:NtQueryValueKey (0xa8,L"Class",2,0x8ef900,256)
--
setupapi:SetupDiGetClassDevsExW {4d36e978-e325-11ce-bfc1-08002be10318} (null) 00000000 0x00000002 00000000 (null) 00000000
setupapi:SetupDiCreateDeviceInfoListExW {4d36e978-e325-11ce-bfc1-08002be10318} 00000000 (null) 00000000
setupapi:SETUPDI_EnumerateDevices 0092B5E8, {4d36e978-e325-11ce-bfc1-08002be10318}, (null), 00000002
reg:NtCreateKey (0x1c,L"System\\CurrentControlSet\\Enum",(null),0,20019,0x8ef9dc)
reg:NtCreateKey <- 0xa4
reg:RegEnumKeyExW (000000A4,0,008EF9E8,008EF9E0(260),00000000,00000000,00000000,00000000)
reg:NtOpenKeyEx (0xa4,L"DISPLAY",20019,0x8ef9e4)
reg:NtOpenKeyEx <- 0xa8

That’s the result of tracing an execution of MPXConfig3.exe in a Wine environment that only has Wine’s basic COM port registry keys. As an aside, {4d36e978-e325-11ce-bfc1-08002be10318} is “Ports” according to Microsoft.

ProcMon and Windows

The next approach was to go back to Windows, and break out Mark Russinovich’s excellent Process Monitor program. I don’t have any screenshots, but the approach was to set up filters for the process name containing “MPXC”, then starting the configuration program and promptly closing it.

Since I didn’t know what I was looking for precisely, I searched for the same Image File marker in the procmon data, and then started reading all of the registry key accesses. I knew vaguely what I was looking for – the Ports GUID – but only that; it was what the program did after that that I wanted to know. The first registry key of interest was HKLM\SYSTEM\CurrentControlSet\Control\Class{4d36e978-e325-11ce-bfc1-08002be10318}, but I kept scrolling to see what happened after that key was read.

This technique led to the discovery of a second registry area that the configuration program uses, also in HKLM – HKLM\SYSTEM\CurrentControlSet\Enum\FTDIBUS\VID_0403+PID_FD4B+3B020373A. That area of the registry has some data about the FTDI driver, though it turns out a lot of it is not needed to make the configuration program work.

I extracted two registry areas, and sent the files to myself via email; can’t plug USB mass storage into the work laptop when Windows is running (understandable, it’s a large security hole for a corporate entity). Rebooted back into Linux, and pulled the registry files down and put them somewhere that Wine’s regedit program could read.

With the registry keys loaded, I ran the configuration program under Wine, clicked on the dropdown menu for how to communicate with the MiniPlex and hey presto, a COM port connection. Chose it, clicked Connect, and promptly got a dialog warning me that the MiniPlex wasn’t running an up-to-date firmware. Skipped that, and instantly the terminal section of the software started scrolling with NMEA sentences. Holy shit, it works. From Linux, via Wine.

So, what does a trace of the working setup look like, and how does it differ from the non-working? The non-working setup above has a pretty short trace, while a working setup has a much longer trace output.

To start, the handle returned for the Ports query is different – 0x98 instead of 0xA8.

reg:NtOpenKeyEx (0x94,L"{4d36e978-e325-11ce-bfc1-08002be10318}",f003f,0x8efa78)
reg:NtOpenKeyEx <- 0x98
reg:RegQueryValueExW (00000098,L"Class",00000000,00000000,008EFAD0,008EFA7C=512)
reg:NtQueryValueKey (0x98,L"Class",2,0x8ef900,256)

To be sure, I re-ran the traces, and that handle is consistent for each environment (no reg keys vs reg keys). Windows handles are outside of my scope of knowledge, so perhaps they’re different because the overall registry is different.

Anyway, the next portion of the trace is again similar but different, with the handle for accessing CurrentControlSet\Enum changing between the two runs.

setupapi:SetupDiGetClassDevsExW {4d36e978-e325-11ce-bfc1-08002be10318} (null) 00000000 0x00000002 00000000 (null) 00000000
setupapi:SetupDiCreateDeviceInfoListExW {4d36e978-e325-11ce-bfc1-08002be10318} 00000000 (null) 00000000
setupapi:SETUPDI_EnumerateDevices 0092B590, {4d36e978-e325-11ce-bfc1-08002be10318}, (null), 00000002
reg:NtCreateKey (0x1c,L"System\\CurrentControlSet\\Enum",(null),0,20019,0x8ef9dc)
reg:NtCreateKey <- 0x94

That point is basically where the non-working trace stops. On a working (ie, registry keys set) setup, the trace continues.

reg:NtOpenKeyEx (0x98,L"VID_0403+PID_FD4B+3B020373A",20019,0x8ef764)
reg:NtOpenKeyEx <- 0x9c
setupapi:SETUPDI_EnumerateMatchingDevices L"VID_0403+PID_FD4B+3B020373A"
setupapi:SETUPDI_EnumerateMatchingDeviceInstances L"FTDIBUS" L"VID_0403+PID_FD4B+3B020373A"
reg:RegEnumKeyExW (0000009C,0,008EF508,008EF30C(260),00000000,00000000,00000000,00000000)
reg:NtOpenKeyEx (0x9c,L"0000",20019,0x8ef310)
reg:NtOpenKeyEx <- 0xa0
reg:RegQueryValueExW (000000A0,L"ClassGUID",00000000,008EF314,008EF328,008EF30C=80)
reg:NtQueryValueKey (0xa0,L"ClassGUID",2,0x8ef1a0,92)
setupapi:create_device 0092B590, {4d36e978-e325-11ce-bfc1-08002be10318}, L"FTDIBUS\\VID_0403+PID_FD4B+3B020373A\\0000", 0
reg:NtCreateKey (0x1c,L"System\\CurrentControlSet\\Enum",(null),0,f003f,0x8ef220)
reg:NtCreateKey <- 0xa4
reg:NtCreateKey (0xa4,L"FTDIBUS\\VID_0403+PID_FD4B+3B020373A\\0000",(null),0,2001f,0x8ef260)
reg:NtCreateKey <- 0xa8
reg:NtSetValueKey (0xa8,L"ClassGUID",1,0x8ef260,78)
reg:NtOpenKeyEx (0x1c,L"System\\CurrentControlSet\\Control\\Class",f003f,0x8ef0c8)
reg:NtOpenKeyEx <- 0xa4
reg:NtOpenKeyEx (0xa4,L"{4D36E978-E325-11CE-BFC1-08002BE10318}",f003f,0x8ef0cc)
reg:NtOpenKeyEx <- 0xac
reg:RegQueryValueExW (000000AC,L"Class",00000000,00000000,008EF220,008EF18C=64)
reg:NtQueryValueKey (0xac,L"Class",2,0x8ef020,76)
reg:NtSetValueKey (0xa8,L"Class",1,0x8ef220,12)
setupapi:create_device Created new device 0092C898

As best I can tell, the presence of the FTDIBUS key in Enum is what makes this all work, because it tells the configuration program how to communicate with the device.

Registry Keys

The first key needed is a Ports Control\Class key.

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\{4d36e978-e325-11ce-bfc1-08002be10318}\0001]
"ProviderName"="ShipModul"
"MatchingDeviceId"="ftdibus\\comport&vid_0403&pid_fd4b"
"DriverDesc"="MiniPlex-3 Serial Port"

The second is a FTDIBUS Enum key. This key matches up by name (mostly) to the MatchingDeviceId from the Ports key. The FriendlyName key is what gets displayed in the dropdown for Port in the configuration tool.

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\FTDIBUS\VID_0403+PID_FD4B+3B020373A]

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\FTDIBUS\VID_0403+PID_FD4B+3B020373A\0000]
"ClassGUID"="{4d36e978-e325-11ce-bfc1-08002be10318}"
"Driver"="{4d36e978-e325-11ce-bfc1-08002be10318}\\0001"
"FriendlyName"="MiniPlex-3 Serial Port (COM1)"

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\FTDIBUS\VID_0403+PID_FD4B+3B020373A\0000\Device Parameters]
"PortName"="COM1"
"PollingPeriod"=dword:00000000
"ConfigData"=hex:01,03,3f,3f,06,40,06,40,06,40,06,40,06,40,06,40,06,40,06,40,\
  06,40,06,40,06,40,06,40,06,40,00,00,06,40
"MinReadTimeout"=dword:00000000
"MinWriteTimeout"=dword:00000000
"LatencyTimer"=dword:0000000a

I tried to only load the PortName value in the Device Parameters section, but the configuration software didn’t list a COM port as a valid way to access the MiniPlex-3. It turns out that ConfigData is important.

I probably would have saved myself some time if I’d read the INF file for the FTDI port; it’s right there in the drivers package for the MiniPlex-3. The INF file is documented slightly, showing different ConfigData entries for different devices. It also identifies the various USB IDs that show up, so that this approach of pushing registry keys into Wine can be used for the different models of MiniPlex.

First off, four different types of MiniPlex can be identified by the driver.

VID_0403&PID_FD48.DeviceDesc="MiniPlex Serial Port"
VID_0403&PID_FD49.DeviceDesc="MiniPlex Serial Port"
VID_0403&PID_FD4A.DeviceDesc="MiniPlex Serial Port"
VID_0403&PID_FD4B.DeviceDesc="MiniPlex-3 Serial Port"

I found some FTDI documentation, and it indicates that the Enum key will be the VID, PID, and interface name (though I think for the MiniPlex the interface name is replaced by the serial number of the device).

Next, four different ConfigData entries.

; variable baud port
[PortFD48.NT.HW.AddReg]
HKR,,"ConfigData",1,01,03,3F,3F,10,27,88,13,C4,09,E2,04,71,02,38,41,9C,80,4E,C0,34,00,1A,00,0D,00,06,40,03,80,00,00,D0,80

; 57600 baud port
[PortFD49.NT.HW.AddReg]
HKR,,"ConfigData",1,01,03,3F,3F,34,00,34,00,34,00,34,00,34,00,34,00,34,00,34,00,34,00,34,00,34,00,34,00,34,00,00,00,34,00

; MiniPlex-2Wi 115200 baud port
[PortFD4A.NT.HW.AddReg]
HKR,,"ConfigData",1,01,03,3F,3F,1A,00,1A,00,1A,00,1A,00,1A,00,1A,00,1A,00,1A,00,1A,00,1A,00,1A,00,1A,00,1A,00,00,00,1A,00

; MiniPlex-3 406800 baud port
[PortFD4B.NT.HW.AddReg]
HKR,,"ConfigData",1,01,03,3F,3F,06,40,06,40,06,40,06,40,06,40,06,40,06,40,06,40,06,40,06,40,06,40,06,40,06,40,00,00,06,40

That ConfigData block lines up with what’s written to the registry, though in the latest INF file the LatencyTimer data is different. FTDI say the default is a DWORD of 16 for 16 ms. My exported key has a DWORD of A, for 10 ms, and the latest INF file has a value of 2 ms.

Next steps

This solution basically solves my problem – how to run the configuration program for a MiniPlex-3 on Linux. However, it opens up the world of reverse engineering the communication between the software and the device. The company behind the MiniPlex only produce a Windows and MacOS configuration program, but it’s basically doing NMEA sentences to instruct the device on how to change the configuration, so it should be possible to write something in Python or Rust that works natively on Linux.

The advantage of a native program would be the ability to use it on non-x86 computers, like a Raspberry Pi or other ARM-based small computer. These require far less power to operate than a laptop (single digit watts), and are thus more friendly to yachts with battery banks.

I’ve already captured about 15 minutes of communication between the software and the device; the software has a diagnostic log, so I was able to toggle various checkboxes and see what sentence was sent. Lots of bit flags!