view src/g23m-gprs/llc/llc_txf.c @ 268:f2e52cab0a73

abb_inth.c: check all interrupt causes, not just one The original code used if - else if - else if etc constructs, thus the first detected interrupt was the only one handled. However, Iota ITSTATREG is a clear-on-read register, thus if we only handle the first detected interrupt and skip checking the others, then the other interrupts will be lost, if more than one interrupt happened to occur in one ABB interrupt handling cycle - a form of rare race condition. Change the code to check all interrupts that were read in this cycle.
author Mychaela Falconia <falcon@freecalypso.org>
date Sun, 13 Jun 2021 18:17:53 +0000
parents fa8dc04885d8
children
line wrap: on
line source

/* 
+----------------------------------------------------------------------------- 
|  Project :  
|  Modul   :  
+----------------------------------------------------------------------------- 
|  Copyright 2002 Texas Instruments Berlin, AG 
|                 All rights reserved. 
| 
|                 This file is confidential and a trade secret of Texas 
|                 Instruments Berlin, AG 
|                 The receipt of or possession of this file does not convey 
|                 any rights to reproduce or disclose its contents or to 
|                 manufacture, use, or sell anything it may describe, in 
|                 whole, or in part, without the specific written consent of 
|                 Texas Instruments Berlin, AG. 
+----------------------------------------------------------------------------- 
|  Purpose :  This modul is part of the entity LLC and implements all 
|             procedures and functions as described in the 
|             SDL-documentation (TX-statemachine)
+----------------------------------------------------------------------------- 
*/ 

#ifndef LLC_TXF_C
#define LLC_TXF_C
#endif

#define ENTITY_LLC

/*==== INCLUDES =============================================================*/

#include <string.h>     /* to get memcpy() */

#include "typedefs.h"   /* to get Condat data types */
#include "vsi.h"        /* to get a lot of macros */
#include "macdef.h"
#include "gprs.h"
#include "gsm.h"        /* to get a lot of macros */
#include "cnf_llc.h"    /* to get cnf-definitions */
#include "mon_llc.h"    /* to get mon-definitions */
#include "prim.h"       /* to get the definitions of used SAP and directions */
#include "llc.h"        /* to get the global entity definitions */

#include "llc_f.h"      /* to get global functions, e.g. llc_generate_input */
#include "llc_txf.h"    /* to get the global entity definitions */
#include "llc_txp.h"    /* to get the function tx_cci_cipher_cnf */

#include "llc_uitxs.h"  /* to get signal interface to UITX */
#include "llc_itxs.h"   /* to get signal interface to ITX */

#ifndef TI_PS_OP_CIPH_DRIVER
#include "cci_fbsf.h"     /* to get functional interface */
#endif


/*==== CONST ================================================================*/

/*==== LOCAL VARS ===========================================================*/

/*==== PRIVATE FUNCTIONS ====================================================*/

/*==== PUBLIC FUNCTIONS =====================================================*/


/*
+------------------------------------------------------------------------------
| Function    : tx_init
+------------------------------------------------------------------------------
| Description : This procedure initialises all necessary variables of send_pdu.
|
| Parameters  : 
|
+------------------------------------------------------------------------------
*/
GLOBAL void llc_tx_init (void)
{
  TRACE_FUNCTION( "tx_init" );

  /*
   * Initialise TX with state NOT_READY.
   */
  INIT_STATE (TX, TX_TLLI_UNASSIGNED_NOT_READY);

  /*
   * Flush TX queue, if there is (in case of an LLC 
   * restart) old stuff queued.
   */
  tx_clear_buffer();

  return;
} /* tx_init() */


