Send downlink IMMEDIATE ASSIGNMENT on PCH and not on AGCH

The IMSI is used to define paging group on which it is sent.

This is tested with MS that requires correct paging group.
diff --git a/src/gprs_bssgp_pcu.cpp b/src/gprs_bssgp_pcu.cpp
index 74b24bb..733d4e2 100644
--- a/src/gprs_bssgp_pcu.cpp
+++ b/src/gprs_bssgp_pcu.cpp
@@ -32,7 +32,7 @@
 	struct bssgp_ud_hdr *budh;
 	int tfi;
 	uint32_t tlli;
-	int i = 0;
+	int i, j;
 	uint8_t trx, ts;
 	uint8_t *data;
 	uint16_t len;
@@ -55,22 +55,30 @@
 		LOGP(DBSSGP, LOGL_NOTICE, "BSSGP TLLI=0x%08x Rx UL-UD IE_LLC_PDU too large\n", tlli);
 		return bssgp_tx_status(BSSGP_CAUSE_COND_IE_ERR, NULL, msg);
 	}
-	LOGP(DBSSGP, LOGL_INFO, "LLC [SGSN -> PCU] = TLLI: 0x%08x %s\n", tlli, osmo_hexdump(data, len));
 
-	uint16_t imsi_len = 0;
-	uint8_t *imsi;
+	/* read IMSI. if no IMSI exists, use first paging block (any paging),
+	 * because during attachment the IMSI might not be known, so the MS
+	 * will listen to all paging blocks. */
+	char imsi[16] = "000";
 	if (TLVP_PRESENT(tp, BSSGP_IE_IMSI))
 	{
-		imsi_len = TLVP_LEN(tp, BSSGP_IE_IMSI);
-		imsi = (uint8_t *) TLVP_VAL(tp, BSSGP_IE_IMSI);
-		
-		LOGPC(DBSSGP, LOGL_DEBUG, " IMSI = ");
-		for (i = 0; i < imsi_len; i++)
+		uint8_t imsi_len = TLVP_LEN(tp, BSSGP_IE_IMSI);
+		uint8_t *bcd_imsi = (uint8_t *) TLVP_VAL(tp, BSSGP_IE_IMSI);
+		if ((bcd_imsi[0] & 0x08))
+			imsi_len = imsi_len * 2 - 1;
+		else
+			imsi_len = (imsi_len - 1) * 2;
+		for (i = 0, j = 0; j < imsi_len && j < 16; j++)
 		{
-			LOGPC(DBSSGP, LOGL_DEBUG, "%02x", imsi[i]);
+			if (!(j & 1)) {
+				imsi[j] = (bcd_imsi[i] >> 4) + '0';
+				i++;
+			} else
+				imsi[j] = (bcd_imsi[i] & 0xf) + '0';
 		}
-		LOGPC(DBSSGP, LOGL_DEBUG, "\n");
+		imsi[j] = '\0';
 	}
+	LOGP(DBSSGP, LOGL_INFO, "LLC [SGSN -> PCU] = TLLI: 0x%08x IMSI: %s len: %d\n", tlli, imsi, len);
 
 	/* check for existing TBF */
 	if ((tbf = tbf_by_tlli(tlli, GPRS_RLCMAC_DL_TBF))) {
@@ -82,7 +90,7 @@
 			tbf->llc_length = len;
 			memset(&tbf->dir.dl, 0, sizeof(tbf->dir.dl)); /* reset
 								rlc states */
-			gprs_rlcmac_trigger_downlink_assignment(tbf, 1);
+			gprs_rlcmac_trigger_downlink_assignment(tbf, 1, NULL);
 		} else {
 			/* the TBF exists, so we must write it in the queue */
 			struct msgb *llc_msg = msgb_alloc(len, "llc_pdu_queue");
@@ -114,7 +122,7 @@
 		 * 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);
+		gprs_rlcmac_trigger_downlink_assignment(tbf, 0, imsi);
 	}
 
 	return 0;
