M-Audio Ozone (built 2003) with OpenSuse Leap

M-Audio sold around 2003 this MIDI keyboard. Besides the 2 octave keyboard it offers also an external USB Sound card with many audio connections. Moreover it offers controls to control a DAW application. Target Operating System were Mac and Windows.

I got such a keyboard in 2020 and tried to use it with Linux. This is possible and I describe here what has to be done.

The connectivity between Keyboard and PC is USB, with version 1.1. This is slow, but enough for Stereo 48KBit or Mono 96KBit .

I tested the MIDI feature of the keyboard (MIDI out connection) and it works fine. More on that maybe later in another article.

After connecting the USB cable, the device is not recognized as an Audio Device.

[ 4701.664414] usb 1-5: new full-speed USB device number 11 using xhci_hcd
[ 4701.816379] usb 1-5: New USB device found, idVendor=0763, idProduct=2808
[ 4701.816390] usb 1-5: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[ 4701.816396] usb 1-5: Product: Unknown
[ 4701.816401] usb 1-5: Manufacturer: Unknown

To have the device recognized, it is required to load an updated firmware into it. Therefore two tools exist: fxload and madfuload.

While madfuload can read in a binary file, the newer fxload requires a INTELHEX file. It is possible to convert a binary file into a INTELHEX file, so these two tools are basically the same. fxload is part of OpenSuse repositories and can be installed from there. madfuload seems to be outdated, but its source package can still be downloaded

I installed madfuload from source:

  tar xvzf madfuload-1.0.tar.gz
  cd madfuload-1.0/

madfuload requires to give the path in USB device tree with ‚-D‘ option. To get the name of the device, use lsusb:

dennis@linux-h3a7:~> lsusb

Bus 001 Device 021: ID 0763:2808 M-Audio 

This means the path is /dev/bus/usb/001/021. Then load the firmware (as root):

sudo ./madfuload -f ma008100.bin -vvv -D /dev/bus/usb/001/021 -3
 ma008100.bin: 5675 bytes read successfully
 reading device descriptor …
 interface descriptor 0:0
 DFU interface is 0
 DFU descriptor found
 transfer size is 64
 waiting 32 ms
 cannot reset device: (19) No such device

/usr/sbin/hwinfo –sound

Check availability of hardware:

36: USB 00.1: 0401 Multimedia audio controller
  [Created at usb.122]
  Unique ID: NwzV.rkmn5HwiMPD
  Parent ID: k4bc.2DFUsyrieMD
  SysFS ID: /devices/pci0000:00/0000:00:14.0/usb1/1-5/1-5:1.1
  SysFS BusID: 1-5:1.1
  Hardware Class: sound
  Model: "M-Audio Ozone"
  Hotplug: USB
  Vendor: usb 0x0763 "M-Audio"
  Device: usb 0x2008 "M-Audio Ozone"
  Revision: "1.00"
  Speed: 12 Mbps
  Module Alias: "usb:v0763p2008d0100dc00dsc00dp00ic01isc02ip00in01"
  Driver Info #0:
    Driver Status: snd-usb-audio is not active
    Driver Activation Cmd: "modprobe snd-usb-audio"
  Config Status: cfg=new, avail=yes, need=no, active=unknown
  Attached to: #40 (Hub)

See line ‚Driver Activation Cmd‘ how to load the driver (as root). After that, the device is visible and can be used in e.g. audacity and other tools with name like ‚Ozone: USB Audio (hw: 0,1)‘.

sudo modprobe snd-usb-audio

It is also possible to enter the firmware load as a udev rule:


# Using madfuload
ACTION=="add", SUBSYSTEM=="usb", ENV{PRODUCT}=="763/2808/*", RUN+="/usr/local/sbin/madfuload -l -3 -f /usr/local/share/usb/maudio/ma008100.bin -D $env{DEVNAME}"
# Using fxload with two ihx files, one 'loader' and one 'firmware' file
ACTION=="add", SUBSYSTEM=="usb", DEVPATH=="/.0", ENV{PRODUCT}=="763/2808/", RUN+="/sbin/fxload -s /usr/local/share/usb/maudio/MidiSportLoader.ihx -I /usr/local/share/usb/maudio/ma008100.ihx"

Use kmix and kmixcrtl to check the sound card availability, order and more things.

After this basic integration into Linux audio layer, the keyboard can be used as any other audio device.

Computer History Project 2018: Bringing back a Morrow V9054 1.8 GHz spectrum analyzer back to life

