Compare commits

...

19 Commits

Author SHA1 Message Date
Alexander Chemeris
2fb62ea45e Transceier52M: Make error response to an unknown command on UDP command interface more understandable.
Previously we just repeated the last response which could confuse a command sender.
2015-05-19 23:59:12 -04:00
Alexander Chemeris
feb4028b1b Transceiver: Check TSC values to be in [0..7] range. 2015-05-19 23:58:02 -04:00
Alexander Chemeris
7451e47d8a uhd: Output Rx/Tx gain limits to log to make it more transparent. 2015-05-17 23:25:57 -04:00
Tom Tsou
a6e2d6f9a1 Transceiver52M: Add clipping detection on RACH input
Alert user of overdriven RACH input indicated by a positive
threshold detector result. This indication serves as notification
that the receive RF gain level is too high for the configured
transceiver setup.

Signed-off-by: Tom Tsou <tom@tsou.cc>
2015-05-14 00:15:06 -04:00
Alexander Chemeris
456cf7f068 A hack to pass all dev.args to UHD. 2015-05-08 12:41:57 -04:00
Alexander Chemeris
8974a80192 umtrx: Don't use DSP tuning, because LMS6002D PLL steps are small enough.
We end up with DSP tuning just for 2-3Hz, which is meaningless and
only distort the signal.
2015-05-07 22:23:51 -04:00
Alexander Chemeris
63cd7705a9 uhd: Update UmTRX specific code to work with PA Tx gain. 2015-05-07 20:46:20 -04:00
Alexander Chemeris
00cc772f90 uhd: Set RF frontend bandwidth for UmTRX to improve signal quality. 2015-05-07 20:44:06 -04:00
Ivan Kluchnikov
34333e8919 debian: make it possible to install osmo-trx dependencies manually on the system 2015-04-29 17:30:18 +03:00
Alexander Chemeris
947fc8cd1f Revert "UmTRX: Fix Tx gain setting procedure for the best signal quality."
This reverts commit 48da6f97e3.
2015-04-28 13:17:31 -04:00
Alexander Chemeris
cd20d12392 uhd: Use full DAC scale with UmTRX to improve signal quality. 2015-03-25 14:22:37 +03:00
Alexander Chemeris
b61d91b50d Logger: Output ERR log messages to stderr as well. 2015-03-01 10:30:12 +01:00
Ivan Kluchnikov
3531a6a6ac debian: update osmo-trx dependencies
Now we use uhd and umtrx-uhd instead of libuhd-dev.
2015-02-23 17:46:10 +03:00
Ivan Kluchnikov
19c392df82 Handover support for transceiver (TCH/F TCH/H SDCCH4 SDCCH8)
It allows to enable/disable access burst detection on each subslot individually.
2014-12-16 15:12:57 +03:00
Ivan Kluchnikov
48da6f97e3 UmTRX: Fix Tx gain setting procedure for the best signal quality.
We fixed setting Tx gain procedure in UHD, so we should fix it in osmo-trx too.
From our measurements, VGA1 must be 18dB plus-minus one and VGA2 is the best when 23dB or lower.
2014-09-26 17:13:04 +04:00
Ivan Kluchnikov
ed10f05edd debian: Add debug package for the osmo-trx 2014-09-22 17:39:09 +04:00
Ivan Kluchnikov
a5ae4b52ca debian: Add debian directory to ease building packages 2014-09-22 17:38:49 +04:00
Ivan Kluchnikov
50fc8a1e9e Transceiver52M: UHD: Exit on receive more than 100 timeout errors
Receiving timeout is not a fatal error and should be recoverable on network devices.
But for some reasons these errors are not always recoverable, in this case osmo-trx should be restarted.
2014-09-22 17:38:16 +04:00
Alexander Chemeris
79ca9ae694 UmTRX: Manually set Tx gain stages for the best signal quality.
New UHD versions support split configuration of Tx gain stages.
We utilize this to set the gain configuration, optimal for
the Tx signal quality. From our measurements, VGA1 must be
18dB plus-minus one and VGA2 is the best when 23dB or lower.
2014-09-22 17:37:46 +04:00
13 changed files with 236 additions and 22 deletions

