Currently, the AGCH queue length is not limited. This can lead to
large delays and network malfunction if there are many IMM.ASS.REJ
messages.
This patch adds two features:
- Don't accept msgs from the RSL layer when the queue is way too
full (safety measure, mainly if bts_ccch_copy_msg() is not being
called by the L1 layer, currently hard coded to 1000 messages)
- Selectively drop IMM.ASS.REJ from the queue output depending on the
queue length
Ticket: #224
Sponsored-by: On-Waves ehf
---
include/osmo-bts/gsm_data.h | 9 ++++
src/common/bts.c | 123 ++++++++++++++++++++++++++++++++++++++-----
tests/agch/agch_test.c | 4 ++
tests/agch/agch_test.ok | 2 +-
4 files changed, 125 insertions(+), 13 deletions(-)
diff --git a/include/osmo-bts/gsm_data.h b/include/osmo-bts/gsm_data.h
index b139903..c7a0fc6 100644
--- a/include/osmo-bts/gsm_data.h
+++ b/include/osmo-bts/gsm_data.h
@@ -7,6 +7,11 @@
#include <osmo-bts/paging.h>
+#define GSM_BTS_AGCH_QUEUE_THRESH_LEVEL_DEFAULT 41
+#define GSM_BTS_AGCH_QUEUE_THRESH_LEVEL_DISABLE 999999
+#define GSM_BTS_AGCH_QUEUE_LOW_LEVEL_DEFAULT 41
+#define GSM_BTS_AGCH_QUEUE_HIGH_LEVEL_DEFAULT 91
+
struct pcu_sock_state;
struct gsm_network {
@@ -54,6 +59,10 @@ struct gsm_bts_role_bts {
int agch_queue_length;
int agch_max_queue_length;
+ int agch_queue_thresh_level; /* Cleanup threshold in percent of max len */
+ int agch_queue_low_level; /* Low water mark in percent of max len */
+ int agch_queue_high_level; /* High water mark in percent of max len */
+
/* TODO: Use a rate counter group instead */
uint64_t agch_queue_dropped_msgs;
uint64_t agch_queue_merged_msgs;
diff --git a/src/common/bts.c b/src/common/bts.c
index 4ba2ec7..0b14020 100644
--- a/src/common/bts.c
+++ b/src/common/bts.c
@@ -89,6 +89,14 @@ int bts_init(struct gsm_bts *bts)
INIT_LLIST_HEAD(&btsb->agch_queue);
btsb->agch_queue_length = 0;
+ /* enable management with default levels,
+ * raise threshold to GSM_BTS_AGCH_QUEUE_THRESH_LEVEL_DISABLE to
+ * disable this feature.
+ */
+ btsb->agch_queue_low_level = GSM_BTS_AGCH_QUEUE_LOW_LEVEL_DEFAULT;
+ btsb->agch_queue_high_level = GSM_BTS_AGCH_QUEUE_HIGH_LEVEL_DEFAULT;
+ btsb->agch_queue_thresh_level = GSM_BTS_AGCH_QUEUE_THRESH_LEVEL_DEFAULT;
+
/* configurable via VTY */
btsb->paging_state = paging_init(btsb, 200, 0);
@@ -297,6 +305,18 @@ void bts_update_agch_max_queue_length(struct gsm_bts *bts)
int bts_agch_enqueue(struct gsm_bts *bts, struct msgb *msg)
{
struct gsm_bts_role_bts *btsb = bts_role_bts(bts);
+ int hard_limit = 1000;
+
+ if (btsb->agch_queue_length > hard_limit) {
+ LOGP(DSUM, LOGL_ERROR,
+ "AGCH: too many messages in queue, "
+ "refusing message type 0x%02x, length = %d/%d\n",
+ ((struct gsm48_imm_ass *)msgb_l3(msg))->msg_type,
+ btsb->agch_queue_length, btsb->agch_max_queue_length);
+
+ btsb->agch_queue_rejected_msgs++;
+ return -ENOMEM;
+ }
msgb_enqueue(&btsb->agch_queue, msg);
btsb->agch_queue_length++;
@@ -325,30 +345,109 @@ struct msgb *bts_agch_dequeue(struct gsm_bts *bts)
return msg;
}
+/* Remove lower prio messages if the queue has grown to long.
+ *
+ * \return 0 iff the number of messages in the queue would fit into the AGCH
+ * reserved part of the CCCH.
+ */
+static int compact_agch_queue(struct gsm_bts *bts)
+{
+ struct gsm_bts_role_bts *btsb = bts_role_bts(bts);
+ struct msgb *msg, *msg2;
+ int max_len, slope, offs;
+ int level_low = btsb->agch_queue_low_level;
+ int level_high = btsb->agch_queue_high_level;
+ int level_thres = btsb->agch_queue_thresh_level;
+ struct gsm48_system_information_type_3 *si3 = GSM_BTS_SI(bts, SYSINFO_TYPE_3);
+ int bs_ag_blks_res = si3->control_channel_desc.bs_ag_blks_res;
+
+ max_len = btsb->agch_max_queue_length;
+
+ if (max_len == 0)
+ max_len = 1;
+
+ /* TODO: Make the constants configurable */
+ if (btsb->agch_queue_length < max_len * level_thres / 100)
+ goto out;
+
+ /* p^
+ * 1+ /'''''
+ * | /
+ * | /
+ * 0+---/--+----+--> Q length
+ * low high max_len
+ */
+
+ offs = max_len * level_low / 100;
+ if (level_high > level_low)
+ slope = 0x10000 * 100 / (level_high - level_low);
+ else
+ slope = 0x10000 * max_len; /* p_drop >= 1 if len > offs */
+
+ llist_for_each_entry_safe(msg, msg2, &btsb->agch_queue, list) {
+ struct gsm48_imm_ass *imm_ass_cmd = msgb_l3(msg);
+ int p_drop;
+ LOGP(DSUM, LOGL_DEBUG,
+ "AGCH: head is message type 0x%02x, length = %d/%d\n",
+ imm_ass_cmd->msg_type,
+ btsb->agch_queue_length, btsb->agch_max_queue_length);
+
+ if (imm_ass_cmd->msg_type != GSM48_MT_RR_IMM_ASS_REJ)
+ goto out;
+
+ /* IMMEDIATE ASSIGN REJECT */
+
+ p_drop = (btsb->agch_queue_length - offs) * slope / max_len;
+
+ if ((random() & 0xffff) >= p_drop)
+ goto out;
+
+ LOGP(DSUM, LOGL_DEBUG,
+ "AGCH: dropping message type 0x%02x, length = %d/%d, "
+ "p = %f\n",
+ imm_ass_cmd->msg_type,
+ btsb->agch_queue_length, btsb->agch_max_queue_length,
+ p_drop / 65536.0);
+
+ llist_del(&msg->list);
+ btsb->agch_queue_length--;
+ msgb_free(msg);
+
+ btsb->agch_queue_dropped_msgs++;
+ }
+out:
+ return btsb->agch_queue_length > bs_ag_blks_res;
+}
+
int bts_ccch_copy_msg(struct gsm_bts *bts, uint8_t *out_buf, struct gsm_time *gt,
int is_ag_res)
{
- struct msgb *msg;
+ struct msgb *msg = NULL;
struct gsm_bts_role_bts *btsb = bts->role;
- int rc;
+ int rc = 0;
int is_empty = 1;
- struct gsm48_system_information_type_3 *si3 =
- GSM_BTS_SI(bts, SYSINFO_TYPE_3);
- int bs_ag_blks_res = si3->control_channel_desc.bs_ag_blks_res;
- int agch_res_full = btsb->agch_queue_length > bs_ag_blks_res;
+ int agch_res_full;
+
+ /* Do queue house keeping.
+ * This needs to be done every time a CCCH message is requested, since
+ * the queue max length is calculated based on the CCCH block rate and
+ * PCH messages also reduce the drain of the AGCH queue.
+ */
+ agch_res_full = compact_agch_queue(bts);
- if (!is_ag_res) {
+ /* Check for paging messages first if this is PCH */
+ if (!is_ag_res)
rc = paging_gen_msg(btsb->paging_state, out_buf, gt, &is_empty);
- if (!is_empty || !agch_res_full)
- return rc;
- }
+ /* Check whether the block may be overwritten */
+ if (!is_empty || (!is_ag_res && !agch_res_full))
+ return rc;
- /* special queue of messages from IMM ASS CMD */
msg = bts_agch_dequeue(bts);
if (!msg)
- return 0;
+ return rc;
+ /* Copy AGCH message */
memcpy(out_buf, msgb_l3(msg), msgb_l3len(msg));
rc = msgb_l3len(msg);
LOGP(DSUM, LOGL_DEBUG,
diff --git a/tests/agch/agch_test.c b/tests/agch/agch_test.c
index 7e87bcf..a3868c1 100644
--- a/tests/agch/agch_test.c
+++ b/tests/agch/agch_test.c
@@ -129,6 +129,10 @@ static void test_agch_queue(void)
printf("Testing AGCH messages queue handling.\n");
btsb->agch_max_queue_length = 32;
+ btsb->agch_queue_low_level = 30;
+ btsb->agch_queue_high_level = 30;
+ btsb->agch_queue_thresh_level = 60;
+
for (round = 1; round <= num_rounds; round++) {
for (idx = 0; idx < num_ima_per_round; idx++) {
msg = msgb_alloc(GSM_MACBLOCK_LEN, __FUNCTION__);
diff --git a/tests/agch/agch_test.ok b/tests/agch/agch_test.ok
index 70a9797..57439ed 100644
--- a/tests/agch/agch_test.ok
+++ b/tests/agch/agch_test.ok
@@ -1,4 +1,4 @@
Testing AGCH messages queue handling.
AGCH filled: count 720, imm.ass 80, imm.ass.rej 640 (refs 640), queue limit 32, occupied
720, dropped 0, merged 0, rejected 0, ag-res 0, non-res 0
-AGCH drained: multiframes 241, imm.ass 80, imm.ass.rej 641 (refs 641), queue limit 32,
occupied 0, dropped 0, merged 0, rejected 0, ag-res 240, non-res 480
+AGCH drained: multiframes 33, imm.ass 80, imm.ass.rej 17 (refs 17), queue limit 32,
occupied 0, dropped 624, merged 0, rejected 0, ag-res 32, non-res 64
Success
--
1.7.9.5