diff --git a/src/gprs_rlcmac.h b/src/gprs_rlcmac.h
index c47d6d4..abb9980 100644
--- a/src/gprs_rlcmac.h
+++ b/src/gprs_rlcmac.h
@@ -252,7 +252,7 @@
         struct gprs_rlcmac_tbf *tbf, uint32_t fn);
 
 void gprs_rlcmac_trigger_downlink_assignment(gprs_rlcmac_tbf *tbf,
-	uint8_t old_downlink);
+	uint8_t old_downlink, char *imsi);
 
 int gprs_rlcmac_downlink_ack(struct gprs_rlcmac_tbf *tbf, uint8_t final,
         uint8_t ssn, uint8_t *rbb);
diff --git a/src/gprs_rlcmac_data.cpp b/src/gprs_rlcmac_data.cpp
index ea813c6..a87fef1 100644
--- a/src/gprs_rlcmac_data.cpp
+++ b/src/gprs_rlcmac_data.cpp
@@ -263,6 +263,9 @@
 	return 1;
 }
 
+#ifdef DEBUG_DL_ASS_IDLE
+	char debug_imsi[16];
+#endif
 
 void tbf_timer_cb(void *_tbf)
 {
@@ -276,7 +279,7 @@
 	switch (tbf->T) {
 #ifdef DEBUG_DL_ASS_IDLE
 	case 1234:
-		gprs_rlcmac_trigger_downlink_assignment(tbf, 0);
+		gprs_rlcmac_trigger_downlink_assignment(tbf, 0, debug_imsi);
 		break;
 #endif
 	case 0: /* assignment */
@@ -1268,7 +1271,7 @@
 	LOGP(DRLCMAC, LOGL_DEBUG, "Trigger dowlink assignment on PACCH, "
 		"because another LLC PDU has arrived in between\n");
 	memset(&tbf->dir.dl, 0, sizeof(tbf->dir.dl)); /* reset RLC states */
-	gprs_rlcmac_trigger_downlink_assignment(tbf, 1);
+	gprs_rlcmac_trigger_downlink_assignment(tbf, 1, NULL);
 
 	return 0;
 }
@@ -1334,25 +1337,27 @@
 	return msg;
 }
 
