/* Test Port that stacks on top of L1CTL test port and performs LAPDm encoding/decoding, so the user can send * and receive LAPDm frames in decoded TTCN-3 data types. This is particularly useful for sending/receiving * all kinds of hand-crafted LAPDm frames for testing of the remote LAPDm layer */ module LAPDm_RAW_PT { import from GSM_Types all; import from GSM_RR_Types all; import from Osmocom_Types all; 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 { Arfcn arfcn, boolean combined_ccch } /* ask for a dedicated channel to be established */ type record DCCH_establish_req { uint8_t ra } type record DCCH_establish_res { ChannelDescription chan_desc optional, charstring err optional } type record TBF_establish_res { charstring err optional } type record DCCH_release_req { } /* PH-DATA.ind / PH-DATA.req */ type record LAPDm_ph_data { boolean sacch, GsmSapi sapi, 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"}; function LAPDmStart() runs on lapdm_CT { f_init(); ScanEvents(); } /* TS 44.004 Figure 5.1 */ type enumerated ph_state_enum { PH_STATE_NULL, PH_STATE_BCH, PH_STATE_SEARCHING_BCH, PH_STATE_TUNING_DCH, PH_STATE_DCH, PH_STATE_TBF } type component lapdm_CT { /* L1CTL port towards the bottom */ port L1CTL_PT L1CTL; /* Port towards L2 */ port LAPDm_SP_PT LAPDM_SP; /* physical layer state */ var ph_state_enum ph_state := PH_STATE_NULL; /* channel description of the currently active DCH */ var ChannelDescription chan_desc; }; /* wrapper function to log state transitions */ private function set_ph_state(ph_state_enum new_state) runs on lapdm_CT { log("PH-STATE ", ph_state, " -> ", new_state); ph_state := new_state; } private function f_init() runs on lapdm_CT { f_connect_reset(L1CTL); set_ph_state(PH_STATE_NULL); } /* 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_BCH); } /* tune to given ARFCN and start BCCH/CCCH decoding */ private function f_tune_bcch(Arfcn arfcn, boolean combined) runs on lapdm_CT { var L1ctlCcchMode mode := CCCH_MODE_NON_COMBINED; if (combined) { mode := CCCH_MODE_COMBINED; } 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); /* send FB/SB req to sync to cell */ f_L1CTL_FBSB(L1CTL, arfcn, mode); set_ph_state(PH_STATE_BCH); } /* master function establishing a dedicated radio channel */ private function f_establish_dcch(uint8_t ra) runs on lapdm_CT { var ImmediateAssignment imm_ass; var GsmFrameNumber rach_fn; /* send RACH request and obtain FN at which it was sent */ rach_fn := f_L1CTL_RACH(L1CTL, ra); //if (not rach_fn) { return; } /* wait for receiving matching IMM ASS */ imm_ass := f_L1CTL_WAIT_IMM_ASS(L1CTL, ra, rach_fn) //if (not imm_ass) { return; } set_ph_state(PH_STATE_TUNING_DCH); /* store/save channel description */ chan_desc := imm_ass.chan_desc; /* send DM_EST_REQ */ f_L1CTL_DM_EST_REQ_IA(L1CTL, imm_ass); 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) { alt { [] LAPDM_SP.receive(BCCH_tune_req:?) -> value bt { f_tune_bcch(bt.arfcn, bt.combined_ccch); } [] LAPDM_SP.receive {} [] L1CTL.receive {} } } else if (ph_state == PH_STATE_BCH or ph_state == PH_STATE_SEARCHING_BCH) { alt { [] LAPDM_SP.receive(BCCH_tune_req:?) -> value bt { f_tune_bcch(bt.arfcn, bt.combined_ccch); } /* forward CCCH SAPI from L1CTL to User */ [] L1CTL.receive(t_L1CTL_DATA_IND(t_RslChanNr_BCCH(0))) -> value dl { lpd.sacch := false; lpd.sapi := 0; lpd.lapdm.bbis := dec_LapdmFrameBbis(dl.payload.data_ind.payload); LAPDM_SP.send(lpd); } /* forward BCCH SAPI from L1CTL to User */ [] L1CTL.receive(t_L1CTL_DATA_IND(t_RslChanNr_PCH_AGCH(0))) -> value dl { lpd.sacch := false; lpd.sapi := 0; lpd.lapdm.bbis := dec_LapdmFrameBbis(dl.payload.data_ind.payload); LAPDM_SP.send(lpd); } /* Establish dedicated channel */ [] LAPDM_SP.receive(DCCH_establish_req:?) -> value est_req { var DCCH_establish_res res; f_establish_dcch(est_req.ra); if (ph_state == PH_STATE_DCH) { res := { chan_desc, omit }; } else { res := { omit, "Unable to esetablish DCCH" }; } 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 {} } } else if (ph_state == PH_STATE_TUNING_DCH or ph_state == PH_STATE_DCH) { alt { /* decode any received DATA frames for the dedicated channel and pass them up */ [] L1CTL.receive(t_L1CTL_DATA_IND(chan_desc.chan_nr)) -> value dl { if (dl.dl_info.link_id.c == SACCH) { lpd.sacch := true; /* FIXME: how to deal with UI frames in B4 format (lo length!) */ } else { lpd.sacch := false; } lpd.sapi := dl.dl_info.link_id.sapi; lpd.lapdm.b := dec_LapdmFrameB(dl.payload.data_ind.payload); LAPDM_SP.send(lpd); } /* encode any LAPDm record from user and pass it on to L1CTL */ [] LAPDM_SP.receive(LAPDm_ph_data:?) -> value lpd { var octetstring buf; var RslLinkId link_id; if (lpd.sacch) { link_id := valueof(ts_RslLinkID_SACCH(lpd.sapi)); } else { link_id := valueof(ts_RslLinkID_DCCH(lpd.sapi)); } buf := enc_LapdmFrame(lpd.lapdm); L1CTL.send(t_L1CTL_DATA_REQ(chan_desc.chan_nr, link_id, buf)); } /* Release dedicated channel */ [] LAPDM_SP.receive(DCCH_release_req:?) { /* go back to BCCH */ f_release_dcch(); } [] LAPDM_SP.receive {} [] L1CTL.receive {} } } 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) */ } }