After having revived a HP E1498 VXI controller, which is a VXI Slot sized version of a HP9000 UNIX workstation from the late 90ies of the last century (LINK here) and the revival of a military use Subsonic analysis VME mainframe, it was only a question of time and opportunity to start the next computer history project.

The opportunity came in march 2018. Someone at eBay sold several new old stock Spectrum Analyzers.
I always looked for such a tool but was not willing pay 700 Euros or more for it. The eBay offer was roughly 300 Euros including delivery from USofA and customs. The reason for the low price was that the Analyzer came as a VXI slot card. This means no display, no input knobs and switches. A VXI mainframe controls the card and allows to transfer the data to a PC.

For most people, this is not very attractive. But I have a beautiful VXI  mainframe running, no problem to insert the spectrum analyzer card. So I decided to buy it. Some days later the analyzer arrived.

Morrow Technologies V9054 1.8Ghz Spectrum Analyzer

Morrow Technologies is a company that developed complex hardware solution for its customers. At some point in the mid 90s, they decided to develop some measurement devices because they need such tools themselves and to sell them in the VXI market.
They produced several Spectrum Analyzers, most of them are VXI based devices, which was a unique selling point then and as far as I know, is still today. They went out of the Spectrum Analyzer business later but are still
present with their main product line, which are industrial displays. Maybe I am leaving out important things, but this is what I understand from their website and company history.

My device is a Morrow V9054. It can be used in the range 100KHz .. 1.8GHz. It is a C sized VXI card which occupies two slots in the mainframe. Control is completely via VXI Bus. This means all control commands are send via VXI Word Serial Protocol and all responses (e.g. traces) are sent back by the same channel.

Displaying analyzer data in real-time, can this be done?

Displaying trace data with e.g. 25 frames per second requires transferring roughly 25*2K bytes per second from the VXI mainframe to the PC (for 1024 trace points with 16 bit value per point). This is roughly a continuous transfer of 50 KByte/s. Rendering must be able to process 1K data values with a rate of 25 frames per second.

I want to display the data in a web browser and to transfer the data with WebSockets.
So my first evaluation was to check if these requirements can be met with that technology.  I wrote a backend that generates some fake data and runs on the mainframe controller. This backend uses libwebsockets, a C implementation of WebSockets. The frontend was a simple TypeScript browser application that uses a HTML5 Canvas to render the data as pixels like a scope display.

The first test, without rendering, just pure transmission speed test, showed that I can transfer 600KBytes/second between a C written client and server. The VXI mainframe offers only 10MBit LAN access. So the data transfer
will have no problem with the required data transfer rate. The transmission rate would allow more than 200 frames per second.

The second test was to add HTML5 rendering and to use a browser as client. I experimented with several frame rates between 10 and 50 frames per second and found that the browser is not dropping frames up to 40 frames per second. My requirement is 25 fps, so rendering is also no problem.

To summarize, the targeted technologies would allow a nice rendering of the  analyzer data.

The question is then, how fast can the spectrum analyzer deliver its data?

Accessing the Spectrum Analyzer

The Device arrived with one diskette and one CD.
The diskette (20 years old) contained some libraries for Windows 95 and some include files for programmers. The CD contains a Windows 95 application which can access all spectrum analyzers from Morrow.

The libraries for Windows 95 include a PnP driver, a spectrum analyzer library and several transport layer libraries. These libraries include ISA Bus, VISA and TCP/IP. The later supports some Morrow developed protocol for some of their products that allow TCP/IP access. My device does not allow this. It only allows VXI Serial Word Protocol.

The „closest“ library for my environment is the VISA library. It uses SCPI codes to control the  device. My VXI controller supports SCPI so I am able to access the V9054 with that technology.

First test to access the device was successful. The following C program reads  out the firmware version number of the device.

#include <sicl.h>
#include <stdio.h>