/*
+------------------------------------------------------------------------------
| Function    : tx_clear_buffer
+------------------------------------------------------------------------------
| Description : This procedure frees all buffered CCI_CIPHER_CNF primitives.
|
| Parameters  : 
|
+------------------------------------------------------------------------------
*/
GLOBAL void tx_clear_buffer (void)
{
  T_TX_QUEUE        *elem;
  T_TX_QUEUE        *elem_next;


  TRACE_FUNCTION( "tx_clear_buffer" );

  for (elem = llc_data->tx.queue; elem NEQ NULL; elem = elem_next)
  {
    elem_next = elem->next;

    /*
     * Free primitive if any is stored in queue element.
     */
    if (elem->primitive NEQ (ULONG)NULL)
    {
      PFREE (elem->primitive);
    }

    MFREE (elem);
  }

  llc_data->tx.queue = NULL;

  /*
   * Initialise UITX space counter for each SAPI.
   */
  llc_data->tx.queue_counter_uitx[0] = UITX_1_QUEUE_SIZE;
  llc_data->tx.queue_counter_uitx[1] = UITX_3_QUEUE_SIZE;
  llc_data->tx.queue_counter_uitx[2] = UITX_5_QUEUE_SIZE;
  llc_data->tx.queue_counter_uitx[3] = UITX_7_QUEUE_SIZE;
  llc_data->tx.queue_counter_uitx[4] = UITX_9_QUEUE_SIZE;
  llc_data->tx.queue_counter_uitx[5] = UITX_11_QUEUE_SIZE;

  /*
   * Initialise ITX space counter for each SAPI.
   */
  llc_data->tx.queue_counter_itx[0] = ITX_3_QUEUE_SIZE;
  llc_data->tx.queue_counter_itx[1] = ITX_5_QUEUE_SIZE;
  llc_data->tx.queue_counter_itx[2] = ITX_9_QUEUE_SIZE;
  llc_data->tx.queue_counter_itx[3] = ITX_11_QUEUE_SIZE;

  return;
} /* tx_clear_buffer() */

/*
+------------------------------------------------------------------------------
| Function    : tx_cipher_req
+------------------------------------------------------------------------------
| Description : Handles the function tx_cipher_req. This functions sets the
|               ciphering parameters and calls the ciphering driver function.
|
| Parameters  : todo
|
+------------------------------------------------------------------------------
*/
GLOBAL void tx_cipher_req
(
#ifdef LL_DESC
T_CCI_CIPHER_DESC_REQ *cipher_req
#else
T_CCI_CIPHER_REQ *cipher_req
#endif
)

{ 
  T_CIPH_init_cipher_req_parms  init_cipher_req_parms;
  T_CIPH_cipher_req_parms       cipher_req_parms;
  T_CIPH_in_data_list           in_data_list;
  T_CIPH_out_data               out_data;
  T_CIPH_ck                     ck;
  U16                           i;
  U8                            status;

  TRACE_FUNCTION( "tx_cipher_req" );

#ifdef LLC_TRACE_CIPHERING
  TRACE_EVENT("UPLINK NON CIPHERED DATA");
  llc_trace_desc_list3_content(cipher_req->desc_list3);
#endif
  /*
   * Copy pointer to desc's from CIPHER_REQ to the in_data_list
   * The in_data array in allocated dynamically in this func.
   */
  llc_copy_ul_data_to_list(cipher_req, &in_data_list);
  /*
   * Store ciphering parameters 
   */
  cipher_req_parms.gprs_parameters.pm              = cipher_req->pm;
  cipher_req_parms.gprs_parameters.header_size     = cipher_req->header_size;
  cipher_req_parms.gprs_parameters.ciphering_input = cipher_req->ciphering_input;
  cipher_req_parms.gprs_parameters.threshold       = 0;
  init_cipher_req_parms.direction = CIPH_UPLINK_DIR;
  init_cipher_req_parms.algo      = cipher_req->ciphering_algorithm;
  init_cipher_req_parms.ptr_ck = & ck;
  /*
   * Copy ciphering key
   */
  for (i=0; i<8;i++){
    init_cipher_req_parms.ptr_ck->ck_element[i] = cipher_req->kc.key[i];
  }

  {
    /* Use GRLC_DATA_REQ instead of CCI_CIPHER_CNF to avoid PPASS in LLC*/
    PALLOC_SDU (grlc_data_req, GRLC_DATA_REQ,
                (USHORT)(cipher_req->desc_list3.list_len*8 + FCS_SIZE_BITS));
  
    grlc_data_req->sdu.o_buf = 0;
    grlc_data_req->sdu.l_buf = 0;
    out_data.buf = (U32)(&grlc_data_req->sdu.buf[grlc_data_req->sdu.o_buf]);
    /*
     * Initialize ciphering driver and cipher data
     */
#ifdef TI_PS_OP_CIPH_DRIVER
    ciph_init_cipher_req (&init_cipher_req_parms, NULL);
    ciph_cipher_req (&cipher_req_parms, &in_data_list, &out_data, &status);
#else
    ciph_init_cipher_req_sim (&init_cipher_req_parms, NULL);
    ciph_cipher_req_sim (&cipher_req_parms, &in_data_list, &out_data, &status);
#endif

    /*
     * "Send" CIPHER_CNF to LLC
     */
    grlc_data_req->sdu.l_buf = out_data.len * 8;
    grlc_data_req->tlli = cipher_req->reference1;

#ifdef LLC_TRACE_CIPHERING
    TRACE_EVENT("UPLINK CIPHERED DATA");
    llc_trace_sdu(&grlc_data_req->sdu);
#endif
    tx_cci_cipher_cnf (grlc_data_req);

  }
      /*
       * Remove in use mark from the cipher request primitive
       */
  if (cipher_req != NULL) {
    {
      /*
       * Free cipher request, if not further used by other entities
       */
      if (cipher_req->attached_counter == CCI_NO_ATTACHE) {
#ifdef LL_DESC
        llc_cl_desc3_free((T_desc3*)cipher_req->desc_list3.first);
#endif /* LL_DESC */
      }
    }
    MFREE (cipher_req);
    cipher_req = NULL;
  }
      /*
       * Free in_data array from in_data_list 
       * allocated dynamically in llc_copy_ul_data_to_list() 
       */
  if(in_data_list.ptr_in_data != NULL){
    MFREE(in_data_list.ptr_in_data); 
    in_data_list.ptr_in_data = NULL;
  }

} /* tx_cipher_req() */

