aboutsummaryrefslogtreecommitdiffstats
path: root/src/gprs_bssgp_pcu.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/gprs_bssgp_pcu.cpp')
-rw-r--r--src/gprs_bssgp_pcu.cpp223
1 files changed, 207 insertions, 16 deletions
diff --git a/src/gprs_bssgp_pcu.cpp b/src/gprs_bssgp_pcu.cpp
index 8e81304f..44fb7006 100644
--- a/src/gprs_bssgp_pcu.cpp
+++ b/src/gprs_bssgp_pcu.cpp
@@ -25,15 +25,21 @@ struct sgsn_instance *sgsn;
void *tall_bsc_ctx;
struct bssgp_bvc_ctx *bctx = NULL;
struct gprs_nsvc *nsvc = NULL;
+static int bvc_sig_reset = 0, bvc_reset = 0, bvc_unblocked = 0;
extern uint16_t spoof_mcc, spoof_mnc;
+struct osmo_timer_list bvc_timer;
+
+static void bvc_timeout(void *_priv);
+
int gprs_bssgp_pcu_rx_dl_ud(struct msgb *msg, struct tlv_parsed *tp)
{
struct bssgp_ud_hdr *budh;
- int tfi;
+
+ int8_t tfi; /* must be signed */
+
uint32_t tlli;
int i, j;
- uint8_t trx, ts;
uint8_t *data;
uint16_t len;
struct gprs_rlcmac_tbf *tbf;
@@ -78,6 +84,54 @@ int gprs_bssgp_pcu_rx_dl_ud(struct msgb *msg, struct tlv_parsed *tp)
}
imsi[j] = '\0';
}
+
+ /* parse ms radio access capability */
+ uint8_t ms_class = 0;
+ if (TLVP_PRESENT(tp, BSSGP_IE_MS_RADIO_ACCESS_CAP))
+ {
+ bitvec *block;
+ uint8_t cap_len = TLVP_LEN(tp, BSSGP_IE_MS_RADIO_ACCESS_CAP);
+ uint8_t *cap = (uint8_t *) TLVP_VAL(tp, BSSGP_IE_MS_RADIO_ACCESS_CAP);
+ unsigned rp = 0;
+
+ block = bitvec_alloc(cap_len);
+ bitvec_unpack(block, cap);
+ bitvec_read_field(block, rp, 4); // Access Technology Type
+ bitvec_read_field(block, rp, 7); // Length of Access Capabilities
+ bitvec_read_field(block, rp, 3); // RF Power Capability
+ if (bitvec_read_field(block, rp, 1)) // A5 Bits Present
+ bitvec_read_field(block, rp, 7); // A5 Bits
+ bitvec_read_field(block, rp, 1); // ES IND
+ bitvec_read_field(block, rp, 1); // PS
+ bitvec_read_field(block, rp, 1); // VGCS
+ bitvec_read_field(block, rp, 1); // VBS
+ if (bitvec_read_field(block, rp, 1)) { // Multislot Cap Present
+ if (bitvec_read_field(block, rp, 1)) // HSCSD Present
+ bitvec_read_field(block, rp, 5); // Class
+ if (bitvec_read_field(block, rp, 1)) { // GPRS Present
+ ms_class = bitvec_read_field(block, rp, 5); // Class
+ bitvec_read_field(block, rp, 1); // Ext.
+ }
+ if (bitvec_read_field(block, rp, 1)) // SMS Present
+ bitvec_read_field(block, rp, 4); // SMS Value
+ bitvec_read_field(block, rp, 4); // SMS Value
+ }
+ }
+ /* get lifetime */
+ uint16_t delay_csec = 0xffff;
+ if (TLVP_PRESENT(tp, BSSGP_IE_PDU_LIFETIME))
+ {
+ uint8_t lt_len = TLVP_LEN(tp, BSSGP_IE_PDU_LIFETIME);
+ uint16_t *lt = (uint16_t *) TLVP_VAL(tp, BSSGP_IE_PDU_LIFETIME);
+ if (lt_len == 2)
+ delay_csec = ntohs(*lt);
+ else
+ LOGP(DBSSGP, LOGL_NOTICE, "BSSGP invalid length of "
+ "PDU_LIFETIME IE\n");
+ } else
+ LOGP(DBSSGP, LOGL_NOTICE, "BSSGP missing mandatory "
+ "PDU_LIFETIME IE\n");
+
LOGP(DBSSGP, LOGL_INFO, "LLC [SGSN -> PCU] = TLLI: 0x%08x IMSI: %s len: %d\n", tlli, imsi, len);
/* check for existing TBF */
@@ -90,29 +144,83 @@ int gprs_bssgp_pcu_rx_dl_ud(struct msgb *msg, struct tlv_parsed *tp)
tbf->llc_length = len;
memset(&tbf->dir.dl, 0, sizeof(tbf->dir.dl)); /* reset
rlc states */
- gprs_rlcmac_trigger_downlink_assignment(tbf, 1, NULL);
+ tbf->state_flags &= GPRS_RLCMAC_FLAG_TO_MASK; /* keep
+ to flags */
+ tbf->state_flags &= ~(1 << GPRS_RLCMAC_FLAG_CCCH);
+ if (!tbf->ms_class && ms_class)
+ tbf->ms_class = ms_class;
+ tbf_update(tbf);
+ gprs_rlcmac_trigger_downlink_assignment(tbf, tbf, NULL);
} else {
- /* the TBF exists, so we must write it in the queue */
- struct msgb *llc_msg = msgb_alloc(len, "llc_pdu_queue");
+ /* the TBF exists, so we must write it in the queue
+ * we prepend lifetime in front of PDU */
+ struct gprs_rlcmac_bts *bts = gprs_rlcmac_bts;
+ struct timeval *tv;
+ struct msgb *llc_msg = msgb_alloc(len + sizeof(*tv),
+ "llc_pdu_queue");
if (!llc_msg)
return -ENOMEM;
+ tv = (struct timeval *)msgb_put(llc_msg, sizeof(*tv));
+ if (bts->force_llc_lifetime)
+ delay_csec = bts->force_llc_lifetime;
+ /* keep timestap at 0 for infinite delay */
+ if (delay_csec != 0xffff) {
+ /* calculate timestamp of timeout */
+ gettimeofday(tv, NULL);
+ tv->tv_usec += (delay_csec % 100) * 10000;
+ tv->tv_sec += delay_csec / 100;
+ if (tv->tv_usec > 999999) {
+ tv->tv_usec -= 1000000;
+ tv->tv_sec++;
+ }
+ }
memcpy(msgb_put(llc_msg, len), data, len);
msgb_enqueue(&tbf->llc_queue, llc_msg);
+ /* set ms class for updating TBF */
+ if (!tbf->ms_class && ms_class)
+ tbf->ms_class = ms_class;
}
} else {
- // Create new TBF
- tfi = tfi_alloc(&trx, &ts);
+ uint8_t trx, ts, use_trx, first_ts, ta, ss;
+ struct gprs_rlcmac_tbf *old_tbf;
+
+ /* check for uplink data, so we copy our informations */
+ tbf = tbf_by_tlli(tlli, GPRS_RLCMAC_UL_TBF);
+ if (tbf && tbf->dir.ul.contention_resolution_done
+ && !tbf->dir.ul.final_ack_sent) {
+ use_trx = tbf->trx;
+ first_ts = tbf->first_ts;
+ ta = tbf->ta;
+ ss = 0;
+ old_tbf = tbf;
+ } else {
+ use_trx = -1;
+ first_ts = -1;
+ ta = 0; /* FIXME: initial TA */
+ ss = 1; /* PCH assignment only allows one timeslot */
+ old_tbf = NULL;
+ }
+
+ // Create new TBF (any TRX)
+ tfi = tfi_alloc(GPRS_RLCMAC_DL_TBF, &trx, &ts, use_trx, first_ts);
if (tfi < 0) {
LOGP(DRLCMAC, LOGL_NOTICE, "No PDCH resource\n");
/* FIXME: send reject */
return -EBUSY;
}
- tbf = tbf_alloc(tfi, trx, ts);
- tbf->direction = GPRS_RLCMAC_DL_TBF;
+ /* set number of downlink slots according to multislot class */
+ tbf = tbf_alloc(tbf, GPRS_RLCMAC_DL_TBF, tfi, trx, ts, ms_class,
+ ss);
+ if (!tbf) {
+ LOGP(DRLCMAC, LOGL_NOTICE, "No PDCH ressource\n");
+ /* FIXME: send reject */
+ return -EBUSY;
+ }
tbf->tlli = tlli;
tbf->tlli_valid = 1;
+ tbf->ta = ta;
- LOGP(DRLCMAC, LOGL_DEBUG, "TBF: [DOWNLINK] START TFI: %u TLLI: 0x%08x \n", tbf->tfi, tbf->tlli);
+ LOGP(DRLCMAC, LOGL_DEBUG, "TBF: [DOWNLINK] START TFI: %d TLLI: 0x%08x \n", tbf->tfi, tbf->tlli);
/* new TBF, so put first frame */
memcpy(tbf->llc_frame, data, len);
@@ -122,7 +230,7 @@ int gprs_bssgp_pcu_rx_dl_ud(struct msgb *msg, struct tlv_parsed *tp)
* we don't use old_downlink, so the possible uplink is used
* to trigger downlink assignment. if there is no uplink,
* AGCH is used. */
- gprs_rlcmac_trigger_downlink_assignment(tbf, 0, imsi);
+ gprs_rlcmac_trigger_downlink_assignment(tbf, old_tbf, imsi);
}
return 0;
@@ -135,6 +243,9 @@ int gprs_bssgp_pcu_rx_ptp(struct msgb *msg, struct tlv_parsed *tp, struct bssgp_
uint8_t pdu_type = bgph->pdu_type;
unsigned rc = 0;
+ if (!bctx)
+ return -EINVAL;
+
/* If traffic is received on a BVC that is marked as blocked, the
* received PDU shall not be accepted and a STATUS PDU (Cause value:
* BVC Blocked) shall be sent to the peer entity on the signalling BVC */
@@ -192,6 +303,11 @@ int gprs_bssgp_pcu_rx_sign(struct msgb *msg, struct tlv_parsed *tp, struct bssgp
break;
case BSSGP_PDUT_BVC_RESET_ACK:
LOGP(DBSSGP, LOGL_DEBUG, "rx BSSGP_PDUT_BVC_RESET_ACK\n");
+ if (!bvc_sig_reset)
+ bvc_sig_reset = 1;
+ else
+ bvc_reset = 1;
+ bvc_timeout(NULL);
break;
case BSSGP_PDUT_PAGING_PS:
LOGP(DBSSGP, LOGL_DEBUG, "rx BSSGP_PDUT_PAGING_PS\n");
@@ -213,6 +329,8 @@ int gprs_bssgp_pcu_rx_sign(struct msgb *msg, struct tlv_parsed *tp, struct bssgp
break;
case BSSGP_PDUT_BVC_UNBLOCK_ACK:
LOGP(DBSSGP, LOGL_DEBUG, "rx BSSGP_PDUT_BVC_UNBLOCK_ACK\n");
+ bvc_unblocked = 1;
+ bvc_timeout(NULL);
break;
case BSSGP_PDUT_SGSN_INVOKE_TRACE:
LOGP(DBSSGP, LOGL_DEBUG, "rx BSSGP_PDUT_SGSN_INVOKE_TRACE\n");
@@ -259,7 +377,9 @@ int gprs_bssgp_pcu_rcvmsg(struct msgb *msg)
/* look-up or create the BTS context for this BVC */
bctx = btsctx_by_bvci_nsei(ns_bvci, msgb_nsei(msg));
- if (!bctx && pdu_type != BSSGP_PDUT_BVC_RESET_ACK)
+ if (!bctx
+ && pdu_type != BSSGP_PDUT_BVC_RESET_ACK
+ && pdu_type != BSSGP_PDUT_BVC_UNBLOCK_ACK)
{
LOGP(DBSSGP, LOGL_NOTICE, "NSEI=%u/BVCI=%u Rejecting PDU "
"type %u for unknown BVCI\n", msgb_nsei(msg), ns_bvci,
@@ -335,14 +455,22 @@ static int nsvc_signal_cb(unsigned int subsys, unsigned int signal,
case S_NS_UNBLOCK:
if (!nsvc_unblocked) {
nsvc_unblocked = 1;
- LOGP(DPCU, LOGL_NOTICE, "NS-VC is unblocked.\n");
- bssgp_tx_bvc_reset(bctx, bctx->bvci,
- BSSGP_CAUSE_PROTO_ERR_UNSPEC);
+ LOGP(DPCU, LOGL_NOTICE, "NS-VC %d is unblocked.\n",
+ nsvc->nsvci);
+ bvc_sig_reset = 0;
+ bvc_reset = 0;
+ bvc_unblocked = 0;
+ bvc_timeout(NULL);
}
break;
case S_NS_BLOCK:
if (nsvc_unblocked) {
nsvc_unblocked = 0;
+ if (osmo_timer_pending(&bvc_timer))
+ osmo_timer_del(&bvc_timer);
+ bvc_sig_reset = 0;
+ bvc_reset = 0;
+ bvc_unblocked = 0;
LOGP(DPCU, LOGL_NOTICE, "NS-VC is blocked.\n");
}
break;
@@ -351,12 +479,63 @@ static int nsvc_signal_cb(unsigned int subsys, unsigned int signal,
return 0;
}
+int gprs_bssgp_tx_fc_bvc(void)
+{
+ if (!bctx) {
+ LOGP(DBSSGP, LOGL_ERROR, "No bctx\n");
+ return -EIO;
+ }
+ /* FIXME: use real values */
+ return bssgp_tx_fc_bvc(bctx, 1, 6553500, 819100, 50000, 50000,
+ NULL, NULL);
+// return bssgp_tx_fc_bvc(bctx, 1, 84000, 25000, 48000, 45000,
+// NULL, NULL);
+}
+
+static void bvc_timeout(void *_priv)
+{
+ struct gprs_rlcmac_bts *bts = gprs_rlcmac_bts;
+
+ if (!bvc_sig_reset) {
+ LOGP(DBSSGP, LOGL_INFO, "Sending reset on BVCI 0\n");
+ bssgp_tx_bvc_reset(bctx, 0, BSSGP_CAUSE_OML_INTERV);
+ osmo_timer_schedule(&bvc_timer, 1, 0);
+ return;
+ }
+
+ if (!bvc_reset) {
+ LOGP(DBSSGP, LOGL_INFO, "Sending reset on BVCI %d\n",
+ bctx->bvci);
+ bssgp_tx_bvc_reset(bctx, bctx->bvci, BSSGP_CAUSE_OML_INTERV);
+ osmo_timer_schedule(&bvc_timer, 1, 0);
+ return;
+ }
+
+ if (!bvc_unblocked) {
+ LOGP(DBSSGP, LOGL_INFO, "Sending unblock on BVCI %d\n",
+ bctx->bvci);
+ bssgp_tx_bvc_unblock(bctx);
+ osmo_timer_schedule(&bvc_timer, 1, 0);
+ return;
+ }
+
+ LOGP(DBSSGP, LOGL_DEBUG, "Sending flow control info on BVCI %d\n",
+ bctx->bvci);
+ gprs_bssgp_tx_fc_bvc();
+ osmo_timer_schedule(&bvc_timer, bts->fc_interval, 0);
+}
+
/* create BSSGP/NS layer instances */
int gprs_bssgp_create(uint32_t sgsn_ip, uint16_t sgsn_port, uint16_t nsei,
uint16_t nsvci, uint16_t bvci, uint16_t mcc, uint16_t mnc, uint16_t lac,
uint16_t rac, uint16_t cell_id)
{
struct sockaddr_in dest;
+ int rc;
+
+ mcc = ((mcc & 0xf00) >> 8) * 100 + ((mcc & 0x0f0) >> 4) * 10 + (mcc & 0x00f);
+ mnc = ((mnc & 0xf00) >> 8) * 100 + ((mnc & 0x0f0) >> 4) * 10 + (mnc & 0x00f);
+ cell_id = ntohs(cell_id);
if (bctx)
return 0; /* if already created, must return 0: no error */
@@ -366,7 +545,13 @@ int gprs_bssgp_create(uint32_t sgsn_ip, uint16_t sgsn_port, uint16_t nsei,
LOGP(DBSSGP, LOGL_ERROR, "Failed to create NS instance\n");
return -EINVAL;
}
- gprs_ns_nsip_listen(bssgp_nsi);
+ rc = gprs_ns_nsip_listen(bssgp_nsi);
+ if (rc < 0) {
+ LOGP(DBSSGP, LOGL_ERROR, "Failed to create socket\n");
+ gprs_ns_destroy(bssgp_nsi);
+ bssgp_nsi = NULL;
+ return -EINVAL;
+ }
dest.sin_family = AF_INET;
dest.sin_port = htons(sgsn_port);
@@ -398,6 +583,9 @@ int gprs_bssgp_create(uint32_t sgsn_ip, uint16_t sgsn_port, uint16_t nsei,
// bssgp_tx_bvc_reset(bctx, bctx->bvci, BSSGP_CAUSE_PROTO_ERR_UNSPEC);
+ bvc_timer.cb = bvc_timeout;
+
+
return 0;
}
@@ -406,6 +594,9 @@ void gprs_bssgp_destroy(void)
if (!bssgp_nsi)
return;
+ if (osmo_timer_pending(&bvc_timer))
+ osmo_timer_del(&bvc_timer);
+
osmo_signal_unregister_handler(SS_L_NS, nsvc_signal_cb, NULL);
nsvc = NULL;