Harald Welte | d4ba7ff | 2017-07-17 21:00:48 +0200 | [diff] [blame] | 1 | /* Test Port that stacks on top of L1CTL test port and performs LAPDm encoding/decoding, so the user can send |
| 2 | * and receive LAPDm frames in decoded TTCN-3 data types. This is particularly useful for sending/receiving |
| 3 | * all kinds of hand-crafted LAPDm frames for testing of the remote LAPDm layer */ |
| 4 | module LAPDm_RAW_PT { |
| 5 | import from GSM_Types all; |
Harald Welte | ffcad68 | 2017-07-30 22:51:04 +0200 | [diff] [blame] | 6 | import from GSM_RR_Types all; |
Harald Welte | d4ba7ff | 2017-07-17 21:00:48 +0200 | [diff] [blame] | 7 | import from Osmocom_Types all; |
| 8 | import from L1CTL_Types all; |
| 9 | import from L1CTL_PortType all; |
| 10 | import from LAPDm_Types all; |
Harald Welte | 4b6c772 | 2017-08-01 00:07:12 +0200 | [diff] [blame] | 11 | import from RLCMAC_Types all; |
Harald Welte | d4ba7ff | 2017-07-17 21:00:48 +0200 | [diff] [blame] | 12 | |
| 13 | /* request to tune to a given ARFCN and start BCCH decoding */ |
| 14 | type record BCCH_tune_req { |
| 15 | Arfcn arfcn, |
| 16 | boolean combined_ccch |
| 17 | } |
| 18 | |
| 19 | /* ask for a dedicated channel to be established */ |
| 20 | type record DCCH_establish_req { |
| 21 | uint8_t ra |
| 22 | } |
| 23 | |
| 24 | type record DCCH_establish_res { |
| 25 | ChannelDescription chan_desc optional, |
| 26 | charstring err optional |
| 27 | } |
| 28 | |
Harald Welte | 4b6c772 | 2017-08-01 00:07:12 +0200 | [diff] [blame] | 29 | type record TBF_establish_res { |
| 30 | charstring err optional |
| 31 | } |
| 32 | |
Harald Welte | d4ba7ff | 2017-07-17 21:00:48 +0200 | [diff] [blame] | 33 | type record DCCH_release_req { |
| 34 | } |
| 35 | |
| 36 | /* PH-DATA.ind / PH-DATA.req */ |
| 37 | type record LAPDm_ph_data { |
| 38 | boolean sacch, |
| 39 | GsmSapi sapi, |
| 40 | LapdmFrame lapdm |
| 41 | } |
| 42 | |
Harald Welte | 4b6c772 | 2017-08-01 00:07:12 +0200 | [diff] [blame] | 43 | type record TBF_establish_req { |
| 44 | uint8_t ra |
| 45 | } |
| 46 | |
| 47 | /* PH-DATA.ind / PH-DATA.req */ |
| 48 | type record RLCMAC_ph_data_ind { |
| 49 | GprsCodingScheme cs, |
| 50 | RlcmacDlBlock block |
| 51 | } |
| 52 | type record RLCMAC_ph_data_req { |
| 53 | uint8_t tbf_id, |
| 54 | GprsCodingScheme cs, |
| 55 | RlcmacUlBlock block |
| 56 | } |
| 57 | |
Harald Welte | d4ba7ff | 2017-07-17 21:00:48 +0200 | [diff] [blame] | 58 | /* port from our (internal) point of view */ |
| 59 | type port LAPDm_SP_PT message { |
| 60 | in BCCH_tune_req, |
| 61 | DCCH_establish_req, |
| 62 | DCCH_release_req, |
Harald Welte | 4b6c772 | 2017-08-01 00:07:12 +0200 | [diff] [blame] | 63 | TBF_establish_req, |
| 64 | RLCMAC_ph_data_req, |
Harald Welte | d4ba7ff | 2017-07-17 21:00:48 +0200 | [diff] [blame] | 65 | LAPDm_ph_data; |
| 66 | out DCCH_establish_res, |
Harald Welte | 4b6c772 | 2017-08-01 00:07:12 +0200 | [diff] [blame] | 67 | TBF_establish_res, |
| 68 | RLCMAC_ph_data_ind, |
Harald Welte | d4ba7ff | 2017-07-17 21:00:48 +0200 | [diff] [blame] | 69 | LAPDm_ph_data; |
| 70 | } with {extension "internal"}; |
| 71 | |
| 72 | /* port from user (external) point of view */ |
| 73 | type port LAPDm_PT message { |
| 74 | in DCCH_establish_res, |
Harald Welte | 4b6c772 | 2017-08-01 00:07:12 +0200 | [diff] [blame] | 75 | TBF_establish_res, |
| 76 | RLCMAC_ph_data_ind, |
Harald Welte | d4ba7ff | 2017-07-17 21:00:48 +0200 | [diff] [blame] | 77 | LAPDm_ph_data; |
| 78 | out BCCH_tune_req, |
| 79 | DCCH_establish_req, |
| 80 | DCCH_release_req, |
Harald Welte | 4b6c772 | 2017-08-01 00:07:12 +0200 | [diff] [blame] | 81 | TBF_establish_req, |
| 82 | RLCMAC_ph_data_req, |
Harald Welte | d4ba7ff | 2017-07-17 21:00:48 +0200 | [diff] [blame] | 83 | LAPDm_ph_data; |
| 84 | } with {extension "internal"}; |
| 85 | |
| 86 | function LAPDmStart() runs on lapdm_CT { |
| 87 | f_init(); |
| 88 | ScanEvents(); |
| 89 | } |
| 90 | |
| 91 | /* TS 44.004 Figure 5.1 */ |
| 92 | type enumerated ph_state_enum { |
| 93 | PH_STATE_NULL, |
| 94 | PH_STATE_BCH, |
| 95 | PH_STATE_SEARCHING_BCH, |
| 96 | PH_STATE_TUNING_DCH, |
Harald Welte | 4b6c772 | 2017-08-01 00:07:12 +0200 | [diff] [blame] | 97 | PH_STATE_DCH, |
| 98 | PH_STATE_TBF |
Harald Welte | d4ba7ff | 2017-07-17 21:00:48 +0200 | [diff] [blame] | 99 | } |
| 100 | |
| 101 | type component lapdm_CT { |
Harald Welte | d4ba7ff | 2017-07-17 21:00:48 +0200 | [diff] [blame] | 102 | |
| 103 | /* L1CTL port towards the bottom */ |
| 104 | port L1CTL_PT L1CTL; |
| 105 | /* Port towards L2 */ |
| 106 | port LAPDm_SP_PT LAPDM_SP; |
| 107 | |
| 108 | /* physical layer state */ |
| 109 | var ph_state_enum ph_state := PH_STATE_NULL; |
| 110 | |
| 111 | /* channel description of the currently active DCH */ |
| 112 | var ChannelDescription chan_desc; |
| 113 | }; |
| 114 | |
| 115 | /* wrapper function to log state transitions */ |
| 116 | private function set_ph_state(ph_state_enum new_state) runs on lapdm_CT { |
| 117 | log("PH-STATE ", ph_state, " -> ", new_state); |
| 118 | ph_state := new_state; |
| 119 | } |
| 120 | |
| 121 | private function f_init() runs on lapdm_CT { |
Harald Welte | b26cfff | 2017-08-25 09:56:47 +0200 | [diff] [blame] | 122 | f_connect_reset(L1CTL); |
Harald Welte | d4ba7ff | 2017-07-17 21:00:48 +0200 | [diff] [blame] | 123 | set_ph_state(PH_STATE_NULL); |
| 124 | } |
| 125 | |
| 126 | /* release the dedicated radio channel */ |
| 127 | private function f_release_dcch() runs on lapdm_CT { |
| 128 | L1CTL.send(t_L1CTL_DM_REL_REQ(chan_desc.chan_nr)); |
Harald Welte | 4b6c772 | 2017-08-01 00:07:12 +0200 | [diff] [blame] | 129 | set_ph_state(PH_STATE_BCH); |
Harald Welte | d4ba7ff | 2017-07-17 21:00:48 +0200 | [diff] [blame] | 130 | } |
| 131 | |
| 132 | /* tune to given ARFCN and start BCCH/CCCH decoding */ |
| 133 | private function f_tune_bcch(Arfcn arfcn, boolean combined) runs on lapdm_CT { |
| 134 | var L1ctlCcchMode mode := CCCH_MODE_NON_COMBINED; |
| 135 | if (combined) { |
| 136 | mode := CCCH_MODE_COMBINED; |
| 137 | } |
| 138 | |
| 139 | if (ph_state == PH_STATE_DCH) { |
| 140 | /* release any previous DCH */ |
| 141 | f_release_dcch(); |
Harald Welte | 4b6c772 | 2017-08-01 00:07:12 +0200 | [diff] [blame] | 142 | } else if (ph_state == PH_STATE_TBF) { |
| 143 | f_release_tbf(); |
Harald Welte | d4ba7ff | 2017-07-17 21:00:48 +0200 | [diff] [blame] | 144 | } |
| 145 | |
| 146 | set_ph_state(PH_STATE_SEARCHING_BCH); |
| 147 | |
| 148 | /* send FB/SB req to sync to cell */ |
| 149 | f_L1CTL_FBSB(L1CTL, arfcn, mode); |
| 150 | set_ph_state(PH_STATE_BCH); |
| 151 | } |
| 152 | |
| 153 | /* master function establishing a dedicated radio channel */ |
| 154 | private function f_establish_dcch(uint8_t ra) runs on lapdm_CT { |
| 155 | var ImmediateAssignment imm_ass; |
| 156 | var GsmFrameNumber rach_fn; |
| 157 | |
| 158 | /* send RACH request and obtain FN at which it was sent */ |
| 159 | rach_fn := f_L1CTL_RACH(L1CTL, ra); |
| 160 | //if (not rach_fn) { return; } |
| 161 | |
| 162 | /* wait for receiving matching IMM ASS */ |
| 163 | imm_ass := f_L1CTL_WAIT_IMM_ASS(L1CTL, ra, rach_fn) |
| 164 | //if (not imm_ass) { return; } |
| 165 | set_ph_state(PH_STATE_TUNING_DCH); |
| 166 | |
| 167 | /* store/save channel description */ |
| 168 | chan_desc := imm_ass.chan_desc; |
| 169 | |
| 170 | /* send DM_EST_REQ */ |
| 171 | f_L1CTL_DM_EST_REQ_IA(L1CTL, imm_ass); |
| 172 | set_ph_state(PH_STATE_DCH); |
| 173 | } |
| 174 | |
Harald Welte | 4b6c772 | 2017-08-01 00:07:12 +0200 | [diff] [blame] | 175 | /* initialize a tfi_usf array with "not used" value 255 for all TN */ |
| 176 | function f_TfiUsfArrInit() return TfiUsfArr { |
| 177 | var TfiUsfArr tua := { 255, 255, 255, 255, 255, 255, 255, 255 }; |
| 178 | return tua; |
| 179 | } |
| 180 | |
| 181 | /* set TFI/USF value for one given timeslot number (index) */ |
| 182 | function f_TfiUsfArrSet(inout TfiUsfArr a, in uint8_t idx, in uint8_t tfi_usf) { |
| 183 | a[idx] := tfi_usf; |
| 184 | } |
| 185 | |
| 186 | /* Match an IMM.ASS for an Uplink TBF with a dynamic allocation */ |
| 187 | template ImmediateAssignment t_IMM_ASS_TBF_UL_DYN(uint8_t ra, GsmFrameNumber fn) modifies t_IMM_ASS := { |
| 188 | ded_or_tbf := { spare := ?, tma := ?, downlink := false, tbf := true}, |
| 189 | chan_desc := omit, |
| 190 | pkt_chan_desc := ?, |
| 191 | rest_octets := { |
| 192 | presence := '11'B, |
| 193 | ll := omit, |
| 194 | lh := omit, |
| 195 | hl := omit, |
| 196 | hh := { |
| 197 | presence := '00'B, |
| 198 | ul := { |
| 199 | presence := '1'B, |
| 200 | dynamic := { |
| 201 | tfi_assignment := ?, |
| 202 | polling := ?, |
| 203 | spare := '0'B, |
| 204 | usf := ?, |
| 205 | usf_granularity := ?, |
| 206 | p0_present := ?, |
| 207 | p0 := *, |
| 208 | pr_mode := *, |
| 209 | ch_coding_cmd := ?, |
| 210 | tlli_block_chan_coding:= ?, |
| 211 | alpha_present := ?, |
| 212 | alpha := *, |
| 213 | gamma := ?, |
| 214 | ta_index_present := ?, |
| 215 | ta_index := *, |
| 216 | tbf_starting_time_present := ?, |
| 217 | tbf_starting_time := * |
| 218 | }, |
| 219 | single := omit |
| 220 | }, |
| 221 | dl := omit |
| 222 | } |
| 223 | } |
| 224 | }; |
| 225 | |
| 226 | private function f_establish_tbf(uint8_t ra) runs on lapdm_CT { |
| 227 | var ImmediateAssignment imm_ass; |
| 228 | var GsmFrameNumber rach_fn; |
| 229 | var TfiUsfArr tua := f_TfiUsfArrInit(); |
| 230 | |
| 231 | /* send RACH request and obtain FN at which it was sent */ |
| 232 | rach_fn := f_L1CTL_RACH(L1CTL, ra); |
| 233 | |
| 234 | /* wait for receiving matching IMM ASS */ |
| 235 | imm_ass := f_L1CTL_WAIT_IMM_ASS(L1CTL, ra, rach_fn); |
| 236 | |
| 237 | if (match(imm_ass, t_IMM_ASS_TBF_UL_DYN(ra, rach_fn))) { |
| 238 | set_ph_state(PH_STATE_TBF); |
| 239 | |
| 240 | /* store/save channel description */ |
| 241 | //chan_desc := imm_ass.chan_desc; |
| 242 | |
| 243 | /* Important: ARFCN, TN, TSC, USF, USF_GRANULARITY, CH_CODING_CMD */ |
| 244 | f_TfiUsfArrSet(tua, imm_ass.pkt_chan_desc.tn, imm_ass.rest_octets.hh.ul.dynamic.usf); |
| 245 | f_L1CTL_TBF_CFG(L1CTL, true, tua); |
| 246 | } else { |
| 247 | /* FIXME: single block uplink allocation */ |
| 248 | log("Failed to match ", t_IMM_ASS_TBF_UL_DYN(ra, rach_fn)); |
| 249 | log("Non-dynamic UL TBF assignment not supported yet"); |
| 250 | } |
| 251 | } |
| 252 | |
| 253 | private function f_release_tbf() runs on lapdm_CT { |
| 254 | var TfiUsfArr tua := f_TfiUsfArrInit(); |
| 255 | /* send "all timeslots unused" for both UL and DL */ |
| 256 | f_L1CTL_TBF_CFG(L1CTL, true, tua); |
| 257 | f_L1CTL_TBF_CFG(L1CTL, false, tua); |
| 258 | /* L1 will then fall back to BCCH/CCCH */ |
| 259 | set_ph_state(PH_STATE_BCH); |
| 260 | } |
| 261 | |
Harald Welte | d4ba7ff | 2017-07-17 21:00:48 +0200 | [diff] [blame] | 262 | function ScanEvents() runs on lapdm_CT { |
| 263 | var L1ctlDlMessage dl; |
| 264 | var BCCH_tune_req bt; |
| 265 | var LAPDm_ph_data lpd; |
Harald Welte | 4b6c772 | 2017-08-01 00:07:12 +0200 | [diff] [blame] | 266 | var RLCMAC_ph_data_ind rpdi; |
| 267 | var RLCMAC_ph_data_req rpdr; |
Harald Welte | d4ba7ff | 2017-07-17 21:00:48 +0200 | [diff] [blame] | 268 | var DCCH_establish_req est_req; |
| 269 | var DCCH_establish_res est_res; |
Harald Welte | 4b6c772 | 2017-08-01 00:07:12 +0200 | [diff] [blame] | 270 | var TBF_establish_req tbf_req; |
Harald Welte | d4ba7ff | 2017-07-17 21:00:48 +0200 | [diff] [blame] | 271 | |
| 272 | while (true) { |
| 273 | if (ph_state == PH_STATE_NULL) { |
| 274 | alt { |
| 275 | [] LAPDM_SP.receive(BCCH_tune_req:?) -> value bt { |
| 276 | f_tune_bcch(bt.arfcn, bt.combined_ccch); |
| 277 | } |
| 278 | |
| 279 | [] LAPDM_SP.receive {} |
| 280 | [] L1CTL.receive {} |
| 281 | |
| 282 | } |
| 283 | } else if (ph_state == PH_STATE_BCH or ph_state == PH_STATE_SEARCHING_BCH) { |
| 284 | alt { |
| 285 | [] LAPDM_SP.receive(BCCH_tune_req:?) -> value bt { |
| 286 | f_tune_bcch(bt.arfcn, bt.combined_ccch); |
| 287 | } |
| 288 | |
| 289 | /* forward CCCH SAPI from L1CTL to User */ |
| 290 | [] L1CTL.receive(t_L1CTL_DATA_IND(t_RslChanNr_BCCH(0))) -> value dl { |
| 291 | lpd.sacch := false; |
| 292 | lpd.sapi := 0; |
| 293 | lpd.lapdm.bbis := dec_LapdmFrameBbis(dl.payload.data_ind.payload); |
| 294 | LAPDM_SP.send(lpd); |
| 295 | } |
| 296 | |
| 297 | /* forward BCCH SAPI from L1CTL to User */ |
| 298 | [] L1CTL.receive(t_L1CTL_DATA_IND(t_RslChanNr_PCH_AGCH(0))) -> value dl { |
| 299 | lpd.sacch := false; |
| 300 | lpd.sapi := 0; |
| 301 | lpd.lapdm.bbis := dec_LapdmFrameBbis(dl.payload.data_ind.payload); |
| 302 | LAPDM_SP.send(lpd); |
| 303 | } |
| 304 | |
| 305 | /* Establish dedicated channel */ |
| 306 | [] LAPDM_SP.receive(DCCH_establish_req:?) -> value est_req { |
| 307 | var DCCH_establish_res res; |
| 308 | f_establish_dcch(est_req.ra); |
| 309 | if (ph_state == PH_STATE_DCH) { |
| 310 | res := { chan_desc, omit }; |
| 311 | } else { |
| 312 | res := { omit, "Unable to esetablish DCCH" }; |
| 313 | } |
| 314 | LAPDM_SP.send(res); |
| 315 | } |
| 316 | |
Harald Welte | 4b6c772 | 2017-08-01 00:07:12 +0200 | [diff] [blame] | 317 | /* Establish TBF / packet transfer mode */ |
| 318 | [] LAPDM_SP.receive(TBF_establish_req:?) -> value tbf_req { |
| 319 | var TBF_establish_res res; |
| 320 | f_establish_tbf(tbf_req.ra); |
| 321 | if (ph_state == PH_STATE_TBF) { |
| 322 | res := { err := omit }; |
| 323 | } else { |
| 324 | res := { err := "Unable to establish TBF" }; |
| 325 | } |
| 326 | LAPDM_SP.send(res); |
| 327 | } |
| 328 | |
Harald Welte | d4ba7ff | 2017-07-17 21:00:48 +0200 | [diff] [blame] | 329 | [] LAPDM_SP.receive {} |
| 330 | [] L1CTL.receive {} |
| 331 | |
| 332 | } |
| 333 | |
| 334 | } else if (ph_state == PH_STATE_TUNING_DCH or ph_state == PH_STATE_DCH) { |
| 335 | alt { |
| 336 | |
| 337 | /* decode any received DATA frames for the dedicated channel and pass them up */ |
| 338 | [] L1CTL.receive(t_L1CTL_DATA_IND(chan_desc.chan_nr)) -> value dl { |
| 339 | if (dl.dl_info.link_id.c == SACCH) { |
| 340 | lpd.sacch := true; |
| 341 | /* FIXME: how to deal with UI frames in B4 format (lo length!) */ |
| 342 | } else { |
| 343 | lpd.sacch := false; |
| 344 | } |
| 345 | lpd.sapi := dl.dl_info.link_id.sapi; |
| 346 | lpd.lapdm.b := dec_LapdmFrameB(dl.payload.data_ind.payload); |
| 347 | LAPDM_SP.send(lpd); |
| 348 | } |
| 349 | |
| 350 | /* encode any LAPDm record from user and pass it on to L1CTL */ |
| 351 | [] LAPDM_SP.receive(LAPDm_ph_data:?) -> value lpd { |
| 352 | var octetstring buf; |
| 353 | var RslLinkId link_id; |
| 354 | if (lpd.sacch) { |
| 355 | link_id := valueof(ts_RslLinkID_SACCH(lpd.sapi)); |
| 356 | } else { |
| 357 | link_id := valueof(ts_RslLinkID_DCCH(lpd.sapi)); |
| 358 | } |
| 359 | buf := enc_LapdmFrame(lpd.lapdm); |
| 360 | L1CTL.send(t_L1CTL_DATA_REQ(chan_desc.chan_nr, link_id, buf)); |
| 361 | } |
| 362 | |
| 363 | /* Release dedicated channel */ |
| 364 | [] LAPDM_SP.receive(DCCH_release_req:?) { |
| 365 | /* go back to BCCH */ |
| 366 | f_release_dcch(); |
| 367 | } |
| 368 | |
| 369 | [] LAPDM_SP.receive {} |
| 370 | [] L1CTL.receive {} |
| 371 | |
| 372 | |
| 373 | } |
Harald Welte | 4b6c772 | 2017-08-01 00:07:12 +0200 | [diff] [blame] | 374 | } else if (ph_state == PH_STATE_TBF) { |
| 375 | alt { |
| 376 | |
| 377 | /* decode + forward any blocks from L1 to L23*/ |
| 378 | [] L1CTL.receive(t_L1CTL_DATA_IND(t_RslChanNr_PDCH(?))) -> value dl { |
| 379 | rpdi.block := dec_RlcmacDlBlock(dl.payload.data_ind.payload); |
| 380 | rpdi.cs := CS1; /* FIXME */ |
| 381 | log("RPDI: ", rpdi); |
| 382 | LAPDM_SP.send(rpdi); |
| 383 | } |
| 384 | |
| 385 | [] L1CTL.receive { } |
| 386 | |
| 387 | /* encode + forward any blocks from L23 to L1 */ |
| 388 | [] LAPDM_SP.receive(RLCMAC_ph_data_req:?) -> value rpdr { |
| 389 | var octetstring buf; |
| 390 | |
| 391 | buf := enc_RlcmacUlBlock(rpdr.block); |
| 392 | L1CTL.send(t_L1CTL_DATA_TBF_REQ(buf, L1CTL_CS1, rpdr.tbf_id)); |
| 393 | } |
| 394 | |
| 395 | /* FIXME: release TBF mode */ |
| 396 | [] LAPDM_SP.receive(DCCH_release_req:?) { |
| 397 | /* go back to BCCH */ |
| 398 | f_release_tbf(); |
| 399 | } |
| 400 | |
| 401 | } |
Harald Welte | d4ba7ff | 2017-07-17 21:00:48 +0200 | [diff] [blame] | 402 | } |
Harald Welte | 4b6c772 | 2017-08-01 00:07:12 +0200 | [diff] [blame] | 403 | |
Harald Welte | d4ba7ff | 2017-07-17 21:00:48 +0200 | [diff] [blame] | 404 | } /* while (1) */ |
| 405 | } |
| 406 | } |