TRX API
The purpose of this tutorial is to explain about the architecture and details of TRX API. TRX API is a set of specification defined by Amarisoft which is designed for implementing software libraries connecting various RF hardware (e.g, sdr card, cpri card, USRP etc) to Amarisoft software (e.g, Amarisoft Callbox). We are sharing the specification of TRX API for other users to implement their own software libraries to connect user's hardware to Amarisoft application.
Table of Contents
Introduction
TRX API represents a critical architectural interface designed by Amarisoft to bridge the gap between diverse radio frequency (RF) hardware and the suite of Amarisoft software solutions, such as the Amarisoft Callbox. By standardizing the communication protocols and data exchange formats between the software and a variety of RF front-ends—including SDR cards, CPRI cards, and USRP devices—the TRX API enables seamless integration and interoperability in advanced wireless communication environments. The API abstracts hardware-specific implementation details, offering a unified set of function calls and signaling mechanisms that facilitate low-latency, high-throughput data transfer essential for real-time wireless protocol stacks. Within the broader telecommunications ecosystem, the TRX API empowers users to leverage custom or third-party RF hardware with Amarisoft’s software, thus fostering innovation, flexibility, and rapid prototyping in wireless network research and development. This architecture is pivotal in enabling software-defined radio solutions, supporting evolving standards, and accelerating deployment cycles for experimental and commercial wireless systems.
-
Context of TRX API
- TRX API is a hardware abstraction layer for integrating RF front-ends with Amarisoft's telecom software.
- It supports a variety of hardware interfaces, including SDR, CPRI, and USRP, ensuring broad compatibility.
- The specification defines standard data paths, timing synchronization, and control signaling between hardware and software components.
-
Relevance and Importance
- Enables rapid development and deployment of wireless networks with flexible hardware choices.
- Supports research, prototyping, and commercial deployments by decoupling software from hardware dependencies.
- Promotes community-driven innovation by allowing third parties to develop their own TRX-compliant libraries for new or proprietary RF hardware.
-
Learning Outcomes
- Understand the high-level architecture and design principles behind the TRX API.
- Gain insight into how Amarisoft software interfaces with RF hardware through standardized APIs.
- Acquire the foundational knowledge required to implement or adapt TRX API libraries for custom hardware.
- Develop awareness of best practices for integrating and validating new RF hardware within Amarisoft’s ecosystem.
-
Prerequisite Knowledge
- Familiarity with wireless communication systems and RF hardware concepts.
- Basic understanding of software APIs, device drivers, and data path architectures.
- Experience with C/C++ programming and software integration is advantageous for implementation work.
- Prior exposure to Amarisoft software stack is helpful but not strictly required.
Summary of the Tutorial
This tutorial provides an overview of the TRX API structure, its implementation in Amarisoft products, interaction mechanisms between the application and TRX API software, and the use of a dummy template (trx_example.c) for TRX driver development. The document also outlines the procedures and methodologies for initializing and connecting TRX drivers in a test or development environment.
-
Underlying Structure and Architecture
- The TRX Driver exposes a set of APIs (TRX API) mapped to various TRX operation functions within the Amarisoft Application.
- TRX API controls or communicates with RF hardware either directly or through an additional RF Driver layer.
- In Amarisoft SDR implementations, the TRX driver (e.g., trx_sdr.so) interfaces with user-level (libsdr.so) and kernel-level (sdr.ko) drivers to achieve hardware abstraction and control.
-
Interaction between Application Software and TRX API
- Three main interaction mechanisms are described:
- Type 1: Function Invocation
- Application software invokes TRX API functions (e.g., trx_xxxx_func()).
- Acts as triggers for TRX operations and is handled through callback functions.
- Type 2: Parameter Retrieval
- Application queries the TRX API for various parameters (e.g., trx_get_param_xxxx()).
- Allows retrieval of configuration and operational parameters by the TRX API.
- Type 3: Data & Configuration Exchange
- Parameters are shared between the application and TRX API via a common parameter set.
- Ensures correct RF driver configuration and coherence between software stack and SDR hardware.
- Type 1: Function Invocation
- Three main interaction mechanisms are described:
-
TRX Dummy Template (trx_example.c) for Driver Development
- A template source file (trx_example.c) is provided as a starting point for TRX driver development.
- Users are instructed to:
- Unpack (untar) the template from the installation package (not installed by default).
- Compile it using the provided Makefile.
- Modify function names and add custom code to suit specific RF hardware requirements.
- The template includes essential API functions such as:
- trx_driver_init
- trx_dummy_start
- trx_dummy_read
- trx_dummy_write
- trx_dummy_end
-
Test Procedure: TRX Driver Initialization (trx_driver_init)
- The trx_driver_init function is the first to be called for establishing the connection between the Amarisoft Application and the TRX driver.
- Main steps:
- Checks ABI compatibility between the LTEENB and the TRX driver; logs error and returns -1 on mismatch.
- Allocates and initializes the internal state structure (e.g., TRXDummyState), setting important fields (e.g., dump_max).
- Updates the TRXState structure by mapping operational callbacks (end, write, read, start, get_sample_rate) to corresponding dummy functions.
- Links driver state to user-defined structures for internal use (e.g., s1->opaque = s).
- Returns 0 on successful initialization.
- This procedure sets up the driver with dummy behaviors for all core operations, serving as the foundation for further customization.
Overall, the tutorial guides users through understanding the TRX API structure, adapting the provided template for their own hardware, and correctly initializing the driver to establish communication with the Amarisoft software stack. The focus is on high-level procedures and code structure necessary for custom TRX driver development and testing.
Underlying Structure
The underlying structure and functionality of TRX API are illustrate as below. As shown here, the component labeled as TRX Driver has a set of APIs (i.e, TRX API) which are mapped to various trx operation functions of Amarisoft Application and controls/communicate with RF hardware indirectly (i.e, via RF Driver(another layer of driver stack) or directly.

Implementation Example in Amarisoft Product
Following is some of example architectures of TRX driver being used in Amarisoft product. As illustrated here, TRX API has widely and long been used in Amarisoft software stack implying that the architecture has well been verified.

As a concrete example, the architecture of Amarisoft sdr driver is illustrate below. For Amarisoft SDR card, the TRX driver named trx_sdr.so is implemented according to TRX API specification. In case of Amarisoft sdr, trx_sdr (TRX Driver) is mapped to Amarisoft Callbox or UEsim software and communicating with RF driver (libsdr.so and sdr.ko) which communicate/control the physical SDR card.

Interaction between Application SW and TRX API SW
In order for Application SW (e.g, Amarisoft eNB/gNB) and TRX API SW (e.g, libsdr) work together seamlessly, there should be sophisticated mechanism for interaction between them. There are roughly three types of interaction mechanism as shown below.

Followings are brief descriptions of each of these mechanism
- Type 1: Function Invocation
- LTE Software calls TRX API functions (e.g., trx_xxxx_func()).
- Acts as a trigger for TRX API operations.
- Controlled through callback function calls.
- Type 2: Parameter Retrieval
- LTE Software queries TRX API for parameters using calls like trx_get_param_xxxx().
- TRX API App retrieves configuration parameters from LTE Software.
- Type 3: Data & Configuration Exchange
- LTE Software and TRX API App share parameters through a shared parameter set.
- This exchange ensures:
- Correct RF driver configuration (rf_driver params).
- Consistency between LTE stack and SDR hardware control.
- Values can be both set and retrieved .
TRX_Dummy : The Template
As a starter of the TRX driver implementation, we provide a simple template of TRX driver named trx_example.c. You may use this template as a starting point, change the name of the function and adding your own code for controlling/connecting to RF hardware.
Overall Structure
Overall structure of trx_example.c is as shown below. It has several core APIs as specified in TRX API specification, named trx_driver_init, trx_dummy_start, trx_dummy_read, trx_dummy_write, trx_dummy_end respectively. Each of these functions are mapped to trx functions in Amarisoft Application as specified in the specification. One major difference between this template and real implementation is that the API implemented this function does not have any real connection to lower layer drivers which are connected to any hardware.

Where to get ?
The TRX API template is included in installation package but does not get installed on the system during the installation procedure. You need to unpack (untar) this component manually from the installation package.

Once you unpack (untar) the trx_example component, you will get the trx_example.c source code, trx_driver header file and Makefile to compile it.

Code Review
Now let's look into the source file and see what it mean. What you can do with this part is to try to understand overall code structure and figure out where to put your own routine to control/communicate with your own RF hardware.
trx_driver_init
As you see in the diagram of TRX API lifetime , trx_driver_init would be the first function being called to establish connection between Amarisoft Application software and TRX driver. As the name indicate, it performs various steps for initialization of TRX driver and make mapping between TRX API interface functions (e.g, trx_read_func2, trx_write_func2 etc) and user defined functions(e.g, trx_dummy_read, trx_dummy_write etc). Usually this functions does various parameter initialization but would not perform any of RF hardware configuration.
|
int trx_driver_init(TRXState *s1) { TRXDummyState *s; double val;
if (s1->trx_api_version != TRX_API_VERSION) { fprintf(stderr, "ABI compatibility mismatch between LTEENB and TRX driver (LTEENB ABI version=%d, TRX driver ABI version=%d)\n", s1->trx_api_version, TRX_API_VERSION); return -1; }
s = malloc(sizeof(TRXDummyState)); memset(s, 0, sizeof(*s)); s->dump_max = 0;
/* option to dump the maximum sample value */ if (trx_get_param_double(s1, &val, "dump_max") >= 0) s->dump_max = (val != 0);
s1->opaque = s; s1->trx_end_func = trx_dummy_end; s1->trx_write_func2 = trx_dummy_write; s1->trx_read_func2 = trx_dummy_read; s1->trx_start_func2 = trx_dummy_start; s1->trx_get_sample_rate_func = trx_dummy_get_sample_rate; return 0; } |
trx_dummy_start
As you see in the diagram of TRX API lifetime , this function corresponds to 'Start' step of the diagram. This is the good place where you start RF hardware and initialize the time stamp. You may add your own routine for starting your RF hardware (e.g, function call for your hardware specific API) in this function.
|
/* * This function initializes the dummy start process for a TRX session. * It first retrieves the internal state from the opaque pointer in the TRXState structure. The function checks if the number of RF ports is exactly one, as only one TX port is supported. * If not, it returns -1 to indicate an error. * It then sets the sample rate by dividing the numerator by the denominator from the provided parameters. The TX and RX channel counts are also set based on the input parameters. * A timestamp is generated using `gettimeofday` to compute the first RX timestamp in sample rate units. This involves converting the current time in seconds to sample rate units and adding the * microseconds part, also converted to sample rate units.Finally, it records the last disp time using `get_time_us()` and returns 0 to indicate success. */ static int trx_dummy_start(TRXState *s1, const TRXDriverParams2 *p) { TRXDummyState *s = s1->opaque; struct timeval tv;
if (p->rf_port_count != 1) return -1; /* only one TX port is supported */
s->sample_rate = p->sample_rate[0].num / p->sample_rate[0].den; s->tx_channel_count = p->tx_channel_count; s->rx_channel_count = p->rx_channel_count;
gettimeofday(&tv, NULL); /* compute first RX timetamp in sample rate units */ s->rx_timestamp = (int64_t)tv.tv_sec * s->sample_rate + ((int64_t)tv.tv_usec * s->sample_rate / 1000000);
s->last_disp_time = get_time_us(); return 0; } |
trx_dummy_read
trx_dummy_read is the function where you put the routine to read(recieve) IQ data from RF hardware. You have to implement a routine in this function that waits until IQ data from hardware is available and read a chunk of data when available. And then put the time stamp to the read chunk. (
|
/* * This function simulates reading samples for a TRX session. * It updates the timestamp for received samples and increments the received sample count. The function then calculates the end time for the current batch of samples based on the updated timestamp. * A loop is used to simulate the delay until the end time, using the PC's real-time clock. If the calculated delay is more than 10 milliseconds, it's capped at 10 milliseconds to avoid long sleeps. * Finally, for each RX channel, it fills the provided sample buffer with zeros to simulate receiving blank samples. * The function returns the count of samples supposedly read, which matches the requested count. * * Parameters: * - s1: Pointer to the TRXState structure, which includes the opaque pointer to the TRXDummyState. * - timestamp: The timestamp for the samples being read. * - samples: An array of pointers to the sample buffers for each TX channel. * - count: The number of samples per channel. * - rf_port_index: The index of the RF port being written to. * - md: Metadata associated with the write operation, including flags. */ static int trx_dummy_read(TRXState *s1, trx_timestamp_t *ptimestamp, void **psamples, int count, int rf_port_index, TRXReadMetadata *md) { TRXDummyState *s = s1->opaque; int64_t end_time, d; TRXComplex *samples; int j;
*ptimestamp = s->rx_timestamp; s->rx_timestamp += count; s->rx_count += count; end_time = ts_to_time(s, s->rx_timestamp); /* Since we don't have a real sample source, we just return zero samples and use the PC real time clock as time source */ for(;;) { d = end_time - get_time_us(); if (d <= 0) break; if (d > 10000) d = 10000; usleep(d); }
for(j = 0; j < s->rx_channel_count; j++) { samples = psamples[j]; memset(samples, 0, count * sizeof(TRXComplex)); } return count; } |
trx_dummy_write
trx_dummy_write is the function where you put the routine to write(transmit) IQ data to RF hardware. You have to implement a routine in this function that send a chunk of IQ data to RF hardware. (
|
/* * This function handles the writing of samples in the dummy TRX implementation. It processes the samples provided to it, updating the maximum sample value and saturation count. * * Parameters: * - s1: Pointer to the TRXState structure, which includes the opaque pointer to the TRXDummyState. * - timestamp: The timestamp for the samples being written. * - samples: An array of pointers to the sample buffers for each TX channel. * - count: The number of samples per channel. * - rf_port_index: The index of the RF port being written to. * - md: Metadata associated with the write operation, including flags. * * The function first checks if the write operation is not just padding and if the dump_max flag is set. * It then iterates over each TX channel and each sample within the channel: * - Computes the absolute value of each sample. * - Updates the saturation count if the sample value is at or above the maximum representable value (1.0). * - Tracks the maximum sample value encountered. * * If more than 2 seconds have passed since the last display, it prints the maximum sample value and saturation count, then resets these metrics and updates the last display time. * Finally, it increments the total count of transmitted samples. */
static void trx_dummy_write(TRXState *s1, trx_timestamp_t timestamp, const void **samples, int count, int rf_port_index, TRXWriteMetadata *md) { TRXDummyState *s = s1->opaque;
if (!(md->flags & TRX_WRITE_FLAG_PADDING) && s->dump_max) { const float *tab; int i, j; float v_max, v;
v_max = s->max_sample; for(j = 0; j < s->tx_channel_count; j++) { tab = (const float *)samples[j]; for(i = 0; i < count * 2; i++) { v = fabsf(tab[i]); /* Note: 1.0 corresponds to the maximum value */ if (v >= 1.0) s->sat_count++; if (v > v_max) { v_max = v; } } } s->max_sample = v_max; if ((get_time_us() - s->last_disp_time) >= 2000000) { printf("max_sample=%0.3f sat=%d\n", s->max_sample, s->sat_count); s->max_sample = 0; s->sat_count = 0; s->last_disp_time = get_time_us(); } }
s->tx_count += count; } |
/*
* Initializes the TRX driver with a given TRXState.
* This function checks for ABI compatibility between the LTEENB and the TRX driver. If there's a mismatch, it logs an error message and returns -1 to indicate failure.
* On success, it allocates and initializes a TRXDummyState structure, setting its `dump_max` field based on a parameter.
* It then updates the TRXState with pointers to dummy functions for various operations (end, write, read, start, get_sample_rate), effectively setting up the TRX driver to use these dummy
* implementations.
* Returns 0 on successful initialization.
* The critical part of this function is to map the user defined function to TRX API template function pointer
* - `s1->opaque` is assigned to `s`, linking the TRX driver state with a user-defined structure for internal use.
* - The next lines assign dummy functions to the TRX driver's operational callbacks:
* - trx_dummy_end maps to `trx_end_func` for ending a TRX session,
* - trx_dummy_write maps to `trx_write_func2` for writing data,
* - trx_dummy_read maps to `trx_read_func2` for reading data,
* - trx_dummy_start maps to `trx_start_func2` for starting a TRX session,
* - trx_dummy_get_sample_rate_func maps to `trx_get_sample_rate_func` for getting the sample rate.
* These assignments ensure that the TRX driver operates with predefined dummy behaviors for critical operations.
*/