1# Copyright 2019 Oticon A/S 2# SPDX-License-Identifier: Apache-2.0 3 4#EDTT Transport driver for BabbleSim 5# Any EDTT Transport shall implement the following interface: 6# __init__(args) 7# connect() 8# send(idx, message) 9# recv(idx, number_bytes, timeout) 10# wait(time) 11# close() 12# get_time() 13# n_devices : Number of devices it is connected to 14 15import struct; 16import os; 17import fcntl; 18 19from components.bsim_device import BSimDevice 20from components.bsim_lib import ( PB_MSG_DISCONNECT, PB_MSG_TERMINATE, PB_MSG_WAIT, PB_MSG_WAIT_END, TIME_NEVER, create_com_folder, create_fifo_if_not_there, test_and_create_lock_file); 21 22class EDTTT: 23 TO_EDTT = 0; 24 TO_DEVICE = 1; 25 TO_PHY = 1; 26 PhyFIFOs = [-1,-1]; 27 PhyFIFO_names = ["",""]; 28 FIFOs = []; 29 FIFOnames = []; 30 verbosity = 0; 31 last_t = 0; #last timestamp received from the Phy 32 Connected = False; 33 n_devices = 0; 34 low_level_device = None 35 com_path = "" 36 lock_path = "" 37 autoterminate = True 38 RxWait = 10000 39 40 def __init__(self, pending_args, TraceClass): 41 self.Trace = TraceClass; 42 import argparse 43 parser = argparse.ArgumentParser(prog="BabbleSim transport options:", add_help=False) 44 parser.add_argument("-s", "--sim_id", required=True, help="Simulation id"); 45 parser.add_argument("-d", "--EDTT_tool_dev_nbr", required=True, type=int, help="Device number of the EDTT tool itself"); 46 parser.add_argument("-D", "--number-of-devices", required=True, type=int, help="Number of simulated devices we will connect to (the low level device does not count)"); 47 parser.add_argument("-devs", "--devices-numbers", required=True, type=int, nargs='+', help="Set of simulated devices we will connect to. There should <number-of-devices> in this list, where the first one will be the \"device 0\" for EDTT and so forth"); 48 parser.add_argument("-RxWait", required=False, default=10000, type=float, help="(10e3) while there is no enough data for a read, the simulation will be advanced in this steps"); 49 parser.add_argument("-l", "--low-level-device", required=False, help="Enable BSim low level device; Note that this requires --low-level-device-nbr to be supplied", action='store_true'); 50 parser.add_argument("--low-level-device-nbr", required=False, type=int, help="Device number of the BSim low level device"); 51 parser.add_argument("--DontAutoTerminate", required=False, action='store_true', help="Do not terminate simulation when the test ends (by default it will)"); 52 (args, discard) = parser.parse_known_args(pending_args) 53 54 self.sim_id = args.sim_id; 55 self.EDTT_tool_dev_nbr = args.EDTT_tool_dev_nbr; 56 self.n_devices = args.number_of_devices; 57 self.devices_numbers = args.devices_numbers; 58 self.RxWait = int(args.RxWait) 59 60 if args.low_level_device: 61 if not args.low_level_device_nbr: 62 raise Exception("--low-level-device-nbr is required when the BSim low level device is enabled") 63 self.low_level_device = BSimDevice(int(args.low_level_device_nbr), self.sim_id, TraceClass) 64 65 if len(args.devices_numbers) < args.number_of_devices: 66 raise Exception("--devices-numbers must have as many elements as configured with number-of-devices") 67 68 if args.low_level_device_nbr and (args.low_level_device_nbr <= args.EDTT_tool_dev_nbr): 69 raise Exception("The low-level-device-nbr must be higher than the EDTT tool device number (otherwise we will deadlock)") 70 71 self.FIFOs = [[-1, -1] for i in range(0,args.number_of_devices)]; 72 self.FIFOnames = [["", ""] for i in range(0,args.number_of_devices)]; 73 74 if args.DontAutoTerminate: 75 self.autoterminate = false 76 77 def connect(self): 78 self.Trace.trace(3,"Connecting to EDTT Phy and devices"); 79 self.com_path = create_com_folder(self.sim_id); 80 self.lock_path = test_and_create_lock_file(self.com_path, self.EDTT_tool_dev_nbr, self.Trace) 81 82 #Note that python ignores SIGPIPE by default 83 84 self.PhyFIFO_names[self.TO_PHY] = "%s/%s.d%i.dtp" % (self.com_path, "2G4", self.EDTT_tool_dev_nbr) 85 self.PhyFIFO_names[self.TO_EDTT] = "%s/%s.d%i.ptd" % (self.com_path, "2G4", self.EDTT_tool_dev_nbr) 86 87 try: 88 create_fifo_if_not_there(self.PhyFIFO_names[self.TO_PHY]); 89 create_fifo_if_not_there(self.PhyFIFO_names[self.TO_EDTT]); 90 91 #Note that the order of opening them needs to be like this. To match the Phy order and avoid deadlocking the other 92 #We purposedly want to block until the other side is connected to hold until everything is ready 93 self.PhyFIFOs[self.TO_EDTT] = os.open(self.PhyFIFO_names[self.TO_EDTT], os.O_RDONLY); 94 self.PhyFIFOs[self.TO_PHY] = os.open(self.PhyFIFO_names[self.TO_PHY], os.O_WRONLY); 95 96 if (self.PhyFIFOs[self.TO_PHY] == -1): 97 raise Exception("Could not open FIFO %s"% self.PhyFIFO_names[self.TO_PHY]); 98 if (self.PhyFIFOs[self.TO_EDTT] == -1): 99 raise Exception("Could not open FIFO %s"% self.PhyFIFO_names[self.TO_EDTT]); 100 except: 101 self.cleanup() 102 raise 103 104 self.Trace.trace(6,"Connected to Phy"); 105 106 if self.low_level_device: 107 self.low_level_device.connect() 108 self.Trace.trace(8,"Low level device connected to Phy"); 109 110 try: 111 for i in range(0, self.n_devices): 112 d = self.devices_numbers[i]; 113 self.Trace.trace(8,"Connecting to device %i"%d); 114 self.FIFOnames[i][self.TO_DEVICE] = "%s/Device%i.PTTin" % (self.com_path, d) 115 self.FIFOnames[i][self.TO_EDTT] = "%s/Device%i.PTTout" % (self.com_path, d) 116 #Long ago the EDTT was called PTT. The FIFOs were never renamed as it would be a backwards compatibility change. 117 118 create_fifo_if_not_there(self.FIFOnames[i][self.TO_EDTT]); 119 create_fifo_if_not_there(self.FIFOnames[i][self.TO_DEVICE]); 120 121 #Note that we don't set O_NONBLOCK during the creating of the FIFO as we want to block until the device is there 122 self.FIFOs[i][self.TO_EDTT] = os.open(self.FIFOnames[i][self.TO_EDTT], os.O_RDONLY); 123 if (self.FIFOs[i][self.TO_EDTT] == -1): 124 raise Exception("Could not open FIFO %s"% self.FIFOnames[i][self.TO_EDTT]); 125 126 #we want the read end to be non bloking (if the device didn't produce anything yet, we need to let it run a bit) 127 flags = fcntl.fcntl(self.FIFOs[i][self.TO_EDTT], fcntl.F_GETFL); 128 flags |= os.O_NONBLOCK; 129 fcntl.fcntl(self.FIFOs[i][self.TO_EDTT], fcntl.F_SETFL, flags); 130 131 self.FIFOs[i][self.TO_DEVICE] = os.open(self.FIFOnames[i][self.TO_DEVICE], os.O_WRONLY); 132 if (self.FIFOs[i][self.TO_DEVICE] == -1): 133 raise Exception("Could not open FIFO %s"% self.FIFOnames[i][self.TO_DEVICE]); 134 135 #we want the write end to be non bloking (if for whatever reason 136 # we fill up the FIFO, we would deadlock as the device is stalled 137 # => better to catch it in the write function) 138 flags = fcntl.fcntl(self.FIFOs[i][self.TO_DEVICE], fcntl.F_GETFL); 139 flags |= os.O_NONBLOCK; 140 fcntl.fcntl(self.FIFOs[i][self.TO_DEVICE], fcntl.F_SETFL, flags); 141 142 self.Trace.trace(8,"Connected to device %i"%d); 143 except: 144 self.cleanup() 145 raise 146 147 self.Connected = True; 148 self.Trace.trace(4,"Connected to Phy and all devices"); 149 150 # Wait for dump files to have been opened by the 2G4 phy 151 # Since there isn't a method implemented for synchronizing that, use the 152 # fact that any commands to the 2G4 phy will not run until after it has 153 #opened the files for writing; So use a minimal wait as a blocking mechanism 154 self.wait_until_t(1) 155 156 def cleanup(self): 157 if self.lock_path: 158 try: 159 os.remove(self.lock_path) 160 except: 161 pass 162 self.lock_path = "" 163 164 if self.low_level_device: 165 self.Trace.trace(4,"Cleaning up low-level device"); 166 self.low_level_device.cleanup() 167 168 self.Trace.trace(4,"Cleaning up transport"); 169 try: 170 if self.PhyFIFOs[self.TO_EDTT]: 171 os.close(self.PhyFIFOs[self.TO_EDTT]); 172 self.PhyFIFOs[self.TO_EDTT] = 0 173 self.Trace.trace(9,"Closed FIFO to Phy (->EDTT direction)"); 174 if self.PhyFIFOs[self.TO_PHY]: 175 os.close(self.PhyFIFOs[self.TO_PHY]); 176 self.PhyFIFOs[self.TO_PHY] = 0 177 self.Trace.trace(9,"Closed FIFO to Phy (->Phy direction)"); 178 os.remove(self.PhyFIFO_names[self.TO_PHY]); 179 os.remove(self.PhyFIFO_names[self.TO_EDTT]); 180 181 for i in range(0, self.n_devices): 182 self.Trace.trace(9,"Cleaning up interface to Device %i"%i); 183 if self.FIFOs[i][self.TO_EDTT]: 184 os.close(self.FIFOs[i][self.TO_EDTT]); 185 self.FIFOs[i][self.TO_EDTT] = 0 186 self.Trace.trace(9,"Closed FIFO to Device %i (->EDTT direction)"%i); 187 if self.FIFOs[i][self.TO_DEVICE]: 188 os.close(self.FIFOs[i][self.TO_DEVICE]); 189 self.FIFOs[i][self.TO_DEVICE] = 0 190 self.Trace.trace(9,"Closed FIFO to Device %i (->Device direction)"%i); 191 os.remove(self.FIFOnames[i][self.TO_DEVICE]); 192 os.remove(self.FIFOnames[i][self.TO_EDTT]); 193 except OSError: 194 self.Trace.trace(9,"(minor) Error closing FIFO " 195 "(most likely either file does not exist yet)"); 196 197 def __disconnect(self): 198 if self.Connected: 199 self.Connected = False 200 if self.autoterminate: 201 msg = struct.pack('=I', PB_MSG_TERMINATE) 202 self.Trace.trace(4,"Terminating simulation") 203 else: 204 msg = struct.pack('=I', PB_MSG_DISCONNECT) 205 self.Trace.trace(4,"Disconnecting from Phy") 206 self.__write_to_phy(msg); 207 if self.low_level_device: 208 self.low_level_device.disconnect() 209 210 def close(self): 211 self.__disconnect(); 212 self.cleanup(); 213 214 def __write_to_device(self, d, content): 215 # Write content to device d 216 try: 217 written = os.write(self.FIFOs[d][self.TO_DEVICE], content); 218 if written != len(content): 219 raise; 220 except: 221 self.Trace.trace(4,"The Device %i disappeared when trying to " 222 "write to it"%d); 223 self.close(); 224 raise Exception("Abruptly disconnected from device %i"%d); 225 226 def __read_from_device(self, d, nbytes): 227 # Atttempt a non-blocking read of nbytes from device d 228 pkt=b"" 229 try: 230 pkt = os.read(self.FIFOs[d][self.TO_EDTT], nbytes); 231 except BlockingIOError: 232 #No enough data available yet, we just return an empty packet 233 #or whatever we got so far 234 pass 235 except: 236 self.Trace.trace(4,"The Device %i disappeared when trying to " 237 "read from it"%d); 238 self.close(); 239 raise Exception("Abruptly disconnected from device %i"%d); 240 241 return pkt; 242 243 def __write_to_phy(self, content): 244 # Write content to the Phy 245 try: 246 written = os.write(self.PhyFIFOs[self.TO_PHY], content); 247 if written != len(content): 248 raise; 249 except: 250 self.Trace.trace(4,"The Phy disappeared when trying to " 251 "write to it"); 252 self.Connected = False 253 self.close(); 254 raise Exception("Abruptly disconnected from Phy"); 255 256 def __read_from_phy(self, nbytes): 257 # Read (blocking) nbytes from the Phy. 258 # If less bytes are read, we consider it an error 259 try: 260 pkt = os.read(self.PhyFIFOs[self.TO_EDTT], nbytes); 261 if len(pkt) != nbytes: 262 raise; 263 return pkt; 264 except: 265 self.Trace.trace(4,"The Phy disappeared when trying to " 266 "read from it"); 267 self.Connected = False 268 self.close(); 269 raise Exception("Abruptly disconnected from Phy"); 270 271 def send(self, idx, message): 272 #Send <message> to device <idx> 273 if (idx > self.n_devices -1): 274 raise Exception("Trying to access unconnected device %i"%idx); 275 276 self.Trace.trace(8,"Writing %i bytes to device %i"%(len(message),idx)); 277 self.__write_to_device(idx,message) 278 # a send is immediate (no time advance) 279 self.Trace.btsnoop.send(idx, message) 280 281 def recv(self, idx, number_bytes, to=None): 282 #Attempt to receive <number_bytes> from device <idx>, with a timeout of <to> ms 283 if (idx > self.n_devices -1): 284 raise Exception("Trying to access unconnected device %i"%idx); 285 286 if to == None: 287 to = self.default_to 288 if ( number_bytes == 0 ): 289 return b"" 290 291 timeout = to*1000 + self.last_t; 292 293 self.Trace.trace(8, "Recv of %iB from dev %d, timeout %i ms"% (number_bytes, idx, to) ) 294 295 # Let's try to read from the device. We will either manage right away 296 # or we will need to let time advance while we keep retrying 297 pending_to_read = number_bytes 298 readsofar = 0 299 packet = b"" 300 while self.last_t < timeout: 301 segment = self.__read_from_device(idx, pending_to_read) 302 packet += segment 303 nread = len(segment) 304 readsofar += nread 305 pending_to_read -= nread; 306 if pending_to_read > 0: #we need to wait a bit 307 self.Trace.trace(6, "During recv of %iB from dev %d, pending %i, Waiting for %s us"% 308 (number_bytes, idx, pending_to_read, str(self.RxWait)) ) 309 self.wait(self.RxWait/1000) 310 else: 311 break 312 313 if pending_to_read != 0: 314 self.Trace.trace(2, "Attempt to recv from dev %d, but only read %i out of %i bytes"%(idx, readsofar, number_bytes)) 315 else: 316 self.Trace.trace(8, "Attempt to recv from dev %d, read all %i bytes"%(idx, number_bytes)) 317 318 return packet 319 320 def wait(self, delay_in_ms): 321 end_of_wait = int(delay_in_ms*1000 + self.last_t); 322 return self.wait_until_t(end_of_wait) 323 324 def wait_until_t(self, end_of_wait): 325 if self.last_t >= end_of_wait: 326 self.Trace.trace(3, "Ignoring end_of_wait with a time not in the future: simulation time: %s; Requested end of wait: %s" % (self.last_t, end_of_wait)) 327 return 328 329 self.Trace.trace(8, "Waiting until %d"%end_of_wait) 330 331 if self.low_level_device: 332 self.low_level_device.wait(end_of_wait) 333 334 if self.Connected: 335 #A wait request to the Phy is a 32bit PB_MSG_WAIT header followed by 336 #a 64 bit time_to_wait integer value in microseconds 337 msg = struct.pack("=IQ", PB_MSG_WAIT, end_of_wait) 338 self.__write_to_phy(msg) 339 340 # Block until we get the reply from the Phy which a 32 bit value. 341 # It can either be an indication that the wait ended PB_MSG_WAIT_END 342 # or a PB_MSG_DISCONNECT == an order to disconnected 343 raw_header = self.__read_from_phy(4) 344 345 header, = struct.unpack("=I", raw_header) 346 if header == PB_MSG_DISCONNECT: 347 self.Trace.trace(2, "Phy told us to disconnect") 348 self.Connected = False 349 self.__disconnect() 350 raise Exception("Simulated terminated by the Phy") 351 elif header != PB_MSG_WAIT_END: 352 raise Exception("Low level communication with PHY failed; Received invalid response %s" % header) 353 354 self.last_t = end_of_wait 355 356 def get_time(self): 357 #return current time in miliseconds 358 return self.last_t/1000; 359 360 def get_last_t(self): 361 #return current time in microseconds 362 return self.last_t 363