View File

@@ -192,7 +192,7 @@ Log::~Log()
if (mDummyInit) return;
// Anything at or above LOG_CRIT is an "alarm".
// Save alarms in the local list and echo them to stderr.
if (mPriority <= LOG_CRIT) {
if (mPriority <= LOG_ERR) {
if (sLoggerInited) addAlarm(mStream.str().c_str());
cerr << mStream.str() << endl;
}

View File

@@ -91,7 +91,8 @@ Transceiver::Transceiver(int wBasePort,
: mBasePort(wBasePort), mAddr(TRXAddress),
mTransmitLatency(wTransmitLatency), mClockSocket(NULL),
mRadioInterface(wRadioInterface), mSPSTx(wSPS), mSPSRx(1), mChans(wChans),
mOn(false), mTxFreq(0.0), mRxFreq(0.0), mPower(-10), mMaxExpectedDelay(0)
mOn(false), mTxFreq(0.0), mRxFreq(0.0), mPower(-10), mMaxExpectedDelay(0),
mTSC(0)
{
GSM::Time startTime(random() % gHyperframe,0);
@@ -105,6 +106,12 @@ Transceiver::Transceiver(int wBasePort,
txFullScale = mRadioInterface->fullScaleInputValue();
rxFullScale = mRadioInterface->fullScaleOutputValue();
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++)
mHandover[i][j] = false;
}
}
Transceiver::~Transceiver()
@@ -296,6 +303,12 @@ Transceiver::CorrType Transceiver::expectedCorrType(GSM::Time currTime,
size_t chan)
{
TransceiverState *state = &mStates[chan];
static int tchh_subslot[26] = { 0,1,0,1,0,1,0,1,0,1,0,1,0,0,1,0,1,0,1,0,1,0,1,0,1,1 };
static int sdcch4_subslot[102] = { 3,3,3,3,0,0,2,2,2,2,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,2,2,2,2,
3,3,3,3,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,2,2,2,2 };
static int sdcch8_subslot[102] = { 5,5,5,5,6,6,6,6,7,7,7,7,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,6,6,6,6,7,7,7,7,0,0,0,0,
1,1,1,1,2,2,2,2,3,3,3,3,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,6,6,6,6,7,7,7,7,4,4,4,4 };
unsigned burstTN = currTime.TN();
unsigned burstFN = currTime.FN();
@@ -307,6 +320,8 @@ Transceiver::CorrType Transceiver::expectedCorrType(GSM::Time currTime,
return IDLE;
break;
case I:
if (mHandover[burstTN][0])
return RACH;
return TSC;
/*if (burstFN % 26 == 25)
return IDLE;
@@ -317,6 +332,8 @@ Transceiver::CorrType Transceiver::expectedCorrType(GSM::Time currTime,
return TSC;
break;
case III:
if (mHandover[burstTN][tchh_subslot[burstFN % 26]])
return RACH;
return TSC;
break;
case IV:
@@ -331,6 +348,8 @@ Transceiver::CorrType Transceiver::expectedCorrType(GSM::Time currTime,
return RACH;
else if ((mod51 == 45) || (mod51 == 46))
return RACH;
else if (mHandover[burstTN][sdcch4_subslot[burstFN % 102]])
return RACH;
else
return TSC;
break;
@@ -338,6 +357,8 @@ Transceiver::CorrType Transceiver::expectedCorrType(GSM::Time currTime,
case VII:
if ((burstFN % 51 <= 14) && (burstFN % 51 >= 12))
return IDLE;
else if (mHandover[burstTN][sdcch8_subslot[burstFN % 102]])
return RACH;
else
return TSC;
break;
@@ -503,10 +524,18 @@ SoftVector *Transceiver::pullRadioVector(GSM::Time &wTime, int &RSSI,
else
success = detectRACH(state, *burst, amp, toa);
if (!success) {
if (success == 0) {
state->mNoises.insert(avg);
delete radio_burst;
return NULL;
} else if (success < 0) {
if (success == -SIGERR_CLIP) {
LOG(ALERT) << "Clipping detected on RACH input";
} else {
LOG(ALERT) << "Unhandled RACH error";
}
delete radio_burst;
return NULL;
}
/* Demodulate and set output info */
@@ -583,6 +612,10 @@ void Transceiver::driveControl(size_t chan)
sprintf(response,"RSP POWERON 1");
else {
sprintf(response,"RSP POWERON 0");
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++)
mHandover[i][j] = false;
}
if (!chan && !mOn) {
// Prepare for thread start
mPower = -20;
@@ -609,6 +642,20 @@ void Transceiver::driveControl(size_t chan)
}
}
}
else if (strcmp(command,"HANDOVER")==0){
int ts=0,ss=0;
sscanf(buffer,"%3s %s %d %d",cmdcheck,command,&ts,&ss);
mHandover[ts][ss] = true;
LOG(WARNING) << "HANDOVER RACH at timeslot " << ts << " subslot " << ss;
sprintf(response,"RSP HANDOVER 0 %d %d",ts,ss);
}
else if (strcmp(command,"NOHANDOVER")==0){
int ts=0,ss=0;
sscanf(buffer,"%3s %s %d %d",cmdcheck,command,&ts,&ss);
mHandover[ts][ss] = false;
LOG(WARNING) << "NOHANDOVER at timeslot " << ts << " subslot " << ss;
sprintf(response,"RSP NOHANDOVER 0 %d %d",ts,ss);
}
else if (strcmp(command,"SETMAXDLY")==0) {
//set expected maximum time-of-arrival
int maxDelay;
@@ -685,7 +732,7 @@ void Transceiver::driveControl(size_t chan)
// set TSC
unsigned TSC;
sscanf(buffer, "%3s %s %d", cmdcheck, command, &TSC);
if (mOn)
if (mOn || (TSC<0) || (TSC>7))
sprintf(response, "RSP SETTSC 1 %d", TSC);
else if (chan && (TSC != mTSC))
sprintf(response, "RSP SETTSC 1 %d", TSC);
@@ -696,7 +743,7 @@ void Transceiver::driveControl(size_t chan)
}
}
else if (strcmp(command,"SETSLOT")==0) {
// set TSC
// set slot type
int corrCode;
int timeslot;
sscanf(buffer,"%3s %s %d %d",cmdcheck,command,&timeslot,&corrCode);
@@ -712,6 +759,7 @@ void Transceiver::driveControl(size_t chan)
}
else {
LOG(WARNING) << "bogus command " << command << " on control interface.";
sprintf(response,"RSP ERR 1");
}
mCtrlSockets[chan]->write(response, strlen(response) + 1);