int main(int argc, char **argv) { 
 unsigned short response; 
 unsigned short rpe;
 unsigned int ret;

 INST id = iopen("vxi,126");

 unsigned short cmd = 0xc8ff; // abort normal operation
 ret = ivxiws(id, cmd, &response, &rpe);
 if (response != 0xfffe) {
   printf("Error1: %xn", response);
 cmd = 0xfcff; // begin normal operation
 ret = ivxiws(id, cmd, &response, &rpe);

 cmd = 0x7c00; // get version
 ret = ivxiws(id, cmd, &response, &rpe);
 int major = response >> 4;
 int minor = response & 0xf;
 printf("Version: %d.%dn", *major, *minor );

The only two SICL functions used in this low level example are iopen() and ivxiws(). iopen() opens a communication channel to the device and ivxiws() is executing the Word Serial Protocol.

The card was sold with some manuals. I hoped to get a programmer’s guide in paper or on the CD. But I got no programmers guide. At least I have the Windows 95 compiled libraries…

We have 2018, IT has moved to mystical areas today. So hey, let’s decompile the DLLs.
I checked the available free decompilers. My first try was with Snowman (LINK). It was able to decompile, but I was not able to understand the resulting C code 🙁 . After some hours of trying, I gave up.

Second try was with RetDec. The results were much more  understandable for me, but I need two days to get the first clue whats going on in the decompiled source. Each DLL decompiled in a ~40.000 line of code file.
Most of the functions were named function_0xabfdssada() or so, and so were the parameters and variables.

I searched for known command values in the C Code. I knew some of the codes from the technical guide.  Some other codes are standard codes defined by the VXI spec. All codes are 2 byte words like 0x7c00 or 0xc7ff.
After several hours of searching, I found a function in the code that inquires the firmware version from the Spectrum Analyzer. Yahoo!
Soon I found further codes, all in the Visa library. This was the first sign that that what I was trying was possible with my means.

One of the header files describe a very large struct SA9052 which contains all configuration for the device. This device is used all through the spectrum analyzer library mtcsa32.dll .
The decompiler of course did not know anything about that header file. This means an original code like:

analyzer_config->start = 0.0; // a float value

was decompiled to:

(a1 + 16) = 0;
(a1 + 20) = 0;

So it is very important to know / calculate all byte offsets in the struct. Then the decompiled code can be converted to the original lines, which are of course much more readable.

I needed 2 further days to analyze the struct byte offsets. The compiler aligns contained datataypes to addresses. E.g. a double value is 8 bytes size and its start address inside the struct must then be a multiple of 8.
If the address which would be possible as next free address (e.g. 12) is not a multiple of 8, the next higher value dividable by 8 is used as offset (here: 16). The same is true for 4 byte data types like int32 and two byte data types
like int16.

My calculated offset values were mostly correct, but not all. By analyzing the code, I found a field used by the code but not contained in the struct definition. All offsets after that missing fields were wrong. Puh!
After inserting that field in the struct definition, I could decode all assignments back to the original source code.
I can do that manually. But when looking at ~130.000 LOC, this is not possible. An automated replacement is more or less required, but currently I am still doing this manually at the code pieces I am working on.
The replacement must be quite smart and requires a C code parser…

Going forward

After having a basic understanding what to do and how to do it, I wrote a simple test program for the first API function called mr90xx_init() .

#include <sicl.h>
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <sapform.h>
#include <sa_defin.h>
#include <str_9052.h>
#include <mr_defin.h>
#include <mrapp.h>
#include "helper.h"

int main(int argc, char **argv) {
  char sessionString[50];
  ViChar message[128];
  ViStatus mr90xxStatus;
  ViSession sessionId;

  sprintf(sessionString, "vxi,126");
  mr90xxStatus = mr90xx_init(sessionString, VI_TRUE, VI_TRUE, &sessionId);
  if (mr90xxStatus != MR90XX_IE_SUCCESS) {
    printf("Error mr90xx_initn");
  } else {
    printf("mr90xx_init OKnn");

Then I created empty files for the three decompiled DLLs named sa.c (for mrtsa32.dll), pnp.c (for mrtpnp32.dll) and visa.c for (mrtvsa32.dll). These files should contain at the end the manually changed code. I call these files the Clean Room Files.

The API function mr90xx_init() is inside mrtpnp.dll. So I copied that function to pnp.c The function calls other functions, so I copied all functions that were called by any function into the file.

By trying to compile and link my test.c , the Linker tells me what functions are still missing.

Result was that I have the complete code from the PnP DLL for the single function mr90xx_init() in a source file and not more.
Remaining are references to functions outside (to other DLLs) and to Windows APIs.

For all referenced functions in mrtsa32.dll, I did the same job and copied everything into the file sa.c. This results in a file sa.c that contains the complete code from the mrtsa32 DLL for the single function mr90xx_init() in a source file and not more.

I found that for the last library, the libvsa32.dll, the Morrow developers used another approach. There is no direct reference to VISA functions in the PnP code. They use a function table to call the transport driver function. This is a feature and allows to call different transport drivers. The driver DLL is loaded by the library depending on its configuration.

The issue here is to find the mapping and to understand how function parameters and return values are handled. I see offsets that are outside the SA9052 struct and that maybe the function pointers. But how are the functions are actually called?

At about that time I started to move my code to github. The complete project is there.

This post will be continued… newest development is always on github…