/*
+------------------------------------------------------------------------------
| Function    : tx_send_cipher_req
+------------------------------------------------------------------------------
| Description : This procedure fills all necessary parameters in the primitive 
|               CCI_CIPHER_REQ and sends the primitive to CCI.
|
| Parameters  : cci_cipher_req  - a valid pointer to a CCI_CIPHER_REQ primitive
|               frame_type      - indicates frame type (e.g. UI_FRAME)
|               protected_mode  - PM bit setting for CCI_CIPHER_REQ
|               ns              - N(U) for UI frames
|               cipher          - indicates if frame shall be ciphered or not
|               oc              - 
|
+------------------------------------------------------------------------------
*/
GLOBAL void tx_send_cipher_req 
(
#ifdef LL_DESC
  T_CCI_CIPHER_DESC_REQ *cci_cipher_desc_req,
#else
  T_CCI_CIPHER_REQ *cci_cipher_desc_req,
#endif
  T_PDU_TYPE frame_type,
  UBYTE protected_mode,
  T_FRAME_NUM ns,
  UBYTE cipher,
  ULONG oc
)
{
  TRACE_FUNCTION( "tx_send_cipher_req" );

  cci_cipher_desc_req->pm = protected_mode;

  if ( (cipher EQ LL_CIPHER_ON)  AND 
       (llc_data->ciphering_algorithm NEQ LLGMM_CIPHER_NO_ALGORITHM) )
  {
    cci_cipher_desc_req->ciphering_algorithm = llc_data->ciphering_algorithm;
    memcpy (&cci_cipher_desc_req->kc, &llc_data->kc, sizeof(T_kc));

    llc_generate_input (llc_data->current_sapi, frame_type, ns, 
      &cci_cipher_desc_req->ciphering_input, oc);

    cci_cipher_desc_req->direction = CCI_DIRECTION_UPLINK;
  }
  else /* LL_CIPHER_OFF */
  {
    cci_cipher_desc_req->ciphering_algorithm = CCI_CIPHER_NO_ALGORITHM;
  }

  tx_cipher_req (cci_cipher_desc_req);
  return;
} /* tx_send_cipher_desc_req() */


