LAPDm_RAW_PT: Add support for TBF mode (GPRS)
diff --git a/library/LAPDm_RAW_PT.ttcn b/library/LAPDm_RAW_PT.ttcn
index 06225bb..06f5590 100644
--- a/library/LAPDm_RAW_PT.ttcn
+++ b/library/LAPDm_RAW_PT.ttcn
@@ -8,6 +8,7 @@
 	import from L1CTL_Types all;
 	import from L1CTL_PortType all;
 	import from LAPDm_Types all;
+	import from RLCMAC_Types all;
 
 	/* request to tune to a given ARFCN and start BCCH decoding */
 	type record BCCH_tune_req {
@@ -25,6 +26,10 @@
 		charstring err optional
 	}
 
+	type record TBF_establish_res {
+		charstring err optional
+	}
+
 	type record DCCH_release_req {
 	}
 
@@ -35,23 +40,46 @@
 		LapdmFrame lapdm
 	}
 
+	type record TBF_establish_req {
+		uint8_t ra
+	}
+
+	/* PH-DATA.ind / PH-DATA.req */
+	type record RLCMAC_ph_data_ind {
+		GprsCodingScheme cs,
+		RlcmacDlBlock block
+	}
+	type record RLCMAC_ph_data_req {
+		uint8_t tbf_id,
+		GprsCodingScheme cs,
+		RlcmacUlBlock block
+	}
+
 	/* port from our (internal) point of view */
 	type port LAPDm_SP_PT message {
 		in	BCCH_tune_req,
 			DCCH_establish_req,
 			DCCH_release_req,
+			TBF_establish_req,
+			RLCMAC_ph_data_req,
 			LAPDm_ph_data;
 		out	DCCH_establish_res,
+			TBF_establish_res,
+			RLCMAC_ph_data_ind,
 			LAPDm_ph_data;
 	} with {extension "internal"};
 
 	/* port from user (external) point of view */
 	type port LAPDm_PT message {
 		in	DCCH_establish_res,
+			TBF_establish_res,
+			RLCMAC_ph_data_ind,
 			LAPDm_ph_data;
 		out	BCCH_tune_req,
 			DCCH_establish_req,
 			DCCH_release_req,
+			TBF_establish_req,
+			RLCMAC_ph_data_req,
 			LAPDm_ph_data;
 	} with {extension "internal"};
 
@@ -66,7 +94,8 @@
 		PH_STATE_BCH,
 		PH_STATE_SEARCHING_BCH,
 		PH_STATE_TUNING_DCH,
-		PH_STATE_DCH
+		PH_STATE_DCH,
+		PH_STATE_TBF
 	}
 
 	type component lapdm_CT {
@@ -98,7 +127,7 @@
 	/* release the dedicated radio channel */
 	private function f_release_dcch() runs on lapdm_CT {
 		L1CTL.send(t_L1CTL_DM_REL_REQ(chan_desc.chan_nr));
-		set_ph_state(PH_STATE_NULL);
+		set_ph_state(PH_STATE_BCH);
 	}
 
 	/* tune to given ARFCN and start BCCH/CCCH decoding */
@@ -111,6 +140,8 @@
 		if (ph_state == PH_STATE_DCH) {
 			/* release any previous DCH */
 			f_release_dcch();
+		} else if (ph_state == PH_STATE_TBF) {
+			f_release_tbf();
 		}
 
 		set_ph_state(PH_STATE_SEARCHING_BCH);
@@ -142,12 +173,102 @@
 		set_ph_state(PH_STATE_DCH);
 	}
 
