osmo-bts/src/osmo-bts-sysmo/tch.c

570 lines
16 KiB
C

/* Traffic channel support for Sysmocom BTS L1 */
/* (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 <stdint.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.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/timer.h>
#include <osmocom/core/bits.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/trau/osmo_ortp.h>
#include <osmo-bts/logging.h>
#include <osmo-bts/bts.h>
#include <osmo-bts/gsm_data.h>
#include <osmo-bts/measurement.h>
#include <sysmocom/femtobts/femtobts.h>
#include <sysmocom/femtobts/gsml1prim.h>
#include <sysmocom/femtobts/gsml1const.h>
#include <sysmocom/femtobts/gsml1types.h>
#include "femtobts.h"
#include "l1_if.h"
/* input octet-aligned, output not octet-aligned */
void osmo_nibble_shift_right(uint8_t *out, const uint8_t *in,
unsigned int num_nibbles)
{
unsigned int i;
unsigned int num_whole_bytes = num_nibbles / 2;
/* first byte: upper nibble empty, lower nibble from src */
out[0] = (in[0] >> 4);
/* bytes 1.. */
for (i = 1; i < num_whole_bytes; i++)
out[i] = ((in[i-1] & 0xF) << 4) | (in[i] >> 4);
/* shift the last nibble, in case there's an odd count */
i = num_whole_bytes;
if (num_nibbles & 1)
out[i] = ((in[i-1] & 0xF) << 4) | (in[i] >> 4);
else
out[i] = (in[i-1] & 0xF) << 4;
}
/* input unaligned, output octet-aligned */
void osmo_nibble_shift_left_unal(uint8_t *out, const uint8_t *in,
unsigned int num_nibbles)
{
unsigned int i;
unsigned int num_whole_bytes = num_nibbles / 2;
for (i = 0; i < num_whole_bytes; i++)
out[i] = ((in[i] & 0xF) << 4) | (in[i+1] >> 4);
/* shift the last nibble, in case there's an odd count */
i = num_whole_bytes;
if (num_nibbles & 1)
out[i] = (in[i] & 0xF) << 4;
}
#define GSM_FR_BITS 260
#define GSM_EFR_BITS 244
#define GSM_FR_BYTES 33 /* TS 101318 Chapter 5.1: 260 bits + 4bit sig */
#define GSM_HR_BYTES 14 /* TS 101318 Chapter 5.2: 112 bits, no sig */
#define GSM_EFR_BYTES 31 /* TS 101318 Chapter 5.3: 244 bits + 4bit sig */
static struct msgb *l1_to_rtppayload_fr(uint8_t *l1_payload, uint8_t payload_len)
{
struct msgb *msg = msgb_alloc_headroom(1024, 128, "L1C-to-RTP");
uint8_t *cur;
/* step1: reverse the bit-order of each payload byte */
osmo_revbytebits_buf(l1_payload, payload_len);
cur = msgb_put(msg, GSM_FR_BYTES);
/* step2: we need to shift the entire L1 payload by 4 bits right */
osmo_nibble_shift_right(cur, l1_payload, GSM_FR_BITS/4);
cur[0] |= 0xD0;
return msg;
}
/*! \brief convert GSM-FR from RTP payload to L1 format
* \param[out] l1_payload payload part of L1 buffer
* \param[in] rtp_payload pointer to RTP payload data
* \param[in] payload_len length of \a rtp_payload
* \returns number of \a l1_payload bytes filled
*/
static int rtppayload_to_l1_fr(uint8_t *l1_payload, uint8_t *rtp_payload,
unsigned int payload_len)
{
/* step2: we need to shift the RTP payload left by one nibble*/
osmo_nibble_shift_left_unal(l1_payload, rtp_payload, GSM_FR_BITS/4);
/* step1: reverse the bit-order of each payload byte */
osmo_revbytebits_buf(l1_payload, payload_len);
return GSM_FR_BYTES;
}
#ifdef GsmL1_TchPlType_Efr
static struct msgb *l1_to_rtppayload_efr(uint8_t *l1_payload, uint8_t payload_len)
{
struct msgb *msg = msgb_alloc_headroom(1024, 128, "L1C-to-RTP");
uint8_t *cur;
/* step1: reverse the bit-order of each payload byte */
osmo_revbytebits_buf(l1_payload, payload_len);
cur = msgb_put(msg, GSM_EFR_BYTES);
/* step 2: we need to shift the entire L1 payload by 4 bits right */
osmo_nibble_shift_right(cur, l1_payload, GSM_EFR_BITS/4);
cur[0] |= 0xC0;
return msg;
}
#else
#warning No EFR support in L1
#endif
static struct msgb *l1_to_rtppayload_hr(uint8_t *l1_payload, uint8_t payload_len)
{
struct msgb *msg = msgb_alloc_headroom(1024, 128, "L1C-to-RTP");
uint8_t *cur;
if (payload_len != GSM_HR_BYTES) {
LOGP(DL1C, LOGL_ERROR, "L1 HR frame length %u != expected %u\n",
payload_len, GSM_HR_BYTES);
return NULL;
}
cur = msgb_put(msg, GSM_HR_BYTES);
memcpy(cur, l1_payload, GSM_HR_BYTES);
/* reverse the bit-order of each payload byte */
osmo_revbytebits_buf(cur, GSM_HR_BYTES);
return msg;
}
/*! \brief convert GSM-FR from RTP payload to L1 format
* \param[out] l1_payload payload part of L1 buffer
* \param[in] rtp_payload pointer to RTP payload data
* \param[in] payload_len length of \a rtp_payload
* \returns number of \a l1_payload bytes filled
*/
static int rtppayload_to_l1_hr(uint8_t *l1_payload, uint8_t *rtp_payload,
unsigned int payload_len)
{
if (payload_len != GSM_HR_BYTES) {
LOGP(DL1C, LOGL_ERROR, "RTP HR frame length %u != expected %u\n",
payload_len, GSM_HR_BYTES);
return 0;
}
memcpy(l1_payload, rtp_payload, GSM_HR_BYTES);
/* reverse the bit-order of each payload byte */
osmo_revbytebits_buf(l1_payload, GSM_HR_BYTES);
return GSM_HR_BYTES;
}
#define AMR_TOC_QBIT 0x04
#define AMR_CMR_NONE 0xF
static struct msgb *l1_to_rtppayload_amr(uint8_t *l1_payload, uint8_t payload_len,
struct amr_multirate_conf *amr_mrc)
{
struct msgb *msg = msgb_alloc_headroom(1024, 128, "L1C-to-RTP");
uint8_t *cur;
u_int8_t cmr;
uint8_t ft = l1_payload[2] & 0xF;
uint8_t cmr_idx = l1_payload[1];
uint8_t amr_if2_len = payload_len - 2;
#if 0
/* CMR == Unset means CMR was not transmitted at this TDMA */
if (cmr_idx >= GsmL1_AmrCodecMode_Unset)
cmr = AMR_CMR_NONE;
else if (cmr_idx >= amr_mrc->num_modes) {
/* Make sure the CMR of the phone is in the active codec set */
LOGP(DL1C, LOGL_NOTICE, "L1->RTP: overriding CMR IDX %u\n", cmr_idx);
cmr = AMR_CMR_NONE;
} else {
cmr = amr_mrc->mode[cmr_idx].mode;
}
#else
cmr = AMR_CMR_NONE;
#endif
/* RFC 3267 4.4.1 Payload Header */
msgb_put_u8(msg, (cmr << 4));
/* RFC 3267 AMR TOC */
msgb_put_u8(msg, AMR_TOC_QBIT | (ft << 3));
cur = msgb_put(msg, amr_if2_len-1);
/* step1: reverse the bit-order within every byte */
osmo_revbytebits_buf(l1_payload+2, amr_if2_len);
/* step2: shift everything left by one nibble */
osmo_nibble_shift_left_unal(cur, l1_payload+2, amr_if2_len*2 -1);
return msg;
}
enum amr_frame_type {
AMR_FT_SID_AMR = 8,
};
int get_amr_mode_idx(const struct amr_multirate_conf *amr_mrc, uint8_t cmi)
{
unsigned int i;
for (i = 0; i < amr_mrc->num_modes; i++) {
if (amr_mrc->mode[i].mode == cmi)
return i;
}
return -EINVAL;
}
/*! \brief convert AMR from RTP payload to L1 format
* \param[out] l1_payload payload part of L1 buffer
* \param[in] rtp_payload pointer to RTP payload data
* \param[in] payload_len length of \a rtp_payload
* \returns number of \a l1_payload bytes filled
*/
static int rtppayload_to_l1_amr(uint8_t *l1_payload, uint8_t *rtp_payload,
uint8_t payload_len,
struct gsm_lchan *lchan)
{
struct amr_multirate_conf *amr_mrc = &lchan->tch.amr_mr;
uint8_t ft = (rtp_payload[1] >> 3) & 0xf;
uint8_t cmr = rtp_payload[0] >> 4;
uint8_t cmi, sti;
uint8_t *l1_cmi_idx = l1_payload;
uint8_t *l1_cmr_idx = l1_payload+1;
uint8_t amr_if2_core_len = payload_len - 2;
int rc;
/* step1: shift everything right one nibble; make space for FT */
osmo_nibble_shift_right(l1_payload+2, rtp_payload+2, amr_if2_core_len*2);
/* step2: reverse the bit-order within every byte of the IF2
* core frame contained in the RTP payload */
osmo_revbytebits_buf(l1_payload+2, amr_if2_core_len+1);
/* CMI in downlink tells the L1 encoder which encoding function
* it will use, so we have to use the frame type */
switch (ft) {
case 0: case 1: case 2: case 3:
case 4: case 5: case 6: case 7:
cmi = ft;
LOGP(DRTP, LOGL_DEBUG, "SPEECH frame with CMI %u\n", cmi);
break;
case AMR_FT_SID_AMR:
/* extract the mode indiciation from last bits of
* 39 bit SID frame (Table 6 / 26.101) */
cmi = (rtp_payload[2+4] >> 1) & 0x7;
sti = rtp_payload[2+4] & 0x10;
LOGP(DRTP, LOGL_DEBUG, "SID %s frame with CMI %u\n",
sti ? "UPDATE" : "FIRST", cmi);
break;
default:
LOGP(DRTP, LOGL_ERROR, "unsupported AMR FT 0x%02x\n", ft);
return -EINVAL;
break;
}
rc = get_amr_mode_idx(amr_mrc, cmi);
if (rc < 0) {
LOGP(DRTP, LOGL_ERROR, "AMR CMI %u not part of AMR MR set\n",
cmi);
*l1_cmi_idx = 0;
} else
*l1_cmi_idx = rc;
/* Codec Mode Request is in upper 4 bits of RTP payload header,
* and we simply copy the CMR into the CMC */
if (cmr == 0xF) {
/* FIXME: we need some state about the last codec mode */
*l1_cmr_idx = 0;
} else {
rc = get_amr_mode_idx(amr_mrc, cmr);
if (rc < 0) {
/* FIXME: we need some state about the last codec mode */
LOGP(DRTP, LOGL_INFO, "RTP->L1: overriding CMR %u\n", cmr);
*l1_cmr_idx = 0;
} else
*l1_cmr_idx = rc;
}
#if 0
/* check for bad quality indication */
if (rtp_payload[1] & AMR_TOC_QBIT) {
/* obtain frame type from AMR FT */
l1_payload[2] = ft;
} else {
/* bad quality, we should indicate that... */
if (ft == AMR_FT_SID_AMR) {
/* FIXME: Should we do GsmL1_TchPlType_Amr_SidBad? */
l1_payload[2] = ft;
} else {
l1_payload[2] = ft;
}
}
#endif
/* lower 4 bit of first FR2 byte contains FT */
l1_payload[2] |= ft;
if (ft == AMR_FT_SID_AMR) {
/* store the last SID frame in lchan context */
unsigned int copy_len;
copy_len = OSMO_MIN(payload_len+1,
ARRAY_SIZE(lchan->tch.last_sid.buf));
lchan->tch.last_sid.len = copy_len;
memcpy(lchan->tch.last_sid.buf, l1_payload, copy_len);
}
return payload_len+1;
}
#define RTP_MSGB_ALLOC_SIZE 512
/*! \brief call-back function for incoming RTP
* \param rs RTP Socket
* \param[in] rtp_pl buffer containing RTP payload
* \param[in] rtp_pl_len length of \a rtp_pl
*
* This function prepares a msgb with a L1 PH-DATA.req primitive and
* queues it into lchan->dl_tch_queue.
*
* Note that the actual L1 primitive header is not fully initialized
* yet, as things like the frame number, etc. are unknown at the time we
* pre-fill the primtive.
*/
void bts_model_rtp_rx_cb(struct osmo_rtp_socket *rs, uint8_t *rtp_pl,
unsigned int rtp_pl_len)
{
struct gsm_lchan *lchan = rs->priv;
struct msgb *msg = l1p_msgb_alloc();
GsmL1_Prim_t *l1p = msgb_l1prim(msg);
GsmL1_PhDataReq_t *data_req = &l1p->u.phDataReq;
GsmL1_MsgUnitParam_t *msu_param = &data_req->msgUnitParam;
uint8_t *payload_type = &msu_param->u8Buffer[0];
uint8_t *l1_payload = &msu_param->u8Buffer[1];
int rc;
DEBUGP(DRTP, "%s RTP IN: %s\n", gsm_lchan_name(lchan),
osmo_hexdump(rtp_pl, rtp_pl_len));
switch (lchan->tch_mode) {
case GSM48_CMODE_SPEECH_V1:
if (lchan->type == GSM_LCHAN_TCH_F) {
*payload_type = GsmL1_TchPlType_Fr;
rc = rtppayload_to_l1_fr(l1_payload,
rtp_pl, rtp_pl_len);
} else{
*payload_type = GsmL1_TchPlType_Hr;
rc = rtppayload_to_l1_hr(l1_payload,
rtp_pl, rtp_pl_len);
}
break;
#ifdef GsmL1_TchPlType_Efr
case GSM48_CMODE_SPEECH_EFR:
*payload_type = GsmL1_TchPlType_Efr;
rc = FIXME;
break;
#endif
case GSM48_CMODE_SPEECH_AMR:
*payload_type = GsmL1_TchPlType_Amr;
rc = rtppayload_to_l1_amr(l1_payload, rtp_pl,
rtp_pl_len, lchan);
break;
default:
/* we don't support CSD modes */
rc = -1;
break;
}
if (rc < 0) {
LOGP(DRTP, LOGL_ERROR, "%s unable to parse RTP payload\n",
gsm_lchan_name(lchan));
msgb_free(msg);
return;
}
msu_param->u8Size = rc + 1;
DEBUGP(DRTP, "%s RTP->L1: %s\n", gsm_lchan_name(lchan),
osmo_hexdump(msu_param->u8Buffer, msu_param->u8Size));
/* make sure the number of entries in the dl_tch_queue is never
* more than 3 */
{
struct msgb *tmp;
int count = 0;
llist_for_each_entry(tmp, &lchan->dl_tch_queue, list)
count++;
DEBUGP(DL1C, "%s DL TCH queue length = %u\n",
gsm_lchan_name(lchan), count);
while (count >= 2) {
tmp = msgb_dequeue(&lchan->dl_tch_queue);
msgb_free(tmp);
count--;
}
}
/* enqueue msgb to be transmitted to L1 */
msgb_enqueue(&lchan->dl_tch_queue, msg);
}
/*! \brief receive a traffic L1 primitive for a given lchan */
int l1if_tch_rx(struct gsm_lchan *lchan, struct msgb *l1p_msg)
{
GsmL1_Prim_t *l1p = msgb_l1prim(l1p_msg);
GsmL1_PhDataInd_t *data_ind = &l1p->u.phDataInd;
uint8_t payload_type = data_ind->msgUnitParam.u8Buffer[0];
uint8_t *payload = data_ind->msgUnitParam.u8Buffer + 1;
uint8_t payload_len;
struct msgb *rmsg = NULL;
if (data_ind->msgUnitParam.u8Size < 1) {
LOGP(DL1C, LOGL_ERROR, "%s Rx Payload size 0\n",
gsm_lchan_name(lchan));
return -EINVAL;
}
payload_len = data_ind->msgUnitParam.u8Size - 1;
switch (payload_type) {
case GsmL1_TchPlType_Fr:
#ifdef GsmL1_TchPlType_Efr
case GsmL1_TchPlType_Efr:
#endif
if (lchan->type != GSM_LCHAN_TCH_F)
goto err_payload_match;
break;
case GsmL1_TchPlType_Hr:
if (lchan->type != GSM_LCHAN_TCH_H)
goto err_payload_match;
break;
case GsmL1_TchPlType_Amr:
if (lchan->type != GSM_LCHAN_TCH_H &&
lchan->type != GSM_LCHAN_TCH_F)
goto err_payload_match;
break;
default:
LOGP(DL1C, LOGL_NOTICE, "%s Rx Payload Type %s is unsupported\n",
gsm_lchan_name(lchan),
get_value_string(femtobts_tch_pl_names, payload_type));
break;
}
LOGP(DL1C, LOGL_DEBUG, "%s Rx codec frame (%u): %s\n", gsm_lchan_name(lchan),
payload_len, osmo_hexdump(payload, payload_len));
switch (payload_type) {
case GsmL1_TchPlType_Fr:
rmsg = l1_to_rtppayload_fr(payload, payload_len);
break;
case GsmL1_TchPlType_Hr:
rmsg = l1_to_rtppayload_hr(payload, payload_len);
break;
#ifdef GsmL1_TchPlType_Efr
case GsmL1_TchPlType_Efr
rmsg = l1_to_rtppayload_efr(payload, payload_len);
break;
#endif
case GsmL1_TchPlType_Amr:
rmsg = l1_to_rtppayload_amr(payload, payload_len,
&lchan->tch.amr_mr);
break;
}
if (rmsg) {
LOGP(DL1C, LOGL_DEBUG, "%s Rx -> RTP: %s\n",
gsm_lchan_name(lchan), osmo_hexdump(rmsg->data, rmsg->len));
/* hand rmsg to RTP code for transmission */
if (lchan->abis_ip.rtp_socket)
osmo_rtp_send_frame(lchan->abis_ip.rtp_socket,
rmsg->data, rmsg->len, 160);
msgb_free(rmsg);
}
return 0;
err_payload_match:
LOGP(DL1C, LOGL_ERROR, "%s Rx Payload Type %s incompatible with lchan\n",
gsm_lchan_name(lchan),
get_value_string(femtobts_tch_pl_names, payload_type));
return -EINVAL;
}
struct msgb *gen_empty_tch_msg(struct gsm_lchan *lchan)
{
struct msgb *msg = l1p_msgb_alloc();
GsmL1_Prim_t *l1p = msgb_l1prim(msg);
GsmL1_PhDataReq_t *data_req = &l1p->u.phDataReq;
GsmL1_MsgUnitParam_t *msu_param = &data_req->msgUnitParam;
uint8_t *payload_type = &msu_param->u8Buffer[0];
uint8_t *l1_payload = &msu_param->u8Buffer[1];
switch (lchan->tch_mode) {
case GSM48_CMODE_SPEECH_AMR:
*payload_type = GsmL1_TchPlType_Amr;
if (lchan->tch.last_sid.len) {
memcpy(l1_payload, lchan->tch.last_sid.buf,
lchan->tch.last_sid.len);
msu_param->u8Size = lchan->tch.last_sid.len+1;
} else {
/* FIXME: decide if we should send SPEECH_BAD or
* SID_BAD */
#if 0
*payload_type = GsmL1_TchPlType_Amr_SidBad;
memset(l1_payload, 0xFF, 5);
msu_param->u8Size = 5 + 3;
#else
/* send an all-zero SID */
msu_param->u8Size = 8;
#endif
}
break;
default:
msgb_free(msg);
msg = NULL;
break;
}
return msg;
}