/*
+------------------------------------------------------------------------------
| Function    : tx_reserve_buffer
+------------------------------------------------------------------------------
| Description : This procedure allocates an element for the local transmit 
|               queue. If cause is DEFAULT or MOBILITY_MANAGEMENT, the element 
|               is appended at the end of the queue, otherwise it is inserted 
|               at the beginning. Necessary data like primitive header 
|               information and additional important data for the (resulting) 
|               GRLC_xDATA_REQ primitive is stored in the reserved buffer 
|               element. The primitive pointer in the element is set to NULL, 
|               to indicate that the element is not yet 'ready to send'. 
|               Parameter reservation_no will be set to the allocation number 
|               of the element. Parameter buffer_available will be set to TRUE 
|               if additional data primitives can be buffered for the current 
|               sapi, otherwise it will be set to FALSE.
|
| Parameters  : ll_unitdata_req   - a valid pointer to a LL_UNITDATA_REQ
|                                   primitive, containing the data to be sent
|               prim_type         - indicates GRLC_DATA/UNITDATA_REQ
|               cause             - frame cause, only valid for GRLC_DATA_REQ
|               rx_service        - service for flow control (if any)
|               reservation_no    - associated number for the buffer entry
|               buffer_available  - still buffer space available for current 
|                                   SAPI
|
+------------------------------------------------------------------------------
*/
GLOBAL void tx_reserve_buffer 
  (
#ifdef LL_DESC                               
  T_LL_UNITDESC_REQ *ll_unitdesc_req,
#else
  T_LL_UNITDATA_REQ *ll_unitdesc_req,
#endif
  T_PRIM_TYPE prim_type,
  UBYTE cause,
  T_SERVICE rx_service,
  ULONG *reservation_no,
  BOOL *buffer_available
  )
{
  T_TX_QUEUE        *elem;
  T_TX_QUEUE        **insert;

  static ULONG allocation_number = 0;

  TRACE_FUNCTION ("tx_reserve_buffer");

  MALLOC (elem, sizeof(T_TX_QUEUE));
  
  /*
   * Increase allocation number. Use of reservation_no must correspond 
   * to use in tx_store_buffer().
   */
  *reservation_no = ++(allocation_number);

  /*
   * Store all required information in new queue element. Member primitive 
   * is set to NULL to indicate that the element is not yet ready to be sent.
   * Copy necessary primitive header information to ph_* variables.
   */
  elem->primitive       = (ULONG)NULL;
  elem->prim_type       = prim_type;
  elem->reference       = allocation_number;
  elem->rx_service      = rx_service;
  elem->remove_frame    = FALSE;
  elem->ph_sapi         = ll_unitdesc_req->sapi;  
  elem->ph_tlli         = ll_unitdesc_req->tlli;
  elem->ph_grlc_qos_peak = ll_unitdesc_req->ll_qos.peak;
  elem->ph_radio_prio   = ll_unitdesc_req->radio_prio;
  elem->ph_cause        = cause;
#ifdef REL99 
  elem->ph_pkt_flow_id  = (UBYTE)ll_unitdesc_req->pkt_flow_id;
#endif /* REL99 */

  if ((cause EQ GRLC_DTACS_DEF) OR (cause EQ GRLC_DTACS_MOBILITY_MANAGEMENT))
  {
    /*
     * "Normal" frame cause, append element at the end of queue. Let insert
     * either point to llc_data->tx.queue or to the member .next of an element.
     */
    insert = &llc_data->tx.queue;
    while (*insert NEQ NULL)
    {
      insert = &((*insert)->next);
    }

    /*
     * Insert new element at found location and mark element as last in queue.
     */
    *insert = elem;
    elem->next = NULL;
  }
  else /* GRLC_DTACS_PAGE_RESPONSE OR GRLC_DTACS_CELL_UPDATE */
  {
    /*
     * No "normal" frame cause, thus LLGMM_TRIGGER_REQ must have been
     * received. Insert element at the beginning of queue.
     */
    elem->next = llc_data->tx.queue;
    llc_data->tx.queue = elem;
  }

  /*
   * Check if a service for flow control is specified (currently only 
   * SERVICE_UITX and SERVICE_ITX are recognized).
   */
  *buffer_available = FALSE;

  if (rx_service EQ SERVICE_UITX)
  {
    /*
     * Decrement the space counter of UITX primitives in the queue for the
     * current SAPI. This means that one place of the queue is occupied.
     * Check if there is space for one more primitive in the queue.
     */
    int n = llc_data->tx.queue_counter_uitx[UIMAP(llc_data->current_sapi)];
    
    if (n > 0)
    {
      llc_data->tx.queue_counter_uitx[UIMAP(llc_data->current_sapi)]--;

      if (n > 1) 
      {
        *buffer_available = TRUE;
      }
    }
    else
    {
      TRACE_EVENT ("Check uitx-queue flow");
    }
  }
  else if (rx_service EQ SERVICE_ITX)
  {
    /*
     * Decrement the space counter of ITX primitives in the queue for the
     * current SAPI. This means that one place of the queue is occupied.
     * Check if there is space for one more primitive in the queue.
     */
    int n = llc_data->tx.queue_counter_itx[IMAP(llc_data->current_sapi)];

    if (n > 0)
    {
      llc_data->tx.queue_counter_itx[IMAP(llc_data->current_sapi)]--;

      if (n > 1)
      {
        *buffer_available = TRUE;
      }
    }
    else
    {
      TRACE_EVENT ("Check itx-queue flow");
    }
  }
  else
  {
    /*
     * All other services (e.g. SERVICE_U) are allowed to send primitives 
     * per default. I.E. no flow control, except for service UITX and ITX.
     */
    *buffer_available = TRUE;
  }
} /* tx_reserve_buffer() */


