| /* 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) */ |
| } |
| } |