forked from cellular-infrastructure/osmo-pcu
Compare commits
56 Commits
master
...
users/prav
Author | SHA1 | Date |
---|---|---|
pravin | bd1e4bcc4c | |
pravin | 0a918983f3 | |
pravin | 624471e672 | |
Aravind Sirsikar | a36b5d7896 | |
Alexander Couzens | cb846ecbbc | |
Alexander Couzens | b82bd92e57 | |
Aravind Sirsikar | 93ef548a43 | |
Aravind Sirsikar | 6c0ff27cb5 | |
Aravind Sirsikar | 7c70740484 | |
Aravind Sirsikar | b7cc6a4743 | |
Alexander Couzens | 2fcfc29020 | |
Alexander Couzens | ce936f3cd4 | |
Alexander Couzens | c1c9d6a9d8 | |
Alexander Couzens | c8fd4b7c42 | |
Alexander Couzens | f929e62525 | |
Alexander Couzens | 4acb6b7251 | |
Alexander Couzens | 95e379241a | |
Alexander Couzens | 543756adbe | |
Alexander Couzens | 2cb1547993 | |
Alexander Couzens | 9736d00b12 | |
Yves Godin | 660709dc7c | |
Aravind Sirsikar | b3c85bac48 | |
Aravind Sirsikar | 7106c361d6 | |
Aravind Sirsikar | db39e6cb72 | |
Aravind Sirsikar | 2da0dbc7e5 | |
Aravind Sirsikar | 14099d7b1b | |
Aravind Sirsikar | 459e856b70 | |
Aravind Sirsikar | ebebf5d175 | |
Aravind Sirsikar | ef12a45d46 | |
Aravind Sirsikar | be3dc223f1 | |
Aravind Sirsikar | d14add5c2e | |
Aravind Sirsikar | f666f9585d | |
Aravind Sirsikar | 7b01c15b6c | |
Aravind Sirsikar | 5974ec7609 | |
Max | 58b6646750 | |
Alexander Couzens | ed3ae4a392 | |
Yves Godin | f0bb25450c | |
Max | de810f2005 | |
Max | cad867ec8d | |
Max | 280448ba7b | |
Holger Hans Peter Freyther | 1aa7527302 | |
Holger Hans Peter Freyther | ca025c02ef | |
Holger Hans Peter Freyther | 97e48a3252 | |
Harald Welte | 63d33ad2d7 | |
Aravind Sirsikar | 7952282b78 | |
Aravind Sirsikar | a859a21800 | |
Aravind Sirsikar | 7a05b039c8 | |
Bhargava Abhyankar | e44383baa4 | |
Aravind Sirsikar | 5a5d2b7a27 | |
Saurabh Sharan | 2b09c39c9c | |
Saurabh Sharan | bacb65b48b | |
Saurabh Sharan | 656eed5975 | |
Holger Hans Peter Freyther | 173ef90a53 | |
Holger Hans Peter Freyther | fd263b0dfd | |
Holger Hans Peter Freyther | 99db40ad2d | |
Max | 22d7e75e1f |
|
@ -14,10 +14,11 @@ config.guess
|
|||
config.sub
|
||||
config.status
|
||||
configure
|
||||
compile
|
||||
depcomp
|
||||
install-sh
|
||||
missing
|
||||
libtool
|
||||
*libtool
|
||||
ltmain.sh
|
||||
|
||||
core
|
||||
|
@ -43,3 +44,17 @@ tests/codel/codel_test
|
|||
tests/emu/pcu_emu
|
||||
tests/testsuite
|
||||
tests/testsuite.log
|
||||
tests/edge/EdgeTest
|
||||
tests/llc/LlcTest
|
||||
|
||||
# ignore debian files
|
||||
.tarball-version
|
||||
debian/autoreconf.after
|
||||
debian/autoreconf.before
|
||||
debian/files
|
||||
debian/*.debhelper*
|
||||
debian/*.substvars
|
||||
debian/osmo-pcu-dbg/
|
||||
debian/osmo-pcu.substvars
|
||||
debian/osmo-pcu/
|
||||
debian/tmp/
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
[gerrit]
|
||||
host=gerrit.osmocom.org
|
||||
project=osmo-pcu
|
19
configure.ac
19
configure.ac
|
@ -35,6 +35,25 @@ AC_ARG_ENABLE(sysmocom-dsp,
|
|||
AC_MSG_RESULT([$enable_sysmocom_dsp])
|
||||
AM_CONDITIONAL(ENABLE_SYSMODSP, test "x$enable_sysmocom_dsp" = "xyes")
|
||||
|
||||
AC_MSG_CHECKING([whether to enable direct PHY access for PDCH of NuRAN Wireless Litecell 1.5 BTS])
|
||||
AC_ARG_ENABLE(lc15bts-phy,
|
||||
AC_HELP_STRING([--enable-lc15bts-phy],
|
||||
[enable code for Litecell 1.5 PHY [default=no]]),
|
||||
[enable_lc15bts_phy="$enableval"],[enable_lc15bts_phy="no"])
|
||||
AC_ARG_WITH([litecell15], [AS_HELP_STRING([--with-litecell15=INCLUDE_DIR], [Location of the litecell 1.5 API header files])],
|
||||
[litecell15_incdir="$withval"],[litecell15_incdir="$incdir"])
|
||||
AC_SUBST([LITECELL15_INCDIR], $litecell15_incdir)
|
||||
AC_MSG_RESULT([$enable_lc15bts_phy])
|
||||
AM_CONDITIONAL(ENABLE_LC15BTS_PHY, test "x$enable_lc15bts_phy" = "xyes")
|
||||
if test "$enable_litecell15" = "yes"; then
|
||||
oldCPPFLAGS=$CPPFLAGS
|
||||
CPPFLAGS="$CPPFLAGS -I$LITECELL15_INCDIR -I$srcdir/include $LIBOSMOCORE_CFLAGS"
|
||||
AC_CHECK_HEADER([nrw/litecell15/litecell15.h],[],
|
||||
[AC_MSG_ERROR([nrw/litecell15/litecell15.h can not be found in $litecell15_incdir])],
|
||||
[#include <nrw/litecell15/litecell15.h>])
|
||||
CPPFLAGS=$oldCPPFLAGS
|
||||
fi
|
||||
|
||||
AC_ARG_ENABLE([vty_tests],
|
||||
AC_HELP_STRING([--enable-vty-tests],
|
||||
[Include the VTY tests in make check [default=no]]),
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
if [ $sysmobts = "no" -a $sysmodsp = "yes" ]; then
|
||||
echo "This config does not make sense."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
rm -rf deps/install
|
||||
mkdir deps || true
|
||||
cd deps
|
||||
osmo-deps.sh libosmocore
|
||||
|
||||
cd libosmocore
|
||||
autoreconf --install --force
|
||||
./configure --prefix=$PWD/../install
|
||||
$MAKE $PARALLEL_MAKE install
|
||||
|
||||
# Install the API
|
||||
cd ../
|
||||
if ! test -d layer1-api;
|
||||
then
|
||||
git clone git://git.sysmocom.de/sysmo-bts/layer1-api.git layer1-api
|
||||
fi
|
||||
|
||||
cd layer1-api
|
||||
git fetch origin
|
||||
git reset --hard origin/master
|
||||
mkdir -p $PWD/../install/include/sysmocom/femtobts/
|
||||
cp include/*.h ../install/include/sysmocom/femtobts/
|
||||
|
||||
cd ../../
|
||||
autoreconf --install --force
|
||||
BTS_CONFIG="--enable-sysmocom-bts=$sysmobts --enable-sysmocom-dsp=$sysmodsp"
|
||||
if [ $sysmobts = "no" ]; then
|
||||
BTS_CONFIG="$BTS_CONFIG --enable-vty-tests"
|
||||
fi
|
||||
|
||||
PKG_CONFIG_PATH=$PWD/deps/install/lib/pkgconfig ./configure $BTS_CONFIG
|
||||
PKG_CONFIG_PATH=$PWD/deps/install/lib/pkgconfig $MAKE $PARALLEL_MAKE
|
||||
DISTCHECK_CONFIGURE_FLAGS="$BTS_CONFIG" AM_DISTCHECK_CONFIGURE_FLAGS="$BTS_CONFIG" PKG_CONFIG_PATH=$PWD/deps/install/lib/pkgconfig LD_LIBRARY_PATH=$PWD/deps/install/lib $MAKE distcheck
|
|
@ -0,0 +1,5 @@
|
|||
osmo-pcu (0.3) UNRELEASED; urgency=medium
|
||||
|
||||
* Initial release.
|
||||
|
||||
-- Holger Hans Peter Freyther <holger@moiji-mobile.com> Fri, 01 Apr 2016 18:59:00 +0200
|
|
@ -0,0 +1 @@
|
|||
7
|
|
@ -0,0 +1,24 @@
|
|||
Source: osmo-pcu
|
||||
Section: net
|
||||
Priority: optional
|
||||
Maintainer: Holger Hans Peter Freyther <holger@moiji-mobile.com>
|
||||
Build-Depends: debhelper (>= 7.0.0~), dh-autoreconf, dh-systemd (>= 1.5), autotools-dev, pkg-config, libosmocore-dev
|
||||
Standards-Version: 3.8.4
|
||||
Homepage: http://osmocom.org/projects/osmopcu
|
||||
Vcs-Git: git://git.osmocom.org/osmo-pcu
|
||||
Vcs-Browser: http://git.osmocom.org/osmo-pcu/
|
||||
|
||||
Package: osmo-pcu
|
||||
Architecture: any
|
||||
Depends: ${shlibs:Depends}, ${misc:Depends}
|
||||
Description: osmo-pcu GSM PCU for GPRS and EDGE
|
||||
osmo-pcu for GPRS and EDGE support in the network
|
||||
|
||||
Package: osmo-pcu-dbg
|
||||
Architecture: any
|
||||
Section: debug
|
||||
Priority: extra
|
||||
Depends: osmo-pcu (= ${binary:Version}), ${misc:Depends}
|
||||
Description: Debug symbols for the osmo-pcu
|
||||
Make debugging possible
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
etc/osmocom/osmo-pcu.cfg
|
||||
usr/bin/osmo-pcu
|
|
@ -0,0 +1,15 @@
|
|||
[Unit]
|
||||
Description=Osmocom osmo-pcu
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/osmo-pcu -c /etc/osmocom/osmo-pcu.cfg
|
||||
Restart=always
|
||||
RestartSec=2
|
||||
|
||||
# Read quickly enough
|
||||
CPUSchedulingPolicy=rr
|
||||
CPUSchedulingPriority=1
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
|
@ -0,0 +1,19 @@
|
|||
#!/usr/bin/make -f
|
||||
|
||||
DEBIAN := $(shell dpkg-parsechangelog | grep ^Version: | cut -d' ' -f2)
|
||||
DEBVERS := $(shell echo '$(DEBIAN)' | cut -d- -f1)
|
||||
VERSION := $(shell echo '$(DEBVERS)' | sed -e 's/[+-].*//' -e 's/~//g')
|
||||
|
||||
#export DH_VERBOSE=1
|
||||
export DEB_BUILD_HARDENING=1
|
||||
|
||||
|
||||
%:
|
||||
dh $@ --with=systemd --with autoreconf --fail-missing
|
||||
|
||||
override_dh_strip:
|
||||
dh_strip --dbg-package=osmo-pcu-dbg
|
||||
|
||||
override_dh_autoreconf:
|
||||
echo $(VERSION) > .tarball-version
|
||||
dh_autoreconf
|
|
@ -0,0 +1 @@
|
|||
3.0 (native)
|
|
@ -18,10 +18,15 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
AUTOMAKE_OPTIONS = subdir-objects
|
||||
AM_CPPFLAGS = $(STD_DEFINES_AND_INCLUDES) $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGB_CFLAGS) $(LIBOSMOGSM_CFLAGS)
|
||||
|
||||
if ENABLE_SYSMODSP
|
||||
AM_CPPFLAGS += -DENABLE_SYSMODSP
|
||||
AM_CPPFLAGS += -DENABLE_DIRECT_PHY
|
||||
endif
|
||||
|
||||
if ENABLE_LC15BTS_PHY
|
||||
AM_CPPFLAGS += -DENABLE_DIRECT_PHY
|
||||
endif
|
||||
|
||||
AM_CXXFLAGS = -Wall -ldl -pthread
|
||||
|
@ -57,18 +62,14 @@ libgprs_la_SOURCES = \
|
|||
rlc.cpp \
|
||||
osmobts_sock.cpp \
|
||||
gprs_codel.c \
|
||||
gprs_coding_scheme.cpp
|
||||
gprs_coding_scheme.cpp \
|
||||
egprs_rlc_compression.cpp
|
||||
|
||||
bin_PROGRAMS = \
|
||||
osmo-pcu
|
||||
|
||||
noinst_PROGRAMS =
|
||||
|
||||
if ENABLE_SYSMODSP
|
||||
noinst_PROGRAMS += \
|
||||
osmo-pcu-remote
|
||||
endif
|
||||
|
||||
noinst_HEADERS = \
|
||||
gprs_debug.h \
|
||||
csn1.h \
|
||||
|
@ -83,8 +84,6 @@ noinst_HEADERS = \
|
|||
bitvector.h \
|
||||
pcu_vty.h \
|
||||
pcu_vty_functions.h \
|
||||
sysmo_l1_if.h \
|
||||
femtobts.h \
|
||||
tbf.h \
|
||||
bts.h \
|
||||
poll_controller.h \
|
||||
|
@ -96,19 +95,65 @@ noinst_HEADERS = \
|
|||
pcu_utils.h \
|
||||
cxx_linuxlist.h \
|
||||
gprs_codel.h \
|
||||
gprs_coding_scheme.h
|
||||
gprs_coding_scheme.h \
|
||||
egprs_rlc_compression.h
|
||||
|
||||
osmo_pcu_SOURCES = pcu_main.cpp
|
||||
|
||||
if ENABLE_SYSMODSP
|
||||
osmo_pcu_SOURCES += sysmo_l1_if.c \
|
||||
sysmo_l1_hw.c \
|
||||
femtobts.c
|
||||
AM_CPPFLAGS += -I$(srcdir)/osmo-bts-sysmo
|
||||
|
||||
osmo_pcu_remote_SOURCES = pcu_main.cpp \
|
||||
sysmo_l1_if.c \
|
||||
sysmo_l1_fwd.c \
|
||||
femtobts.c
|
||||
EXTRA_DIST = \
|
||||
osmo-bts-sysmo/sysmo_l1_if.c \
|
||||
osmo-bts-sysmo/sysmo_l1_if.h \
|
||||
osmo-bts-sysmo/sysmo_l1_hw.c \
|
||||
osmo-bts-sysmo/femtobts.c \
|
||||
osmo-bts-sysmo/femtobts.h
|
||||
|
||||
noinst_HEADERS += \
|
||||
osmo-bts-sysmo/sysmo_l1_if.h \
|
||||
osmo-bts-sysmo/femtobts.h
|
||||
|
||||
noinst_PROGRAMS += \
|
||||
osmo-pcu-remote
|
||||
|
||||
osmo_pcu_SOURCES += \
|
||||
osmo-bts-sysmo/sysmo_l1_if.c \
|
||||
osmo-bts-sysmo/sysmo_l1_hw.c \
|
||||
osmo-bts-sysmo/femtobts.c
|
||||
|
||||
osmo_pcu_remote_SOURCES = \
|
||||
pcu_main.cpp \
|
||||
osmo-bts-sysmo/sysmo_l1_if.c \
|
||||
osmo-bts-sysmo/sysmo_l1_fwd.c \
|
||||
osmo-bts-sysmo/femtobts.c
|
||||
|
||||
osmo_pcu_remote_LDADD = \
|
||||
libgprs.la \
|
||||
$(LIBOSMOGB_LIBS) \
|
||||
$(LIBOSMOCORE_LIBS) \
|
||||
$(LIBOSMOGSM_LIBS) \
|
||||
$(COMMON_LA)
|
||||
endif
|
||||
|
||||
if ENABLE_LC15BTS_PHY
|
||||
AM_CPPFLAGS += -I$(LITECELL15_INCDIR) -I$(srcdir)/osmo-bts-litecell15
|
||||
|
||||
EXTRA_DIST = \
|
||||
osmo-bts-litecell15/lc15_l1_if.c \
|
||||
osmo-bts-litecell15/lc15_l1_if.h \
|
||||
osmo-bts-litecell15/lc15_l1_hw.c \
|
||||
osmo-bts-litecell15/lc15bts.c \
|
||||
osmo-bts-litecell15/lc15bts.h
|
||||
|
||||
noinst_HEADERS += \
|
||||
osmo-bts-litecell15/lc15_l1_if.h \
|
||||
osmo-bts-litecell15/lc15bts.h
|
||||
|
||||
osmo_pcu_SOURCES += \
|
||||
osmo-bts-litecell15/lc15_l1_if.c \
|
||||
osmo-bts-litecell15/lc15_l1_hw.c \
|
||||
osmo-bts-litecell15/lc15bts.c
|
||||
endif
|
||||
|
||||
osmo_pcu_LDADD = \
|
||||
|
@ -118,13 +163,4 @@ osmo_pcu_LDADD = \
|
|||
$(LIBOSMOGSM_LIBS) \
|
||||
$(COMMON_LA)
|
||||
|
||||
if ENABLE_SYSMODSP
|
||||
osmo_pcu_remote_LDADD = \
|
||||
libgprs.la \
|
||||
$(LIBOSMOGB_LIBS) \
|
||||
$(LIBOSMOCORE_LIBS) \
|
||||
$(LIBOSMOGSM_LIBS) \
|
||||
$(COMMON_LA)
|
||||
endif
|
||||
|
||||
#MOSTLYCLEANFILES += testSource testDestination
|
||||
|
|
12
src/bts.cpp
12
src/bts.cpp
|
@ -71,6 +71,8 @@ static const struct rate_ctr_desc bts_ctr_description[] = {
|
|||
{ "rlc.ack.failed", "RLC Ack Failed "},
|
||||
{ "rlc.rel.timedout", "RLC Release Timeout "},
|
||||
{ "rlc.late-block", "RLC Late Block "},
|
||||
{ "rlc.sent-dummy", "RLC Sent Dummy "},
|
||||
{ "rlc.sent-control", "RLC Sent Control "},
|
||||
{ "decode.errors", "Decode Errors "},
|
||||
{ "sba.allocated", "SBA Allocated "},
|
||||
{ "sba.freed", "SBA Freed "},
|
||||
|
@ -1315,6 +1317,7 @@ int gprs_rlcmac_pdch::rcv_block(uint8_t *data, uint8_t len, uint32_t fn,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*! \brief process egprs and gprs data blocks */
|
||||
int gprs_rlcmac_pdch::rcv_data_block(uint8_t *data, uint32_t fn,
|
||||
struct pcu_l1_meas *meas, GprsCodingScheme cs)
|
||||
{
|
||||
|
@ -1333,15 +1336,6 @@ int gprs_rlcmac_pdch::rcv_data_block(uint8_t *data, uint32_t fn,
|
|||
cs.name());
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!cs.isEgprsGmsk()) {
|
||||
LOGP(DRLCMACUL, LOGL_ERROR,
|
||||
"Got %s RLC block but EGPRS is not implemented "
|
||||
"for 8PSK yet\n",
|
||||
cs.name());
|
||||
bts()->decode_error();
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
LOGP(DRLCMACUL, LOGL_DEBUG, " UL data: %s\n", osmo_hexdump(data, len));
|
||||
|
|
11
src/bts.h
11
src/bts.h
|
@ -183,12 +183,15 @@ struct gprs_rlcmac_bts {
|
|||
uint8_t force_two_phase;
|
||||
uint8_t alpha, gamma;
|
||||
uint8_t egprs_enabled;
|
||||
|
||||
/* Value 0 to support resegmentation enabled in DL, 1 for no reseg */
|
||||
uint8_t arq_type;
|
||||
uint32_t dl_tbf_idle_msec; /* hold time for idle DL TBFs */
|
||||
uint32_t ms_idle_sec;
|
||||
uint8_t cs_adj_enabled;
|
||||
uint8_t cs_adj_upper_limit;
|
||||
uint8_t cs_adj_lower_limit;
|
||||
struct {int16_t low; int16_t high;} cs_lqual_ranges[4];
|
||||
struct {int16_t low; int16_t high; } cs_lqual_ranges[9];
|
||||
uint16_t cs_downgrade_threshold; /* downgrade if less packets left (DL) */
|
||||
uint16_t ws_base;
|
||||
uint16_t ws_pdch; /* increase WS by this value per PDCH */
|
||||
|
@ -233,6 +236,8 @@ public:
|
|||
CTR_RLC_ACK_FAILED,
|
||||
CTR_RLC_REL_TIMEDOUT,
|
||||
CTR_RLC_LATE_BLOCK,
|
||||
CTR_RLC_SENT_DUMMY,
|
||||
CTR_RLC_SENT_CONTROL,
|
||||
CTR_DECODE_ERRORS,
|
||||
CTR_SBA_ALLOCATED,
|
||||
CTR_SBA_FREED,
|
||||
|
@ -309,6 +314,8 @@ public:
|
|||
void rlc_ack_failed();
|
||||
void rlc_rel_timedout();
|
||||
void rlc_late_block();
|
||||
void rlc_sent_dummy();
|
||||
void rlc_sent_control();
|
||||
void decode_error();
|
||||
void sba_allocated();
|
||||
void sba_freed();
|
||||
|
@ -449,6 +456,8 @@ CREATE_COUNT_INLINE(rlc_ack_timedout, CTR_RLC_ACK_TIMEDOUT);
|
|||
CREATE_COUNT_INLINE(rlc_ack_failed, CTR_RLC_ACK_FAILED);
|
||||
CREATE_COUNT_INLINE(rlc_rel_timedout, CTR_RLC_REL_TIMEDOUT);
|
||||
CREATE_COUNT_INLINE(rlc_late_block, CTR_RLC_LATE_BLOCK);
|
||||
CREATE_COUNT_INLINE(rlc_sent_dummy, CTR_RLC_SENT_DUMMY);
|
||||
CREATE_COUNT_INLINE(rlc_sent_control, CTR_RLC_SENT_CONTROL);
|
||||
CREATE_COUNT_INLINE(decode_error, CTR_DECODE_ERRORS)
|
||||
CREATE_COUNT_INLINE(sba_allocated, CTR_SBA_ALLOCATED)
|
||||
CREATE_COUNT_INLINE(sba_freed, CTR_SBA_FREED)
|
||||
|
|
|
@ -2400,7 +2400,12 @@ gint16 csnStreamEncoder(csnStream_t* ar, const CSN_DESCR* pDescr, bitvec *vector
|
|||
guint8 bits_to_handle = remaining_bits_len%8;
|
||||
if (bits_to_handle > 0)
|
||||
{
|
||||
guint8 fl = filler&(0xff>>(8-bits_to_handle));
|
||||
/* section 11 of 44.060
|
||||
* The padding bits may be the 'null' string. Otherwise, the
|
||||
* padding bits starts with bit '0', followed by 'spare padding'
|
||||
* < padding bits > ::= { null | 0 < spare padding > ! < Ignore : 1 bit** = < no string > > } ;
|
||||
*/
|
||||
guint8 fl = filler&(0xff>>(8-bits_to_handle + 1));
|
||||
bitvec_write_field(vector, writeIndex, fl, bits_to_handle);
|
||||
LOGPC(DCSN1, LOGL_NOTICE, "%u|", fl);
|
||||
remaining_bits_len -= bits_to_handle;
|
||||
|
@ -2499,6 +2504,7 @@ gint16 csnStreamEncoder(csnStream_t* ar, const CSN_DESCR* pDescr, bitvec *vector
|
|||
bitvec_write_field(vector, writeIndex, !Tag, 1);
|
||||
LOGPC(DCSN1, LOGL_NOTICE, "%s = %u | ", pDescr->sz , (unsigned)(!Tag));
|
||||
bit_offset++;
|
||||
remaining_bits_len--;
|
||||
|
||||
pDescr++;
|
||||
break;
|
||||
|
|
288
src/decoding.cpp
288
src/decoding.cpp
|
@ -30,9 +30,9 @@ extern "C" {
|
|||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <sys/time.h>
|
||||
#define LENGTH_TO_END 255
|
||||
/*
|
||||
/*!
|
||||
* \returns num extensions fields (num frames == offset) on success,
|
||||
* -errno otherwise.
|
||||
*/
|
||||
|
@ -197,6 +197,7 @@ int Decoding::rlc_data_from_ul_data(
|
|||
e = rdbi->e;
|
||||
if (e) {
|
||||
if (chunks_size > 0) {
|
||||
/* Block without LI means it only contains data of one LLC PDU */
|
||||
chunks[num_chunks].offset = offs;
|
||||
chunks[num_chunks].length = LENGTH_TO_END;
|
||||
chunks[num_chunks].is_complete = is_last_block;
|
||||
|
@ -344,78 +345,20 @@ void Decoding::extract_rbb(const struct bitvec *rbb, char *show_rbb)
|
|||
int Decoding::rlc_parse_ul_data_header(struct gprs_rlc_data_info *rlc,
|
||||
const uint8_t *data, GprsCodingScheme cs)
|
||||
{
|
||||
const struct gprs_rlc_ul_header_egprs_3 *egprs3;
|
||||
const struct rlc_ul_header *gprs;
|
||||
unsigned int e_ti_header;
|
||||
unsigned int cur_bit = 0;
|
||||
int punct, punct2, with_padding, cps;
|
||||
unsigned int offs;
|
||||
|
||||
switch(cs.headerTypeData()) {
|
||||
case GprsCodingScheme::HEADER_GPRS_DATA:
|
||||
gprs = static_cast<struct rlc_ul_header *>
|
||||
((void *)data);
|
||||
|
||||
gprs_rlc_data_info_init_ul(rlc, cs, false);
|
||||
|
||||
rlc->r = gprs->r;
|
||||
rlc->si = gprs->si;
|
||||
rlc->tfi = gprs->tfi;
|
||||
rlc->cps = 0;
|
||||
rlc->rsb = 0;
|
||||
|
||||
rlc->num_data_blocks = 1;
|
||||
rlc->block_info[0].cv = gprs->cv;
|
||||
rlc->block_info[0].pi = gprs->pi;
|
||||
rlc->block_info[0].bsn = gprs->bsn;
|
||||
rlc->block_info[0].e = gprs->e;
|
||||
rlc->block_info[0].ti = gprs->ti;
|
||||
rlc->block_info[0].spb = 0;
|
||||
|
||||
cur_bit += rlc->data_offs_bits[0];
|
||||
|
||||
/* skip data area */
|
||||
cur_bit += cs.maxDataBlockBytes() * 8;
|
||||
case GprsCodingScheme::HEADER_GPRS_DATA :
|
||||
cur_bit = rlc_parse_ul_data_header_gprs(rlc, data, cs);
|
||||
break;
|
||||
case GprsCodingScheme::HEADER_EGPRS_DATA_TYPE_3:
|
||||
egprs3 = static_cast<struct gprs_rlc_ul_header_egprs_3 *>
|
||||
((void *)data);
|
||||
|
||||
cps = (egprs3->cps_a << 0) | (egprs3->cps_b << 2);
|
||||
gprs_rlc_mcs_cps_decode(cps, cs, &punct, &punct2, &with_padding);
|
||||
gprs_rlc_data_info_init_ul(rlc, cs, with_padding);
|
||||
|
||||
rlc->r = egprs3->r;
|
||||
rlc->si = egprs3->si;
|
||||
rlc->tfi = (egprs3->tfi_a << 0) | (egprs3->tfi_b << 2);
|
||||
rlc->cps = cps;
|
||||
rlc->rsb = egprs3->rsb;
|
||||
|
||||
rlc->num_data_blocks = 1;
|
||||
rlc->block_info[0].cv = egprs3->cv;
|
||||
rlc->block_info[0].pi = egprs3->pi;
|
||||
rlc->block_info[0].spb = egprs3->spb;
|
||||
rlc->block_info[0].bsn =
|
||||
(egprs3->bsn1_a << 0) | (egprs3->bsn1_b << 5);
|
||||
|
||||
cur_bit += rlc->data_offs_bits[0] - 2;
|
||||
|
||||
offs = rlc->data_offs_bits[0] / 8;
|
||||
OSMO_ASSERT(rlc->data_offs_bits[0] % 8 == 1);
|
||||
|
||||
e_ti_header = (data[offs-1] + (data[offs] << 8)) >> 7;
|
||||
rlc->block_info[0].e = !!(e_ti_header & 0x01);
|
||||
rlc->block_info[0].ti = !!(e_ti_header & 0x02);
|
||||
cur_bit += 2;
|
||||
|
||||
/* skip data area */
|
||||
cur_bit += cs.maxDataBlockBytes() * 8;
|
||||
case GprsCodingScheme::HEADER_EGPRS_DATA_TYPE_3 :
|
||||
cur_bit = rlc_parse_ul_data_header_egprs_type_3(rlc, data, cs);
|
||||
break;
|
||||
case GprsCodingScheme::HEADER_EGPRS_DATA_TYPE_2 :
|
||||
cur_bit = rlc_parse_ul_data_header_egprs_type_2(rlc, data, cs);
|
||||
break;
|
||||
case GprsCodingScheme::HEADER_EGPRS_DATA_TYPE_1 :
|
||||
cur_bit = rlc_parse_ul_data_header_egprs_type_1(rlc, data, cs);
|
||||
break;
|
||||
|
||||
case GprsCodingScheme::HEADER_EGPRS_DATA_TYPE_1:
|
||||
case GprsCodingScheme::HEADER_EGPRS_DATA_TYPE_2:
|
||||
/* TODO: Support both header types */
|
||||
/* fall through */
|
||||
default:
|
||||
LOGP(DRLCMACDL, LOGL_ERROR,
|
||||
"Decoding of uplink %s data blocks not yet supported.\n",
|
||||
|
@ -426,6 +369,181 @@ int Decoding::rlc_parse_ul_data_header(struct gprs_rlc_data_info *rlc,
|
|||
return cur_bit;
|
||||
}
|
||||
|
||||
int Decoding::rlc_parse_ul_data_header_egprs_type_3(
|
||||
struct gprs_rlc_data_info *rlc,
|
||||
const uint8_t *data,
|
||||
const GprsCodingScheme &cs)
|
||||
{
|
||||
int punct, punct2, with_padding, cps;
|
||||
unsigned int e_ti_header, offs, cur_bit = 0;
|
||||
const struct gprs_rlc_ul_header_egprs_3 *egprs3;
|
||||
|
||||
egprs3 = static_cast < struct gprs_rlc_ul_header_egprs_3 * >
|
||||
((void *)data);
|
||||
|
||||
cps = (egprs3->cps_a << 0) | (egprs3->cps_b << 2);
|
||||
gprs_rlc_mcs_cps_decode(cps, cs, &punct, &punct2, &with_padding);
|
||||
gprs_rlc_data_info_init_ul(rlc, cs, with_padding);
|
||||
|
||||
rlc->r = egprs3->r;
|
||||
rlc->si = egprs3->si;
|
||||
rlc->tfi = (egprs3->tfi_a << 0) | (egprs3->tfi_b << 2);
|
||||
rlc->cps = cps;
|
||||
rlc->rsb = egprs3->rsb;
|
||||
|
||||
rlc->num_data_blocks = 1;
|
||||
rlc->block_info[0].cv = egprs3->cv;
|
||||
rlc->block_info[0].pi = egprs3->pi;
|
||||
rlc->block_info[0].spb = egprs3->spb;
|
||||
rlc->block_info[0].bsn =
|
||||
(egprs3->bsn1_a << 0) | (egprs3->bsn1_b << 5);
|
||||
|
||||
cur_bit += rlc->data_offs_bits[0] - 2;
|
||||
offs = rlc->data_offs_bits[0] / 8;
|
||||
OSMO_ASSERT(rlc->data_offs_bits[0] % 8 == 1);
|
||||
e_ti_header = (data[offs-1] + (data[offs] << 8)) >> 7;
|
||||
rlc->block_info[0].e = !!(e_ti_header & 0x01);
|
||||
rlc->block_info[0].ti = !!(e_ti_header & 0x02);
|
||||
cur_bit += 2;
|
||||
/* skip data area */
|
||||
cur_bit += cs.maxDataBlockBytes() * 8;
|
||||
|
||||
return cur_bit;
|
||||
}
|
||||
|
||||
int Decoding::rlc_parse_ul_data_header_egprs_type_2(
|
||||
struct gprs_rlc_data_info *rlc,
|
||||
const uint8_t *data,
|
||||
const GprsCodingScheme &cs)
|
||||
{
|
||||
const struct gprs_rlc_ul_header_egprs_2 *egprs2;
|
||||
unsigned int e_ti_header, offs, cur_bit = 0;
|
||||
int punct, punct2, with_padding, cps;
|
||||
|
||||
egprs2 = static_cast < struct gprs_rlc_ul_header_egprs_2 * >
|
||||
((void *)data);
|
||||
|
||||
cps = (egprs2->cps_a << 0) | (egprs2->cps_b << 2);
|
||||
gprs_rlc_mcs_cps_decode(cps, cs, &punct, &punct2, &with_padding);
|
||||
gprs_rlc_data_info_init_ul(rlc, cs, with_padding);
|
||||
|
||||
rlc->r = egprs2->r;
|
||||
rlc->si = egprs2->si;
|
||||
rlc->tfi = (egprs2->tfi_a << 0) | (egprs2->tfi_b << 2);
|
||||
rlc->cps = cps;
|
||||
rlc->rsb = egprs2->rsb;
|
||||
|
||||
rlc->num_data_blocks = 1;
|
||||
rlc->block_info[0].cv = egprs2->cv;
|
||||
rlc->block_info[0].pi = egprs2->pi;
|
||||
rlc->block_info[0].bsn =
|
||||
(egprs2->bsn1_a << 0) | (egprs2->bsn1_b << 5);
|
||||
|
||||
cur_bit += rlc->data_offs_bits[0] - 2;
|
||||
|
||||
offs = rlc->data_offs_bits[0] / 8;
|
||||
OSMO_ASSERT(rlc->data_offs_bits[0] % 8 == 7);
|
||||
|
||||
e_ti_header = (data[offs] & 0x60) >> 5;
|
||||
rlc->block_info[0].e = !!(e_ti_header & 0x01);
|
||||
rlc->block_info[0].ti = !!(e_ti_header & 0x02);
|
||||
cur_bit += 2;
|
||||
|
||||
/* skip data area */
|
||||
cur_bit += cs.maxDataBlockBytes() * 8;
|
||||
|
||||
return cur_bit;
|
||||
}
|
||||
|
||||
int Decoding::rlc_parse_ul_data_header_egprs_type_1(
|
||||
struct gprs_rlc_data_info *rlc,
|
||||
const uint8_t *data, const GprsCodingScheme &cs)
|
||||
{
|
||||
struct gprs_rlc_ul_header_egprs_1 *egprs1;
|
||||
unsigned int e_ti_header, cur_bit = 0, offs;
|
||||
int punct, punct2, with_padding;
|
||||
|
||||
egprs1 = static_cast < struct gprs_rlc_ul_header_egprs_1 * >
|
||||
((void *)data);
|
||||
gprs_rlc_mcs_cps_decode(egprs1->cps, cs, &punct, &punct2,
|
||||
&with_padding);
|
||||
gprs_rlc_data_info_init_ul(rlc, cs, with_padding);
|
||||
|
||||
rlc->r = egprs1->r;
|
||||
rlc->si = egprs1->si;
|
||||
rlc->tfi = (egprs1->tfi_a << 0) | (egprs1->tfi_b << 2);
|
||||
rlc->cps = egprs1->cps;
|
||||
rlc->rsb = egprs1->rsb;
|
||||
rlc->num_data_blocks = 2;
|
||||
rlc->block_info[0].cv = egprs1->cv;
|
||||
rlc->block_info[0].pi = egprs1->pi;
|
||||
rlc->block_info[0].bsn =
|
||||
(egprs1->bsn1_a << 0) | (egprs1->bsn1_b << 5);
|
||||
|
||||
cur_bit += rlc->data_offs_bits[0] - 2;
|
||||
offs = rlc->data_offs_bits[0] / 8;
|
||||
OSMO_ASSERT(rlc->data_offs_bits[0] % 8 == 0);
|
||||
|
||||
e_ti_header = data[offs - 1] >> 6;
|
||||
rlc->block_info[0].e = (e_ti_header & 0x01);
|
||||
rlc->block_info[0].ti = !!(e_ti_header & 0x02);
|
||||
cur_bit += 2;
|
||||
|
||||
rlc->block_info[1].cv = egprs1->cv;
|
||||
rlc->block_info[1].pi = egprs1->pi;
|
||||
rlc->block_info[1].bsn = rlc->block_info[0].bsn +
|
||||
((egprs1->bsn2_a << 0) | (egprs1->bsn2_b << 2));
|
||||
rlc->block_info[1].bsn = rlc->block_info[1].bsn & (RLC_EGPRS_SNS - 1);
|
||||
|
||||
if ((rlc->block_info[1].bsn != rlc->block_info[0].bsn) &&
|
||||
(rlc->block_info[0].cv == 0))
|
||||
rlc->block_info[0].cv = 1;
|
||||
|
||||
cur_bit = rlc->data_offs_bits[1] - 2;
|
||||
|
||||
offs = rlc->data_offs_bits[1] / 8;
|
||||
OSMO_ASSERT(rlc->data_offs_bits[1] % 8 == 2);
|
||||
|
||||
e_ti_header = (data[offs] & (0x03));
|
||||
rlc->block_info[1].e = (e_ti_header & 0x01);
|
||||
rlc->block_info[1].ti = !!(e_ti_header & 0x02);
|
||||
cur_bit += 2;
|
||||
/* skip data area */
|
||||
cur_bit += cs.maxDataBlockBytes() * 8;
|
||||
|
||||
return cur_bit;
|
||||
}
|
||||
|
||||
int Decoding::rlc_parse_ul_data_header_gprs(struct gprs_rlc_data_info *rlc,
|
||||
const uint8_t *data, const GprsCodingScheme &cs)
|
||||
{
|
||||
const struct rlc_ul_header *gprs;
|
||||
unsigned int cur_bit = 0;
|
||||
|
||||
gprs = static_cast < struct rlc_ul_header * >
|
||||
((void *)data);
|
||||
|
||||
gprs_rlc_data_info_init_ul(rlc, cs, false);
|
||||
|
||||
rlc->r = gprs->r;
|
||||
rlc->si = gprs->si;
|
||||
rlc->tfi = gprs->tfi;
|
||||
rlc->cps = 0;
|
||||
rlc->rsb = 0;
|
||||
rlc->num_data_blocks = 1;
|
||||
rlc->block_info[0].cv = gprs->cv;
|
||||
rlc->block_info[0].pi = gprs->pi;
|
||||
rlc->block_info[0].bsn = gprs->bsn;
|
||||
rlc->block_info[0].e = gprs->e;
|
||||
rlc->block_info[0].ti = gprs->ti;
|
||||
rlc->block_info[0].spb = 0;
|
||||
cur_bit += rlc->data_offs_bits[0];
|
||||
/* skip data area */
|
||||
cur_bit += cs.maxDataBlockBytes() * 8;
|
||||
|
||||
return cur_bit;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Copy LSB bitstream RLC data block to byte aligned buffer.
|
||||
*
|
||||
|
@ -536,7 +654,7 @@ int Decoding::decode_egprs_acknack_bits(const EGPRS_AckNack_Desc_t *desc,
|
|||
bool have_bitmap;
|
||||
int implicitly_acked_blocks;
|
||||
int ssn = desc->STARTING_SEQUENCE_NUMBER;
|
||||
int rc;
|
||||
struct timeval t0, t1;
|
||||
|
||||
if (desc->FINAL_ACK_INDICATION)
|
||||
return handle_final_ack(bits, bsn_begin, bsn_end, window);
|
||||
|
@ -576,26 +694,16 @@ int Decoding::decode_egprs_acknack_bits(const EGPRS_AckNack_Desc_t *desc,
|
|||
|
||||
if (crbb_len > 0) {
|
||||
int old_len = bits->cur_bit;
|
||||
struct bitvec crbb;
|
||||
|
||||
crbb.data = (uint8_t *)desc->CRBB;
|
||||
crbb.data_len = sizeof(desc->CRBB);
|
||||
crbb.cur_bit = desc->CRBB_LENGTH;
|
||||
|
||||
rc = osmo_t4_decode(&crbb, desc->CRBB_STARTING_COLOR_CODE,
|
||||
bits);
|
||||
|
||||
if (rc < 0) {
|
||||
LOGP(DRLCMACUL, LOGL_NOTICE,
|
||||
"Failed to decode CRBB: "
|
||||
"length %d, data '%s'\n",
|
||||
desc->CRBB_LENGTH,
|
||||
osmo_hexdump(crbb.data, crbb.data_len));
|
||||
/* We don't know the SSN offset for the URBB,
|
||||
* return what we have so far and assume the
|
||||
* bitmap has stopped here */
|
||||
goto aborted;
|
||||
}
|
||||
LOGP(DRLCMACDL, LOGL_DEBUG, "Compress bitmap exist,"
|
||||
"CRBB LEN =%d and Starting color code =%d",
|
||||
desc->CRBB_LENGTH, desc->CRBB_STARTING_COLOR_CODE);
|
||||
gettimeofday(&t0, 0);
|
||||
decompress_crbb(desc->CRBB_LENGTH, desc->CRBB_STARTING_COLOR_CODE, desc->CRBB, bits);
|
||||
gettimeofday(&t1, 0);
|
||||
long long elapsed = (t1.tv_sec-t0.tv_sec)*1000000LL + t1.tv_usec-t0.tv_usec;
|
||||
LOGP(DRLCMACDL, LOGL_DEBUG,
|
||||
"PRAVIN:time elapsed for decompression %d\n", elapsed);
|
||||
|
||||
LOGP(DRLCMACDL, LOGL_DEBUG,
|
||||
"CRBB len: %d, decoded len: %d, cc: %d, crbb: '%s'\n",
|
||||
|
|
|
@ -28,10 +28,11 @@ struct bitvec;
|
|||
|
||||
class Decoding {
|
||||
public:
|
||||
/* represents (parts) LLC PDUs within one RLC Data block */
|
||||
struct RlcData {
|
||||
uint8_t offset;
|
||||
uint8_t length;
|
||||
bool is_complete;
|
||||
bool is_complete; /* if this PDU ends in this block */
|
||||
};
|
||||
|
||||
static int rlc_data_from_ul_data(
|
||||
|
@ -43,7 +44,22 @@ public:
|
|||
|
||||
static void extract_rbb(const uint8_t *rbb, char *extracted_rbb);
|
||||
static void extract_rbb(const struct bitvec *rbb, char *show_rbb);
|
||||
|
||||
static int rlc_parse_ul_data_header_egprs_type_3(
|
||||
struct gprs_rlc_data_info *rlc,
|
||||
const uint8_t *data,
|
||||
const GprsCodingScheme &cs);
|
||||
static int rlc_parse_ul_data_header_egprs_type_2(
|
||||
struct gprs_rlc_data_info *rlc,
|
||||
const uint8_t *data,
|
||||
const GprsCodingScheme &cs);
|
||||
static int rlc_parse_ul_data_header_egprs_type_1(
|
||||
struct gprs_rlc_data_info *rlc,
|
||||
const uint8_t *data,
|
||||
const GprsCodingScheme &cs);
|
||||
static int rlc_parse_ul_data_header_gprs(
|
||||
struct gprs_rlc_data_info *rlc,
|
||||
const uint8_t *data,
|
||||
const GprsCodingScheme &cs);
|
||||
static int rlc_parse_ul_data_header(struct gprs_rlc_data_info *rlc,
|
||||
const uint8_t *data, GprsCodingScheme cs);
|
||||
static unsigned int rlc_copy_to_aligned_buffer(
|
||||
|
@ -60,6 +76,12 @@ public:
|
|||
struct gprs_rlc_dl_window *window);
|
||||
static int decode_gprs_acknack_bits(
|
||||
const Ack_Nack_Description_t *desc,
|
||||
bitvec *bits, int *bsn_begin, int *bsn_end,
|
||||
gprs_rlc_dl_window *window);
|
||||
bitvec * bits, int *bsn_begin, int *bsn_end,
|
||||
gprs_rlc_dl_window * window);
|
||||
static void decompress_crbb(
|
||||
int8_t compress_bmap_len,
|
||||
uint8_t clr_code_bit,
|
||||
const uint8_t *orig_buf,
|
||||
bitvec * dest
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,313 @@
|
|||
/* egprs_rlc_compression.h
|
||||
* Routines for EGPRS RLC bitmap compression handling
|
||||
*/
|
||||
#include <egprs_rlc_compression.h>
|
||||
#include <errno.h>
|
||||
#include <decoding.h>
|
||||
|
||||
egprs_compress *egprs_compress::s_instance = 0;
|
||||
Node *egprs_compress::ones_list = NULL;
|
||||
Node *egprs_compress::zeros_list = NULL;
|
||||
|
||||
void egprs_compress::build_codeword(Node *root, const char *cdwd[])
|
||||
{
|
||||
Node *iter; /* iterate the node on the tree */
|
||||
int len; /* length of the code word */
|
||||
int i; /* iterater */
|
||||
uint16_t idx; /* interate index of the code word table */
|
||||
|
||||
root->left = NULL;
|
||||
root->right = NULL;
|
||||
root->run_length = NULL;
|
||||
|
||||
for (idx = 0; idx < MAX_CDWDTBL_LEN; idx++) {
|
||||
len = strlen((const char *)cdwd[idx]);
|
||||
iter = root;
|
||||
for (i = 0; i < len; i++) {
|
||||
if (cdwd[idx][i] == '0') {
|
||||
if (iter->left == NULL) {
|
||||
iter->left = (Node *)malloc(sizeof(Node));
|
||||
/* create a clean node */
|
||||
iter->left->left = NULL;
|
||||
iter->left->right = NULL;
|
||||
iter->left->run_length = NULL;
|
||||
}
|
||||
iter = iter->left;
|
||||
} else if (cdwd[idx][i] == '1') {
|
||||
if (iter->right == NULL) {
|
||||
iter->right = (Node *)malloc(sizeof(Node));
|
||||
/* create a clean node */
|
||||
iter->right->left = NULL;
|
||||
iter->right->right = NULL;
|
||||
iter->right->run_length = NULL;
|
||||
}
|
||||
iter = iter->right;
|
||||
}
|
||||
}
|
||||
if (iter != NULL) {
|
||||
iter->run_length = (uint16_t *)malloc(sizeof(uint16_t));
|
||||
*(iter->run_length) = (uint16_t)NULL;
|
||||
if (idx < 64)
|
||||
*(iter->run_length) = idx;
|
||||
else
|
||||
*(iter->run_length) = (idx - 63) * 64;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char *one_run_len_code_list[MAX_CDWDTBL_LEN] = {
|
||||
"00110101",
|
||||
"000111",
|
||||
"0111",
|
||||
"1000",
|
||||
"1011",
|
||||
"1100",
|
||||
"1110",
|
||||
"1111",
|
||||
"10011",
|
||||
"10100",
|
||||
"00111",
|
||||
"01000",
|
||||
"001000",
|
||||
"000011",
|
||||
"110100",
|
||||
"110101",
|
||||
"101010",
|
||||
"101011",
|
||||
"0100111",
|
||||
"0001100",
|
||||
"0001000",
|
||||
"0010111",
|
||||
"0000011",
|
||||
"0000100",
|
||||
"0101000",
|
||||
"0101011",
|
||||
"0010011",
|
||||
"0100100",
|
||||
"0011000",
|
||||
"00000010",
|
||||
"00000011",
|
||||
"00011010",
|
||||
"00011011",
|
||||
"00010010",
|
||||
"00010011",
|
||||
"00010100",
|
||||
"00010101",
|
||||
"00010110",
|
||||
"00010111",
|
||||
"00101000",
|
||||
"00101001",
|
||||
"00101010",
|
||||
"00101011",
|
||||
"00101100",
|
||||
"00101101",
|
||||
"00000100",
|
||||
"00000101",
|
||||
"00001010",
|
||||
"00001011",
|
||||
"01010010",
|
||||
"01010011",
|
||||
"01010100",
|
||||
"01010101",
|
||||
"00100100",
|
||||
"00100101",
|
||||
"01011000",
|
||||
"01011001",
|
||||
"01011010",
|
||||
"01011011",
|
||||
"01001010",
|
||||
"01001011",
|
||||
"00110010",
|
||||
"00110011",
|
||||
"00110100",
|
||||
"11011",
|
||||
"10010",
|
||||
"010111",
|
||||
"0110111",
|
||||
"00110110",
|
||||
"00110111",
|
||||
"01100100",
|
||||
"01100101",
|
||||
"01101000",
|
||||
"01100111",
|
||||
"011001100",
|
||||
"011001101",
|
||||
"011010010",
|
||||
"011010011",
|
||||
"011010100"
|
||||
};
|
||||
|
||||
const char *zero_run_len_code_list[MAX_CDWDTBL_LEN] = {
|
||||
"0000110111",
|
||||
"10",
|
||||
"11",
|
||||
"010",
|
||||
"011",
|
||||
"0011",
|
||||
"0010",
|
||||
"00011",
|
||||
"000101",
|
||||
"000100",
|
||||
"0000100",
|
||||
"0000101",
|
||||
"0000111",
|
||||
"00000100",
|
||||
"00000111",
|
||||
"000011000",
|
||||
"0000010111",
|
||||
"0000011000",
|
||||
"0000001000",
|
||||
"00001100111",
|
||||
"00001101000",
|
||||
"00001101100",
|
||||
"00000110111",
|
||||
"00000101000",
|
||||
"00000010111",
|
||||
"00000011000",
|
||||
"000011001010",
|
||||
"000011001011",
|
||||
"000011001100",
|
||||
"000011001101",
|
||||
"000001101000",
|
||||
"000001101001",
|
||||
"000001101010",
|
||||
"000001101011",
|
||||
"000011010010",
|
||||
"000011010011",
|
||||
"000011010100",
|
||||
"000011010101",
|
||||
"000011010110",
|
||||
"000011010111",
|
||||
"000001101100",
|
||||
"000001101101",
|
||||
"000011011010",
|
||||
"000011011011",
|
||||
"000001010100",
|
||||
"000001010101",
|
||||
"000001010110",
|
||||
"000001010111",
|
||||
"000001100100",
|
||||
"000001100101",
|
||||
"000001010010",
|
||||
"000001010011",
|
||||
"000000100100",
|
||||
"000000110111",
|
||||
"000000111000",
|
||||
"000000100111",
|
||||
"000000101000",
|
||||
"000001011000",
|
||||
"000001011001",
|
||||
"000000101011",
|
||||
"000000101100",
|
||||
"000001011010",
|
||||
"000001100110",
|
||||
"000001100111",
|
||||
"0000001111",
|
||||
"000011001000",
|
||||
"000011001001",
|
||||
"000001011011",
|
||||
"000000110011",
|
||||
"000000110100",
|
||||
"000000110101",
|
||||
"0000001101100",
|
||||
"0000001101101",
|
||||
"0000001001010",
|
||||
"0000001001011",
|
||||
"0000001001100",
|
||||
"0000001001101",
|
||||
"0000001110010",
|
||||
"0000001110011"
|
||||
};
|
||||
|
||||
int search_runlen(
|
||||
Node *root, /* root of Ones or Zeros tree */
|
||||
const uint8_t *bmbuf, /* Received compressed bitmap buf */
|
||||
uint8_t bit_pos, /* the start bit pos to read codeword */
|
||||
uint8_t *len_codewd, /* length of code word */
|
||||
uint16_t *rlen)
|
||||
{
|
||||
Node *iter;
|
||||
uint8_t dir;
|
||||
|
||||
iter = root;
|
||||
*len_codewd = 0;
|
||||
|
||||
while (iter->run_length == 0) {
|
||||
if ((iter->left == NULL) && (iter->right == NULL))
|
||||
return -1;
|
||||
|
||||
/* get the bit value at the bitpos and put it in right most of dir */
|
||||
dir = ((bmbuf[BITS_TO_BYTES(bit_pos)-1]
|
||||
>>(7-(MOD8(bit_pos)))) & 0x01);
|
||||
(bit_pos)++;
|
||||
(*len_codewd)++;
|
||||
|
||||
if (((dir&0x01) == 0) && (iter->left != NULL))
|
||||
iter = iter->left;
|
||||
|
||||
else if (((dir&0x01) == 1) && (iter->right != NULL))
|
||||
iter = iter->right;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
(*rlen) = *(iter->run_length);
|
||||
|
||||
return 1;
|
||||
} /* search_runlen */
|
||||
|
||||
void Decoding::decompress_crbb(
|
||||
int8_t compress_bmap_len, /* compressed bitmap length */
|
||||
uint8_t clr_code_bit, /* run length of Ones or Zeros */
|
||||
const uint8_t *orig_crbb_buf, /* received block crbb bitmap */
|
||||
bitvec * dest)
|
||||
{
|
||||
|
||||
uint8_t bit_pos = 0;
|
||||
uint8_t nbits = 0; /* number of bits of codeword */
|
||||
uint16_t run_length = 0;
|
||||
uint16_t cbmaplen = 0; /* compressed bitmap part after decompression */
|
||||
unsigned wp = dest->cur_bit;
|
||||
egprs_compress *compress = egprs_compress::instance();
|
||||
|
||||
while (compress_bmap_len > 0) {
|
||||
if (clr_code_bit == 1) {
|
||||
search_runlen(compress->ones_list, orig_crbb_buf,
|
||||
bit_pos, &nbits, &run_length);
|
||||
/*If run length > 64, need makeup and terminating code*/
|
||||
if (run_length < 64)
|
||||
clr_code_bit = 0;
|
||||
cbmaplen = cbmaplen + run_length;
|
||||
/* put run length of Ones in uncompressed bitmap */
|
||||
while (run_length != 0) {
|
||||
if (run_length > 8) {
|
||||
bitvec_write_field(dest, wp, 0xff, 8);
|
||||
run_length = run_length - 8;
|
||||
} else {
|
||||
bitvec_write_field(dest, wp, 0xff,
|
||||
run_length);
|
||||
run_length = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
search_runlen(compress->zeros_list, orig_crbb_buf,
|
||||
bit_pos, &nbits, &run_length);
|
||||
/*If run length > 64, need makeup and terminating code*/
|
||||
if (run_length < 64)
|
||||
clr_code_bit = 1;
|
||||
cbmaplen = cbmaplen + run_length;
|
||||
/* put run length of Zeros in uncompressed bitmap */
|
||||
while (run_length != 0) {
|
||||
if (run_length > 8) {
|
||||
bitvec_write_field(dest, wp, 0x00, 8);
|
||||
run_length = run_length - 8;
|
||||
} else {
|
||||
bitvec_write_field(dest, wp, 0x00,
|
||||
run_length);
|
||||
run_length = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
bit_pos = bit_pos + nbits;
|
||||
compress_bmap_len = compress_bmap_len - nbits;
|
||||
}
|
||||
} /* Decompress_CRBB */
|
|
@ -0,0 +1,60 @@
|
|||
/* egprs_rlc_compression.h
|
||||
* Routines for EGPRS RLC bitmap compression handling
|
||||
*/
|
||||
#include <gprs_rlcmac.h>
|
||||
#include <gprs_debug.h>
|
||||
|
||||
extern "C" {
|
||||
#include <osmocom/core/talloc.h>
|
||||
#include <osmocom/core/msgb.h>
|
||||
#include <osmocom/core/stats.h>
|
||||
}
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#define MAX_CDWDTBL_LEN 79 /* total number of codewords */
|
||||
#define BITS_TO_BYTES(X) ((X ? (X/8):0)+1)
|
||||
#define MOD8(X) (((X)+8) & (0x07))
|
||||
|
||||
typedef struct node {
|
||||
struct node *left;
|
||||
struct node *right;
|
||||
uint16_t *run_length;
|
||||
} Node;
|
||||
|
||||
extern const char *one_run_len_code_list[MAX_CDWDTBL_LEN];
|
||||
extern const char *zero_run_len_code_list[MAX_CDWDTBL_LEN];
|
||||
|
||||
/* Creating singleton class
|
||||
*/
|
||||
class egprs_compress
|
||||
{
|
||||
static egprs_compress *s_instance;
|
||||
|
||||
egprs_compress()
|
||||
{
|
||||
ones_list = (Node *)malloc(sizeof(Node));
|
||||
zeros_list = (Node *)malloc(sizeof(Node));
|
||||
}
|
||||
void build_codeword(Node *root, const char *cdwd[]);
|
||||
public:
|
||||
static Node *ones_list;
|
||||
static Node *zeros_list;
|
||||
|
||||
void decode_tree_init(void)
|
||||
{
|
||||
instance()->build_codeword(
|
||||
ones_list, one_run_len_code_list);
|
||||
instance()->build_codeword(
|
||||
zeros_list, zero_run_len_code_list);
|
||||
}
|
||||
static egprs_compress *instance()
|
||||
{
|
||||
if (!s_instance)
|
||||
s_instance = new egprs_compress;
|
||||
|
||||
return s_instance;
|
||||
}
|
||||
};
|
|
@ -276,7 +276,8 @@ void Encoding::write_packet_uplink_assignment(
|
|||
bitvec_write_field(dest, wp,0x0,1); // No CONTENTION_RESOLUTION_TLLI
|
||||
bitvec_write_field(dest, wp,0x0,1); // No COMPACT reduced MA
|
||||
bitvec_write_field(dest, wp,tbf->current_cs().to_num()-1, 4); // EGPRS Modulation and Coding IE
|
||||
bitvec_write_field(dest, wp,0x0,1); // No RESEGMENT
|
||||
/* No RESEGMENT */
|
||||
bitvec_write_field(dest, wp, 0x1, 1);
|
||||
bitvec_write_field(dest, wp,ws_enc,5); // EGPRS Window Size
|
||||
bitvec_write_field(dest, wp,0x0,1); // No Access Technologies Request
|
||||
bitvec_write_field(dest, wp,0x0,1); // No ARAC RETRANSMISSION REQUEST
|
||||
|
@ -567,6 +568,9 @@ static void write_packet_ack_nack_desc_egprs(
|
|||
eow = false;
|
||||
urbb_len = rest_bits - 9;
|
||||
/* TODO: use compression (see above) */
|
||||
} else {
|
||||
eow = true;
|
||||
urbb_len = num_blocks;
|
||||
}
|
||||
|
||||
if (urbb_len + crbb_len == rest_bits)
|
||||
|
@ -610,9 +614,11 @@ static void write_packet_uplink_ack_egprs(
|
|||
struct gprs_rlcmac_ul_tbf *tbf, bool is_final)
|
||||
{
|
||||
bitvec_write_field(dest, wp, 0, 2); // fixed 00
|
||||
bitvec_write_field(dest, wp, 2, 4); // CHANNEL_CODING_COMMAND: MCS-3
|
||||
// bitvec_write_field(dest, wp, tbf->current_cs() - 1, 4); // CHANNEL_CODING_COMMAND
|
||||
bitvec_write_field(dest, wp, 0, 1); // 0: no RESEGMENT (nyi)
|
||||
/* CHANNEL_CODING_COMMAND */
|
||||
bitvec_write_field(dest, wp,
|
||||
tbf->current_cs().to_num() - 1, 4);
|
||||
/* 0: no RESEGMENT (nyi) */
|
||||
bitvec_write_field(dest, wp, 1, 1);
|
||||
bitvec_write_field(dest, wp, 1, 1); // PRE_EMPTIVE_TRANSMISSION, TODO: This resembles GPRS, change it?
|
||||
bitvec_write_field(dest, wp, 0, 1); // 0: no PRR_RETRANSMISSION_REQUEST, TODO: clarify
|
||||
bitvec_write_field(dest, wp, 0, 1); // 0: no ARAC_RETRANSMISSION_REQUEST, TODO: clarify
|
||||
|
@ -891,6 +897,16 @@ unsigned int Encoding::rlc_copy_from_aligned_buffer(
|
|||
return rdbi->data_len;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief (GPRS) put llc pdu into an rlc/mac block. fragment the llc pdu if needed
|
||||
* \param rdbi rlc/mac block info
|
||||
* \param llc llc pdu
|
||||
* \param offset given offset within the rlc/mac block
|
||||
* \param num_chunks count the chunks (llc pdu data) within rlc/mac
|
||||
* \param data_block buffer holds rlc/mac data
|
||||
* \param is_final if this is the last rlc/mac within a TBF
|
||||
* \return the state of the rlc/mac like if there is more space for another chunk
|
||||
*/
|
||||
static Encoding::AppendResult rlc_data_to_dl_append_gprs(
|
||||
struct gprs_rlc_data_block_info *rdbi,
|
||||
gprs_llc *llc, int *offset, int *num_chunks,
|
||||
|
@ -1012,6 +1028,16 @@ static Encoding::AppendResult rlc_data_to_dl_append_gprs(
|
|||
return Encoding::AR_COMPLETED_BLOCK_FILLED;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief (EGPRS) put llc pdu into an rlc/mac block. fragment the llc pdu if needed
|
||||
* \param rdbi rlc/mac block info
|
||||
* \param llc llc pdu
|
||||
* \param offset given offset within the rlc/mac block
|
||||
* \param num_chunks count the chunks (llc pdu data) within rlc/mac
|
||||
* \param data_block buffer holds rlc/mac data
|
||||
* \param is_final if this is the last rlc/mac within a TBF
|
||||
* \return the state of the rlc/mac like if there is more space for another chunk
|
||||
*/
|
||||
static Encoding::AppendResult rlc_data_to_dl_append_egprs(
|
||||
struct gprs_rlc_data_block_info *rdbi,
|
||||
gprs_llc *llc, int *offset, int *num_chunks,
|
||||
|
@ -1154,6 +1180,17 @@ static Encoding::AppendResult rlc_data_to_dl_append_egprs(
|
|||
return Encoding::AR_COMPLETED_BLOCK_FILLED;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Encoding::rlc_data_to_dl_append
|
||||
* \param rdbi rlc/mac block info
|
||||
* \param cs the coding scheme to use
|
||||
* \param llc llc pdu
|
||||
* \param offset given offset within the rlc/mac block
|
||||
* \param num_chunks count the chunks (llc pdu data) within rlc/mac
|
||||
* \param data_block buffer holds rlc/mac data
|
||||
* \param is_final if this is the last rlc/mac within a TBF
|
||||
* \return the state of the rlc/mac like if there is more space for another chunk
|
||||
*/
|
||||
Encoding::AppendResult Encoding::rlc_data_to_dl_append(
|
||||
struct gprs_rlc_data_block_info *rdbi, GprsCodingScheme cs,
|
||||
gprs_llc *llc, int *offset, int *num_chunks,
|
||||
|
|
|
@ -21,6 +21,41 @@
|
|||
|
||||
#include "gprs_coding_scheme.h"
|
||||
|
||||
/*
|
||||
* 44.060 Table 8.1.1.1 and Table 8.1.1.2
|
||||
* In has 3 level indexing. 0th level is ARQ type
|
||||
* 1st level is Original MCS( index 0 corresponds to MCS1 and so on)
|
||||
* 2nd level is MS MCS (index 0 corresponds to MCS1 and so on)
|
||||
* in 0th level indexing only ARQ type 2 is supported i.e index 1 for
|
||||
* incremental redundancy
|
||||
*/
|
||||
|
||||
enum GprsCodingScheme::Scheme GprsCodingScheme::egprs_mcs_retx_tbl[MAX_NUM_ARQ]
|
||||
[MAX_NUM_MCS][MAX_NUM_MCS] = {
|
||||
{
|
||||
{MCS1, MCS1, MCS1, MCS1, MCS1, MCS1, MCS1, MCS1, MCS1},
|
||||
{MCS2, MCS2, MCS2, MCS2, MCS2, MCS2, MCS2, MCS2, MCS2},
|
||||
{MCS3, MCS3, MCS3, MCS3, MCS3, MCS3, MCS3, MCS3, MCS3},
|
||||
{MCS1, MCS1, MCS1, MCS4, MCS4, MCS4, MCS4, MCS4, MCS4},
|
||||
{MCS2, MCS2, MCS2, MCS2, MCS5, MCS5, MCS7, MCS7, MCS7},
|
||||
{MCS3, MCS3, MCS3, MCS3, MCS3, MCS6, MCS6, MCS6, MCS9},
|
||||
{MCS2, MCS2, MCS2, MCS2, MCS5, MCS5, MCS7, MCS7, MCS7},
|
||||
{MCS3, MCS3, MCS3, MCS3, MCS3, MCS6, MCS6, MCS8, MCS8},
|
||||
{MCS3, MCS3, MCS3, MCS3, MCS3, MCS6, MCS6, MCS6, MCS9}
|
||||
},
|
||||
{
|
||||
{MCS1, MCS1, MCS1, MCS1, MCS1, MCS1, MCS1, MCS1, MCS1},
|
||||
{MCS2, MCS2, MCS2, MCS2, MCS2, MCS2, MCS2, MCS2, MCS2},
|
||||
{MCS3, MCS3, MCS3, MCS3, MCS3, MCS3, MCS3, MCS3, MCS3},
|
||||
{MCS4, MCS4, MCS4, MCS4, MCS4, MCS4, MCS4, MCS4, MCS4},
|
||||
{MCS5, MCS5, MCS5, MCS5, MCS5, MCS5, MCS7, MCS7, MCS7},
|
||||
{MCS6, MCS6, MCS6, MCS6, MCS6, MCS6, MCS6, MCS6, MCS9},
|
||||
{MCS5, MCS5, MCS5, MCS5, MCS5, MCS5, MCS7, MCS7, MCS7},
|
||||
{MCS6, MCS6, MCS6, MCS6, MCS6, MCS6, MCS6, MCS8, MCS8},
|
||||
{MCS6, MCS6, MCS6, MCS6, MCS6, MCS6, MCS6, MCS6, MCS9}
|
||||
}
|
||||
};
|
||||
|
||||
static struct {
|
||||
struct {
|
||||
unsigned int bytes;
|
||||
|
|
|
@ -26,6 +26,12 @@
|
|||
|
||||
class GprsCodingScheme {
|
||||
public:
|
||||
|
||||
#define MAX_NUM_ARQ 2 /* max. number of ARQ */
|
||||
#define MAX_NUM_MCS 9 /* max. number of MCS */
|
||||
#define EGPRS_ARQ1 0x0
|
||||
#define EGPRS_ARQ2 0x1
|
||||
|
||||
enum Scheme {
|
||||
UNKNOWN,
|
||||
CS1, CS2, CS3, CS4,
|
||||
|
@ -64,6 +70,7 @@ public:
|
|||
unsigned int to_num() const;
|
||||
|
||||
GprsCodingScheme& operator =(Scheme s);
|
||||
bool operator == (Scheme s);
|
||||
GprsCodingScheme& operator =(GprsCodingScheme o);
|
||||
|
||||
bool isValid() const {return UNKNOWN <= m_scheme && m_scheme <= MCS9;}
|
||||
|
@ -105,6 +112,12 @@ public:
|
|||
static GprsCodingScheme getEgprsByNum(unsigned num);
|
||||
|
||||
static const char *modeName(Mode mode);
|
||||
static Scheme get_retx_mcs(const GprsCodingScheme &mcs,
|
||||
const GprsCodingScheme &retx_mcs,
|
||||
const unsigned char arq_type);
|
||||
|
||||
static enum Scheme egprs_mcs_retx_tbl[MAX_NUM_ARQ]
|
||||
[MAX_NUM_MCS][MAX_NUM_MCS];
|
||||
private:
|
||||
GprsCodingScheme(int s); /* fail on use */
|
||||
GprsCodingScheme& operator =(int s); /* fail on use */
|
||||
|
@ -188,6 +201,13 @@ inline bool operator ==(GprsCodingScheme a, GprsCodingScheme b)
|
|||
return GprsCodingScheme::Scheme(a) == GprsCodingScheme::Scheme(b);
|
||||
}
|
||||
|
||||
inline bool GprsCodingScheme::operator == (Scheme scheme)
|
||||
{
|
||||
if (this->m_scheme == scheme)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool operator !=(GprsCodingScheme a, GprsCodingScheme b)
|
||||
{
|
||||
return !(a == b);
|
||||
|
@ -214,3 +234,11 @@ inline bool operator >=(GprsCodingScheme a, GprsCodingScheme b)
|
|||
return a == b || a > b;
|
||||
}
|
||||
|
||||
inline GprsCodingScheme::Scheme GprsCodingScheme::get_retx_mcs(
|
||||
const GprsCodingScheme &mcs,
|
||||
const GprsCodingScheme &demanded_mcs,
|
||||
const unsigned char arq_type)
|
||||
{
|
||||
return egprs_mcs_retx_tbl[arq_type][mcs.to_num() - 1]
|
||||
[demanded_mcs.to_num() - 1];
|
||||
}
|
||||
|
|
|
@ -237,7 +237,7 @@ void GprsMs::set_mode(GprsCodingScheme::Mode mode)
|
|||
if (!m_current_cs_ul.isEgprs()) {
|
||||
m_current_cs_ul = GprsCodingScheme::getEgprsByNum(
|
||||
m_bts->bts_data()->initial_mcs_ul);
|
||||
if (!m_current_cs_dl.isValid())
|
||||
if (!m_current_cs_ul.isValid())
|
||||
m_current_cs_ul = GprsCodingScheme::MCS1;
|
||||
}
|
||||
if (!m_current_cs_dl.isEgprs()) {
|
||||
|
@ -574,6 +574,11 @@ GprsCodingScheme GprsMs::max_cs_ul() const
|
|||
return GprsCodingScheme(GprsCodingScheme::MCS4);
|
||||
}
|
||||
|
||||
void GprsMs::set_current_cs_dl(GprsCodingScheme::Scheme scheme)
|
||||
{
|
||||
m_current_cs_dl = scheme;
|
||||
}
|
||||
|
||||
GprsCodingScheme GprsMs::max_cs_dl() const
|
||||
{
|
||||
struct gprs_rlcmac_bts *bts_data;
|
||||
|
@ -632,8 +637,8 @@ void GprsMs::update_cs_ul(const pcu_l1_meas *meas)
|
|||
high = bts_data->cs_lqual_ranges[current_cs_num-1].high;
|
||||
} else if (m_current_cs_ul.isEgprs()) {
|
||||
/* TODO, use separate table */
|
||||
if (current_cs_num > 4)
|
||||
current_cs_num = 4;
|
||||
if (current_cs_num > 9)
|
||||
current_cs_num = 9;
|
||||
low = bts_data->cs_lqual_ranges[current_cs_num-1].low;
|
||||
high = bts_data->cs_lqual_ranges[current_cs_num-1].high;
|
||||
} else {
|
||||
|
|
|
@ -86,6 +86,7 @@ public:
|
|||
uint8_t egprs_ms_class() const;
|
||||
void set_ms_class(uint8_t ms_class);
|
||||
void set_egprs_ms_class(uint8_t ms_class);
|
||||
void set_current_cs_dl(GprsCodingScheme::Scheme scheme);
|
||||
|
||||
GprsCodingScheme current_cs_ul() const;
|
||||
GprsCodingScheme current_cs_dl() const;
|
||||
|
|
|
@ -132,7 +132,7 @@ static struct msgb *sched_select_ctrl_msg(
|
|||
|
||||
/*
|
||||
* Assignments for the same direction have lower precedence,
|
||||
* because they may kill the TBF when the CONTOL ACK is
|
||||
* because they may kill the TBF when the CONTROL ACK is
|
||||
* received, thus preventing the others from being processed.
|
||||
*/
|
||||
|
||||
|
@ -339,14 +339,23 @@ int gprs_rlcmac_rcv_rts_block(struct gprs_rlcmac_bts *bts,
|
|||
/* Prio 1: select control message */
|
||||
msg = sched_select_ctrl_msg(trx, ts, fn, block_nr, pdch, ul_ass_tbf,
|
||||
dl_ass_tbf, ul_ack_tbf);
|
||||
if (msg)
|
||||
bts->bts->rlc_sent_control();
|
||||
|
||||
/* Prio 2: select data message for downlink */
|
||||
if (!msg)
|
||||
if (!msg) {
|
||||
msg = sched_select_downlink(bts, trx, ts, fn, block_nr, pdch);
|
||||
if (msg)
|
||||
bts->bts->rlc_sent();
|
||||
}
|
||||
|
||||
/* Prio 3: send dummy contol message */
|
||||
if (!msg)
|
||||
if (!msg) {
|
||||
/* increase counter */
|
||||
msg = sched_dummy();
|
||||
if (msg)
|
||||
bts->bts->rlc_sent_dummy();
|
||||
}
|
||||
|
||||
if (!msg)
|
||||
return -ENOMEM;
|
||||
|
|
|
@ -0,0 +1,213 @@
|
|||
/* Interface handler for Nuran Wireless Litecell 1.5 L1 (real hardware) */
|
||||
|
||||
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
|
||||
* based on:
|
||||
* femto_l1_hw.c
|
||||
* (C) 2011 by Harald Welte <laforge@gnumonks.org>
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <osmocom/core/talloc.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
#include <osmocom/core/select.h>
|
||||
#include <osmocom/core/write_queue.h>
|
||||
#include <osmocom/core/timer.h>
|
||||
#include <osmocom/gsm/gsm_utils.h>
|
||||
|
||||
#include <nrw/litecell15/litecell15.h>
|
||||
#include <nrw/litecell15/gsml1prim.h>
|
||||
#include <nrw/litecell15/gsml1const.h>
|
||||
#include <nrw/litecell15/gsml1types.h>
|
||||
|
||||
#include "gprs_debug.h"
|
||||
#include "lc15bts.h"
|
||||
#include "lc15_l1_if.h"
|
||||
|
||||
#define DEV_SYS_DSP2ARM_NAME "/dev/msgq/litecell15_dsp2arm_trx"
|
||||
#define DEV_SYS_ARM2DSP_NAME "/dev/msgq/litecell15_arm2dsp_trx"
|
||||
#define DEV_L1_DSP2ARM_NAME "/dev/msgq/gsml1_sig_dsp2arm_trx"
|
||||
#define DEV_L1_ARM2DSP_NAME "/dev/msgq/gsml1_sig_arm2dsp_trx"
|
||||
|
||||
#define DEV_TCH_DSP2ARM_NAME "/dev/msgq/gsml1_tch_dsp2arm_trx"
|
||||
#define DEV_TCH_ARM2DSP_NAME "/dev/msgq/gsml1_tch_arm2dsp_trx"
|
||||
#define DEV_PDTCH_DSP2ARM_NAME "/dev/msgq/gsml1_pdtch_dsp2arm_trx"
|
||||
#define DEV_PDTCH_ARM2DSP_NAME "/dev/msgq/gsml1_pdtch_arm2dsp_trx"
|
||||
|
||||
static const char *rd_devnames[] = {
|
||||
[MQ_SYS_READ] = DEV_SYS_DSP2ARM_NAME,
|
||||
[MQ_L1_READ] = DEV_L1_DSP2ARM_NAME,
|
||||
[MQ_TCH_READ] = DEV_TCH_DSP2ARM_NAME,
|
||||
[MQ_PDTCH_READ] = DEV_PDTCH_DSP2ARM_NAME,
|
||||
};
|
||||
|
||||
static const char *wr_devnames[] = {
|
||||
[MQ_SYS_WRITE] = DEV_SYS_ARM2DSP_NAME,
|
||||
[MQ_L1_WRITE] = DEV_L1_ARM2DSP_NAME,
|
||||
[MQ_TCH_WRITE] = DEV_TCH_ARM2DSP_NAME,
|
||||
[MQ_PDTCH_WRITE]= DEV_PDTCH_ARM2DSP_NAME,
|
||||
};
|
||||
|
||||
/* callback when there's something to read from the l1 msg_queue */
|
||||
static int l1if_fd_cb(struct osmo_fd *ofd, unsigned int what)
|
||||
{
|
||||
//struct msgb *msg = l1p_msgb_alloc();
|
||||
struct msgb *msg = msgb_alloc_headroom(sizeof(Litecell15_Prim_t) + 128,
|
||||
128, "1l_fd");
|
||||
struct lc15l1_hdl *fl1h = ofd->data;
|
||||
int rc;
|
||||
|
||||
msg->l1h = msg->data;
|
||||
rc = read(ofd->fd, msg->l1h, msgb_tailroom(msg));
|
||||
if (rc < 0) {
|
||||
if (rc != -1)
|
||||
LOGP(DL1IF, LOGL_ERROR, "error reading from L1 msg_queue: %s\n",
|
||||
strerror(errno));
|
||||
msgb_free(msg);
|
||||
return rc;
|
||||
}
|
||||
msgb_put(msg, rc);
|
||||
|
||||
switch (ofd->priv_nr) {
|
||||
case MQ_SYS_WRITE:
|
||||
if (rc != sizeof(Litecell15_Prim_t))
|
||||
LOGP(DL1IF, LOGL_NOTICE, "%u != "
|
||||
"sizeof(Litecell15_Prim_t)\n", rc);
|
||||
return l1if_handle_sysprim(fl1h, msg);
|
||||
case MQ_L1_WRITE:
|
||||
case MQ_TCH_WRITE:
|
||||
case MQ_PDTCH_WRITE:
|
||||
if (rc != sizeof(GsmL1_Prim_t))
|
||||
LOGP(DL1IF, LOGL_NOTICE, "%u != "
|
||||
"sizeof(GsmL1_Prim_t)\n", rc);
|
||||
return l1if_handle_l1prim(ofd->priv_nr, fl1h, msg);
|
||||
default:
|
||||
/* The compiler can't know that priv_nr is an enum. Assist. */
|
||||
LOGP(DL1IF, LOGL_FATAL, "writing on a wrong queue: %d\n",
|
||||
ofd->priv_nr);
|
||||
exit(0);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/* callback when we can write to one of the l1 msg_queue devices */
|
||||
static int l1fd_write_cb(struct osmo_fd *ofd, struct msgb *msg)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = write(ofd->fd, msg->l1h, msgb_l1len(msg));
|
||||
if (rc < 0) {
|
||||
LOGP(DL1IF, LOGL_ERROR, "error writing to L1 msg_queue: %s\n",
|
||||
strerror(errno));
|
||||
return rc;
|
||||
} else if (rc < msg->len) {
|
||||
LOGP(DL1IF, LOGL_ERROR, "short write to L1 msg_queue: "
|
||||
"%u < %u\n", rc, msg->len);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int l1if_transport_open(int q, struct lc15l1_hdl *hdl)
|
||||
{
|
||||
int rc;
|
||||
char buf[PATH_MAX];
|
||||
|
||||
/* Step 1: Open all msg_queue file descriptors */
|
||||
struct osmo_fd *read_ofd = &hdl->read_ofd[q];
|
||||
struct osmo_wqueue *wq = &hdl->write_q[q];
|
||||
struct osmo_fd *write_ofd = &hdl->write_q[q].bfd;
|
||||
|
||||
snprintf(buf, sizeof(buf)-1, "%s%d", rd_devnames[q], hdl->hw_info.trx_nr);
|
||||
buf[sizeof(buf)-1] = '\0';
|
||||
|
||||
rc = open(buf, O_RDONLY);
|
||||
if (rc < 0) {
|
||||
LOGP(DL1IF, LOGL_FATAL, "unable to open msg_queue %s: %s\n",
|
||||
buf, strerror(errno));
|
||||
return rc;
|
||||
}
|
||||
read_ofd->fd = rc;
|
||||
read_ofd->priv_nr = q;
|
||||
read_ofd->data = hdl;
|
||||
read_ofd->cb = l1if_fd_cb;
|
||||
read_ofd->when = BSC_FD_READ;
|
||||
rc = osmo_fd_register(read_ofd);
|
||||
if (rc < 0) {
|
||||
close(read_ofd->fd);
|
||||
read_ofd->fd = -1;
|
||||
return rc;
|
||||
}
|
||||
|
||||
snprintf(buf, sizeof(buf)-1, "%s%d", wr_devnames[q], hdl->hw_info.trx_nr);
|
||||
buf[sizeof(buf)-1] = '\0';
|
||||
|
||||
rc = open(buf, O_WRONLY);
|
||||
if (rc < 0) {
|
||||
LOGP(DL1IF, LOGL_FATAL, "unable to open msg_queue %s: %s\n",
|
||||
buf, strerror(errno));
|
||||
goto out_read;
|
||||
}
|
||||
osmo_wqueue_init(wq, 10);
|
||||
wq->write_cb = l1fd_write_cb;
|
||||
write_ofd->fd = rc;
|
||||
write_ofd->priv_nr = q;
|
||||
write_ofd->data = hdl;
|
||||
write_ofd->when = BSC_FD_WRITE;
|
||||
rc = osmo_fd_register(write_ofd);
|
||||
if (rc < 0) {
|
||||
close(write_ofd->fd);
|
||||
write_ofd->fd = -1;
|
||||
goto out_read;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
out_read:
|
||||
close(hdl->read_ofd[q].fd);
|
||||
osmo_fd_unregister(&hdl->read_ofd[q]);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int l1if_transport_close(int q, struct lc15l1_hdl *hdl)
|
||||
{
|
||||
struct osmo_fd *read_ofd = &hdl->read_ofd[q];
|
||||
struct osmo_fd *write_ofd = &hdl->write_q[q].bfd;
|
||||
|
||||
osmo_fd_unregister(read_ofd);
|
||||
close(read_ofd->fd);
|
||||
read_ofd->fd = -1;
|
||||
|
||||
osmo_fd_unregister(write_ofd);
|
||||
close(write_ofd->fd);
|
||||
write_ofd->fd = -1;
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,410 @@
|
|||
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
|
||||
* based on:
|
||||
* femto_l1_if.c
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <nrw/litecell15/litecell15.h>
|
||||
#include <nrw/litecell15/gsml1prim.h>
|
||||
#include <nrw/litecell15/gsml1const.h>
|
||||
#include <nrw/litecell15/gsml1types.h>
|
||||
|
||||
#include <osmocom/core/gsmtap.h>
|
||||
#include <osmocom/core/talloc.h>
|
||||
#include <osmocom/core/timer.h>
|
||||
#include <lc15_l1_if.h>
|
||||
#include <gprs_debug.h>
|
||||
#include <pcu_l1_if.h>
|
||||
|
||||
extern void *tall_pcu_ctx;
|
||||
|
||||
uint32_t l1if_ts_to_hLayer2(uint8_t trx, uint8_t ts)
|
||||
{
|
||||
return (ts << 16) | (trx << 24);
|
||||
}
|
||||
|
||||
/* allocate a msgb containing a GsmL1_Prim_t */
|
||||
struct msgb *l1p_msgb_alloc(void)
|
||||
{
|
||||
struct msgb *msg = msgb_alloc(sizeof(GsmL1_Prim_t), "l1_prim");
|
||||
|
||||
if (msg)
|
||||
msg->l1h = msgb_put(msg, sizeof(GsmL1_Prim_t));
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
static int l1if_req_pdch(struct lc15l1_hdl *fl1h, struct msgb *msg)
|
||||
{
|
||||
struct osmo_wqueue *wqueue = &fl1h->write_q[MQ_PDTCH_WRITE];
|
||||
|
||||
if (osmo_wqueue_enqueue(wqueue, msg) != 0) {
|
||||
LOGP(DL1IF, LOGL_ERROR, "PDTCH queue full. dropping message.\n");
|
||||
msgb_free(msg);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void *prim_init(GsmL1_Prim_t *prim, GsmL1_PrimId_t id, struct lc15l1_hdl *gl1)
|
||||
{
|
||||
prim->id = id;
|
||||
|
||||
switch (id) {
|
||||
case GsmL1_PrimId_MphInitReq:
|
||||
//prim->u.mphInitReq.hLayer1 = (HANDLE)gl1->hLayer1;
|
||||
break;
|
||||
case GsmL1_PrimId_MphCloseReq:
|
||||
prim->u.mphCloseReq.hLayer1 = (HANDLE)gl1->hLayer1;
|
||||
break;
|
||||
case GsmL1_PrimId_MphConnectReq:
|
||||
prim->u.mphConnectReq.hLayer1 = (HANDLE)gl1->hLayer1;
|
||||
break;
|
||||
case GsmL1_PrimId_MphDisconnectReq:
|
||||
prim->u.mphDisconnectReq.hLayer1 = (HANDLE)gl1->hLayer1;
|
||||
break;
|
||||
case GsmL1_PrimId_MphActivateReq:
|
||||
prim->u.mphActivateReq.hLayer1 = (HANDLE)gl1->hLayer1;
|
||||
break;
|
||||
case GsmL1_PrimId_MphDeactivateReq:
|
||||
prim->u.mphDeactivateReq.hLayer1 = (HANDLE)gl1->hLayer1;
|
||||
break;
|
||||
case GsmL1_PrimId_MphConfigReq:
|
||||
prim->u.mphConfigReq.hLayer1 = (HANDLE)gl1->hLayer1;
|
||||
break;
|
||||
case GsmL1_PrimId_MphMeasureReq:
|
||||
prim->u.mphMeasureReq.hLayer1 = (HANDLE)gl1->hLayer1;
|
||||
break;
|
||||
case GsmL1_PrimId_MphInitCnf:
|
||||
case GsmL1_PrimId_MphCloseCnf:
|
||||
case GsmL1_PrimId_MphConnectCnf:
|
||||
case GsmL1_PrimId_MphDisconnectCnf:
|
||||
case GsmL1_PrimId_MphActivateCnf:
|
||||
case GsmL1_PrimId_MphDeactivateCnf:
|
||||
case GsmL1_PrimId_MphConfigCnf:
|
||||
case GsmL1_PrimId_MphMeasureCnf:
|
||||
break;
|
||||
case GsmL1_PrimId_MphTimeInd:
|
||||
break;
|
||||
case GsmL1_PrimId_MphSyncInd:
|
||||
break;
|
||||
case GsmL1_PrimId_PhEmptyFrameReq:
|
||||
prim->u.phEmptyFrameReq.hLayer1 = (HANDLE)gl1->hLayer1;
|
||||
break;
|
||||
case GsmL1_PrimId_PhDataReq:
|
||||
prim->u.phDataReq.hLayer1 = (HANDLE)gl1->hLayer1;
|
||||
break;
|
||||
case GsmL1_PrimId_PhConnectInd:
|
||||
break;
|
||||
case GsmL1_PrimId_PhReadyToSendInd:
|
||||
break;
|
||||
case GsmL1_PrimId_PhDataInd:
|
||||
break;
|
||||
case GsmL1_PrimId_PhRaInd:
|
||||
break;
|
||||
default:
|
||||
LOGP(DL1IF, LOGL_ERROR, "unknown L1 primitive %u\n", id);
|
||||
break;
|
||||
}
|
||||
return &prim->u;
|
||||
}
|
||||
|
||||
struct sapi_dir {
|
||||
GsmL1_Sapi_t sapi;
|
||||
GsmL1_Dir_t dir;
|
||||
};
|
||||
|
||||
static const struct sapi_dir pdtch_sapis[] = {
|
||||
{ GsmL1_Sapi_Pdtch, GsmL1_Dir_TxDownlink },
|
||||
{ GsmL1_Sapi_Pdtch, GsmL1_Dir_RxUplink },
|
||||
{ GsmL1_Sapi_Ptcch, GsmL1_Dir_TxDownlink },
|
||||
{ GsmL1_Sapi_Prach, GsmL1_Dir_RxUplink },
|
||||
#if 0
|
||||
{ GsmL1_Sapi_Ptcch, GsmL1_Dir_RxUplink },
|
||||
{ GsmL1_Sapi_Pacch, GsmL1_Dir_TxDownlink },
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
/* connect PDTCH */
|
||||
int l1if_connect_pdch(void *obj, uint8_t ts)
|
||||
{
|
||||
struct lc15l1_hdl *fl1h = obj;
|
||||
struct msgb *msg = l1p_msgb_alloc();
|
||||
GsmL1_MphConnectReq_t *cr;
|
||||
|
||||
cr = prim_init(msgb_l1prim(msg), GsmL1_PrimId_MphConnectReq, fl1h);
|
||||
cr->u8Tn = ts;
|
||||
cr->logChComb = GsmL1_LogChComb_XIII;
|
||||
|
||||
return l1if_req_pdch(fl1h, msg);
|
||||
}
|
||||
|
||||
static int handle_ph_readytosend_ind(struct lc15l1_hdl *fl1h,
|
||||
GsmL1_PhReadyToSendInd_t *rts_ind)
|
||||
{
|
||||
struct gsm_time g_time;
|
||||
int rc = 0;
|
||||
|
||||
gsm_fn2gsmtime(&g_time, rts_ind->u32Fn);
|
||||
|
||||
DEBUGP(DL1IF, "Rx PH-RTS.ind %02u/%02u/%02u SAPI=%s\n",
|
||||
g_time.t1, g_time.t2, g_time.t3,
|
||||
get_value_string(lc15bts_l1sapi_names, rts_ind->sapi));
|
||||
|
||||
switch (rts_ind->sapi) {
|
||||
case GsmL1_Sapi_Pdtch:
|
||||
case GsmL1_Sapi_Pacch:
|
||||
rc = pcu_rx_rts_req_pdtch(fl1h->trx_no, rts_ind->u8Tn,
|
||||
rts_ind->u16Arfcn, rts_ind->u32Fn, rts_ind->u8BlockNbr);
|
||||
case GsmL1_Sapi_Ptcch:
|
||||
// FIXME
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void get_meas(struct pcu_l1_meas *meas, const GsmL1_MeasParam_t *l1_meas)
|
||||
{
|
||||
meas->rssi = (int8_t) (l1_meas->fRssi);
|
||||
meas->have_rssi = 1;
|
||||
meas->ber = (uint8_t) (l1_meas->fBer * 100);
|
||||
meas->have_ber = 1;
|
||||
meas->bto = (int16_t) (l1_meas->i16BurstTiming);
|
||||
meas->have_bto = 1;
|
||||
meas->link_qual = (int16_t) (l1_meas->fLinkQuality);
|
||||
meas->have_link_qual = 1;
|
||||
}
|
||||
|
||||
static int handle_ph_data_ind(struct lc15l1_hdl *fl1h,
|
||||
GsmL1_PhDataInd_t *data_ind, struct msgb *l1p_msg)
|
||||
{
|
||||
int rc = 0;
|
||||
struct pcu_l1_meas meas = {0};
|
||||
|
||||
DEBUGP(DL1IF, "Rx PH-DATA.ind %s (hL2 %08x): %s\n",
|
||||
get_value_string(lc15bts_l1sapi_names, data_ind->sapi),
|
||||
data_ind->hLayer2,
|
||||
osmo_hexdump(data_ind->msgUnitParam.u8Buffer,
|
||||
data_ind->msgUnitParam.u8Size));
|
||||
|
||||
/*
|
||||
* TODO: Add proper bad frame handling here. This could be used
|
||||
* to switch the used CS. Avoid a crash with the PCU right now
|
||||
* feed "0 - 1" amount of data.
|
||||
*/
|
||||
if (data_ind->msgUnitParam.u8Size == 0)
|
||||
return -1;
|
||||
|
||||
gsmtap_send(fl1h->gsmtap, data_ind->u16Arfcn | GSMTAP_ARFCN_F_UPLINK,
|
||||
data_ind->u8Tn, GSMTAP_CHANNEL_PACCH, 0,
|
||||
data_ind->u32Fn, 0, 0, data_ind->msgUnitParam.u8Buffer+1,
|
||||
data_ind->msgUnitParam.u8Size-1);
|
||||
|
||||
get_meas(&meas, &data_ind->measParam);
|
||||
|
||||
switch (data_ind->sapi) {
|
||||
case GsmL1_Sapi_Pdtch:
|
||||
case GsmL1_Sapi_Pacch:
|
||||
/* drop incomplete UL block */
|
||||
if (data_ind->msgUnitParam.u8Buffer[0]
|
||||
!= GsmL1_PdtchPlType_Full)
|
||||
break;
|
||||
/* PDTCH / PACCH frame handling */
|
||||
pcu_rx_data_ind_pdtch(fl1h->trx_no, data_ind->u8Tn,
|
||||
data_ind->msgUnitParam.u8Buffer + 1,
|
||||
data_ind->msgUnitParam.u8Size - 1,
|
||||
data_ind->u32Fn,
|
||||
&meas);
|
||||
break;
|
||||
case GsmL1_Sapi_Ptcch:
|
||||
// FIXME
|
||||
break;
|
||||
default:
|
||||
LOGP(DL1IF, LOGL_NOTICE, "Rx PH-DATA.ind for unknown L1 SAPI %s\n",
|
||||
get_value_string(lc15bts_l1sapi_names, data_ind->sapi));
|
||||
break;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
#define MIN_QUAL_RACH 5.0f
|
||||
|
||||
static int handle_ph_ra_ind(struct lc15l1_hdl *fl1h, GsmL1_PhRaInd_t *ra_ind)
|
||||
{
|
||||
uint8_t acc_delay;
|
||||
|
||||
if (ra_ind->measParam.fLinkQuality < MIN_QUAL_RACH)
|
||||
return 0;
|
||||
|
||||
DEBUGP(DL1IF, "Rx PH-RA.ind");
|
||||
|
||||
/* check for under/overflow / sign */
|
||||
if (ra_ind->measParam.i16BurstTiming < 0)
|
||||
acc_delay = 0;
|
||||
else
|
||||
acc_delay = ra_ind->measParam.i16BurstTiming >> 2;
|
||||
|
||||
LOGP(DL1IF, LOGL_NOTICE, "got (P)RACH request, TA = %u (ignored)\n",
|
||||
acc_delay);
|
||||
|
||||
#warning "The (P)RACH request is just dropped here"
|
||||
|
||||
#if 0
|
||||
if (acc_delay > bts->max_ta) {
|
||||
LOGP(DL1C, LOGL_INFO, "ignoring RACH request %u > max_ta(%u)\n",
|
||||
acc_delay, btsb->max_ta);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* handle any random indication from the L1 */
|
||||
int l1if_handle_l1prim(int wq, struct lc15l1_hdl *fl1h, struct msgb *msg)
|
||||
{
|
||||
GsmL1_Prim_t *l1p = msgb_l1prim(msg);
|
||||
int rc = 0;
|
||||
|
||||
LOGP(DL1IF, LOGL_DEBUG, "Rx L1 prim %s on queue %d\n",
|
||||
get_value_string(lc15bts_l1prim_names, l1p->id), wq);
|
||||
|
||||
switch (l1p->id) {
|
||||
#if 0
|
||||
case GsmL1_PrimId_MphTimeInd:
|
||||
rc = handle_mph_time_ind(fl1h, &l1p->u.mphTimeInd);
|
||||
break;
|
||||
case GsmL1_PrimId_MphSyncInd:
|
||||
break;
|
||||
case GsmL1_PrimId_PhConnectInd:
|
||||
break;
|
||||
#endif
|
||||
case GsmL1_PrimId_PhReadyToSendInd:
|
||||
rc = handle_ph_readytosend_ind(fl1h, &l1p->u.phReadyToSendInd);
|
||||
break;
|
||||
case GsmL1_PrimId_PhDataInd:
|
||||
rc = handle_ph_data_ind(fl1h, &l1p->u.phDataInd, msg);
|
||||
break;
|
||||
case GsmL1_PrimId_PhRaInd:
|
||||
rc = handle_ph_ra_ind(fl1h, &l1p->u.phRaInd);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
msgb_free(msg);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int l1if_handle_sysprim(struct lc15l1_hdl *fl1h, struct msgb *msg)
|
||||
{
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
/* send packet data request to L1 */
|
||||
int l1if_pdch_req(void *obj, uint8_t ts, int is_ptcch, uint32_t fn,
|
||||
uint16_t arfcn, uint8_t block_nr, uint8_t *data, uint8_t len)
|
||||
{
|
||||
struct lc15l1_hdl *fl1h = obj;
|
||||
struct msgb *msg;
|
||||
GsmL1_Prim_t *l1p;
|
||||
GsmL1_PhDataReq_t *data_req;
|
||||
GsmL1_MsgUnitParam_t *msu_param;
|
||||
struct gsm_time g_time;
|
||||
|
||||
gsm_fn2gsmtime(&g_time, fn);
|
||||
|
||||
DEBUGP(DL1IF, "TX packet data %02u/%02u/%02u is_ptcch=%d ts=%d "
|
||||
"block_nr=%d, arfcn=%d, len=%d\n", g_time.t1, g_time.t2,
|
||||
g_time.t3, is_ptcch, ts, block_nr, arfcn, len);
|
||||
|
||||
msg = l1p_msgb_alloc();
|
||||
l1p = msgb_l1prim(msg);
|
||||
l1p->id = GsmL1_PrimId_PhDataReq;
|
||||
data_req = &l1p->u.phDataReq;
|
||||
data_req->hLayer1 = (HANDLE)fl1h->hLayer1;
|
||||
data_req->sapi = (is_ptcch) ? GsmL1_Sapi_Ptcch : GsmL1_Sapi_Pdtch;
|
||||
data_req->subCh = GsmL1_SubCh_NA;
|
||||
data_req->u8BlockNbr = block_nr;
|
||||
data_req->u8Tn = ts;
|
||||
data_req->u32Fn = fn;
|
||||
msu_param = &data_req->msgUnitParam;
|
||||
msu_param->u8Size = len;
|
||||
memcpy(msu_param->u8Buffer, data, len);
|
||||
|
||||
gsmtap_send(fl1h->gsmtap, arfcn, data_req->u8Tn, GSMTAP_CHANNEL_PACCH,
|
||||
0, data_req->u32Fn, 0, 0,
|
||||
data_req->msgUnitParam.u8Buffer,
|
||||
data_req->msgUnitParam.u8Size);
|
||||
|
||||
|
||||
/* transmit */
|
||||
if (osmo_wqueue_enqueue(&fl1h->write_q[MQ_PDTCH_WRITE], msg) != 0) {
|
||||
LOGP(DL1IF, LOGL_ERROR, "PDTCH queue full. dropping message.\n");
|
||||
msgb_free(msg);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void *l1if_open_pdch(uint8_t trx_no, uint32_t hlayer1)
|
||||
{
|
||||
struct lc15l1_hdl *fl1h;
|
||||
int rc;
|
||||
|
||||
fl1h = talloc_zero(tall_pcu_ctx, struct lc15l1_hdl);
|
||||
if (!fl1h)
|
||||
return NULL;
|
||||
|
||||
fl1h->hLayer1 = hlayer1;
|
||||
fl1h->trx_no = trx_no;
|
||||
/* hardware queues are numbered starting from 1 */
|
||||
fl1h->hw_info.trx_nr = trx_no + 1;
|
||||
|
||||
DEBUGP(DL1IF, "PCU: Using TRX HW#%u\n", fl1h->hw_info.trx_nr);
|
||||
|
||||
rc = l1if_transport_open(MQ_PDTCH_WRITE, fl1h);
|
||||
if (rc < 0) {
|
||||
talloc_free(fl1h);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fl1h->gsmtap = gsmtap_source_init("localhost", GSMTAP_UDP_PORT, 1);
|
||||
if (fl1h->gsmtap)
|
||||
gsmtap_source_add_sink(fl1h->gsmtap);
|
||||
|
||||
return fl1h;
|
||||
}
|
||||
|
||||
int l1if_close_pdch(void *obj)
|
||||
{
|
||||
struct lc15l1_hdl *fl1h = obj;
|
||||
if (fl1h)
|
||||
l1if_transport_close(MQ_PDTCH_WRITE, fl1h);
|
||||
talloc_free(fl1h);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
|
||||
* based on:
|
||||
* femto_l1_if.h
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _LC15_L1_IF_H
|
||||
#define _LC15_L1_IF_H
|
||||
|
||||
#include <osmocom/core/select.h>
|
||||
#include <osmocom/core/write_queue.h>
|
||||
#include <osmocom/core/gsmtap_util.h>
|
||||
#include <osmocom/gsm/gsm_utils.h>
|
||||
#include "lc15bts.h"
|
||||
|
||||
enum {
|
||||
MQ_SYS_READ,
|
||||
MQ_L1_READ,
|
||||
MQ_TCH_READ,
|
||||
MQ_PDTCH_READ,
|
||||
_NUM_MQ_READ
|
||||
};
|
||||
|
||||
enum {
|
||||
MQ_SYS_WRITE,
|
||||
MQ_L1_WRITE,
|
||||
MQ_TCH_WRITE,
|
||||
MQ_PDTCH_WRITE,
|
||||
_NUM_MQ_WRITE
|
||||
};
|
||||
|
||||
struct lc15l1_hdl {
|
||||
struct gsm_time gsm_time;
|
||||
uint32_t hLayer1; /* handle to the L1 instance in the DSP */
|
||||
uint32_t dsp_trace_f;
|
||||
struct llist_head wlc_list;
|
||||
|
||||
struct gsmtap_inst *gsmtap;
|
||||
uint32_t gsmtap_sapi_mask;
|
||||
|
||||
uint8_t trx_no;
|
||||
|
||||
struct osmo_timer_list alive_timer;
|
||||
unsigned int alive_prim_cnt;
|
||||
|
||||
struct osmo_fd read_ofd[_NUM_MQ_READ]; /* osmo file descriptors */
|
||||
struct osmo_wqueue write_q[_NUM_MQ_WRITE];
|
||||
|
||||
struct {
|
||||
int trx_nr; /* <1-2> */
|
||||
} hw_info;
|
||||
};
|
||||
|
||||
#define msgb_l1prim(msg) ((GsmL1_Prim_t *)(msg)->l1h)
|
||||
#define msgb_sysprim(msg) ((Litecell15_Prim_t *)(msg)->l1h)
|
||||
|
||||
typedef int l1if_compl_cb(struct msgb *l1_msg, void *data);
|
||||
|
||||
/* send a request primitive to the L1 and schedule completion call-back */
|
||||
int l1if_req_compl(struct lc15l1_hdl *fl1h, struct msgb *msg,
|
||||
int is_system_prim, l1if_compl_cb *cb, void *data);
|
||||
|
||||
int l1if_reset(struct lc15l1_hdl *hdl);
|
||||
int l1if_activate_rf(struct lc15l1_hdl *hdl, int on);
|
||||
int l1if_set_trace_flags(struct lc15l1_hdl *hdl, uint32_t flags);
|
||||
int l1if_set_txpower(struct lc15l1_hdl *fl1h, float tx_power);
|
||||
|
||||
struct msgb *l1p_msgb_alloc(void);
|
||||
struct msgb *sysp_msgb_alloc(void);
|
||||
|
||||
uint32_t l1if_lchan_to_hLayer2(struct gsm_lchan *lchan);
|
||||
struct gsm_lchan *l1if_hLayer2_to_lchan(struct gsm_bts_trx *trx, uint32_t hLayer2);
|
||||
|
||||
int l1if_handle_sysprim(struct lc15l1_hdl *fl1h, struct msgb *msg);
|
||||
int l1if_handle_l1prim(int wq, struct lc15l1_hdl *fl1h, struct msgb *msg);
|
||||
|
||||
/* tch.c */
|
||||
int l1if_tch_rx(struct gsm_lchan *lchan, struct msgb *l1p_msg);
|
||||
int l1if_tch_fill(struct gsm_lchan *lchan, uint8_t *l1_buffer);
|
||||
struct msgb *gen_empty_tch_msg(struct gsm_lchan *lchan);
|
||||
|
||||
/*
|
||||
* The implementation of these functions is selected by either compiling and
|
||||
* linking sysmo_l1_hw.c or sysmo_l1_fwd.c
|
||||
*/
|
||||
int l1if_transport_open(int q, struct lc15l1_hdl *hdl);
|
||||
int l1if_transport_close(int q, struct lc15l1_hdl *hdl);
|
||||
|
||||
#endif /* _SYSMO_L1_IF_H */
|
|
@ -0,0 +1,332 @@
|
|||
/* NuRAN Wireless Litecell 1.5 L1 API related definitions */
|
||||
|
||||
/* Copyright (C) 2015 by Yves Godin <support@nuranwireless.com>
|
||||
* based on:
|
||||
* sysmobts.c
|
||||
* (C) 2011 by Harald Welte <laforge@gnumonks.org>
|
||||
*
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <nrw/litecell15/litecell15.h>
|
||||
#include <nrw/litecell15/gsml1const.h>
|
||||
#include <nrw/litecell15/gsml1dbg.h>
|
||||
|
||||
#include "lc15bts.h"
|
||||
|
||||
enum l1prim_type lc15bts_get_l1prim_type(GsmL1_PrimId_t id)
|
||||
{
|
||||
switch (id) {
|
||||
case GsmL1_PrimId_MphInitReq: return L1P_T_REQ;
|
||||
case GsmL1_PrimId_MphCloseReq: return L1P_T_REQ;
|
||||
case GsmL1_PrimId_MphConnectReq: return L1P_T_REQ;
|
||||
case GsmL1_PrimId_MphDisconnectReq: return L1P_T_REQ;
|
||||
case GsmL1_PrimId_MphActivateReq: return L1P_T_REQ;
|
||||
case GsmL1_PrimId_MphDeactivateReq: return L1P_T_REQ;
|
||||
case GsmL1_PrimId_MphConfigReq: return L1P_T_REQ;
|
||||
case GsmL1_PrimId_MphMeasureReq: return L1P_T_REQ;
|
||||
case GsmL1_PrimId_MphInitCnf: return L1P_T_CONF;
|
||||
case GsmL1_PrimId_MphCloseCnf: return L1P_T_CONF;
|
||||
case GsmL1_PrimId_MphConnectCnf: return L1P_T_CONF;
|
||||
case GsmL1_PrimId_MphDisconnectCnf: return L1P_T_CONF;
|
||||
case GsmL1_PrimId_MphActivateCnf: return L1P_T_CONF;
|
||||
case GsmL1_PrimId_MphDeactivateCnf: return L1P_T_CONF;
|
||||
case GsmL1_PrimId_MphConfigCnf: return L1P_T_CONF;
|
||||
case GsmL1_PrimId_MphMeasureCnf: return L1P_T_CONF;
|
||||
case GsmL1_PrimId_PhEmptyFrameReq: return L1P_T_REQ;
|
||||
case GsmL1_PrimId_PhDataReq: return L1P_T_REQ;
|
||||
case GsmL1_PrimId_MphTimeInd: return L1P_T_IND;
|
||||
case GsmL1_PrimId_MphSyncInd: return L1P_T_IND;
|
||||
case GsmL1_PrimId_PhConnectInd: return L1P_T_IND;
|
||||
case GsmL1_PrimId_PhReadyToSendInd: return L1P_T_IND;
|
||||
case GsmL1_PrimId_PhDataInd: return L1P_T_IND;
|
||||
case GsmL1_PrimId_PhRaInd: return L1P_T_IND;
|
||||
default: return L1P_T_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
const struct value_string lc15bts_l1prim_names[GsmL1_PrimId_NUM+1] = {
|
||||
{ GsmL1_PrimId_MphInitReq, "MPH-INIT.req" },
|
||||
{ GsmL1_PrimId_MphCloseReq, "MPH-CLOSE.req" },
|
||||
{ GsmL1_PrimId_MphConnectReq, "MPH-CONNECT.req" },
|
||||
{ GsmL1_PrimId_MphDisconnectReq,"MPH-DISCONNECT.req" },
|
||||
{ GsmL1_PrimId_MphActivateReq, "MPH-ACTIVATE.req" },
|
||||
{ GsmL1_PrimId_MphDeactivateReq,"MPH-DEACTIVATE.req" },
|
||||
{ GsmL1_PrimId_MphConfigReq, "MPH-CONFIG.req" },
|
||||
{ GsmL1_PrimId_MphMeasureReq, "MPH-MEASURE.req" },
|
||||
{ GsmL1_PrimId_MphInitCnf, "MPH-INIT.conf" },
|
||||
{ GsmL1_PrimId_MphCloseCnf, "MPH-CLOSE.conf" },
|
||||
{ GsmL1_PrimId_MphConnectCnf, "MPH-CONNECT.conf" },
|
||||
{ GsmL1_PrimId_MphDisconnectCnf,"MPH-DISCONNECT.conf" },
|
||||
{ GsmL1_PrimId_MphActivateCnf, "MPH-ACTIVATE.conf" },
|
||||
{ GsmL1_PrimId_MphDeactivateCnf,"MPH-DEACTIVATE.conf" },
|
||||
{ GsmL1_PrimId_MphConfigCnf, "MPH-CONFIG.conf" },
|
||||
{ GsmL1_PrimId_MphMeasureCnf, "MPH-MEASURE.conf" },
|
||||
{ GsmL1_PrimId_MphTimeInd, "MPH-TIME.ind" },
|
||||
{ GsmL1_PrimId_MphSyncInd, "MPH-SYNC.ind" },
|
||||
{ GsmL1_PrimId_PhEmptyFrameReq, "PH-EMPTY_FRAME.req" },
|
||||
{ GsmL1_PrimId_PhDataReq, "PH-DATA.req" },
|
||||
{ GsmL1_PrimId_PhConnectInd, "PH-CONNECT.ind" },
|
||||
{ GsmL1_PrimId_PhReadyToSendInd,"PH-READY_TO_SEND.ind" },
|
||||
{ GsmL1_PrimId_PhDataInd, "PH-DATA.ind" },
|
||||
{ GsmL1_PrimId_PhRaInd, "PH-RA.ind" },
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
GsmL1_PrimId_t lc15bts_get_l1prim_conf(GsmL1_PrimId_t id)
|
||||
{
|
||||
switch (id) {
|
||||
case GsmL1_PrimId_MphInitReq: return GsmL1_PrimId_MphInitCnf;
|
||||
case GsmL1_PrimId_MphCloseReq: return GsmL1_PrimId_MphCloseCnf;
|
||||
case GsmL1_PrimId_MphConnectReq: return GsmL1_PrimId_MphConnectCnf;
|
||||
case GsmL1_PrimId_MphDisconnectReq: return GsmL1_PrimId_MphDisconnectCnf;
|
||||
case GsmL1_PrimId_MphActivateReq: return GsmL1_PrimId_MphActivateCnf;
|
||||
case GsmL1_PrimId_MphDeactivateReq: return GsmL1_PrimId_MphDeactivateCnf;
|
||||
case GsmL1_PrimId_MphConfigReq: return GsmL1_PrimId_MphConfigCnf;
|
||||
case GsmL1_PrimId_MphMeasureReq: return GsmL1_PrimId_MphMeasureCnf;
|
||||
default: return -1; // Weak
|
||||
}
|
||||
}
|
||||
|
||||
enum l1prim_type lc15bts_get_sysprim_type(Litecell15_PrimId_t id)
|
||||
{
|
||||
switch (id) {
|
||||
case Litecell15_PrimId_SystemInfoReq: return L1P_T_REQ;
|
||||
case Litecell15_PrimId_SystemInfoCnf: return L1P_T_CONF;
|
||||
case Litecell15_PrimId_SystemFailureInd: return L1P_T_IND;
|
||||
case Litecell15_PrimId_ActivateRfReq: return L1P_T_REQ;
|
||||
case Litecell15_PrimId_ActivateRfCnf: return L1P_T_CONF;
|
||||
case Litecell15_PrimId_DeactivateRfReq: return L1P_T_REQ;
|
||||
case Litecell15_PrimId_DeactivateRfCnf: return L1P_T_CONF;
|
||||
case Litecell15_PrimId_SetTraceFlagsReq: return L1P_T_REQ;
|
||||
case Litecell15_PrimId_Layer1ResetReq: return L1P_T_REQ;
|
||||
case Litecell15_PrimId_Layer1ResetCnf: return L1P_T_CONF;
|
||||
case Litecell15_PrimId_SetCalibTblReq: return L1P_T_REQ;
|
||||
case Litecell15_PrimId_SetCalibTblCnf: return L1P_T_CONF;
|
||||
case Litecell15_PrimId_MuteRfReq: return L1P_T_REQ;
|
||||
case Litecell15_PrimId_MuteRfCnf: return L1P_T_CONF;
|
||||
case Litecell15_PrimId_SetRxAttenReq: return L1P_T_REQ;
|
||||
case Litecell15_PrimId_SetRxAttenCnf: return L1P_T_CONF;
|
||||
default: return L1P_T_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
const struct value_string lc15bts_sysprim_names[Litecell15_PrimId_NUM+1] = {
|
||||
{ Litecell15_PrimId_SystemInfoReq, "SYSTEM-INFO.req" },
|
||||
{ Litecell15_PrimId_SystemInfoCnf, "SYSTEM-INFO.conf" },
|
||||
{ Litecell15_PrimId_SystemFailureInd, "SYSTEM-FAILURE.ind" },
|
||||
{ Litecell15_PrimId_ActivateRfReq, "ACTIVATE-RF.req" },
|
||||
{ Litecell15_PrimId_ActivateRfCnf, "ACTIVATE-RF.conf" },
|
||||
{ Litecell15_PrimId_DeactivateRfReq, "DEACTIVATE-RF.req" },
|
||||
{ Litecell15_PrimId_DeactivateRfCnf, "DEACTIVATE-RF.conf" },
|
||||
{ Litecell15_PrimId_SetTraceFlagsReq, "SET-TRACE-FLAGS.req" },
|
||||
{ Litecell15_PrimId_Layer1ResetReq, "LAYER1-RESET.req" },
|
||||
{ Litecell15_PrimId_Layer1ResetCnf, "LAYER1-RESET.conf" },
|
||||
{ Litecell15_PrimId_SetCalibTblReq, "SET-CALIB.req" },
|
||||
{ Litecell15_PrimId_SetCalibTblCnf, "SET-CALIB.cnf" },
|
||||
{ Litecell15_PrimId_MuteRfReq, "MUTE-RF.req" },
|
||||
{ Litecell15_PrimId_MuteRfCnf, "MUTE-RF.cnf" },
|
||||
{ Litecell15_PrimId_SetRxAttenReq, "SET-RX-ATTEN.req" },
|
||||
{ Litecell15_PrimId_SetRxAttenCnf, "SET-RX-ATTEN-CNF.cnf" },
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
Litecell15_PrimId_t lc15bts_get_sysprim_conf(Litecell15_PrimId_t id)
|
||||
{
|
||||
switch (id) {
|
||||
case Litecell15_PrimId_SystemInfoReq: return Litecell15_PrimId_SystemInfoCnf;
|
||||
case Litecell15_PrimId_ActivateRfReq: return Litecell15_PrimId_ActivateRfCnf;
|
||||
case Litecell15_PrimId_DeactivateRfReq: return Litecell15_PrimId_DeactivateRfCnf;
|
||||
case Litecell15_PrimId_Layer1ResetReq: return Litecell15_PrimId_Layer1ResetCnf;
|
||||
case Litecell15_PrimId_SetCalibTblReq: return Litecell15_PrimId_SetCalibTblCnf;
|
||||
case Litecell15_PrimId_MuteRfReq: return Litecell15_PrimId_MuteRfCnf;
|
||||
case Litecell15_PrimId_SetRxAttenReq: return Litecell15_PrimId_SetRxAttenCnf;
|
||||
default: return -1; // Weak
|
||||
}
|
||||
}
|
||||
|
||||
const struct value_string lc15bts_l1sapi_names[GsmL1_Sapi_NUM+1] = {
|
||||
{ GsmL1_Sapi_Idle, "IDLE" },
|
||||
{ GsmL1_Sapi_Fcch, "FCCH" },
|
||||
{ GsmL1_Sapi_Sch, "SCH" },
|
||||
{ GsmL1_Sapi_Sacch, "SACCH" },
|
||||
{ GsmL1_Sapi_Sdcch, "SDCCH" },
|
||||
{ GsmL1_Sapi_Bcch, "BCCH" },
|
||||
{ GsmL1_Sapi_Pch, "PCH" },
|
||||
{ GsmL1_Sapi_Agch, "AGCH" },
|
||||
{ GsmL1_Sapi_Cbch, "CBCH" },
|
||||
{ GsmL1_Sapi_Rach, "RACH" },
|
||||
{ GsmL1_Sapi_TchF, "TCH/F" },
|
||||
{ GsmL1_Sapi_FacchF, "FACCH/F" },
|
||||
{ GsmL1_Sapi_TchH, "TCH/H" },
|
||||
{ GsmL1_Sapi_FacchH, "FACCH/H" },
|
||||
{ GsmL1_Sapi_Nch, "NCH" },
|
||||
{ GsmL1_Sapi_Pdtch, "PDTCH" },
|
||||
{ GsmL1_Sapi_Pacch, "PACCH" },
|
||||
{ GsmL1_Sapi_Pbcch, "PBCCH" },
|
||||
{ GsmL1_Sapi_Pagch, "PAGCH" },
|
||||
{ GsmL1_Sapi_Ppch, "PPCH" },
|
||||
{ GsmL1_Sapi_Pnch, "PNCH" },
|
||||
{ GsmL1_Sapi_Ptcch, "PTCCH" },
|
||||
{ GsmL1_Sapi_Prach, "PRACH" },
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
const struct value_string lc15bts_l1status_names[GSML1_STATUS_NUM+1] = {
|
||||
{ GsmL1_Status_Success, "Success" },
|
||||
{ GsmL1_Status_Generic, "Generic error" },
|
||||
{ GsmL1_Status_NoMemory, "Not enough memory" },
|
||||
{ GsmL1_Status_Timeout, "Timeout" },
|
||||
{ GsmL1_Status_InvalidParam, "Invalid parameter" },
|
||||
{ GsmL1_Status_Busy, "Resource busy" },
|
||||
{ GsmL1_Status_NoRessource, "No more resources" },
|
||||
{ GsmL1_Status_Uninitialized, "Trying to use uninitialized resource" },
|
||||
{ GsmL1_Status_NullInterface, "Trying to call a NULL interface" },
|
||||
{ GsmL1_Status_NullFctnPtr, "Trying to call a NULL function ptr" },
|
||||
{ GsmL1_Status_BadCrc, "Bad CRC" },
|
||||
{ GsmL1_Status_BadUsf, "Bad USF" },
|
||||
{ GsmL1_Status_InvalidCPS, "Invalid CPS field" },
|
||||
{ GsmL1_Status_UnexpectedBurst, "Unexpected burst" },
|
||||
{ GsmL1_Status_UnavailCodec, "AMR codec is unavailable" },
|
||||
{ GsmL1_Status_CriticalError, "Critical error" },
|
||||
{ GsmL1_Status_OverheatError, "Overheat error" },
|
||||
{ GsmL1_Status_DeviceError, "Device error" },
|
||||
{ GsmL1_Status_FacchError, "FACCH / TCH order error" },
|
||||
{ GsmL1_Status_AlreadyDeactivated, "Lchan already deactivated" },
|
||||
{ GsmL1_Status_TxBurstFifoOvrn, "FIFO overrun" },
|
||||
{ GsmL1_Status_TxBurstFifoUndr, "FIFO underrun" },
|
||||
{ GsmL1_Status_NotSynchronized, "Not synchronized" },
|
||||
{ GsmL1_Status_Unsupported, "Unsupported feature" },
|
||||
{ GsmL1_Status_ClockError, "System clock error" },
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
const struct value_string lc15bts_tracef_names[29] = {
|
||||
{ DBG_DEBUG, "DEBUG" },
|
||||
{ DBG_L1WARNING, "L1_WARNING" },
|
||||
{ DBG_ERROR, "ERROR" },
|
||||
{ DBG_L1RXMSG, "L1_RX_MSG" },
|
||||
{ DBG_L1RXMSGBYTE, "L1_RX_MSG_BYTE" },
|
||||
{ DBG_L1TXMSG, "L1_TX_MSG" },
|
||||
{ DBG_L1TXMSGBYTE, "L1_TX_MSG_BYTE" },
|
||||
{ DBG_MPHCNF, "MPH_CNF" },
|
||||
{ DBG_MPHIND, "MPH_IND" },
|
||||
{ DBG_MPHREQ, "MPH_REQ" },
|
||||
{ DBG_PHIND, "PH_IND" },
|
||||
{ DBG_PHREQ, "PH_REQ" },
|
||||
{ DBG_PHYRF, "PHY_RF" },
|
||||
{ DBG_PHYRFMSGBYTE, "PHY_MSG_BYTE" },
|
||||
{ DBG_MODE, "MODE" },
|
||||
{ DBG_TDMAINFO, "TDMA_INFO" },
|
||||
{ DBG_BADCRC, "BAD_CRC" },
|
||||
{ DBG_PHINDBYTE, "PH_IND_BYTE" },
|
||||
{ DBG_PHREQBYTE, "PH_REQ_BYTE" },
|
||||
{ DBG_DEVICEMSG, "DEVICE_MSG" },
|
||||
{ DBG_RACHINFO, "RACH_INFO" },
|
||||
{ DBG_LOGCHINFO, "LOG_CH_INFO" },
|
||||
{ DBG_MEMORY, "MEMORY" },
|
||||
{ DBG_PROFILING, "PROFILING" },
|
||||
{ DBG_TESTCOMMENT, "TEST_COMMENT" },
|
||||
{ DBG_TEST, "TEST" },
|
||||
{ DBG_STATUS, "STATUS" },
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
const struct value_string lc15bts_tracef_docs[29] = {
|
||||
{ DBG_DEBUG, "Debug Region" },
|
||||
{ DBG_L1WARNING, "L1 Warning Region" },
|
||||
{ DBG_ERROR, "Error Region" },
|
||||
{ DBG_L1RXMSG, "L1_RX_MSG Region" },
|
||||
{ DBG_L1RXMSGBYTE, "L1_RX_MSG_BYTE Region" },
|
||||
{ DBG_L1TXMSG, "L1_TX_MSG Region" },
|
||||
{ DBG_L1TXMSGBYTE, "L1_TX_MSG_BYTE Region" },
|
||||
{ DBG_MPHCNF, "MphConfirmation Region" },
|
||||
{ DBG_MPHIND, "MphIndication Region" },
|
||||
{ DBG_MPHREQ, "MphRequest Region" },
|
||||
{ DBG_PHIND, "PhIndication Region" },
|
||||
{ DBG_PHREQ, "PhRequest Region" },
|
||||
{ DBG_PHYRF, "PhyRF Region" },
|
||||
{ DBG_PHYRFMSGBYTE, "PhyRF Message Region" },
|
||||
{ DBG_MODE, "Mode Region" },
|
||||
{ DBG_TDMAINFO, "TDMA Info Region" },
|
||||
{ DBG_BADCRC, "Bad CRC Region" },
|
||||
{ DBG_PHINDBYTE, "PH_IND_BYTE" },
|
||||
{ DBG_PHREQBYTE, "PH_REQ_BYTE" },
|
||||
{ DBG_DEVICEMSG, "Device Message Region" },
|
||||
{ DBG_RACHINFO, "RACH Info" },
|
||||
{ DBG_LOGCHINFO, "LOG_CH_INFO" },
|
||||
{ DBG_MEMORY, "Memory Region" },
|
||||
{ DBG_PROFILING, "Profiling Region" },
|
||||
{ DBG_TESTCOMMENT, "Test Comments" },
|
||||
{ DBG_TEST, "Test Region" },
|
||||
{ DBG_STATUS, "Status Region" },
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
const struct value_string lc15bts_tch_pl_names[] = {
|
||||
{ GsmL1_TchPlType_NA, "N/A" },
|
||||
{ GsmL1_TchPlType_Fr, "FR" },
|
||||
{ GsmL1_TchPlType_Hr, "HR" },
|
||||
{ GsmL1_TchPlType_Efr, "EFR" },
|
||||
{ GsmL1_TchPlType_Amr, "AMR(IF2)" },
|
||||
{ GsmL1_TchPlType_Amr_SidBad, "AMR(SID BAD)" },
|
||||
{ GsmL1_TchPlType_Amr_Onset, "AMR(ONSET)" },
|
||||
{ GsmL1_TchPlType_Amr_Ratscch, "AMR(RATSCCH)" },
|
||||
{ GsmL1_TchPlType_Amr_SidUpdateInH, "AMR(SID_UPDATE INH)" },
|
||||
{ GsmL1_TchPlType_Amr_SidFirstP1, "AMR(SID_FIRST P1)" },
|
||||
{ GsmL1_TchPlType_Amr_SidFirstP2, "AMR(SID_FIRST P2)" },
|
||||
{ GsmL1_TchPlType_Amr_SidFirstInH, "AMR(SID_FIRST INH)" },
|
||||
{ GsmL1_TchPlType_Amr_RatscchMarker, "AMR(RATSCCH MARK)" },
|
||||
{ GsmL1_TchPlType_Amr_RatscchData, "AMR(RATSCCH DATA)" },
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
const struct value_string lc15bts_dir_names[] = {
|
||||
{ GsmL1_Dir_TxDownlink, "TxDL" },
|
||||
{ GsmL1_Dir_TxUplink, "TxUL" },
|
||||
{ GsmL1_Dir_RxUplink, "RxUL" },
|
||||
{ GsmL1_Dir_RxDownlink, "RxDL" },
|
||||
{ GsmL1_Dir_TxDownlink|GsmL1_Dir_RxUplink, "BOTH" },
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
const struct value_string lc15bts_chcomb_names[] = {
|
||||
{ GsmL1_LogChComb_0, "dummy" },
|
||||
{ GsmL1_LogChComb_I, "tch_f" },
|
||||
{ GsmL1_LogChComb_II, "tch_h" },
|
||||
{ GsmL1_LogChComb_IV, "ccch" },
|
||||
{ GsmL1_LogChComb_V, "ccch_sdcch4" },
|
||||
{ GsmL1_LogChComb_VII, "sdcch8" },
|
||||
{ GsmL1_LogChComb_XIII, "pdtch" },
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
const uint8_t pdch_msu_size[_NUM_PDCH_CS] = {
|
||||
[PDCH_CS_1] = 23,
|
||||
[PDCH_CS_2] = 34,
|
||||
[PDCH_CS_3] = 40,
|
||||
[PDCH_CS_4] = 54,
|
||||
[PDCH_MCS_1] = 27,
|
||||
[PDCH_MCS_2] = 33,
|
||||
[PDCH_MCS_3] = 42,
|
||||
[PDCH_MCS_4] = 49,
|
||||
[PDCH_MCS_5] = 60,
|
||||
[PDCH_MCS_6] = 78,
|
||||
[PDCH_MCS_7] = 118,
|
||||
[PDCH_MCS_8] = 142,
|
||||
[PDCH_MCS_9] = 154
|
||||
};
|
|
@ -0,0 +1,64 @@
|
|||
#ifndef LC15BTS_H
|
||||
#define LC15BTS_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <osmocom/core/utils.h>
|
||||
|
||||
#include <nrw/litecell15/litecell15.h>
|
||||
#include <nrw/litecell15/gsml1const.h>
|
||||
|
||||
/*
|
||||
* Depending on the firmware version either GsmL1_Prim_t or Litecell15_Prim_t
|
||||
* is the bigger struct. For earlier firmware versions the GsmL1_Prim_t was the
|
||||
* bigger struct.
|
||||
*/
|
||||
#define LC15BTS_PRIM_SIZE \
|
||||
(OSMO_MAX(sizeof(Litecell15_Prim_t), sizeof(GsmL1_Prim_t)) + 128)
|
||||
|
||||
enum l1prim_type {
|
||||
L1P_T_INVALID, /* this must be 0 to detect uninitialized elements */
|
||||
L1P_T_REQ,
|
||||
L1P_T_CONF,
|
||||
L1P_T_IND,
|
||||
};
|
||||
|
||||
enum l1prim_type lc15bts_get_l1prim_type(GsmL1_PrimId_t id);
|
||||
const struct value_string lc15bts_l1prim_names[GsmL1_PrimId_NUM+1];
|
||||
GsmL1_PrimId_t lc15bts_get_l1prim_conf(GsmL1_PrimId_t id);
|
||||
|
||||
enum l1prim_type lc15bts_get_sysprim_type(Litecell15_PrimId_t id);
|
||||
const struct value_string lc15bts_sysprim_names[Litecell15_PrimId_NUM+1];
|
||||
Litecell15_PrimId_t lc15bts_get_sysprim_conf(Litecell15_PrimId_t id);
|
||||
|
||||
const struct value_string lc15bts_l1sapi_names[GsmL1_Sapi_NUM+1];
|
||||
const struct value_string lc15bts_l1status_names[GSML1_STATUS_NUM+1];
|
||||
|
||||
const struct value_string lc15bts_tracef_names[29];
|
||||
const struct value_string lc15bts_tracef_docs[29];
|
||||
|
||||
const struct value_string lc15bts_tch_pl_names[15];
|
||||
|
||||
const struct value_string lc15bts_clksrc_names[10];
|
||||
|
||||
const struct value_string lc15bts_dir_names[6];
|
||||
|
||||
enum pdch_cs {
|
||||
PDCH_CS_1,
|
||||
PDCH_CS_2,
|
||||
PDCH_CS_3,
|
||||
PDCH_CS_4,
|
||||
PDCH_MCS_1,
|
||||
PDCH_MCS_2,
|
||||
PDCH_MCS_3,
|
||||
PDCH_MCS_4,
|
||||
PDCH_MCS_5,
|
||||
PDCH_MCS_6,
|
||||
PDCH_MCS_7,
|
||||
PDCH_MCS_8,
|
||||
PDCH_MCS_9,
|
||||
_NUM_PDCH_CS
|
||||
};
|
||||
|
||||
const uint8_t pdch_msu_size[_NUM_PDCH_CS];
|
||||
|
||||
#endif /* LC15BTS_H */
|
|
@ -155,7 +155,7 @@ static int handle_ph_readytosend_ind(struct femtol1_hdl *fl1h,
|
|||
switch (rts_ind->sapi) {
|
||||
case GsmL1_Sapi_Pdtch:
|
||||
case GsmL1_Sapi_Pacch:
|
||||
rc = pcu_rx_rts_req_pdtch((long)fl1h->priv, rts_ind->u8Tn,
|
||||
rc = pcu_rx_rts_req_pdtch(fl1h->trx_no, rts_ind->u8Tn,
|
||||
rts_ind->u16Arfcn, rts_ind->u32Fn, rts_ind->u8BlockNbr);
|
||||
case GsmL1_Sapi_Ptcch:
|
||||
// FIXME
|
||||
|
@ -215,7 +215,7 @@ static int handle_ph_data_ind(struct femtol1_hdl *fl1h,
|
|||
!= GsmL1_PdtchPlType_Full)
|
||||
break;
|
||||
/* PDTCH / PACCH frame handling */
|
||||
pcu_rx_data_ind_pdtch((long)fl1h->priv, data_ind->u8Tn,
|
||||
pcu_rx_data_ind_pdtch(fl1h->trx_no, data_ind->u8Tn,
|
||||
data_ind->msgUnitParam.u8Buffer + 1,
|
||||
data_ind->msgUnitParam.u8Size - 1,
|
||||
data_ind->u32Fn,
|
||||
|
@ -357,7 +357,7 @@ int l1if_pdch_req(void *obj, uint8_t ts, int is_ptcch, uint32_t fn,
|
|||
return 0;
|
||||
}
|
||||
|
||||
void *l1if_open_pdch(void *priv, uint32_t hlayer1, struct gsmtap_inst *gsmtap)
|
||||
void *l1if_open_pdch(uint8_t trx_no, uint32_t hlayer1, struct gsmtap_inst *gsmtap)
|
||||
{
|
||||
struct femtol1_hdl *fl1h;
|
||||
int rc;
|
||||
|
@ -367,7 +367,7 @@ void *l1if_open_pdch(void *priv, uint32_t hlayer1, struct gsmtap_inst *gsmtap)
|
|||
return NULL;
|
||||
|
||||
fl1h->hLayer1 = hlayer1;
|
||||
fl1h->priv = priv;
|
||||
fl1h->trx_no = trx_no;
|
||||
fl1h->clk_cal = 0;
|
||||
/* default clock source: OCXO */
|
||||
fl1h->clk_src = SuperFemto_ClkSrcId_Ocxo;
|
|
@ -38,7 +38,7 @@ struct femtol1_hdl {
|
|||
struct gsmtap_inst *gsmtap;
|
||||
uint32_t gsmtap_sapi_mask;
|
||||
|
||||
void *priv; /* user reference */
|
||||
uint8_t trx_no;
|
||||
|
||||
struct osmo_timer_list alive_timer;
|
||||
unsigned int alive_prim_cnt;
|
|
@ -100,7 +100,7 @@ static void pcu_sock_close(struct pcu_sock_state *state, int lost)
|
|||
|
||||
/* disable all slots, kick all TBFs */
|
||||
for (trx = 0; trx < 8; trx++) {
|
||||
#ifdef ENABLE_SYSMODSP
|
||||
#ifdef ENABLE_DIRECT_PHY
|
||||
if (bts->trx[trx].fl1h) {
|
||||
l1if_close_pdch(bts->trx[trx].fl1h);
|
||||
bts->trx[trx].fl1h = NULL;
|
||||
|
|
|
@ -44,7 +44,8 @@ extern "C" {
|
|||
|
||||
// FIXME: move this, when changed from c++ to c.
|
||||
extern "C" {
|
||||
void *l1if_open_pdch(void *priv, uint32_t hlayer1, struct gsmtap_inst *gsmtap);
|
||||
void *l1if_open_pdch(uint8_t trx_no, uint32_t hlayer1,
|
||||
struct gsmtap_inst *gsmtap);
|
||||
int l1if_connect_pdch(void *obj, uint8_t ts);
|
||||
int l1if_pdch_req(void *obj, uint8_t ts, int is_ptcch, uint32_t fn,
|
||||
uint16_t arfcn, uint8_t block_nr, uint8_t *data, uint8_t len);
|
||||
|
@ -128,7 +129,7 @@ void pcu_l1if_tx_pdtch(msgb *msg, uint8_t trx, uint8_t ts, uint16_t arfcn,
|
|||
{
|
||||
struct gprs_rlcmac_bts *bts = bts_main_data();
|
||||
|
||||
#ifdef ENABLE_SYSMODSP
|
||||
#ifdef ENABLE_DIRECT_PHY
|
||||
if (bts->trx[trx].fl1h) {
|
||||
l1if_pdch_req(bts->trx[trx].fl1h, ts, 0, fn, arfcn, block_nr,
|
||||
msg->data, msg->len);
|
||||
|
@ -147,7 +148,7 @@ void pcu_l1if_tx_ptcch(msgb *msg, uint8_t trx, uint8_t ts, uint16_t arfcn,
|
|||
{
|
||||
struct gprs_rlcmac_bts *bts = bts_main_data();
|
||||
|
||||
#ifdef ENABLE_SYSMODSP
|
||||
#ifdef ENABLE_DIRECT_PHY
|
||||
if (bts->trx[trx].fl1h) {
|
||||
l1if_pdch_req(bts->trx[trx].fl1h, ts, 1, fn, arfcn, block_nr,
|
||||
msg->data, msg->len);
|
||||
|
@ -330,8 +331,8 @@ static int pcu_rx_info_ind(struct gsm_pcu_if_info_ind *info_ind)
|
|||
struct gprs_bssgp_pcu *pcu;
|
||||
struct gprs_rlcmac_pdch *pdch;
|
||||
struct in_addr ia;
|
||||
int rc = 0;
|
||||
int trx, ts;
|
||||
int rc = 0, ts;
|
||||
uint8_t trx;
|
||||
int i;
|
||||
|
||||
if (info_ind->version != PCU_IF_VERSION) {
|
||||
|
@ -445,12 +446,12 @@ bssgp_failed:
|
|||
bts->trx[trx].arfcn = info_ind->trx[trx].arfcn;
|
||||
if ((info_ind->flags & PCU_IF_FLAG_SYSMO)
|
||||
&& info_ind->trx[trx].hlayer1) {
|
||||
#ifdef ENABLE_SYSMODSP
|
||||
#ifdef ENABLE_DIRECT_PHY
|
||||
LOGP(DL1IF, LOGL_DEBUG, " TRX %d hlayer1=%x\n", trx,
|
||||
info_ind->trx[trx].hlayer1);
|
||||
if (!bts->trx[trx].fl1h)
|
||||
bts->trx[trx].fl1h = l1if_open_pdch(
|
||||
(void *)trx,
|
||||
trx,
|
||||
info_ind->trx[trx].hlayer1,
|
||||
bts->gsmtap);
|
||||
if (!bts->trx[trx].fl1h) {
|
||||
|
@ -471,7 +472,7 @@ bssgp_failed:
|
|||
if ((info_ind->trx[trx].pdch_mask & (1 << ts))) {
|
||||
/* FIXME: activate dynamically at RLCMAC */
|
||||
if (!pdch->is_enabled()) {
|
||||
#ifdef ENABLE_SYSMODSP
|
||||
#ifdef ENABLE_DIRECT_PHY
|
||||
if ((info_ind->flags &
|
||||
PCU_IF_FLAG_SYSMO))
|
||||
l1if_connect_pdch(
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include <signal.h>
|
||||
#include <sched.h>
|
||||
#include <bts.h>
|
||||
#include <egprs_rlc_compression.h>
|
||||
extern "C" {
|
||||
#include "pcu_vty.h"
|
||||
#include <osmocom/vty/telnet_interface.h>
|
||||
|
@ -46,7 +47,7 @@ void *tall_pcu_ctx;
|
|||
extern void *bv_tall_ctx;
|
||||
static int quit = 0;
|
||||
static int rt_prio = -1;
|
||||
static char *gsmtap_addr = "localhost"; // FIXME: use gengetopt's default value instead
|
||||
static const char *gsmtap_addr = "localhost"; // FIXME: use gengetopt's default value instead
|
||||
|
||||
static void print_help()
|
||||
{
|
||||
|
@ -189,8 +190,8 @@ int main(int argc, char *argv[])
|
|||
bts->cs_adj_lower_limit = 10; /* Increase CS if the error rate is below */
|
||||
bts->max_cs_ul = 4;
|
||||
bts->max_cs_dl = 4;
|
||||
bts->max_mcs_ul = 4;
|
||||
bts->max_mcs_dl = 4;
|
||||
bts->max_mcs_ul = 9;
|
||||
bts->max_mcs_dl = 9;
|
||||
/* CS-1 to CS-4 */
|
||||
bts->cs_lqual_ranges[0].low = -256;
|
||||
bts->cs_lqual_ranges[0].high = 6;
|
||||
|
@ -210,6 +211,12 @@ int main(int argc, char *argv[])
|
|||
bts->dl_tbf_idle_msec = 2000;
|
||||
bts->llc_idle_ack_csec = 10;
|
||||
|
||||
/*
|
||||
* By default resegmentation is supported in DL
|
||||
* TODO: VTY should be updated to make it configurable
|
||||
*/
|
||||
bts->arq_type = 0;
|
||||
|
||||
msgb_set_talloc_ctx(tall_pcu_ctx);
|
||||
|
||||
osmo_init_logging(&gprs_log_info);
|
||||
|
@ -253,6 +260,8 @@ int main(int argc, char *argv[])
|
|||
if (!bts->alloc_algorithm)
|
||||
bts->alloc_algorithm = alloc_algorithm_dynamic;
|
||||
|
||||
egprs_compress::instance()->decode_tree_init();
|
||||
|
||||
rc = pcu_l1if_open();
|
||||
|
||||
if (rc < 0)
|
||||
|
|
|
@ -102,7 +102,7 @@ int pcu_vty_show_ms_all(struct vty *vty, struct gprs_rlcmac_bts *bts_data)
|
|||
llist_for_each(ms_iter, &bts->ms_store().ms_list()) {
|
||||
GprsMs *ms = ms_iter->entry();
|
||||
|
||||
vty_out(vty, "MS TLLI=%08x, TA=%d, CS-UL=%s, CS-DL=%s, LLC=%d, "
|
||||
vty_out(vty, "MS TLLI=%08x, TA=%d, CS-UL=%s, CS-DL=%s, LLC=%zd, "
|
||||
"IMSI=%s%s",
|
||||
ms->tlli(),
|
||||
ms->ta(), ms->current_cs_ul().name(),
|
||||
|
@ -136,9 +136,9 @@ static int show_ms(struct vty *vty, GprsMs *ms)
|
|||
if (slots & (1 << i))
|
||||
vty_out(vty, "%d ", i);
|
||||
vty_out(vty, "%s", VTY_NEWLINE);
|
||||
vty_out(vty, " LLC queue length: %d%s", ms->llc_queue()->size(),
|
||||
vty_out(vty, " LLC queue length: %zd%s", ms->llc_queue()->size(),
|
||||
VTY_NEWLINE);
|
||||
vty_out(vty, " LLC queue octets: %d%s", ms->llc_queue()->octets(),
|
||||
vty_out(vty, " LLC queue octets: %zd%s", ms->llc_queue()->octets(),
|
||||
VTY_NEWLINE);
|
||||
if (ms->l1_meas()->have_rssi)
|
||||
vty_out(vty, " RSSI: %d dBm%s",
|
||||
|
|
130
src/rlc.cpp
130
src/rlc.cpp
|
@ -33,6 +33,9 @@ uint8_t *gprs_rlc_data::prepare(size_t block_data_len)
|
|||
memset(block, 0x0, sizeof(block));
|
||||
memset(block, 0x2b, block_data_len);
|
||||
|
||||
/* Initial value of puncturing scheme */
|
||||
next_ps = EGPRS_PS_1;
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
|
@ -282,7 +285,8 @@ bool gprs_rlc_ul_window::invalidate_bsn(const uint16_t bsn)
|
|||
}
|
||||
|
||||
static void gprs_rlc_data_header_init(struct gprs_rlc_data_info *rlc,
|
||||
GprsCodingScheme cs, bool with_padding, unsigned int header_bits)
|
||||
GprsCodingScheme cs, bool with_padding, unsigned int header_bits,
|
||||
unsigned int spb = 0)
|
||||
{
|
||||
unsigned int i;
|
||||
unsigned int padding_bits = with_padding ? cs.optionalPaddingBits() : 0;
|
||||
|
@ -297,7 +301,7 @@ static void gprs_rlc_data_header_init(struct gprs_rlc_data_info *rlc,
|
|||
|
||||
for (i = 0; i < rlc->num_data_blocks; i++) {
|
||||
gprs_rlc_data_block_info_init(&rlc->block_info[i], cs,
|
||||
with_padding);
|
||||
with_padding, spb);
|
||||
|
||||
rlc->data_offs_bits[i] =
|
||||
header_bits + padding_bits +
|
||||
|
@ -307,10 +311,10 @@ static void gprs_rlc_data_header_init(struct gprs_rlc_data_info *rlc,
|
|||
}
|
||||
|
||||
void gprs_rlc_data_info_init_dl(struct gprs_rlc_data_info *rlc,
|
||||
GprsCodingScheme cs, bool with_padding)
|
||||
GprsCodingScheme cs, bool with_padding, unsigned int spb)
|
||||
{
|
||||
return gprs_rlc_data_header_init(rlc, cs, with_padding,
|
||||
cs.numDataHeaderBitsDL());
|
||||
cs.numDataHeaderBitsDL(), spb);
|
||||
}
|
||||
|
||||
void gprs_rlc_data_info_init_ul(struct gprs_rlc_data_info *rlc,
|
||||
|
@ -321,7 +325,7 @@ void gprs_rlc_data_info_init_ul(struct gprs_rlc_data_info *rlc,
|
|||
}
|
||||
|
||||
void gprs_rlc_data_block_info_init(struct gprs_rlc_data_block_info *rdbi,
|
||||
GprsCodingScheme cs, bool with_padding)
|
||||
GprsCodingScheme cs, bool with_padding, unsigned int spb)
|
||||
{
|
||||
unsigned int data_len = cs.maxDataBlockBytes();
|
||||
if (with_padding)
|
||||
|
@ -333,24 +337,35 @@ void gprs_rlc_data_block_info_init(struct gprs_rlc_data_block_info *rdbi,
|
|||
rdbi->e = 1;
|
||||
rdbi->cv = 15;
|
||||
rdbi->pi = 0;
|
||||
rdbi->spb = 0;
|
||||
rdbi->spb = spb;
|
||||
}
|
||||
|
||||
unsigned int gprs_rlc_mcs_cps(GprsCodingScheme cs, int punct, int punct2,
|
||||
int with_padding)
|
||||
unsigned int gprs_rlc_mcs_cps(GprsCodingScheme cs,
|
||||
enum egprs_puncturing_values punct,
|
||||
enum egprs_puncturing_values punct2, int with_padding)
|
||||
{
|
||||
switch (GprsCodingScheme::Scheme(cs)) {
|
||||
case GprsCodingScheme::MCS1: return 0b1011 + punct % 2;
|
||||
case GprsCodingScheme::MCS2: return 0b1001 + punct % 2;
|
||||
case GprsCodingScheme::MCS1: return 0b1011 +
|
||||
punct % EGPRS_MAX_PS_NUM_2;
|
||||
case GprsCodingScheme::MCS2: return 0b1001 +
|
||||
punct % EGPRS_MAX_PS_NUM_2;
|
||||
case GprsCodingScheme::MCS3: return (with_padding ? 0b0110 : 0b0011) +
|
||||
punct % 3;
|
||||
case GprsCodingScheme::MCS4: return 0b0000 + punct % 3;
|
||||
case GprsCodingScheme::MCS5: return 0b100 + punct % 2;
|
||||
punct % EGPRS_MAX_PS_NUM_3;
|
||||
case GprsCodingScheme::MCS4: return 0b0000 +
|
||||
punct % EGPRS_MAX_PS_NUM_3;
|
||||
case GprsCodingScheme::MCS5: return 0b100 +
|
||||
punct % EGPRS_MAX_PS_NUM_2;
|
||||
case GprsCodingScheme::MCS6: return (with_padding ? 0b010 : 0b000) +
|
||||
punct % 2;
|
||||
case GprsCodingScheme::MCS7: return 0b10100 + 3 * (punct % 3) + punct2 % 3;
|
||||
case GprsCodingScheme::MCS8: return 0b01011 + 3 * (punct % 3) + punct2 % 3;
|
||||
case GprsCodingScheme::MCS9: return 0b00000 + 4 * (punct % 3) + punct2 % 3;
|
||||
punct % EGPRS_MAX_PS_NUM_2;
|
||||
case GprsCodingScheme::MCS7: return 0b10100 +
|
||||
3 * (punct % EGPRS_MAX_PS_NUM_3) +
|
||||
punct2 % EGPRS_MAX_PS_NUM_3;
|
||||
case GprsCodingScheme::MCS8: return 0b01011 +
|
||||
3 * (punct % EGPRS_MAX_PS_NUM_3) +
|
||||
punct2 % EGPRS_MAX_PS_NUM_3;
|
||||
case GprsCodingScheme::MCS9: return 0b00000 +
|
||||
4 * (punct % EGPRS_MAX_PS_NUM_3) +
|
||||
punct2 % EGPRS_MAX_PS_NUM_3;
|
||||
default: ;
|
||||
}
|
||||
|
||||
|
@ -385,3 +400,84 @@ void gprs_rlc_mcs_cps_decode(unsigned int cps,
|
|||
default: ;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Finds the PS value for retransmission with MCS change,
|
||||
* retransmission with no MCS change, fresh transmission cases.
|
||||
* The return value shall be used for current transmission only
|
||||
* 44.060 9.3.2.1 defines the PS selection for MCS change case
|
||||
* cs_current is the output of MCS selection algorithm for retx
|
||||
* cs is coding scheme of previous transmission of RLC data block
|
||||
*/
|
||||
enum egprs_puncturing_values gprs_get_punct_scheme(
|
||||
enum egprs_puncturing_values punct,
|
||||
const GprsCodingScheme &cs,
|
||||
const GprsCodingScheme &cs_current,
|
||||
unsigned int spb)
|
||||
{
|
||||
/* If it is second segment of the split block
|
||||
* dont change the puncturing scheme
|
||||
*/
|
||||
if (spb == 3)
|
||||
return punct;
|
||||
|
||||
/* TS 44.060 9.3.2.1.1 */
|
||||
if ((GprsCodingScheme::Scheme(cs) == GprsCodingScheme::MCS9) &&
|
||||
(GprsCodingScheme::Scheme(cs_current) == GprsCodingScheme::MCS6)) {
|
||||
if ((punct == EGPRS_PS_1) || (punct == EGPRS_PS_3))
|
||||
return EGPRS_PS_1;
|
||||
else if (punct == EGPRS_PS_2)
|
||||
return EGPRS_PS_2;
|
||||
} else if ((GprsCodingScheme::Scheme(cs) == GprsCodingScheme::MCS6) &&
|
||||
(GprsCodingScheme::Scheme(cs_current) == GprsCodingScheme::MCS9)) {
|
||||
if (punct == EGPRS_PS_1)
|
||||
return EGPRS_PS_3;
|
||||
else if (punct == EGPRS_PS_2)
|
||||
return EGPRS_PS_2;
|
||||
} else if ((GprsCodingScheme::Scheme(cs) == GprsCodingScheme::MCS7) &&
|
||||
(GprsCodingScheme::Scheme(cs_current) == GprsCodingScheme::MCS5))
|
||||
return EGPRS_PS_1;
|
||||
else if ((GprsCodingScheme::Scheme(cs) == GprsCodingScheme::MCS5) &&
|
||||
(GprsCodingScheme::Scheme(cs_current) == GprsCodingScheme::MCS7))
|
||||
return EGPRS_PS_2;
|
||||
else if (cs != cs_current)
|
||||
return EGPRS_PS_1;
|
||||
/* TS 44.060 9.3.2.1.1 ends here */
|
||||
/*
|
||||
* Below else will handle fresh transmission, retransmission with no
|
||||
* MCS change case
|
||||
*/
|
||||
else
|
||||
return punct;
|
||||
return EGPRS_PS_INVALID;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function calculates puncturing scheme for retransmission of a RLC
|
||||
* block with same MCS. The computed value shall be used for next transmission
|
||||
* of the same RLC block
|
||||
* TS 44.060 10.4.8a.3.1, 10.4.8a.2.1, 10.4.8a.1.1
|
||||
*/
|
||||
void gprs_update_punct_scheme(enum egprs_puncturing_values *punct,
|
||||
const GprsCodingScheme &cs)
|
||||
{
|
||||
switch (GprsCodingScheme::Scheme(cs)) {
|
||||
case GprsCodingScheme::MCS1 :
|
||||
case GprsCodingScheme::MCS2 :
|
||||
case GprsCodingScheme::MCS5 :
|
||||
case GprsCodingScheme::MCS6 :
|
||||
*punct = ((enum egprs_puncturing_values)((*punct + 1) %
|
||||
EGPRS_MAX_PS_NUM_2));
|
||||
break;
|
||||
case GprsCodingScheme::MCS3 :
|
||||
case GprsCodingScheme::MCS4 :
|
||||
case GprsCodingScheme::MCS7 :
|
||||
case GprsCodingScheme::MCS8 :
|
||||
case GprsCodingScheme::MCS9 :
|
||||
*punct = ((enum egprs_puncturing_values)((*punct + 1) %
|
||||
EGPRS_MAX_PS_NUM_3));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
116
src/rlc.h
116
src/rlc.h
|
@ -27,7 +27,6 @@
|
|||
|
||||
#define RLC_GPRS_SNS 128 /* GPRS, must be power of 2 */
|
||||
#define RLC_GPRS_WS 64 /* max window size */
|
||||
#define RLC_EGPRS_SNS 2048 /* EGPRS, must be power of 2 */
|
||||
#define RLC_EGPRS_MIN_WS 64 /* min window size */
|
||||
#define RLC_EGPRS_MAX_WS 1024 /* min window size */
|
||||
#define RLC_EGPRS_SNS 2048 /* EGPRS, must be power of 2 */
|
||||
|
@ -56,6 +55,43 @@ enum gprs_rlc_dl_bsn_state {
|
|||
GPRS_RLC_DL_BSN_MAX,
|
||||
};
|
||||
|
||||
/* EDGE resegment status information for UL */
|
||||
enum egprs_rlc_ul_reseg_bsn_state {
|
||||
EGPRS_RESEG_DEFAULT = 0,
|
||||
EGPRS_RESEG_FIRST_SEG_RXD = 0x01,
|
||||
EGPRS_RESEG_SECOND_SEG_RXD = 0x02,
|
||||
EGPRS_RESEG_INVALID
|
||||
};
|
||||
|
||||
/* EDGE resegment status information for DL */
|
||||
enum egprs_rlc_dl_reseg_bsn_state {
|
||||
EGPRS_RESEG_DL_DEFAULT = 0,
|
||||
EGPRS_RESEG_FIRST_SEG_SENT = 0x01,
|
||||
EGPRS_RESEG_SECOND_SEG_SENT = 0x02,
|
||||
EGPRS_RESEG_DL_STATE_INVALID
|
||||
};
|
||||
|
||||
/*
|
||||
* Valid puncturing scheme values
|
||||
* TS 44.060 10.4.8a.3.1, 10.4.8a.2.1, 10.4.8a.1.1
|
||||
*/
|
||||
enum egprs_puncturing_values {
|
||||
EGPRS_PS_1,
|
||||
EGPRS_PS_2,
|
||||
EGPRS_PS_3,
|
||||
EGPRS_PS_INVALID,
|
||||
};
|
||||
|
||||
/*
|
||||
* EGPRS_MAX_PS_NUM_2 is valid for MCS 1,2,5,6.
|
||||
* And EGPRS_MAX_PS_NUM_3 is valid for MCS 3,4,7,8,9
|
||||
* TS 44.060 10.4.8a.3.1, 10.4.8a.2.1, 10.4.8a.1.1
|
||||
*/
|
||||
enum egprs_puncturing_types {
|
||||
EGPRS_MAX_PS_NUM_2 = 2,
|
||||
EGPRS_MAX_PS_NUM_3,
|
||||
EGPRS_MAX_PS_NUM_INVALID,
|
||||
};
|
||||
|
||||
static inline uint16_t mod_sns_half()
|
||||
{
|
||||
|
@ -99,20 +135,50 @@ struct gprs_rlc_data {
|
|||
uint8_t len;
|
||||
|
||||
struct gprs_rlc_data_block_info block_info;
|
||||
/*
|
||||
* cs_current_trans is variable to hold the cs value for
|
||||
* current transmission. cs_current_trans is same as cs during
|
||||
* transmission case. during retransmission cs_current_trans is
|
||||
* fetched from egprs_mcs_retx_tbl table based on
|
||||
* cs and demanded cs.reference is 44.060 Table
|
||||
* 8.1.1.1 and Table 8.1.1.2
|
||||
*/
|
||||
GprsCodingScheme cs_current_trans;
|
||||
GprsCodingScheme cs;
|
||||
|
||||
/*
|
||||
* The MCS of initial transmission
|
||||
* This variable is used for split block
|
||||
* processing in DL
|
||||
*/
|
||||
GprsCodingScheme cs_init;
|
||||
|
||||
/* puncturing scheme value to be used for next transmission*/
|
||||
enum egprs_puncturing_values next_ps;
|
||||
|
||||
/* holds the current status of the block w.r.t UL split blocks*/
|
||||
egprs_rlc_ul_reseg_bsn_state block_status_ul;
|
||||
|
||||
/* Holds the current status of the block w.r.t DL split blocks*/
|
||||
egprs_rlc_dl_reseg_bsn_state block_status_dl;
|
||||
};
|
||||
|
||||
void gprs_rlc_data_info_init_dl(struct gprs_rlc_data_info *rlc,
|
||||
GprsCodingScheme cs, bool with_padding);
|
||||
GprsCodingScheme cs, bool with_padding, unsigned int spb = 0);
|
||||
void gprs_rlc_data_info_init_ul(struct gprs_rlc_data_info *rlc,
|
||||
GprsCodingScheme cs, bool with_padding);
|
||||
void gprs_rlc_data_block_info_init(struct gprs_rlc_data_block_info *rdbi,
|
||||
GprsCodingScheme cs, bool with_padding);
|
||||
unsigned int gprs_rlc_mcs_cps(GprsCodingScheme cs, int punct, int punct2,
|
||||
int with_padding);
|
||||
GprsCodingScheme cs, bool with_padding, unsigned int spb = 0);
|
||||
unsigned int gprs_rlc_mcs_cps(GprsCodingScheme cs, enum egprs_puncturing_values
|
||||
punct, enum egprs_puncturing_values punct2, int with_padding);
|
||||
void gprs_rlc_mcs_cps_decode(unsigned int cps, GprsCodingScheme cs,
|
||||
int *punct, int *punct2, int *with_padding);
|
||||
|
||||
enum egprs_puncturing_values gprs_get_punct_scheme(enum egprs_puncturing_values
|
||||
punct, const GprsCodingScheme &cs,
|
||||
const GprsCodingScheme &cs_current_trans,
|
||||
unsigned int spb);
|
||||
void gprs_update_punct_scheme(enum egprs_puncturing_values *punct,
|
||||
const GprsCodingScheme &cs);
|
||||
/*
|
||||
* I hold the currently transferred blocks and will provide
|
||||
* the routines to manipulate these arrays.
|
||||
|
@ -288,6 +354,44 @@ struct rlc_li_field_egprs {
|
|||
li:7;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
/* TS 44.060 10.3a.4.1.1 */
|
||||
struct gprs_rlc_ul_header_egprs_1 {
|
||||
uint8_t r:1,
|
||||
si:1,
|
||||
cv:4,
|
||||
tfi_a:2;
|
||||
uint8_t tfi_b:3,
|
||||
bsn1_a:5;
|
||||
uint8_t bsn1_b:6,
|
||||
bsn2_a:2;
|
||||
uint8_t bsn2_b;
|
||||
uint8_t cps:5,
|
||||
rsb:1,
|
||||
pi:1,
|
||||
spare_a:1;
|
||||
uint8_t spare_b:6,
|
||||
dummy:2;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
/* TS 44.060 10.3a.4.2.1 */
|
||||
struct gprs_rlc_ul_header_egprs_2 {
|
||||
uint8_t r:1,
|
||||
si:1,
|
||||
cv:4,
|
||||
tfi_a:2;
|
||||
uint8_t tfi_b:3,
|
||||
bsn1_a:5;
|
||||
uint8_t bsn1_b:6,
|
||||
cps_a:2;
|
||||
uint8_t cps_b:1,
|
||||
rsb:1,
|
||||
pi:1,
|
||||
spare_a:5;
|
||||
uint8_t spare_b:5,
|
||||
dummy:3;
|
||||
} __attribute__ ((packed));
|
||||
|
||||
/* TS 44.060 10.3a.4.3.1 */
|
||||
struct gprs_rlc_ul_header_egprs_3 {
|
||||
uint8_t r:1,
|
||||
si:1,
|
||||
|
|
15
src/tbf.h
15
src/tbf.h
|
@ -420,6 +420,9 @@ protected:
|
|||
void start_llc_timer();
|
||||
int analyse_errors(char *show_rbb, uint8_t ssn, ana_result *res);
|
||||
void schedule_next_frame();
|
||||
void egprs_dl_spb_status_get_data_buffer(int bsn, uint8_t **block_data);
|
||||
unsigned int get_egprs_dl_spb_status(int bsn);
|
||||
unsigned int get_egprs_dl_spb_value(int bsn);
|
||||
|
||||
struct osmo_timer_list m_llc_timer;
|
||||
};
|
||||
|
@ -439,6 +442,18 @@ struct gprs_rlcmac_ul_tbf : public gprs_rlcmac_tbf {
|
|||
int assemble_forward_llc(const gprs_rlc_data *data);
|
||||
int snd_ul_ud();
|
||||
|
||||
egprs_rlc_ul_reseg_bsn_state handle_egprs_ul_spb(
|
||||
const struct gprs_rlc_data_info *rlc, struct gprs_rlc_data *block,
|
||||
uint8_t *data, const uint8_t block_idx);
|
||||
|
||||
egprs_rlc_ul_reseg_bsn_state handle_egprs_ul_first_seg(
|
||||
const struct gprs_rlc_data_info *rlc, struct gprs_rlc_data *block,
|
||||
uint8_t *data, const uint8_t block_idx);
|
||||
|
||||
egprs_rlc_ul_reseg_bsn_state handle_egprs_ul_second_seg(
|
||||
const struct gprs_rlc_data_info *rlc, struct gprs_rlc_data *block,
|
||||
uint8_t *data, const uint8_t block_idx);
|
||||
|
||||
/* Please note that all variables here will be reset when changing
|
||||
* from WAIT RELEASE back to FLOW state (re-use of TBF).
|
||||
* All states that need reset must be in this struct, so this is why
|
||||
|
|
215
src/tbf_dl.cpp
215
src/tbf_dl.cpp
|
@ -34,6 +34,7 @@
|
|||
extern "C" {
|
||||
#include <osmocom/core/msgb.h>
|
||||
#include <osmocom/core/talloc.h>
|
||||
#include <osmocom/gprs/gprs_bssgp_bss.h>
|
||||
}
|
||||
|
||||
#include <errno.h>
|
||||
|
@ -43,11 +44,6 @@ extern "C" {
|
|||
/* After sending these frames, we poll for ack/nack. */
|
||||
#define POLL_ACK_AFTER_FRAMES 20
|
||||
|
||||
extern "C" {
|
||||
int bssgp_tx_llc_discarded(struct bssgp_bvc_ctx *bctx, uint32_t tlli,
|
||||
uint8_t num_frames, uint32_t num_octets);
|
||||
}
|
||||
|
||||
static inline void tbf_update_ms_class(struct gprs_rlcmac_tbf *tbf,
|
||||
const uint8_t ms_class)
|
||||
{
|
||||
|
@ -365,7 +361,7 @@ int gprs_rlcmac_dl_tbf::take_next_bsn(uint32_t fn,
|
|||
bsn = m_window.resend_needed();
|
||||
|
||||
if (previous_bsn >= 0) {
|
||||
force_cs = m_rlc.block(previous_bsn)->cs;
|
||||
force_cs = m_rlc.block(previous_bsn)->cs_current_trans;
|
||||
if (!force_cs.isEgprs())
|
||||
return -1;
|
||||
force_data_len = m_rlc.block(previous_bsn)->len;
|
||||
|
@ -379,7 +375,30 @@ int gprs_rlcmac_dl_tbf::take_next_bsn(uint32_t fn,
|
|||
m_window.mod_sns(bsn - previous_bsn) > RLC_EGPRS_MAX_BSN_DELTA)
|
||||
return -1;
|
||||
|
||||
cs2 = m_rlc.block(bsn)->cs;
|
||||
if (is_egprs_enabled()) {
|
||||
m_rlc.block(bsn)->cs_current_trans =
|
||||
GprsCodingScheme::get_retx_mcs(
|
||||
m_rlc.block(bsn)->cs, ms()->current_cs_dl(),
|
||||
bts->bts_data()->arq_type);
|
||||
|
||||
/* TODO: Need to remove this check when MCS-8 -> MCS-6
|
||||
* transistion is handled
|
||||
*/
|
||||
if (m_rlc.block(bsn)->cs == GprsCodingScheme::MCS8)
|
||||
m_rlc.block(bsn)->cs_current_trans =
|
||||
GprsCodingScheme::MCS8;
|
||||
|
||||
LOGP(DRLCMACDL, LOGL_DEBUG,
|
||||
"- current_cs_dl(%d) demanded_mcs(%d)"
|
||||
"cs_trans(%d) arq_type(%d) bsn(%d)\n",
|
||||
m_rlc.block(bsn)->cs.to_num(),
|
||||
ms()->current_cs_dl().to_num(),
|
||||
m_rlc.block(bsn)->cs_current_trans.to_num(),
|
||||
bts->bts_data()->arq_type, bsn);
|
||||
} else
|
||||
m_rlc.block(bsn)->cs_current_trans =
|
||||
m_rlc.block(bsn)->cs;
|
||||
|
||||
data_len2 = m_rlc.block(bsn)->len;
|
||||
if (force_data_len > 0 && force_data_len != data_len2)
|
||||
return -1;
|
||||
|
@ -422,7 +441,7 @@ int gprs_rlcmac_dl_tbf::take_next_bsn(uint32_t fn,
|
|||
"- Sending new dummy block at BSN %d, CS=%s\n",
|
||||
m_window.v_s(), current_cs().name());
|
||||
bsn = create_new_bsn(fn, current_cs());
|
||||
/* Don't send a second block, so don't set cs2 */
|
||||
/* Don't send a second block, so don't set cs_current_trans*/
|
||||
}
|
||||
|
||||
if (bsn < 0) {
|
||||
|
@ -433,7 +452,7 @@ int gprs_rlcmac_dl_tbf::take_next_bsn(uint32_t fn,
|
|||
bts->rlc_resent();
|
||||
}
|
||||
|
||||
*may_combine = cs2.numDataBlocks() > 1;
|
||||
*may_combine = m_rlc.block(bsn)->cs_current_trans.numDataBlocks() > 1;
|
||||
|
||||
return bsn;
|
||||
}
|
||||
|
@ -505,6 +524,9 @@ int gprs_rlcmac_dl_tbf::create_new_bsn(const uint32_t fn, GprsCodingScheme cs)
|
|||
rlc_data = m_rlc.block(bsn);
|
||||
data = rlc_data->prepare(block_data_len);
|
||||
rlc_data->cs = cs;
|
||||
rlc_data->cs_current_trans = cs;
|
||||
rlc_data->block_status_dl = EGPRS_RESEG_DL_DEFAULT;
|
||||
rlc_data->cs_init = cs;
|
||||
rlc_data->len = block_data_len;
|
||||
|
||||
rdbi = &(rlc_data->block_info);
|
||||
|
@ -589,8 +611,9 @@ struct msgb *gprs_rlcmac_dl_tbf::create_dl_acked_block(
|
|||
GprsCodingScheme cs;
|
||||
int bsns[ARRAY_SIZE(rlc.block_info)];
|
||||
unsigned num_bsns;
|
||||
int punct[ARRAY_SIZE(rlc.block_info)];
|
||||
enum egprs_puncturing_values punct[ARRAY_SIZE(rlc.block_info)];
|
||||
bool need_padding = false;
|
||||
unsigned int spb = 0;
|
||||
|
||||
/*
|
||||
* TODO: This is an experimental work-around to put 2 BSN into
|
||||
|
@ -600,7 +623,8 @@ struct msgb *gprs_rlcmac_dl_tbf::create_dl_acked_block(
|
|||
* be put into the data area, even if the resulting CS is higher than
|
||||
* the current limit.
|
||||
*/
|
||||
cs = m_rlc.block(index)->cs;
|
||||
cs = m_rlc.block(index)->cs_current_trans;
|
||||
GprsCodingScheme &cs_init = m_rlc.block(index)->cs_init;
|
||||
bsns[0] = index;
|
||||
num_bsns = 1;
|
||||
|
||||
|
@ -609,14 +633,32 @@ struct msgb *gprs_rlcmac_dl_tbf::create_dl_acked_block(
|
|||
num_bsns += 1;
|
||||
}
|
||||
|
||||
if (num_bsns == 1) {
|
||||
if ((GprsCodingScheme::Scheme(cs_init) == GprsCodingScheme::MCS8) &&
|
||||
(GprsCodingScheme::Scheme(cs) == GprsCodingScheme::MCS6 ||
|
||||
GprsCodingScheme::Scheme(cs) == GprsCodingScheme::MCS3)) {
|
||||
if ((get_egprs_dl_spb_status(index) ==
|
||||
EGPRS_RESEG_DL_DEFAULT) ||
|
||||
(get_egprs_dl_spb_status(index) ==
|
||||
EGPRS_RESEG_SECOND_SEG_SENT))
|
||||
need_padding = true;
|
||||
} else if (num_bsns == 1) {
|
||||
/* TODO: remove the conditional when MCS-6 padding isn't
|
||||
* failing to be decoded by MEs anymore */
|
||||
/* TODO: support of MCS-8 -> MCS-6 transition should be
|
||||
* handled
|
||||
*/
|
||||
if (cs != GprsCodingScheme(GprsCodingScheme::MCS8))
|
||||
cs.decToSingleBlock(&need_padding);
|
||||
}
|
||||
|
||||
gprs_rlc_data_info_init_dl(&rlc, cs, need_padding);
|
||||
spb = get_egprs_dl_spb_value(index);
|
||||
|
||||
LOGP(DRLCMACDL, LOGL_DEBUG, "- need_padding %d spb_status %d spb %d"
|
||||
"(BSN1 %d BSN2 %d)\n",
|
||||
need_padding,
|
||||
get_egprs_dl_spb_status(index), spb, index, index2);
|
||||
|
||||
gprs_rlc_data_info_init_dl(&rlc, cs, need_padding, spb);
|
||||
|
||||
rlc.usf = 7; /* will be set at scheduler */
|
||||
rlc.pr = 0; /* FIXME: power reduction */
|
||||
|
@ -635,7 +677,6 @@ struct msgb *gprs_rlcmac_dl_tbf::create_dl_acked_block(
|
|||
data_block_idx++)
|
||||
{
|
||||
int bsn;
|
||||
GprsCodingScheme cs_enc;
|
||||
uint8_t *block_data;
|
||||
gprs_rlc_data_block_info *rdbi, *block_info;
|
||||
|
||||
|
@ -645,25 +686,38 @@ struct msgb *gprs_rlcmac_dl_tbf::create_dl_acked_block(
|
|||
else
|
||||
bsn = bsns[0];
|
||||
|
||||
cs_enc = m_rlc.block(bsn)->cs;
|
||||
/* Get current puncturing scheme from block */
|
||||
m_rlc.block(bsn)->next_ps = gprs_get_punct_scheme(
|
||||
m_rlc.block(bsn)->next_ps,
|
||||
m_rlc.block(bsn)->cs, cs, spb);
|
||||
|
||||
/* get data and header from current block */
|
||||
block_data = m_rlc.block(bsn)->block;
|
||||
|
||||
/* TODO: Use real puncturing values */
|
||||
punct[data_block_idx] = data_block_idx;
|
||||
if (cs.isEgprs()) {
|
||||
OSMO_ASSERT(m_rlc.block(bsn)->next_ps >= EGPRS_PS_1);
|
||||
OSMO_ASSERT(m_rlc.block(bsn)->next_ps <= EGPRS_PS_3);
|
||||
}
|
||||
punct[data_block_idx] = m_rlc.block(bsn)->next_ps;
|
||||
|
||||
rdbi = &rlc.block_info[data_block_idx];
|
||||
block_info = &m_rlc.block(bsn)->block_info;
|
||||
|
||||
if(rdbi->data_len != m_rlc.block(bsn)->len) {
|
||||
LOGP(DRLCMACDL, LOGL_ERROR,
|
||||
"ERROR: Expected len = %d for %s instead of "
|
||||
"%d in data unit %d (BSN %d, %s)\n",
|
||||
rdbi->data_len, cs.name(), m_rlc.block(bsn)->len,
|
||||
data_block_idx, bsn, cs_enc.name());
|
||||
OSMO_ASSERT(rdbi->data_len == m_rlc.block(bsn)->len);
|
||||
}
|
||||
egprs_dl_spb_status_get_data_buffer(bsn, &block_data);
|
||||
|
||||
/* TODO: Need to handle 2 same bsns
|
||||
* in header type 1
|
||||
*/
|
||||
/*
|
||||
* If it is first segment of the split block set the state of
|
||||
* bsn to nacked. If it is the first segment dont update the
|
||||
* next ps value of bsn
|
||||
*/
|
||||
if (spb == 2)
|
||||
m_window.m_v_b.mark_nacked(bsn);
|
||||
else
|
||||
gprs_update_punct_scheme(&m_rlc.block(bsn)->next_ps,
|
||||
cs);
|
||||
|
||||
m_rlc.block(bsn)->cs = cs;
|
||||
|
||||
rdbi->e = block_info->e;
|
||||
rdbi->cv = block_info->cv;
|
||||
rdbi->bsn = bsn;
|
||||
|
@ -742,8 +796,6 @@ struct msgb *gprs_rlcmac_dl_tbf::create_dl_acked_block(
|
|||
/* Increment TX-counter */
|
||||
m_tx_counter++;
|
||||
|
||||
bts->rlc_sent();
|
||||
|
||||
return dl_msg;
|
||||
}
|
||||
|
||||
|
@ -1125,3 +1177,106 @@ bool gprs_rlcmac_dl_tbf::keep_open(unsigned fn) const
|
|||
keep_time_frames = msecs_to_frames(bts_data()->dl_tbf_idle_msec);
|
||||
return frames_since_last_drain(fn) <= keep_time_frames;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function returns the pointer to data which needs
|
||||
* to be copied. Also updates the status of the block related to
|
||||
* Split block handling in the RLC/MAC block.
|
||||
*/
|
||||
void gprs_rlcmac_dl_tbf::egprs_dl_spb_status_get_data_buffer(int bsn,
|
||||
uint8_t **block_data)
|
||||
{
|
||||
struct gprs_rlc_data *rlc_data = m_rlc.block(bsn);
|
||||
|
||||
GprsCodingScheme &cs_current_trans = m_rlc.block(bsn)->cs_current_trans;
|
||||
GprsCodingScheme &cs_init = m_rlc.block(bsn)->cs_init;
|
||||
|
||||
/*
|
||||
* Table 10.3a.0.1 of 44.060
|
||||
* MCS6,9: second segment starts at 74/2 = 37
|
||||
* MCS5,7: second segment starts at 56/2 = 28
|
||||
* MCS8: second segment starts at 31
|
||||
* MCS4: second segment starts at 44/2 = 22
|
||||
*/
|
||||
if (cs_current_trans.headerTypeData() ==
|
||||
GprsCodingScheme::HEADER_EGPRS_DATA_TYPE_3) {
|
||||
if (rlc_data->block_status_dl == EGPRS_RESEG_FIRST_SEG_SENT) {
|
||||
switch (GprsCodingScheme::Scheme(cs_init)) {
|
||||
case GprsCodingScheme::MCS6 :
|
||||
case GprsCodingScheme::MCS9 :
|
||||
*block_data = &rlc_data->block[37];
|
||||
break;
|
||||
case GprsCodingScheme::MCS7 :
|
||||
case GprsCodingScheme::MCS5 :
|
||||
*block_data = &rlc_data->block[28];
|
||||
break;
|
||||
case GprsCodingScheme::MCS8 :
|
||||
*block_data = &rlc_data->block[31];
|
||||
break;
|
||||
case GprsCodingScheme::MCS4 :
|
||||
*block_data = &rlc_data->block[22];
|
||||
break;
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
break;
|
||||
|
||||
}
|
||||
rlc_data->block_status_dl = EGPRS_RESEG_SECOND_SEG_SENT;
|
||||
return;
|
||||
} else if ((cs_init.headerTypeData() ==
|
||||
GprsCodingScheme::HEADER_EGPRS_DATA_TYPE_1) ||
|
||||
(cs_init.headerTypeData() ==
|
||||
GprsCodingScheme::HEADER_EGPRS_DATA_TYPE_2)) {
|
||||
rlc_data->block_status_dl =
|
||||
EGPRS_RESEG_FIRST_SEG_SENT;
|
||||
} else if ((GprsCodingScheme::Scheme(cs_init) ==
|
||||
GprsCodingScheme::MCS4) &&
|
||||
(GprsCodingScheme::Scheme(cs_current_trans) ==
|
||||
GprsCodingScheme::MCS1)) {
|
||||
rlc_data->block_status_dl = EGPRS_RESEG_FIRST_SEG_SENT;
|
||||
}
|
||||
}
|
||||
*block_data = &rlc_data->block[0];
|
||||
}
|
||||
|
||||
/*
|
||||
* This function returns the status of split block
|
||||
* for RLC/MAC block.
|
||||
*/
|
||||
unsigned int gprs_rlcmac_dl_tbf::get_egprs_dl_spb_status(int bsn)
|
||||
{
|
||||
struct gprs_rlc_data *rlc_data = m_rlc.block(bsn);
|
||||
|
||||
return rlc_data->block_status_dl;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function returns the spb value to be sent OTA
|
||||
* for RLC/MAC block.
|
||||
*/
|
||||
unsigned int gprs_rlcmac_dl_tbf::get_egprs_dl_spb_value(int bsn)
|
||||
{
|
||||
struct gprs_rlc_data *rlc_data = m_rlc.block(bsn);
|
||||
|
||||
GprsCodingScheme &cs_current_trans = m_rlc.block(bsn)->cs_current_trans;
|
||||
GprsCodingScheme &cs_init = m_rlc.block(bsn)->cs_init;
|
||||
|
||||
/* Table 10.4.8b.1 of 44.060 */
|
||||
if (cs_current_trans.headerTypeData() ==
|
||||
GprsCodingScheme::HEADER_EGPRS_DATA_TYPE_3) {
|
||||
if (rlc_data->block_status_dl == EGPRS_RESEG_FIRST_SEG_SENT) {
|
||||
return 3;
|
||||
} else if ((cs_init.headerTypeData() ==
|
||||
GprsCodingScheme::HEADER_EGPRS_DATA_TYPE_1) ||
|
||||
(cs_init.headerTypeData() ==
|
||||
GprsCodingScheme::HEADER_EGPRS_DATA_TYPE_2)) {
|
||||
return 2;
|
||||
} else if ((GprsCodingScheme::Scheme(cs_init) ==
|
||||
GprsCodingScheme::MCS4) &&
|
||||
(GprsCodingScheme::Scheme(cs_current_trans) ==
|
||||
GprsCodingScheme::MCS1)) {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
169
src/tbf_ul.cpp
169
src/tbf_ul.cpp
|
@ -60,7 +60,7 @@ int gprs_rlcmac_ul_tbf::assemble_forward_llc(const gprs_rlc_data *_data)
|
|||
LOGP(DRLCMACUL, LOGL_DEBUG, "- Assembling frames: (len=%d)\n", len);
|
||||
|
||||
num_frames = Decoding::rlc_data_from_ul_data(
|
||||
rdbi, cs, data, &(frames[0]), sizeof(frames),
|
||||
rdbi, cs, data, &(frames[0]), ARRAY_SIZE(frames),
|
||||
&dummy_tlli);
|
||||
|
||||
/* create LLC frames */
|
||||
|
@ -138,6 +138,135 @@ struct msgb *gprs_rlcmac_ul_tbf::create_ul_ack(uint32_t fn, uint8_t ts)
|
|||
return msg;
|
||||
}
|
||||
|
||||
egprs_rlc_ul_reseg_bsn_state gprs_rlcmac_ul_tbf::handle_egprs_ul_second_seg(
|
||||
const struct gprs_rlc_data_info *rlc, struct gprs_rlc_data *block,
|
||||
uint8_t *data, const uint8_t block_idx)
|
||||
{
|
||||
const struct gprs_rlc_data_block_info *rdbi =
|
||||
&rlc->block_info[block_idx];
|
||||
|
||||
uint8_t *rlc_data = &(block->block[0]);
|
||||
|
||||
if (block->block_status_ul & EGPRS_RESEG_FIRST_SEG_RXD) {
|
||||
LOGP(DRLCMACUL, LOGL_DEBUG,
|
||||
"Second seg is received "
|
||||
"first seg is already present "
|
||||
"set the status to complete\n");
|
||||
block->block_status_ul = EGPRS_RESEG_DEFAULT;
|
||||
|
||||
block->len += Decoding::rlc_copy_to_aligned_buffer(rlc,
|
||||
block_idx, data, rlc_data + block->len);
|
||||
block->block_info.data_len += rdbi->data_len;
|
||||
return EGPRS_RESEG_DEFAULT;
|
||||
|
||||
} else if (block->block_status_ul == EGPRS_RESEG_DEFAULT) {
|
||||
LOGP(DRLCMACUL, LOGL_DEBUG,
|
||||
"Second seg is received "
|
||||
"first seg is not received "
|
||||
"set the status to second seg received\n");
|
||||
block->len = Decoding::rlc_copy_to_aligned_buffer(rlc,
|
||||
block_idx, data, rlc_data);
|
||||
block->block_status_ul = EGPRS_RESEG_SECOND_SEG_RXD;
|
||||
block->block_info = *rdbi;
|
||||
return EGPRS_RESEG_SECOND_SEG_RXD;
|
||||
}
|
||||
return EGPRS_RESEG_INVALID;
|
||||
}
|
||||
|
||||
|
||||
egprs_rlc_ul_reseg_bsn_state gprs_rlcmac_ul_tbf::handle_egprs_ul_first_seg(
|
||||
const struct gprs_rlc_data_info *rlc, struct gprs_rlc_data *block,
|
||||
uint8_t *data, const uint8_t block_idx)
|
||||
{
|
||||
const struct gprs_rlc_data_block_info *rdbi =
|
||||
&rlc->block_info[block_idx];
|
||||
uint8_t *rlc_data = &(block->block[0]);
|
||||
|
||||
if (block->block_status_ul & EGPRS_RESEG_SECOND_SEG_RXD) {
|
||||
LOGP(DRLCMACUL, LOGL_DEBUG,
|
||||
"First seg is received "
|
||||
"second seg is already present "
|
||||
"set the status to complete");
|
||||
static uint8_t seg_data[RLC_MAX_LEN];
|
||||
uint8_t len = block->len;
|
||||
|
||||
/* Assembling the resegmented split blocks */
|
||||
memcpy(seg_data, block->block, len);
|
||||
block->len = Decoding::rlc_copy_to_aligned_buffer(rlc,
|
||||
block_idx, data, rlc_data);
|
||||
memcpy(block->block + block->len, seg_data, len);
|
||||
block->len += len;
|
||||
block->block_info.data_len += rdbi->data_len;
|
||||
block->block_status_ul = EGPRS_RESEG_DEFAULT;
|
||||
return EGPRS_RESEG_DEFAULT;
|
||||
} else if (block->block_status_ul == EGPRS_RESEG_DEFAULT) {
|
||||
LOGP(DRLCMACUL, LOGL_DEBUG,
|
||||
"First seg is received "
|
||||
"second seg is not received "
|
||||
"set the status to first seg "
|
||||
"received\n");
|
||||
|
||||
block->block_status_ul = EGPRS_RESEG_FIRST_SEG_RXD;
|
||||
block->len = Decoding::rlc_copy_to_aligned_buffer(rlc,
|
||||
block_idx, data, rlc_data);
|
||||
block->block_info = *rdbi;
|
||||
return EGPRS_RESEG_FIRST_SEG_RXD;
|
||||
}
|
||||
return EGPRS_RESEG_INVALID;
|
||||
}
|
||||
|
||||
egprs_rlc_ul_reseg_bsn_state gprs_rlcmac_ul_tbf::handle_egprs_ul_spb(
|
||||
const struct gprs_rlc_data_info *rlc, struct gprs_rlc_data *block,
|
||||
uint8_t *data, const uint8_t block_idx)
|
||||
{
|
||||
const struct gprs_rlc_data_block_info *rdbi =
|
||||
&rlc->block_info[block_idx];
|
||||
|
||||
LOGP(DRLCMACUL, LOGL_NOTICE,
|
||||
"Got SPB(%d) "
|
||||
"cs(%s) data block with BSN (%d), "
|
||||
"TFI(%d).\n", rdbi->spb, rlc->cs.name(), rdbi->bsn,
|
||||
rlc->tfi);
|
||||
|
||||
egprs_rlc_ul_reseg_bsn_state re_assemble_status = EGPRS_RESEG_INVALID;
|
||||
|
||||
/* Section 10.4.8b of 44.060*/
|
||||
if (rdbi->spb == 2)
|
||||
re_assemble_status = handle_egprs_ul_first_seg(rlc,
|
||||
block, data, block_idx);
|
||||
else if (rdbi->spb == 3)
|
||||
re_assemble_status = handle_egprs_ul_second_seg(rlc,
|
||||
block, data, block_idx);
|
||||
else {
|
||||
LOGP(DRLCMACUL, LOGL_ERROR,
|
||||
"Not supported SPB for this EGPRS configuration\n");
|
||||
OSMO_ASSERT(false);
|
||||
}
|
||||
|
||||
/*
|
||||
* When the block is successfully constructed out of segmented blocks
|
||||
* upgrade the MCS to the type 2
|
||||
*/
|
||||
if (re_assemble_status == EGPRS_RESEG_DEFAULT) {
|
||||
switch (GprsCodingScheme::Scheme(rlc->cs)) {
|
||||
case GprsCodingScheme::MCS3 :
|
||||
block->cs = GprsCodingScheme::MCS6;
|
||||
break;
|
||||
case GprsCodingScheme::MCS2 :
|
||||
block->cs = GprsCodingScheme::MCS5;
|
||||
break;
|
||||
case GprsCodingScheme::MCS1 :
|
||||
block->cs = GprsCodingScheme::MCS4;
|
||||
break;
|
||||
default:
|
||||
OSMO_ASSERT(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return re_assemble_status;
|
||||
}
|
||||
|
||||
/*! \brief receive data from PDCH/L1 */
|
||||
int gprs_rlcmac_ul_tbf::rcv_data_block_acknowledged(
|
||||
const struct gprs_rlc_data_info *rlc,
|
||||
uint8_t *data, struct pcu_l1_meas *meas)
|
||||
|
@ -219,36 +348,26 @@ int gprs_rlcmac_ul_tbf::rcv_data_block_acknowledged(
|
|||
rdbi->bsn, m_window.v_q(),
|
||||
m_window.mod_sns(m_window.v_q() + ws - 1));
|
||||
block = m_rlc.block(rdbi->bsn);
|
||||
block->block_info = *rdbi;
|
||||
block->cs = rlc->cs;
|
||||
OSMO_ASSERT(rdbi->data_len < sizeof(block->block));
|
||||
OSMO_ASSERT(rdbi->data_len <= sizeof(block->block));
|
||||
rlc_data = &(block->block[0]);
|
||||
/* TODO: Handle SPB != 0 -> Set length to 2*len, add offset if
|
||||
* 2nd part. Note that resegmentation is currently disabled
|
||||
* within the UL assignment.
|
||||
*/
|
||||
if (rdbi->spb) {
|
||||
LOGP(DRLCMACUL, LOGL_NOTICE,
|
||||
"Got SPB != 0 but resegmentation has been "
|
||||
"disabled, skipping %s data block with BSN %d, "
|
||||
"TFI=%d.\n", rlc->cs.name(), rdbi->bsn,
|
||||
rlc->tfi);
|
||||
continue;
|
||||
}
|
||||
|
||||
block->len =
|
||||
Decoding::rlc_copy_to_aligned_buffer(rlc, block_idx, data,
|
||||
rlc_data);
|
||||
if (rdbi->spb) {
|
||||
egprs_rlc_ul_reseg_bsn_state re_assemble_status =
|
||||
handle_egprs_ul_spb(rlc, block,
|
||||
data, block_idx);
|
||||
if (re_assemble_status != EGPRS_RESEG_DEFAULT)
|
||||
return 0;
|
||||
} else {
|
||||
block->block_info = *rdbi;
|
||||
block->cs = rlc->cs;
|
||||
block->len =
|
||||
Decoding::rlc_copy_to_aligned_buffer(rlc,
|
||||
block_idx, data, rlc_data);
|
||||
}
|
||||
|
||||
LOGP(DRLCMACUL, LOGL_DEBUG,
|
||||
"%s: data_length=%d, data=%s\n",
|
||||
name(), block->len, osmo_hexdump(rlc_data, block->len));
|
||||
|
||||
/* TODO: Handle SPB != 0 -> set state to partly received
|
||||
* (upper/lower) and continue with the loop, unless the other
|
||||
* part is already present.
|
||||
*/
|
||||
|
||||
/* Get/Handle TLLI */
|
||||
if (rdbi->ti) {
|
||||
num_chunks = Decoding::rlc_data_from_ul_data(
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
#include "encoding.h"
|
||||
#include "rlc.h"
|
||||
#include "llc.h"
|
||||
|
||||
#include "bts.h"
|
||||
extern "C" {
|
||||
#include "pcu_vty.h"
|
||||
|
||||
|
@ -1101,6 +1101,250 @@ const struct log_info debug_log_info = {
|
|||
ARRAY_SIZE(default_categories),
|
||||
};
|
||||
|
||||
static void setup_bts(BTS *the_bts, uint8_t ts_no, uint8_t cs = 1)
|
||||
{
|
||||
gprs_rlcmac_bts *bts;
|
||||
gprs_rlcmac_trx *trx;
|
||||
|
||||
bts = the_bts->bts_data();
|
||||
bts->egprs_enabled = true;
|
||||
bts->alloc_algorithm = alloc_algorithm_a;
|
||||
bts->initial_cs_dl = cs;
|
||||
bts->initial_cs_ul = cs;
|
||||
trx = &bts->trx[0];
|
||||
trx->pdch[ts_no].enable();
|
||||
}
|
||||
static void uplink_header_type_2_parsing_test(BTS *the_bts,
|
||||
uint8_t ts_no, uint32_t tlli, uint32_t *fn, uint16_t qta,
|
||||
uint8_t ms_class)
|
||||
{
|
||||
GprsMs *ms;
|
||||
struct pcu_l1_meas meas;
|
||||
int tfi = 0;
|
||||
gprs_rlcmac_bts *bts;
|
||||
RlcMacUplink_t ulreq = {0};
|
||||
uint8_t data[79] = {0};
|
||||
struct gprs_rlc_ul_header_egprs_2 *egprs2 = NULL;
|
||||
|
||||
egprs2 = (struct gprs_rlc_ul_header_egprs_2 *) data;
|
||||
bts = the_bts->bts_data();
|
||||
|
||||
tfi = 1;
|
||||
|
||||
struct gprs_rlc_data_info rlc;
|
||||
GprsCodingScheme cs;
|
||||
int rc, offs;
|
||||
|
||||
/*without padding*/
|
||||
cs = GprsCodingScheme::MCS5;
|
||||
egprs2 = (struct gprs_rlc_ul_header_egprs_2 *) data;
|
||||
egprs2->r = 1;
|
||||
egprs2->si = 1;
|
||||
egprs2->cv = 7;
|
||||
egprs2->tfi_a = tfi & 0x03;
|
||||
egprs2->tfi_b = (tfi & 0x1c) >> 2;
|
||||
egprs2->bsn1_a = 0;
|
||||
egprs2->bsn1_b = 0;
|
||||
egprs2->cps_a = 3;
|
||||
egprs2->cps_b = 0;
|
||||
egprs2->rsb = 0;
|
||||
egprs2->pi = 0;
|
||||
data[4] = 0x20; /* Setting E field */
|
||||
rc = Decoding::rlc_parse_ul_data_header(&rlc, data, cs);
|
||||
offs = rlc.data_offs_bits[0] / 8;
|
||||
OSMO_ASSERT(offs == 4);
|
||||
OSMO_ASSERT(rlc.tfi == 1);
|
||||
OSMO_ASSERT(rlc.num_data_blocks == 1);
|
||||
OSMO_ASSERT(rlc.block_info[0].e == 1);
|
||||
OSMO_ASSERT(rlc.block_info[0].ti == 0);
|
||||
OSMO_ASSERT(rlc.block_info[0].bsn == 0);
|
||||
|
||||
/* with padding case */
|
||||
cs = GprsCodingScheme::MCS6;
|
||||
egprs2 = (struct gprs_rlc_ul_header_egprs_2 *) data;
|
||||
egprs2->r = 1;
|
||||
egprs2->si = 1;
|
||||
egprs2->cv = 7;
|
||||
egprs2->tfi_a = tfi & 0x03;
|
||||
egprs2->tfi_b = (tfi & 0x1c) >> 2;
|
||||
egprs2->bsn1_a = 0;
|
||||
egprs2->bsn1_b = 0;
|
||||
egprs2->cps_a = 3;
|
||||
egprs2->cps_b = 0;
|
||||
egprs2->rsb = 0;
|
||||
egprs2->pi = 0;
|
||||
data[10] = 0x20; /* Setting E field */
|
||||
rc = Decoding::rlc_parse_ul_data_header(&rlc, data, cs);
|
||||
offs = rlc.data_offs_bits[0] / 8;
|
||||
OSMO_ASSERT(offs == 10);
|
||||
OSMO_ASSERT(rlc.num_data_blocks == 1);
|
||||
OSMO_ASSERT(rlc.tfi == 1);
|
||||
OSMO_ASSERT(rlc.block_info[0].e == 1);
|
||||
OSMO_ASSERT(rlc.block_info[0].ti == 0);
|
||||
OSMO_ASSERT(rlc.block_info[0].bsn == 0);
|
||||
|
||||
egprs2->r = 1;
|
||||
egprs2->si = 1;
|
||||
egprs2->cv = 7;
|
||||
egprs2->tfi_a = tfi & 0x03;
|
||||
egprs2->tfi_b = (tfi & 0x1c) >> 2;
|
||||
egprs2->bsn1_a = 1;
|
||||
egprs2->bsn1_b = 0;
|
||||
egprs2->cps_a = 2;
|
||||
egprs2->cps_b = 0;
|
||||
egprs2->rsb = 0;
|
||||
egprs2->pi = 0;
|
||||
data[10] = 0x20; /* Setting E field */
|
||||
rc = Decoding::rlc_parse_ul_data_header(&rlc, data, cs);
|
||||
offs = rlc.data_offs_bits[0] / 8;
|
||||
OSMO_ASSERT(offs == 10);
|
||||
OSMO_ASSERT(rlc.tfi == 1);
|
||||
OSMO_ASSERT(rlc.num_data_blocks == 1);
|
||||
OSMO_ASSERT(rlc.block_info[0].e == 1);
|
||||
OSMO_ASSERT(rlc.block_info[0].ti == 0);
|
||||
OSMO_ASSERT(rlc.block_info[0].bsn == 1);
|
||||
}
|
||||
|
||||
static void uplink_header_type2_test(void)
|
||||
{
|
||||
BTS the_bts;
|
||||
int ts_no = 7;
|
||||
uint32_t fn = 2654218;
|
||||
uint16_t qta = 31;
|
||||
uint32_t tlli = 0xf1223344;
|
||||
const char *imsi = "0011223344";
|
||||
uint8_t ms_class = 1;
|
||||
gprs_rlcmac_ul_tbf *ul_tbf;
|
||||
GprsMs *ms;
|
||||
|
||||
printf("=== start %s ===\n", __func__);
|
||||
setup_bts(&the_bts, ts_no, 10);
|
||||
|
||||
uplink_header_type_2_parsing_test(&the_bts, ts_no,
|
||||
tlli, &fn, qta, ms_class);
|
||||
printf("=== end %s ===\n", __func__);
|
||||
}
|
||||
|
||||
static void uplink_header_type_1_parsing_test(BTS *the_bts,
|
||||
uint8_t ts_no, uint32_t tlli, uint32_t *fn, uint16_t qta,
|
||||
uint8_t ms_class)
|
||||
{
|
||||
uint8_t trx_no = 0;
|
||||
int tfi = 0;
|
||||
struct gprs_rlcmac_pdch *pdch;
|
||||
gprs_rlcmac_bts *bts;
|
||||
uint8_t data[155] = {0};
|
||||
struct gprs_rlc_ul_header_egprs_1 *egprs1 = NULL;
|
||||
struct gprs_rlc_data_info rlc;
|
||||
GprsCodingScheme cs;
|
||||
int rc, offs;
|
||||
|
||||
egprs1 = (struct gprs_rlc_ul_header_egprs_1 *) data;
|
||||
bts = the_bts->bts_data();
|
||||
|
||||
tfi = 1;
|
||||
|
||||
/* MCS 7 */
|
||||
cs = GprsCodingScheme::MCS7;
|
||||
egprs1 = (struct gprs_rlc_ul_header_egprs_1 *) data;
|
||||
egprs1->si = 1;
|
||||
egprs1->r = 1;
|
||||
egprs1->cv = 7;
|
||||
egprs1->tfi_a = tfi & 0x03;
|
||||
egprs1->tfi_b = (tfi & 0x1c) >> 2;
|
||||
egprs1->bsn1_a = 0;
|
||||
egprs1->bsn1_b = 0;
|
||||
egprs1->bsn2_a = 1;
|
||||
egprs1->bsn2_b = 0;
|
||||
egprs1->cps = 15;
|
||||
egprs1->rsb = 0;
|
||||
egprs1->pi = 0;
|
||||
data[5] = 0xc0;
|
||||
data[5 + 57] = 1;
|
||||
rc = Decoding::rlc_parse_ul_data_header(&rlc, data, cs);
|
||||
OSMO_ASSERT(rlc.num_data_blocks == 2);
|
||||
OSMO_ASSERT(rlc.block_info[0].e == 1);
|
||||
OSMO_ASSERT(rlc.block_info[0].ti == 1);
|
||||
OSMO_ASSERT(rlc.block_info[1].e == 1);
|
||||
OSMO_ASSERT(rlc.block_info[1].ti == 0);
|
||||
OSMO_ASSERT(rlc.block_info[0].bsn == 0);
|
||||
OSMO_ASSERT(rlc.block_info[1].bsn == 1);
|
||||
OSMO_ASSERT(rlc.tfi == 1);
|
||||
|
||||
/* MCS 8 */
|
||||
cs = GprsCodingScheme::MCS8;
|
||||
egprs1 = (struct gprs_rlc_ul_header_egprs_1 *) data;
|
||||
egprs1->si = 1;
|
||||
egprs1->r = 1;
|
||||
egprs1->cv = 7;
|
||||
egprs1->tfi_a = tfi & 0x03;
|
||||
egprs1->tfi_b = (tfi & 0x1c) >> 2;
|
||||
egprs1->bsn1_a = 0;
|
||||
egprs1->bsn1_b = 0;
|
||||
egprs1->bsn2_a = 1;
|
||||
egprs1->bsn2_b = 0;
|
||||
egprs1->cps = 15;
|
||||
egprs1->rsb = 0;
|
||||
egprs1->pi = 0;
|
||||
data[5] = 0xc0;
|
||||
data[5 + 69] = 1;
|
||||
rc = Decoding::rlc_parse_ul_data_header(&rlc, data, cs);
|
||||
OSMO_ASSERT(rlc.num_data_blocks == 2);
|
||||
OSMO_ASSERT(rlc.block_info[0].e == 1);
|
||||
OSMO_ASSERT(rlc.block_info[0].ti == 1);
|
||||
OSMO_ASSERT(rlc.block_info[1].e == 1);
|
||||
OSMO_ASSERT(rlc.block_info[1].ti == 0);
|
||||
OSMO_ASSERT(rlc.block_info[0].bsn == 0);
|
||||
OSMO_ASSERT(rlc.block_info[1].bsn == 1);
|
||||
OSMO_ASSERT(rlc.tfi == 1);
|
||||
|
||||
/* MCS 9 */
|
||||
cs = GprsCodingScheme::MCS9;
|
||||
egprs1 = (struct gprs_rlc_ul_header_egprs_1 *) data;
|
||||
egprs1->si = 1;
|
||||
egprs1->r = 1;
|
||||
egprs1->cv = 7;
|
||||
egprs1->tfi_a = tfi & 0x03;
|
||||
egprs1->tfi_b = (tfi & 0x1c) >> 2;
|
||||
egprs1->bsn1_a = 0;
|
||||
egprs1->bsn1_b = 0;
|
||||
egprs1->bsn2_a = 1;
|
||||
egprs1->bsn2_b = 0;
|
||||
egprs1->cps = 15;
|
||||
egprs1->rsb = 0;
|
||||
egprs1->pi = 0;
|
||||
data[5] = 0xc0;
|
||||
data[5 + 75] = 1;
|
||||
rc = Decoding::rlc_parse_ul_data_header(&rlc, data, cs);
|
||||
OSMO_ASSERT(rlc.num_data_blocks == 2);
|
||||
OSMO_ASSERT(rlc.block_info[0].e == 1);
|
||||
OSMO_ASSERT(rlc.block_info[0].ti == 1);
|
||||
OSMO_ASSERT(rlc.block_info[1].e == 1);
|
||||
OSMO_ASSERT(rlc.block_info[1].ti == 0);
|
||||
OSMO_ASSERT(rlc.block_info[0].bsn == 0);
|
||||
OSMO_ASSERT(rlc.block_info[1].bsn == 1);
|
||||
OSMO_ASSERT(rlc.tfi == 1);
|
||||
}
|
||||
|
||||
void uplink_header_type1_test(void)
|
||||
{
|
||||
BTS the_bts;
|
||||
int ts_no = 7;
|
||||
uint32_t fn = 2654218;
|
||||
uint16_t qta = 31;
|
||||
uint32_t tlli = 0xf1223344;
|
||||
const char *imsi = "0011223344";
|
||||
uint8_t ms_class = 1;
|
||||
gprs_rlcmac_ul_tbf *ul_tbf;
|
||||
GprsMs *ms;
|
||||
|
||||
printf("=== start %s ===\n", __func__);
|
||||
setup_bts(&the_bts, ts_no, 12);
|
||||
uplink_header_type_1_parsing_test(&the_bts, ts_no, tlli, &fn,
|
||||
qta, ms_class);
|
||||
printf("=== end %s ===\n", __func__);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
struct vty_app_info pcu_vty_info = {0};
|
||||
|
@ -1123,6 +1367,9 @@ int main(int argc, char **argv)
|
|||
test_rlc_unaligned_copy();
|
||||
test_rlc_unit_encoder();
|
||||
|
||||
uplink_header_type2_test();
|
||||
uplink_header_type1_test();
|
||||
|
||||
if (getenv("TALLOC_REPORT_FULL"))
|
||||
talloc_report_full(tall_pcu_ctx, stderr);
|
||||
return EXIT_SUCCESS;
|
||||
|
|
|
@ -6,3 +6,7 @@
|
|||
=== end test_rlc_unit_decoder ===
|
||||
=== start test_rlc_unit_encoder ===
|
||||
=== end test_rlc_unit_encoder ===
|
||||
=== start uplink_header_type2_test ===
|
||||
=== end uplink_header_type2_test ===
|
||||
=== start uplink_header_type1_test ===
|
||||
=== end uplink_header_type1_test ===
|
||||
|
|
|
@ -89,9 +89,12 @@ void testRlcMacDownlink()
|
|||
|
||||
std::string testData[] = {
|
||||
"4e082500e3f1a81d080820800b2b2b2b2b2b2b2b2b2b2b", // Packet Downlink Assignment
|
||||
"48282407a6a074227201000b2b2b2b2b2b2b2b2b2b2b2b", // Packet Uplink Assignment
|
||||
"48282407a6a07422720100032b2b2b2b2b2b2b2b2b2b2b", // Packet Uplink Assignment
|
||||
"47240c00400000000000000079eb2ac9402b2b2b2b2b2b", // Packet Uplink Ack Nack
|
||||
"47283c367513ba333004242b2b2b2b2b2b2b2b2b2b2b2b" // Packet Uplink Assignment
|
||||
"47283c367513ba333004242b2b2b2b2b2b2b2b2b2b2b2b", // Packet Uplink Assignment
|
||||
"400820001a3904df0680efb3300b2b2b2b2b2b2b2b2b2b", // Packet Downlink Assignment (EGPRS)
|
||||
"40284f0000001009810c826f4406809dcecb2b2b2b2b2b", // Packet Uplink Assignment (EGPRS)
|
||||
"4024030f2f0000000087b0042b2b2b2b2b2b2b2b2b2b2b" // Packet Uplink Ack Nack (EGPRS)
|
||||
"4913e00850884013a8048b2b2b2b2b2b2b2b2b2b2b2b2b"
|
||||
"412430007fffffffffffffffefd19c7ba12b2b2b2b2b2b"
|
||||
"41942b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b"
|
||||
|
@ -152,9 +155,10 @@ void testRlcMacUplink()
|
|||
bitvec_unhex(resultVector, "2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b");
|
||||
|
||||
std::string testData[] = {
|
||||
"400e1e61d11f2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b", // Packet Uplink Dummy Control Block
|
||||
"400b8020000000000000002480e00b2b2b2b2b2b2b2b2b", // Packet Downlink Ack/Nack
|
||||
"4016713dc094270ca2ae57ef909006aa0fc0001f80222b" // Packet Resource Request
|
||||
"400e1e61d11d2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b", // Packet Uplink Dummy Control Block
|
||||
"400b8020000000000000002480e0032b2b2b2b2b2b2b2b", // Packet Downlink Ack/Nack
|
||||
"4016713dc094270ca2ae57ef909006aa0fc0001f80222b", // Packet Resource Request
|
||||
"40200ffc0021ec010b2b2b2b2b2b2b2b2b2b2b2b2b2b2b", // EPDAN
|
||||
"400a9020000000000000003010012a0800132b2b2b2b2b"
|
||||
};
|
||||
|
||||
|
|
|
@ -7,13 +7,13 @@ vector1 = 4e8250e3f1a81d882080b2b2b2b2b2b2b2b2b2b2b
|
|||
vector1 = 4e8250e3f1a81d882080b2b2b2b2b2b2b2b2b2b2b
|
||||
vector2 = 4e8250e3f1a81d882080b2b2b2b2b2b2b2b2b2b2b
|
||||
vector1 == vector2 : TRUE
|
||||
vector1 = 4828247a6a074227210b2b2b2b2b2b2b2b2b2b2b2b
|
||||
vector1 = 4828247a6a07422721032b2b2b2b2b2b2b2b2b2b2b
|
||||
=========Start DECODE===========
|
||||
+++++++++Finish DECODE++++++++++
|
||||
=========Start ENCODE=============
|
||||
+++++++++Finish ENCODE+++++++++++
|
||||
vector1 = 4828247a6a074227210b2b2b2b2b2b2b2b2b2b2b2b
|
||||
vector2 = 4828247a6a074227210b2b2b2b2b2b2b2b2b2b2b2b
|
||||
vector1 = 4828247a6a07422721032b2b2b2b2b2b2b2b2b2b2b
|
||||
vector2 = 4828247a6a07422721032b2b2b2b2b2b2b2b2b2b2b
|
||||
vector1 == vector2 : TRUE
|
||||
vector1 = 4724c040000000079eb2ac9402b2b2b2b2b2b
|
||||
=========Start DECODE===========
|
||||
|
@ -31,22 +31,46 @@ vector1 = 47283c367513ba33304242b2b2b2b2b2b2b2b2b2b2b2b
|
|||
vector1 = 47283c367513ba33304242b2b2b2b2b2b2b2b2b2b2b2b
|
||||
vector2 = 47283c367513ba33304242b2b2b2b2b2b2b2b2b2b2b2b
|
||||
vector1 == vector2 : TRUE
|
||||
UPLINK
|
||||
vector1 = 40e1e61d11f2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b
|
||||
vector1 = 4082001a394df680efb330b2b2b2b2b2b2b2b2b2b
|
||||
=========Start DECODE===========
|
||||
+++++++++Finish DECODE++++++++++
|
||||
=========Start ENCODE=============
|
||||
+++++++++Finish ENCODE+++++++++++
|
||||
vector1 = 40e1e61d11f2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b
|
||||
vector2 = 40e1e61d11f2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b
|
||||
vector1 = 4082001a394df680efb330b2b2b2b2b2b2b2b2b2b
|
||||
vector2 = 4082001a394df680efb330b2b2b2b2b2b2b2b2b2b
|
||||
vector1 == vector2 : TRUE
|
||||
vector1 = 40b802000000002480e0b2b2b2b2b2b2b2b2b
|
||||
vector1 = 40284f00010981c826f446809dcecb2b2b2b2b2b
|
||||
=========Start DECODE===========
|
||||
+++++++++Finish DECODE++++++++++
|
||||
=========Start ENCODE=============
|
||||
+++++++++Finish ENCODE+++++++++++
|
||||
vector1 = 40b802000000002480e0b2b2b2b2b2b2b2b2b
|
||||
vector2 = 40b802000000002480e0b2b2b2b2b2b2b2b2b
|
||||
vector1 = 40284f00010981c826f446809dcecb2b2b2b2b2b
|
||||
vector2 = 40284f00010981c826f446809dcecb2b2b2b2b2b
|
||||
vector1 == vector2 : TRUE
|
||||
vector1 = 40243f2f000087b042b2b2b2b2b2b2b2b2b2b2b
|
||||
=========Start DECODE===========
|
||||
+++++++++Finish DECODE++++++++++
|
||||
=========Start ENCODE=============
|
||||
+++++++++Finish ENCODE+++++++++++
|
||||
vector1 = 40243f2f000087b042b2b2b2b2b2b2b2b2b2b2b
|
||||
vector2 = 40243f2f000087b042b2b2b2b2b2b2b2b2b2b2b
|
||||
vector1 == vector2 : TRUE
|
||||
UPLINK
|
||||
vector1 = 40e1e61d11d2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b
|
||||
=========Start DECODE===========
|
||||
+++++++++Finish DECODE++++++++++
|
||||
=========Start ENCODE=============
|
||||
+++++++++Finish ENCODE+++++++++++
|
||||
vector1 = 40e1e61d11d2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b
|
||||
vector2 = 40e1e61d11d2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b
|
||||
vector1 == vector2 : TRUE
|
||||
vector1 = 40b802000000002480e032b2b2b2b2b2b2b2b
|
||||
=========Start DECODE===========
|
||||
+++++++++Finish DECODE++++++++++
|
||||
=========Start ENCODE=============
|
||||
+++++++++Finish ENCODE+++++++++++
|
||||
vector1 = 40b802000000002480e032b2b2b2b2b2b2b2b
|
||||
vector2 = 40b802000000002480e032b2b2b2b2b2b2b2b
|
||||
vector1 == vector2 : TRUE
|
||||
vector1 = 4016713dc09427ca2ae57ef90906aafc001f80222b
|
||||
=========Start DECODE===========
|
||||
|
@ -56,3 +80,19 @@ vector1 = 4016713dc09427ca2ae57ef90906aafc001f80222b
|
|||
vector1 = 4016713dc09427ca2ae57ef90906aafc001f80222b
|
||||
vector2 = 4016713dc09427ca2ae57ef90906aafc001f80222b
|
||||
vector1 == vector2 : TRUE
|
||||
vector1 = 4020ffc021ec1b2b2b2b2b2b2b2b2b2b2b2b2b2b2b
|
||||
=========Start DECODE===========
|
||||
+++++++++Finish DECODE++++++++++
|
||||
=========Start ENCODE=============
|
||||
+++++++++Finish ENCODE+++++++++++
|
||||
vector1 = 4020ffc021ec1b2b2b2b2b2b2b2b2b2b2b2b2b2b2b
|
||||
vector2 = 4020ffc021ec1b2b2b2b2b2b2b2b2b2b2b2b2b2b2b
|
||||
vector1 == vector2 : TRUE
|
||||
vector1 = 40a90200000000301012a80132b2b2b2b2b
|
||||
=========Start DECODE===========
|
||||
+++++++++Finish DECODE++++++++++
|
||||
=========Start ENCODE=============
|
||||
+++++++++Finish ENCODE+++++++++++
|
||||
vector1 = 40a90200000000301012a80132b2b2b2b2b
|
||||
vector2 = 40a90200000000301012a80132b2b2b2b2b
|
||||
vector1 == vector2 : TRUE
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -32,6 +32,8 @@
|
|||
=== end test_tbf_ws ===
|
||||
=== start test_tbf_egprs_two_phase ===
|
||||
=== end test_tbf_egprs_two_phase ===
|
||||
=== start test_tbf_egprs_two_phase_spb ===
|
||||
=== end test_tbf_egprs_two_phase_spb ===
|
||||
=== start test_tbf_egprs_dl ===
|
||||
Testing MCS-1
|
||||
Testing MCS-2
|
||||
|
@ -43,3 +45,17 @@ Testing MCS-7
|
|||
Testing MCS-8
|
||||
Testing MCS-9
|
||||
=== end test_tbf_egprs_dl ===
|
||||
=== start test_tbf_egprs_retx_dl ===
|
||||
Testing retx for MCS 6 - 6
|
||||
Testing retx for MCS 1 - 9
|
||||
Testing retx for MCS 2 - 8
|
||||
Testing retx for MCS 5 - 7
|
||||
Testing retx for MCS 6 - 9
|
||||
Testing retx for MCS 7 - 5
|
||||
Testing retx for MCS 9 - 6
|
||||
=== end test_tbf_egprs_retx_dl ===
|
||||
=== start test_tbf_egprs_spb_dl ===
|
||||
Testing retx for MCS 6 to reseg_mcs 3
|
||||
Testing retx for MCS 5 to reseg_mcs 2
|
||||
Testing retx for MCS 4 to reseg_mcs 1
|
||||
=== end test_tbf_egprs_spb_dl ===
|
||||
|
|
Loading…
Reference in New Issue