/*
+------------------------------------------------------------------------------
| Function    : tx_store_buffer
+------------------------------------------------------------------------------
| Description : This procedure stores the given CCI primitive as GRLC primitive 
|               in the already reserved local transmit queue element that is 
|               given with primitive parameter reference. This queue element 
|               is marked as 'ready to send'. The CCI primitive is being 
|               PPASSED as GRLC_DATA_REQ/GRLC_UNITDATA_REQ, according to 
|               prim_type in the queue element. All header information for 
|               the GRLC primitive is being filled in from the already stored 
|               data in the element.
|
| Parameters  : cci_cipher_cnf - a valid pointer to a CCI_CIPHER_CNF primitive
|
+------------------------------------------------------------------------------
*/
GLOBAL void tx_store_buffer (T_GRLC_DATA_REQ *grlc_data_req)
{
  T_TX_QUEUE        *elem = llc_data->tx.queue;


  TRACE_FUNCTION ("tx_store_buffer");

  /*
   * Find corresponding queue entry. Use of reference must correspond to
   * use in tx_reserve_buffer().
   */
  while (elem NEQ NULL)
  {
    if (elem->reference EQ grlc_data_req->tlli)
    {
      break;
    }

    elem = elem->next;
  }
  
  if (elem NEQ NULL)
  {
    if (elem->prim_type EQ PRIM_DATA)
    {
      /*
       * Copy information from stored primitive header variables (ph_*) to 
       * GRLC primitive.
       */
      grlc_data_req->sapi              = elem->ph_sapi;
      grlc_data_req->tlli              = elem->ph_tlli;
#ifdef LL_2to1
      grlc_data_req->grlc_qos.peak      = elem->ph_grlc_qos_peak;
#else
      grlc_data_req->grlc_qos.peak      = elem->ph_grlc_qos_peak;
#endif
      grlc_data_req->radio_prio        = elem->ph_radio_prio;
      grlc_data_req->cause             = elem->ph_cause;

#ifdef REL99 
      grlc_data_req->pkt_flow_id[0]    = elem->ph_pkt_flow_id;
#endif /* REL99*/

#ifdef _SIMULATION_
      /*
       * Initialize all (unused) members of grlc_qos, because otherwise the test
       * cases fail.
       */
#ifdef LL_2to1
      grlc_data_req->grlc_qos.delay     = PS_DELAY_SUB;
      grlc_data_req->grlc_qos.relclass  = PS_RELCLASS_SUB;
      grlc_data_req->grlc_qos.preced    = PS_PRECED_SUB;
      grlc_data_req->grlc_qos.mean      = PS_MEAN_SUB;
#else
      grlc_data_req->grlc_qos.delay     = GRLC_DELAY_SUB;
      grlc_data_req->grlc_qos.relclass  = GRLC_RELCLASS_SUB;
      grlc_data_req->grlc_qos.preced    = GRLC_PRECED_SUB;
      grlc_data_req->grlc_qos.mean      = GRLC_MEAN_SUB;
#endif
      
#endif /* _SIMULATION_ */

      elem->primitive = (ULONG)grlc_data_req;
    }
    else /* PRIM_UNITDATA */
    {
      /*
       * Store CCI primitive as GRLC_UNITDATA_REQ (and mark it as stored).
       */
      PPASS (grlc_data_req, grlc_unitdata_req, GRLC_UNITDATA_REQ);

      /*
       * Copy information from stored primitive header variables (ph_*) to 
       * GRLC primitive (omit ph_cause, because it is not present in 
       * GRLC_UNITDATA_REQ).
       */
      grlc_unitdata_req->sapi              = elem->ph_sapi;
      grlc_unitdata_req->tlli              = elem->ph_tlli;
#ifdef LL_2to1
      grlc_unitdata_req->grlc_qos.peak = elem->ph_grlc_qos_peak;
#else
      grlc_unitdata_req->grlc_qos.peak      = elem->ph_grlc_qos_peak;
#endif
      grlc_unitdata_req->radio_prio        = elem->ph_radio_prio;
#ifdef REL99 
      grlc_unitdata_req->pkt_flow_id[0]    = elem->ph_pkt_flow_id;
#endif /* REL99*/
#ifdef _SIMULATION_
      /*
       * Initialize all (unused) members of grlc_qos, because otherwise the test
       * cases fail.
       */
#ifdef LL_2to1
      grlc_unitdata_req->grlc_qos.delay = PS_DELAY_SUB;
      grlc_unitdata_req->grlc_qos.relclass  = PS_RELCLASS_SUB;
      grlc_unitdata_req->grlc_qos.preced    = PS_PRECED_SUB;
      grlc_unitdata_req->grlc_qos.mean      = PS_MEAN_SUB;
#else
      grlc_unitdata_req->grlc_qos.delay     = GRLC_DELAY_SUB;
      grlc_unitdata_req->grlc_qos.relclass  = GRLC_RELCLASS_SUB;
      grlc_unitdata_req->grlc_qos.preced    = GRLC_PRECED_SUB;
      grlc_unitdata_req->grlc_qos.mean      = GRLC_MEAN_SUB;
#endif /* LL_2to1 */




#endif /* _SIMULATION_ */

      elem->primitive = (ULONG)grlc_unitdata_req;
    }
  }
  else /* elem == NULL */
  {
    /*
     * elem not found is possible in case of an LLC re-init, reset, 
     * unassign, ...
     */
    TRACE_0_INFO("No TX queue entry for given reference found");
    PFREE (grlc_data_req);
  }

} /* tx_store_buffer() */


