This page is meant to describe the process of sending a GSM message on the transmit side to the BladeRF radio.
All the classes used by the ybts module to work with GSM messages are defined and implemented in the /mbts/GSM directory from YateBTS installation package, in the GSM namespace.
The GSMconfig class implements the GSM air interface and assures multithread access through MUTEX mechanisms. The GSM configuration is based on the contents from gConfig file which is used in the init() method; internal control loops are executed with the start() method.
For the physical layer L1, there are implemented a series of abstract encoders and decoders which are then wrapped by an instance of LogicalChannel in order to define the complete L3<->L1 handler.
The dataflow L1->L2, where L2 is the Data Link Layer, is described as follows:
- in TRXManager, the mDemuxTable is consulted in order to pass the radio burst to the corresponding logical channel using a call of the L1FEC::writeLowSideRx;
- four bursts need to be assembled and decoded. For TCH, FACH and SACH this happens in (L1Decoder descendant)::processBursts, which calls countGoodFrame() and handleGoodFrame() or countBadFrame();
- handleGoodFrame() manages in the L1 layer the start/stop timers and power/timing parameters and passes the frame up by SAPMux->writeLowSide();
- int the case of RACH, the method writeLowSideRx() decodes the burst and sends a message directly to gBTS.channelRequest() where it is enqueued for eventual processing;
The L2->L1 data flow is as follows:
- the Data Link Layer object calls SAPMux::writeHighSide which calls L1FEC::writeHighSide;
- the frames may be processed at that time or may be passed downstream, in which case they usually go through sendFrame(). Eventually the frames reach the ARFCNManager;
- in the InterThreadQueue a serviceLoop inspects the frames and decides wheter they must be synchronized to the BTS frame clock.
While the L1FEC::sendLowSideRx method is used to send in an RxBurst for decoding, the L1FEC::writeHighSide method is used to send in an L2Frame for encoding and transmission.
The data link layer, L2 is represented by the class L2DL which is also implemented in the GSM namespace. This is a base class from which all the channels are derived. The interface with the L1 layer is implemented through the writeLowSide method mentioned above while the interface with the L3 layer, or Network Layer, is implemented through the method readHighSide which returns a L3Frame pointer. On the other hand, the interface L3->L2 is made through the method writeHighSide which accepts a reference to a L3Frameobject. All these pure virtual methods, meaning that they are overridden for each channel type.
For the Network Layer, L3, the LogicalChannel was implemented in the GSM namespace. This abstract representation of the GSM logical channel handles L3Frames and communicates with the second layer in terms of sending GSM message. It is a complete logical channel and it also includes processors for the other two layers (L2, L1). Above all, this is a virtual implementation which means that specific channel types are sublasses.
The transmission path
In case of a traffic channel, the flow of method calls in order to send a GSM message is as follows:
- the LogicalChannel sends an L3Frame on downlink using the method send which basically relies on a call to the L2DL object's writeHighSide method to which it passes the L3Frame frame as an argument. This is the point where the GSM message leaves the Layer 3 and enters Layer 2;
- the L2DL::writeHighSide is mapped to the L2LAPDm::writeHighSide method which passes the L3Frame argument to a call of L2LAPDm::sendUFrameUI;
- the L2LAPDm::sendUFrameUI creates a L2Frame from the L3Frame and sends it through L2LAPDm::writeL1NoAck to the L2LAPDm::writeL1. Here is where the GSM message reaches the Layer 1;
- from here on, the control flow is the on described above for L2->L1. Eventually the L2Frame reaches the L1Encoder::writeHighSide method which is overridden for every transmission channel. For the TCH it is used a XCCHL1Encoder::writeHighSide call;
- the XCCHL1Encoder::writeHighSide processes the pending L2Frame and passes it to the XCCHL1Encoder::sendFrame which assures the downstream channel is valid and if it is two things happen: the L2Frame is mapped to a BitVector type in the members of SharedL1Encoder and sent as GSMTAP with gWriteGSMTAP plus it is transmitted with L1Encoder::transmit;
- on the GSMTAP side the BitVector mapped L2Frame is wrapped in a buffer and sent with a socket;
- on the L1Encoder::transmit side the GSM clock is synchronized, the burst is encrypted if specified and is sent to the radio using ARFCNManager *mDownstream (member of the L1Encoder class). The transmission is made by ARFCNManager::writeHighSideTx which writes to the socket.
In case of a Common Control Channel (CCCH), the CCCHLogicalChannel class is used.It is derived from the LogicalChannel class. The main difference when sending a message is given by the fact that CCCH is written by multiple threads. Because of this, the abstract representation of the CCCH has a L3FrameFIFO member to manage the L3Frame frames. The flow of method calls in order to send a GSM message is as follows:
- the CCCHLogicalChannel::send method wrapps the message in an L3Frame and equeues it using the L3FrameFIFO mentioned above (CCCHLogicalChannel::mQ);
- CCCHLogicalChannel::serviceLoop() gets the previously enqueued L3Frame message, pulls it out and passes it to LogicalChannel::send;
- from here, the message reaches the GSM Layer 2, reaches at CCCHL2::writeHighSide (which overrides the pure virtual method L2DL::writeHighSide for the CCCH channel) where an L2Frame frame is created. The newly created L2Frame frame is then passed to (SAPMux)mDownstream->writeHighSide which just calls (L1FEC)mDownStream->writeHighSide and in the end reaches at (L1Encoder)mEncoder->writeHighSide;
- because the transmission is on a CCCH, the last mentioned method maps to XCCHL1Encoder::writeHighSide. Here the frame is processed and sent to XCCHL1Encoder::sendFrame which actually calls L1Encoder::sendFrame;
- from this point on, things happen as in the case of the traffic channel: GSMTAP signal is sent with gWriteGSMTAP, then the frame is encrypted transmitted by L1Encoder::transmit;
- on the transmit side, the GSM clock gets synchronized and eventually the burst is written to the socket with ARFCNManager::writeHighSideTx.
In both cases described above the ARFCNManager::writeHighSideTx call will pass a message through UDPSocket::write. Because the UDPSocket class is inherited from DatagramSocket the previous call will actually map to DatagramSocket::write which does an immediate sendto on the socket. The destination socket used by sendto is set in the UPDSocket class constructor, based on the destination port and IP . If the BladeRF device is connected to your machine, this is the point where the GSM message is actually sent to it by means of libusb.
The Transceiver class uses three UDPsockets to handle the communication with the GSM core. MDataSocket is the socket used for writing/reading, mControlSocket is the one writing/reading control commands while mClockSocket is the socket responsible for writing clock updates to the GSM core. The transmitted GSM message is handled through a thread , mTxServiceLoopThread, and the interface to the radio device is established through an RadioInterface object, mRadioInterface.
To send a burst with the transceiver, there must firstly be set some parameters which are specified through the class constructor:
- the base port number of the UDP sockets;
- the IP address of the TRX manager;
- the number of samples per GSM symbol;
- the initial setting of transmit latency;
- the associated radioInterface object.
The first step is to initialize the transceiver and then start it, which is achieved through the Transceiver::init() and Transceiver::start() methods.Also the transceiver's receive FIFO must be filled with the radioInterface's receive FIFO. The initialization process initializes the filler tables with dummy bursts while the start process starts a thread which processes the control messages from the GSM core. On the other hand, the process of starting the device actually involves starting a control service loop which is a thread aimed at processing the messages from the GSM core. This is done by using the UDPsocket::read.