+	/* initialize a tfi_usf array with "not used" value 255 for all TN */
+	function f_TfiUsfArrInit() return TfiUsfArr {
+		var TfiUsfArr tua := { 255, 255, 255, 255, 255, 255, 255, 255 };
+		return tua;
+	}
+
+	/* set TFI/USF value for one given timeslot number (index) */
+	function f_TfiUsfArrSet(inout TfiUsfArr a, in uint8_t idx, in uint8_t tfi_usf) {
+		a[idx] := tfi_usf;
+	}
+
+	/* Match an IMM.ASS for an Uplink TBF with a dynamic allocation */
+	template ImmediateAssignment t_IMM_ASS_TBF_UL_DYN(uint8_t ra, GsmFrameNumber fn) modifies t_IMM_ASS := {
+		ded_or_tbf := { spare := ?, tma := ?, downlink := false, tbf := true},
+		chan_desc := omit,
+		pkt_chan_desc := ?,
+		rest_octets := {
+			presence := '11'B,
+			ll := omit,
+			lh := omit,
+			hl := omit,
+			hh := {
+				presence := '00'B,
+				ul := {
+					presence := '1'B,
+					dynamic := {
+						tfi_assignment := ?,
+						polling := ?,
+						spare := '0'B,
+						usf := ?,
+						usf_granularity := ?,
+						p0_present := ?,
+						p0 := *,
+						pr_mode := *,
+						ch_coding_cmd := ?,
+						tlli_block_chan_coding:= ?,
+						alpha_present := ?,
+						alpha := *,
+						gamma := ?,
+						ta_index_present := ?,
+						ta_index := *,
+						tbf_starting_time_present := ?,
+						tbf_starting_time := *
+					},
+					single := omit
+				},
+				dl := omit
+			}
+		}
+	};
+
+	private function f_establish_tbf(uint8_t ra) runs on lapdm_CT {
+		var ImmediateAssignment imm_ass;
+		var GsmFrameNumber rach_fn;
+		var TfiUsfArr tua := f_TfiUsfArrInit();
+
+		/* send RACH request and obtain FN at which it was sent */
+		rach_fn := f_L1CTL_RACH(L1CTL, ra);
+
+		/* wait for receiving matching IMM ASS */
+		imm_ass := f_L1CTL_WAIT_IMM_ASS(L1CTL, ra, rach_fn);
+
+		if (match(imm_ass, t_IMM_ASS_TBF_UL_DYN(ra, rach_fn))) {
+			set_ph_state(PH_STATE_TBF);
+
+			/* store/save channel description */
+			//chan_desc := imm_ass.chan_desc;
+
+			/* Important: ARFCN, TN, TSC, USF, USF_GRANULARITY, CH_CODING_CMD */
+			f_TfiUsfArrSet(tua, imm_ass.pkt_chan_desc.tn, imm_ass.rest_octets.hh.ul.dynamic.usf);
+			f_L1CTL_TBF_CFG(L1CTL, true, tua);
+		} else {
+			/* FIXME: single block uplink allocation */
+			log("Failed to match ", t_IMM_ASS_TBF_UL_DYN(ra, rach_fn));
+			log("Non-dynamic UL TBF assignment not supported yet");
+		}
+	}
+
+	private function f_release_tbf() runs on lapdm_CT {
+		var TfiUsfArr tua := f_TfiUsfArrInit();
+		/* send "all timeslots unused" for both UL and DL */
+		f_L1CTL_TBF_CFG(L1CTL, true, tua);
+		f_L1CTL_TBF_CFG(L1CTL, false, tua);
+		/* L1 will then fall back to BCCH/CCCH */
+		set_ph_state(PH_STATE_BCH);
+	}
+
 	function ScanEvents() runs on lapdm_CT {
 		var L1ctlDlMessage dl;
 		var BCCH_tune_req bt;
 		var LAPDm_ph_data lpd;
+		var RLCMAC_ph_data_ind rpdi;
+		var RLCMAC_ph_data_req rpdr;
 		var DCCH_establish_req est_req;
 		var DCCH_establish_res est_res;
+		var TBF_establish_req tbf_req;
 
 		while (true) {
 		if (ph_state == PH_STATE_NULL) {
@@ -194,6 +315,18 @@
 				LAPDM_SP.send(res);
 			}
 
+			/* Establish TBF / packet transfer mode */
+			[] LAPDM_SP.receive(TBF_establish_req:?) -> value tbf_req {
+				var TBF_establish_res res;
+				f_establish_tbf(tbf_req.ra);
+				if (ph_state == PH_STATE_TBF) {
+					res := { err := omit };
+				} else {
+					res := { err := "Unable to establish TBF" };
+				}
+				LAPDM_SP.send(res);
+			}
+
 			[] LAPDM_SP.receive {}
 			[] L1CTL.receive {}
 
@@ -239,7 +372,36 @@
 
 
 			}
+		} else if (ph_state == PH_STATE_TBF) {
+			alt {
+
+			/* decode + forward any blocks from L1 to L23*/
+			[] L1CTL.receive(t_L1CTL_DATA_IND(t_RslChanNr_PDCH(?))) -> value dl {
+				rpdi.block := dec_RlcmacDlBlock(dl.payload.data_ind.payload);
+				rpdi.cs := CS1; /* FIXME */
+				log("RPDI: ", rpdi);
+				LAPDM_SP.send(rpdi);
+			}
+
+			[] L1CTL.receive { }
+
+			/* encode + forward any blocks from L23 to L1 */
+			[] LAPDM_SP.receive(RLCMAC_ph_data_req:?) -> value rpdr {
+				var octetstring buf;
+
+				buf := enc_RlcmacUlBlock(rpdr.block);
+				L1CTL.send(t_L1CTL_DATA_TBF_REQ(buf, L1CTL_CS1, rpdr.tbf_id));
+			}
+
+			/* FIXME: release TBF mode */
+			[] LAPDM_SP.receive(DCCH_release_req:?) {
+				/* go back to BCCH */
+				f_release_tbf();
+			}
+
+			}
 		}
+
 		} /* while (1) */
 	}
 }