/*
+------------------------------------------------------------------------------
| Function    : tx_get_next_frame
+------------------------------------------------------------------------------
| Description : When LLC is not suspended, this procedure gets (and removes) 
|               the first primitive out of the local transmit queue and stores 
|               it in one of the parameters, according to the type of the 
|               primitive (which is written in prim_type). If no frame is 
|               available in the queue (i.e. first queue element is not marked
|               as 'ready to send', or the queue is empty), the value NO_PRIM
|               is written in prim_type. The parameter rx_service indicates the
|               originator service. In case the rx_service is UITX or ITX an 
|               READY signal for flow control will be send after frame 
|               transmission.
|               When LLC is suspended, this procedure returns the first 
|               primitive for SAPI 1 or the first U frame for any SAPI, which 
|               is ready to send.
|
| Parameters  : grlc_data_req - a valid pointer to a pointer to a GRLC-DATA-REQ 
|                              primitive
|               grlc_unitdata_req - a valid pointer to a pointer to a 
|                                  GRLC-UNITDATA-REQ primitive
|               prim_type - will be set to PRIM_DATA or PRIM_UNITDATA, must be
|                           a valid pointer
|               rx_service - indicates the originater service 
|                            must be a valid pointer
|
+------------------------------------------------------------------------------
*/
GLOBAL void tx_get_next_frame (T_GRLC_DATA_REQ **grlc_data_req,
                               T_GRLC_UNITDATA_REQ **grlc_unitdata_req,
                               T_PRIM_TYPE *prim_type,
                               T_SERVICE *rx_service,
                               UBYTE *sapi)
{ 
  int               frame_len;
  int               ctrl_len;
  T_TX_QUEUE        *elem;
  T_TX_QUEUE        **find;


  TRACE_FUNCTION( "tx_get_next_frame" );


  /*
   * Initialise find w/ queue start. find points always to the element 
   * "before" the examined one, to be able to modifiy the queue, if
   * an element is found.
   */
  find = &llc_data->tx.queue;

  /*
   * If LLC is suspended search the queue for the first GRLC_DATA_REQ primitive,
   * otherwise take the first primitive in queue (if any).
   */
  if (llc_data->suspended EQ TRUE)
  {
    /*
     * LLC is in suspended mode. Search the queue for the first primitive 
     * for SAPI 1 or the first U frame for any SAPI
     */
    while ( (*find              NEQ NULL)       AND 
           ((*find)->ph_sapi    NEQ LL_SAPI_1)  AND 
           ((*find)->rx_service NEQ SERVICE_U)  AND
           ((*find)->primitive  NEQ (ULONG)NULL)  )
    {
      find = &((*find)->next);
    }
  }

  /*
   * Let elem point to queue element (NULL, if no element present/found).
   */
  elem = *find;


  /*
   * Check if queue is empty or "first" queue element (depending on suspended
   * mode) is marked as not 'ready to send', i.e. it contains a NULL 
   * primitive pointer.
   */
  if ((elem EQ NULL) OR (elem->primitive EQ (ULONG)NULL))
  {
    /*
     * Queue is empty or "first" element is not yet 'ready to send'.
     */
    *prim_type = NO_PRIM;
  }
  else /* (elem NEQ NULL) AND (elem->primitive NEQ NULL) */
  {
    /*
     * Found a valid (and 'ready to send') element in queue.
     */

    /*
     * Remove found element from queue (set member next of element before
     * the found element to the element behind the found element and thus
     * skip the found element).
     */
    *find = (*find)->next;
  
    /*
     * Store data of element in the given parameters.
     */
    *prim_type  = elem->prim_type;
    *rx_service = elem->rx_service;
    *sapi       = elem->ph_sapi;

    if (elem->prim_type EQ PRIM_DATA)
    {
      *grlc_data_req = (T_GRLC_DATA_REQ *)elem->primitive;

      frame_len = BYTELEN((*grlc_data_req)->sdu.l_buf);
    }
    else /* PRIM_UNITDATA */
    {
      *grlc_unitdata_req = (T_GRLC_UNITDATA_REQ *)elem->primitive;

      frame_len = BYTELEN((*grlc_unitdata_req)->sdu.l_buf);
    }

    /*
     * If the information fild doesn't fit in current N201_U, remove frame
     * (only applies to service U and UITX).
     */
    if (*rx_service == SERVICE_U || *rx_service == SERVICE_UITX)
    {
      ctrl_len = (*rx_service == SERVICE_U) ? U_FRAME_MIN_OCTETS
                                            : UI_FRAME_MIN_OCTETS;

      if (frame_len > llc_data->n201_u_base[UIMAP(*sapi)] + ctrl_len)
      {
        elem->remove_frame = TRUE;
        TRACE_0_INFO("Primitive in TX exceeds N201-U: marked to remove");
      }
    }

    /*
     * If frame is marked to remove, do it here
     */
    if (elem->remove_frame == TRUE)
    {
      if (elem->prim_type EQ PRIM_DATA)
      {
        PFREE (*grlc_data_req);
        TRACE_0_INFO("GRLC_DATA_REQ removed from TX queue");
      }
      else
      {
        PFREE (*grlc_unitdata_req);
        TRACE_0_INFO("GRLC_UNITDATA_REQ removed from TX queue");
      }

      *prim_type  = PRIM_REMOVED;
    }

    /*
     * Remove TX queue entry management element
     */
    MFREE (elem);

    /*
     * Check if service queue space should be tracked (at the moment only 
     * for service UITX and ITX, due to flow control to this service).
     */
    if (*rx_service EQ SERVICE_UITX)
    {
      /*
       * Increment the UITX space counter for the SAPI of the primitive
       * to indicate that the primitive has been stored in queue.
       */
      if (*prim_type EQ PRIM_DATA)
      {
        llc_data->tx.queue_counter_uitx[UIMAP(*sapi)]++;
      }
      else /* PRIM_UNITDATA */
      {
        llc_data->tx.queue_counter_uitx[UIMAP(*sapi)]++;
      }
    }
    else if (*rx_service EQ SERVICE_ITX)
    {
      /*
       * Increment the ITX space counter for the SAPI of the primitive
       * to indicate that the primitive has been stored in queue.
       */
      llc_data->tx.queue_counter_itx[IMAP(*sapi)]++;
    }
  }

  return;
} /* tx_get_next_frame() */