View File

@@ -163,6 +163,7 @@ private:
size_t mChans;
bool mOn; ///< flag to indicate that transceiver is powered on
bool mHandover[8][8]; ///< expect handover to the timeslot/subslot
double mTxFreq; ///< the transmit frequency
double mRxFreq; ///< the receive frequency
int mPower; ///< the transmit power in dB

View File

@@ -37,9 +37,12 @@
#define B2XX_BASE_RT GSMRATE
#define B100_BASE_RT 400000
#define USRP2_BASE_RT 390625
#define TX_AMPL 0.3
#define USRP_TX_AMPL 0.3
#define UMTRX_TX_AMPL 0.7
#define SAMPLE_BUF_SZ (1 << 20)
#define UMTRX_VGA1_DEF -18
enum uhd_dev_type {
USRP1,
USRP2,
@@ -286,7 +289,7 @@ public:
inline TIMESTAMP initialWriteTimestamp() { return ts_initial * sps; }
inline TIMESTAMP initialReadTimestamp() { return ts_initial; }
inline double fullScaleInputValue() { return 32000 * TX_AMPL; }
inline double fullScaleInputValue() { return (dev_type==UMTRX) ? (32000 * UMTRX_TX_AMPL) : (32000 * USRP_TX_AMPL); }
inline double fullScaleOutputValue() { return 32000; }
double setRxGain(double db, size_t chan);
@@ -421,21 +424,43 @@ void uhd_device::init_gains()
{
uhd::gain_range_t range;
range = usrp_dev->get_tx_gain_range();
tx_gain_min = range.start();
tx_gain_max = range.stop();
if (dev_type == UMTRX) {
std::vector<std::string> gain_stages = usrp_dev->get_tx_gain_names(0);
if (gain_stages[0] == "VGA") {
LOG(WARNING) << "Update your UHD version for a proper Tx gain support";
}
if (gain_stages[0] == "VGA" || gain_stages[0] == "PA") {
range = usrp_dev->get_tx_gain_range();
tx_gain_min = range.start();
tx_gain_max = range.stop();
} else {
range = usrp_dev->get_tx_gain_range("VGA2");
tx_gain_min = UMTRX_VGA1_DEF + range.start();
tx_gain_max = UMTRX_VGA1_DEF + range.stop();
}
} else {
range = usrp_dev->get_tx_gain_range();
tx_gain_min = range.start();
tx_gain_max = range.stop();
}
LOG(INFO) << "Supported Tx gain range [" << tx_gain_min << "; " << tx_gain_max << "]";
range = usrp_dev->get_rx_gain_range();
rx_gain_min = range.start();
rx_gain_max = range.stop();
LOG(INFO) << "Supported Rx gain range [" << rx_gain_min << "; " << rx_gain_max << "]";
for (size_t i = 0; i < tx_gains.size(); i++) {
usrp_dev->set_tx_gain((tx_gain_min + tx_gain_max) / 2, i);
double gain = (tx_gain_min + tx_gain_max) / 2;
LOG(INFO) << "Default setting Tx gain for channel " << i << " to " << gain;
usrp_dev->set_tx_gain(gain, i);
tx_gains[i] = usrp_dev->get_tx_gain(i);
}
for (size_t i = 0; i < rx_gains.size(); i++) {
usrp_dev->set_rx_gain((rx_gain_min + rx_gain_max) / 2, i);
double gain = (rx_gain_min + rx_gain_max) / 2;
LOG(INFO) << "Default setting Rx gain for channel " << i << " to " << gain;
usrp_dev->set_rx_gain(gain, i);
rx_gains[i] = usrp_dev->get_rx_gain(i);
}
@@ -514,10 +539,27 @@ double uhd_device::setTxGain(double db, size_t chan)
return 0.0f;
}
usrp_dev->set_tx_gain(db, chan);
tx_gains[chan] = usrp_dev->get_tx_gain(chan);
if (dev_type == UMTRX) {
std::vector<std::string> gain_stages = usrp_dev->get_tx_gain_names(0);
if (gain_stages[0] == "VGA" || gain_stages[0] == "PA") {
usrp_dev->set_tx_gain(db, chan);
tx_gains[chan] = usrp_dev->get_tx_gain(chan);
} else {
// New UHD versions support split configuration of
// Tx gain stages. We utilize this to set the gain
// configuration, optimal for the Tx signal quality.
// From our measurements, VGA1 must be 18dB plus-minus
// one and VGA2 is the best when 23dB or lower.
usrp_dev->set_tx_gain(UMTRX_VGA1_DEF, "VGA1", chan);
usrp_dev->set_tx_gain(db-UMTRX_VGA1_DEF, "VGA2", chan);
tx_gains[chan] = usrp_dev->get_tx_gain(chan);
}
} else {
usrp_dev->set_tx_gain(db, chan);
tx_gains[chan] = usrp_dev->get_tx_gain(chan);
}
LOG(INFO) << "Set TX gain to " << tx_gains[chan] << "dB";
LOG(INFO) << "Set TX gain to " << tx_gains[chan] << "dB (asked for " << db << "dB)";
return tx_gains[chan];
}
@@ -532,7 +574,7 @@ double uhd_device::setRxGain(double db, size_t chan)
usrp_dev->set_rx_gain(db, chan);
rx_gains[chan] = usrp_dev->get_rx_gain(chan);
LOG(INFO) << "Set RX gain to " << rx_gains[chan] << "dB";
LOG(INFO) << "Set RX gain to " << rx_gains[chan] << "dB (asked for " << db << "dB)";
return rx_gains[chan];
}
@@ -630,7 +672,7 @@ int uhd_device::open(const std::string &args, bool extref)
// Use the first found device
LOG(INFO) << "Using discovered UHD device " << dev_addrs[0].to_string();
try {
usrp_dev = uhd::usrp::multi_usrp::make(dev_addrs[0]);
usrp_dev = uhd::usrp::multi_usrp::make(addr);
} catch(...) {
LOG(ALERT) << "UHD make failed, device " << dev_addrs[0].to_string();
return -1;
@@ -673,6 +715,16 @@ int uhd_device::open(const std::string &args, bool extref)
if (set_rates(_tx_rate, _rx_rate) < 0)
return -1;
// Set RF frontend bandwidth
if (dev_type == UMTRX) {
// Setting LMS6002D LPF to 500kHz gives us the best signal quality
for (size_t i = 0; i < chans; i++) {
usrp_dev->set_tx_bandwidth(500*1000*2, i);
if (!diversity)
usrp_dev->set_rx_bandwidth(500*1000*2, i);
}
}
/* Create TX and RX streamers */
uhd::stream_args_t stream_args("sc16");
for (size_t i = 0; i < chans; i++)
@@ -823,6 +875,7 @@ void uhd_device::setPriority(float prio)
int uhd_device::check_rx_md_err(uhd::rx_metadata_t &md, ssize_t num_smpls)
{
uhd::time_spec_t ts;
static int err_count = 0;
if (!num_smpls) {
LOG(ERR) << str_code(md);
@@ -830,6 +883,11 @@ int uhd_device::check_rx_md_err(uhd::rx_metadata_t &md, ssize_t num_smpls)
switch (md.error_code) {
case uhd::rx_metadata_t::ERROR_CODE_TIMEOUT:
LOG(ALERT) << "UHD: Receive timed out";
if (err_count > 100) {
err_count = 0;
return ERROR_UNRECOVERABLE;
}
err_count++;
case uhd::rx_metadata_t::ERROR_CODE_OVERFLOW:
case uhd::rx_metadata_t::ERROR_CODE_LATE_COMMAND:
case uhd::rx_metadata_t::ERROR_CODE_BROKEN_CHAIN:
@@ -1007,7 +1065,19 @@ uhd::tune_request_t uhd_device::select_freq(double freq, size_t chan, bool tx)
std::vector<double> freqs;
uhd::tune_request_t treq(freq);
if ((chans == 1) || ((chans == 2) && dev_type == UMTRX)) {
if (dev_type == UMTRX) {
if (offset > 0.0)
return uhd::tune_request_t(freq, offset);
// Don't use DSP tuning, because LMS6002D PLL steps are small enough.
// We end up with DSP tuning just for 2-3Hz, which is meaningless and
// only distort the signal (because cordic is not ideal).
treq.target_freq = freq;
treq.rf_freq_policy = uhd::tune_request_t::POLICY_MANUAL;
treq.rf_freq = freq;
treq.dsp_freq_policy = uhd::tune_request_t::POLICY_MANUAL;
treq.dsp_freq = 0.0;
} else if (chans == 1) {
if (offset == 0.0)
return treq;

View File

@@ -34,6 +34,8 @@ extern "C" {
#include "scale.h"
#include "mult.h"
}
/* Clipping detection threshold */
#define CLIP_THRESH 30000.0f
using namespace GSM;
@@ -1340,7 +1342,7 @@ static int detectBurst(signalVector &burst,
/* Correlate */
if (!convolve(&burst, sync->sequence, &corr,
CUSTOM, start, len, sps, 0)) {
return -1;
return -SIGERR_INTERNAL;
}
/* Peak detection - place restrictions at correlation edges */
@@ -1369,6 +1371,18 @@ static int detectBurst(signalVector &burst,
return 1;
}
static int detectClipping(signalVector &burst, float thresh)
{
for (size_t i = 0; i < burst.size(); i++) {
if (fabs(burst[i].real()) > thresh)
return 1;
if (fabs(burst[i].imag()) > thresh)
return 1;
}
return 0;
}
/*
* RACH burst detection
*
@@ -1390,7 +1404,10 @@ int detectRACHBurst(signalVector &rxBurst,
CorrelationSequence *sync;
if ((sps != 1) && (sps != 4))
return -1;
return -SIGERR_UNSUPPORTED;
if (detectClipping(rxBurst, CLIP_THRESH))
return -SIGERR_CLIP;
target = 8 + 40;
head = 4;
@@ -1443,7 +1460,7 @@ int analyzeTrafficBurst(signalVector &rxBurst, unsigned tsc, float thresh,
CorrelationSequence *sync;
if ((tsc < 0) || (tsc > 7) || ((sps != 1) && (sps != 4)))
return -1;
return -SIGERR_UNSUPPORTED;
target = 3 + 58 + 16 + 5;
head = 4;
@@ -1459,7 +1476,7 @@ int analyzeTrafficBurst(signalVector &rxBurst, unsigned tsc, float thresh,
delete corr;
if (rc < 0) {
return -1;
return -SIGERR_INTERNAL;
} else if (!rc) {
if (amp)
*amp = 0.0f;

View File

@@ -28,6 +28,14 @@ enum ConvType {
UNDEFINED,
};
enum signalError {
SIGERR_NONE,
SIGERR_BOUNDS,
SIGERR_CLIP,
SIGERR_UNSUPPORTED,
SIGERR_INTERNAL,
};
/** Convert a linear number to a dB value */
float dB(float x);

5
debian/changelog vendored Normal file
View File

@@ -0,0 +1,5 @@
osmo-trx (0.1.8) precise; urgency=low
* Initial release (Closes: #nnnn) <nnnn is the bug number of your ITP
-- Ivan Klyuchnikov <Ivan.Kluchnikov@fairwaves.ru> Sun, 9 Mar 2014 14:10:10 +0400

1
debian/compat vendored Normal file
View File

@@ -0,0 +1 @@
9

24
debian/control vendored Normal file
View File

@@ -0,0 +1,24 @@
Source: osmo-trx
Maintainer: Ivan Klyuchnikov <ivan.kluchnikov@fairwaves.ru>
Section: net
Priority: optional
Standards-Version: 3.9.3
Build-Depends: debhelper (>= 9), autotools-dev, libdbd-sqlite3, pkg-config, dh-autoreconf, uhd, umtrx-uhd, libusb-1.0-0-dev, libboost-all-dev
Homepage: http://openbsc.osmocom.org/trac/wiki/OsmoTRX
Vcs-Git: git://git.osmocom.org/osmo-trx
Vcs-Browser: http://cgit.osmocom.org/osmo-trx
Package: osmo-trx
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}, libdbd-sqlite3
Description: OsmoTRX is a software-defined radio transceiver that implements the Layer 1 physical layer of a BTS
Package: osmo-trx-dbg
Architecture: any
Section: debug
Priority: extra
Depends: osmo-trx (= ${binary:Version}), ${misc:Depends}
Description: Debug symbols for the osmo-trx
Make debugging possible

25
debian/copyright vendored Normal file
View File

@@ -0,0 +1,25 @@
The Debian packaging is:
Copyright (C) 2014 Max <max.suraev@fairwaves.ru>
It was downloaded from:
git://git.osmocom.org/osmo-trx
Upstream Authors:
Thomas Tsou <tom@tsou.cc>
David A. Burgess <dburgess@kestrelsp.com>
Harvind S. Samra <hssamra@kestrelsp.com>
Raffi Sevlian <raffisev@gmail.com>
Copyright:
Copyright (C) 2012-2013 Thomas Tsou <tom@tsou.cc>
Copyright (C) 2011 Range Networks, Inc.
Copyright (C) 2008-2011 Free Software Foundation, Inc.
License:
GNU Affero General Public License, Version 3

1
debian/osmo-trx.install vendored Normal file
View File

@@ -0,0 +1 @@
/usr/bin/osmo-trx

13
debian/rules vendored Executable file
View File

@@ -0,0 +1,13 @@
#!/usr/bin/make -f
export DEB_BUILD_HARDENING=1
%:
dh $@ --with autoreconf
override_dh_shlibdeps:
dh_shlibdeps --dpkg-shlibdeps-params=--ignore-missing-info
override_dh_strip:
dh_strip --dbg-package=osmo-trx-dbg

1
debian/source/format vendored Normal file
View File

@@ -0,0 +1 @@
3.0 (native)