-static void gprs_rlcmac_downlink_assignment(gprs_rlcmac_tbf *tbf, uint8_t poll)
+static void gprs_rlcmac_downlink_assignment(gprs_rlcmac_tbf *tbf, uint8_t poll,
+	char *imsi)
 {
-	LOGP(DRLCMAC, LOGL_INFO, "TX: START TFI: %u TLLI: 0x%08x Immediate Assignment Downlink (AGCH)\n", tbf->tfi, tbf->tlli);
+	LOGP(DRLCMAC, LOGL_INFO, "TX: START TFI: %u TLLI: 0x%08x Immediate Assignment Downlink (PCH)\n", tbf->tfi, tbf->tlli);
 	bitvec *immediate_assignment = bitvec_alloc(22); /* without plen */
 	bitvec_unhex(immediate_assignment, "2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b");
 	/* use request reference that has maximum distance to current time,
 	 * so the assignment will not conflict with possible RACH requests. */
 	int plen = write_immediate_assignment(immediate_assignment, 1, 125, (tbf->pdch->last_rts_fn + 21216) % 2715648, tbf->ta, tbf->arfcn, tbf->ts, tbf->tsc, tbf->tfi, 0, tbf->tlli, poll, tbf->poll_fn);
-	pcu_l1if_tx_agch(immediate_assignment, plen);
+	pcu_l1if_tx_pch(immediate_assignment, plen, imsi);
 	bitvec_free(immediate_assignment);
 }
 
 /* depending on the current TBF, we assign on PACCH or AGCH */
 void gprs_rlcmac_trigger_downlink_assignment(gprs_rlcmac_tbf *tbf,
-	uint8_t old_downlink)
+	uint8_t old_downlink, char *imsi)
 {
 	gprs_rlcmac_tbf *old_tbf;
 
 #ifdef DEBUG_DL_ASS_IDLE
+	strncpy(debug_imsi, imsi);
 	LOGP(DRLCMAC, LOGL_ERROR, "**** DEBUGGING DOWNLINK ASSIGNMENT ****\n");
 #endif
 
@@ -1384,9 +1389,13 @@
 		tbf_timer_start(tbf, 0, Tassign_pacch);
 #endif
 	} else {
-		LOGP(DRLCMAC, LOGL_DEBUG, "Send dowlink assignment for TBF=%d on AGCH, no TBF exist\n", tbf->tfi);
+		LOGP(DRLCMAC, LOGL_DEBUG, "Send dowlink assignment for TBF=%d on PCH, no TBF exist (IMSI=%s)\n", tbf->tfi, imsi);
+		if (!imsi || strlen(imsi) < 3) {
+			LOGP(DRLCMAC, LOGL_ERROR, "No valid IMSI!\n");
+			return;
+		}
 		/* send immediate assignment */
-		gprs_rlcmac_downlink_assignment(tbf, 0);
+		gprs_rlcmac_downlink_assignment(tbf, 0, imsi);
 		/* change state */
 		tbf_new_state(tbf, GPRS_RLCMAC_ASSIGN);
 		/* start timer */
diff --git a/src/pcu_l1_if.cpp b/src/pcu_l1_if.cpp
index f6ba775..865a911 100644
--- a/src/pcu_l1_if.cpp
+++ b/src/pcu_l1_if.cpp
@@ -120,7 +120,18 @@
 	prim->id = GsmL1_PrimId_PhDataReq;
 	prim->u.phDataReq.sapi = GsmL1_Sapi_Agch;
 	bitvec_pack(block, prim->u.phDataReq.msgUnitParam.u8Buffer);
-#warning Please review, if OpenBTS requires AGCH frame without pseudo length:
+	prim->u.phDataReq.msgUnitParam.u8Size = 22;
+	osmo_wqueue_enqueue(&l1fh->udp_wq, msg);
+}
+
+void pcu_l1if_tx_pch(bitvec * block, int plen, char *imsi)
+{
+	struct msgb *msg = l1p_msgb_alloc();
+	GsmL1_Prim_t *prim = msgb_l1prim(msg);
+	
+	prim->id = GsmL1_PrimId_PhDataReq;
+	prim->u.phDataReq.sapi = GsmL1_Sapi_Pch;
+	bitvec_pack(block, prim->u.phDataReq.msgUnitParam.u8Buffer);
 	prim->u.phDataReq.msgUnitParam.u8Size = 22;
 	osmo_wqueue_enqueue(&l1fh->udp_wq, msg);
 }
diff --git a/src/pcu_l1_if.h b/src/pcu_l1_if.h
index b8cc5c9..2aac553 100644
--- a/src/pcu_l1_if.h
+++ b/src/pcu_l1_if.h
@@ -36,6 +36,7 @@
 void pcu_l1if_tx_ptcch(msgb *msg, uint8_t trx, uint8_t ts, uint16_t arfcn, 
         uint32_t fn, uint8_t block_nr);
 void pcu_l1if_tx_agch(bitvec * block, int len);
+void pcu_l1if_tx_pch(bitvec * block, int plen, char *imsi);
 
 int pcu_l1if_open(void);
 void pcu_l1if_close(void);
diff --git a/src/sysmo_l1_if.cpp b/src/sysmo_l1_if.cpp
index 6cce554..ceb28d2 100644
--- a/src/sysmo_l1_if.cpp
+++ b/src/sysmo_l1_if.cpp
@@ -150,6 +150,23 @@
 	pcu_tx_data_req(0, 0, PCU_IF_SAPI_AGCH, 0, 0, 0, data, 23);
 }
 
+void pcu_l1if_tx_pch(bitvec * block, int plen, char *imsi)
+{
+	uint8_t data[23+3]; /* prefix PLEN */
+
+	/* paging group */
+	if (!imsi || strlen(imsi) < 3)
+		return;
+	imsi += strlen(imsi) - 3;
+	data[0] = imsi[0];
+	data[1] = imsi[1];
+	data[2] = imsi[2];
+
+	bitvec_pack(block, data + 3+1);
+	data[3] = (plen << 2) | 0x01;
+	pcu_tx_data_req(0, 0, PCU_IF_SAPI_PCH, 0, 0, 0, data, 23+3);
+}
+
 static void pcu_l1if_tx_bcch(uint8_t *data, int len)
 {
 	pcu_tx_data_req(0, 0, PCU_IF_SAPI_BCCH, 0, 0, 0, data, len);