/*
+------------------------------------------------------------------------------
| Function    : tx_get_first_data_frame
+------------------------------------------------------------------------------
| Description : This procedure finds the first GRLC_DATA_REQ primitive in the 
|               local transmit queue and stores its queue element in the 
|               parameter element, if any. The primitive (element) stays in 
|               the queue. If no GRLC_DATA_REQ primitive is found in the queue, 
|               no element is stored in element, but the value NULL.
|
| Parameters  : elem          - must be a valid pointer to a pointer to a 
|                               TX queue element
|
+------------------------------------------------------------------------------
*/
GLOBAL void tx_get_first_data_frame (T_TX_QUEUE **elem)
{
  TRACE_FUNCTION( "tx_get_first_data_frame" );

  /*
   * Check the local transmit queue until a GRLC_DATA_REQ is found or the end
   * of the queue has been reached. In the latter case, *elem is automatically
   * set to NULL.
   */
  for (*elem = llc_data->tx.queue; *elem NEQ NULL; *elem = (*elem)->next)
  {
    /*
     * Check if primitive is a GRLC_DATA_REQ primitive.
     */
    if ((*elem)->prim_type EQ PRIM_DATA)
    {
      break;
    }
  }

  return;
} /* tx_get_first_data_frame() */


/*
+------------------------------------------------------------------------------
| Function    : tx_remove_data_frames
+------------------------------------------------------------------------------
| Description : This procedure removes all PRIM_DATA for given SAPI and service
|               from TX queue. If an element is removed and 
|
| Parameters  : service - service which requests to delete its primitives
|               sapi    - the sapi of the primitives to delete
|
+------------------------------------------------------------------------------
*/
GLOBAL void tx_remove_data_frames (T_SERVICE service, T_SAPI sapi)
{
  T_TX_QUEUE   *elem;
  T_TX_QUEUE  **find = &llc_data->tx.queue;
  BOOL          elem_removed = FALSE;

  TRACE_FUNCTION( "tx_remove_data_frames" );

  while (*find NEQ NULL)
  {
    /*
     * Let elem point to queue element
     */
    elem = *find;

    /*
     * Check if primitive is a GRLC_DATA_REQ primitive and
     * check the sapi of the queue element
     */
    if ((elem->prim_type  EQ PRIM_DATA) AND
        (elem->ph_sapi    EQ sapi)      AND
        (elem->rx_service EQ service)     )
    {
      elem_removed = TRUE;

      /*
       * Remove found element from queue (set member next of element before
       * the found element to the element behind the found element and thus
       * skip the found element).
       */
      *find = (*find)->next;

      /*
       * Check if we have a valid pointer to a primitive
       */
      if ((elem->primitive NEQ (ULONG)NULL))
      {
        PFREE ((T_GRLC_DATA_REQ *)elem->primitive);

        TRACE_0_INFO("TX queue element and primitive removed");
      }
      else
      {
        /*
         * Primitive is in use at CCI.
         * It is no problem if the corresponding primitive to this entry 
         * returns from CCI, because the reference number will not be found 
         * and the primitive data will then be freed.
         */
        TRACE_0_INFO("In use TX queue element removed");
      }

      /*
       * Remove TX queue entry management element.
       */
      MFREE (elem);

      /*
       * Check if service queue space should be tracked (at the moment only 
       * for service UITX and ITX, due to flow control to this service).
       * In this case increment the space counter for the SAPI of the 
       * primitive to indicate that the primitive has been stored in queue.
       */
      if (service EQ SERVICE_UITX)
      {
        llc_data->tx.queue_counter_uitx[UIMAP(sapi)]++;
      }
      else if (service EQ SERVICE_ITX)
      {
        llc_data->tx.queue_counter_itx[IMAP(sapi)]++;
      }
    }
    else
    {
      /*
       * Point to the next queue element pointer
       */
      find = &((*find)->next);
    }
  }

  /*
   * Now check, if a ready indication must be send must be send to the 
   * requesting service (if at least one elem was removed, send one).
   * Currently only service UITX and ITX are requesting a signal.
   */
  if (elem_removed)
  {
    SWITCH_LLC (sapi);

    if (service EQ SERVICE_UITX)
    {
      sig_tx_uitx_ready_ind();
    }
    else if (service EQ SERVICE_ITX)
    {
      sig_tx_itx_ready_ind();
    }
  }

  return;
} /* tx_remove_data_frames() */