// beta_arb_functions.c

#include "1394.h"
#include "data_structures.h"
#include "phy_services.h"
#include "arb.h"
#include "shared.h"

BetaRequestCode bestReq (byte *apm, byte *ipm, int *bap, int *bip, 
    boolean *in_phase_isoch_request, boolean *in_phase_async_request,
    boolean *i_advance_interval, boolean *a_advance_interval) {
  BetaRequestCode best_req;
  boolean requests_unknown;
  int i;
  best_req.async = own_req.async;
  best_req.isoch = own_req.isoch;
  requests_unknown = FALSE;
  *apm = (odd_async_phase ? 0xf0 : 0x0f); // select async priority mask
  *ipm = (odd_isoch_phase ? 0xf0 : 0x0f); // select isoch priority mask
  *bap = *bip = NPORT;                    // prefer own link

  for (i = 0; i < NPORT; i++) {           // find the best request and associated port
    if (i != senior_port) {               // then prefer junior ports first
	    best_req.async = (active[i] && ((receive_req[i].async & *apm) > 
	        (best_req.async & *apm)) ?
	      receive_req[*bap = i].async : best_req.async);
	    best_req.isoch = (active[i] && ((receive_req[i].isoch & *ipm) > 
	        (best_req.isoch & *ipm)) ?
	      receive_req[*bip = i].isoch : best_req.isoch);
    }
    if (active[i] && (((receive_req[i].async & *apm) == A_NONE) ||
                     ((receive_req[i].isoch & *ipm) == I_NONE))) {
      requests_unknown = TRUE;  // at least one active port hasn't received an updated request
    }
  }
  if (!proxy_root) {                    // finally prefer senior port
  	best_req.async = (active[senior_port] && ((receive_req[senior_port].async & *apm) > 
       (best_req.async & *apm)) ?
     receive_req[*bap = senior_port].async : best_req.async);
   best_req.isoch = (active[senior_port] && ((receive_req[senior_port].isoch & *ipm) > 
       (best_req.isoch & *ipm)) ?
     receive_req[*bip = senior_port].isoch : best_req.isoch);
  }
  
  // in_phase_isoch_request returns true anytime an ISOCH_CURRENT is present or
  // during the iso_cycle when an in-phase isoch request is present
  // in_phase_async_request returns true only during the asynchronous interval
  // when an in-phase asynch request is present
  *in_phase_isoch_request = 
      ((iso_cycle && ((best_req.isoch & *ipm) >= (ISOCH_IN_PHASE & *ipm))) ||
       (!iso_cycle && ((best_req.isoch & *ipm) == (ISOCH_CURRENT & *ipm))));
  *in_phase_async_request = (!iso_cycle && 
      ((best_req.async & *apm) >= (CURRENT & *apm)));

  // i_advance_interval returns true if all inbound isoch requests are known and no requests
  // remain for the current isoch interval
  // a_advance_interval returns true if all inbound async requests are known and no requests
  // remain for the current async interval (including NONE_EVEN/ODD requests used to throttle
  // progression of fairness intervals
  *i_advance_interval = (!requests_unknown && 
      ((best_req.isoch & *ipm) < (ISOCH_IN_PHASE & *ipm)));
  *a_advance_interval = (!requests_unknown && 
      ((best_req.async & *apm) < (odd_async_phase ? NONE_EVEN & *apm : NONE_ODD & *apm)));

  return (best_req);
}

boss_eop_status test_requests(int *best_port, boolean grant_received_from_senior,
                              ArbState *received_grant) {

  int bip = NPORT;                      // best isochronous request port number
  int bap = NPORT;                      // best asynchronous request port number
  byte apm;                             // mask to select the in-phase async requests
  byte ipm;                             // mask to select the in-phase isoch requests
  boolean in_phase_isoch_request, in_phase_async_request;
  boolean i_advance_interval, a_advance_interval;
  BetaRequestCode best_req;
  pktType next_format;                  // holds format and speed of next packet PHY
  speedCode next_speed;                 // will transmit if it GRANTs itself
  boolean request_to_service;
      
  *best_port = NPORT; 

  best_req = bestReq (&apm, &ipm, &bap, &bip, &in_phase_isoch_request, &in_phase_async_request,
                      &i_advance_interval, &a_advance_interval); 
      // find best request

  //  Refrain from passing a grant received from a junior port to either a link or 
  //  another junior port when
  //  (a) the bus is in async phase, and
  //  (b) the cycle master is outside the beta cloud (!B_node_root), and
  //  (c) a cycle start is expected

  if (!grant_received_from_senior && !iso_cycle && 
      !B_node_root && !(link_CS_indications && accelerating)) {
    // kill all async BOSS requests
    // Note that a priority request from a Legacy cycle master to send the cycle start packet
    // will be picked up in A0:Idle by the arb_OK() test. 
    in_phase_async_request = FALSE;
  }

  request_to_service = TRUE;  // Until proven otherwise
  if (own_req.async == BORDER)    // only when senior border
    request_to_service = FALSE;
  else if ((*received_grant == GRANT_ISOCH) && in_phase_isoch_request)
    *best_port = bip;
  else if ((*received_grant == GRANT) && in_phase_async_request)
    *best_port = bap;
  else if ((*received_grant == DATA_END) && (best_req.isoch == ISOCH_CURRENT)) {
    *best_port = bip;
    *received_grant = GRANT_ISOCH;  // upgrade to an explicit isoch grant
  }
  else
    request_to_service = FALSE;

  if (request_to_service) {
    // normally PHY can go ahead and grant the best port or the link at this stage
    // however, need to take care to prevent the possibility of 
    // concatenating a Legacy S100 packet onto a Legacy >S100 packet
    // In this case, PHY needs to give a grant to the senior port, who also
    // has to ensure that this situation cannot happen

    if (*best_port == NPORT) 
      // Since PHY prefers to grant itself, determine what format and speed packet
      // would be sent next
      if (link == Legacy_Link) {
        // Actual requests from Legacy link aren't processed in bestReq, so only
        // need to consider PHY initiated requests
        if (restore_request) {
          next_format = BETA;
          next_speed = S100;
        }
        else { // a loop_test_request
          next_format = LEGACY;
          next_speed = S100;
        }
      }
      else {   // not a Legacy_Link
        if ((*received_grant == GRANT_ISOCH) || 
            (*received_grant == DATA_END)) {  // grant types which launch isoch
          next_format = i_format;
          next_speed = i_speed;
        }
        else if (cycle_start_request) {
          next_format = cyc_start_format;
          next_speed = cyc_start_speed;
        }
        else if (restore_request) {
          next_format = BETA;
          next_speed = S100;
        }
        else if (loop_test_request) {
          next_format = LEGACY;
          next_speed = S100;
        }
        else { // an async request
          next_format = a_format;
          next_speed = a_speed;
        }
      }
    else { // Best port isn't the local PHY, so have to assume the worst and that
           // the next packet might ultimately result in a Legacy S100 packet
      next_format = LEGACY;
      next_speed = S100;
    }

    if (!B_bus && (cur_format == LEGACY) && (cur_speed != S100) && 
        (next_format == BETA || next_speed == S100))
      // just finishing a non-S100 Legacy packet and the next packet could
      // ultimately appear as an S100 Legacy packet.
      return(grant_senior);
    else {
      if (*best_port == NPORT) return(grant_link);
      if (*best_port == senior_port) return(grant_senior);
      else return(grant_junior);
    }
  }
  // nothing to service
  if (*received_grant == DATA_END)  // implicit grant, pass on
    return(grant_senior);
  else if (B_bus) {
    // Check for safe end of intervals, taking care never to
    // end the interval if out of sync with the bus or the grant came from
    // a senior port (indicates a cancelled request which can temporarily
    // block other in-phase requests)

    if (iso_cycle && i_advance_interval &&
        (*received_grant == GRANT_ISOCH) && !grant_received_from_senior) {
      // end of isoch interval
      send_async_start_token = TRUE;
      return(boss_management_actions);
    } else if (!iso_cycle && a_advance_interval && !did_arbrst && 
               (*received_grant == GRANT) && !grant_received_from_senior) {
      // create arb reset if no current phase or left over previous phase requests
      odd_async_phase = !odd_async_phase;
      arb_reset = TRUE;
      did_arbrst = TRUE;                // only once
      return(boss_management_actions);
    } else return(grant_senior);      
  } else //hybrid bus with explicit grant, free stuck DS ports if necessary
    if (cur_format != LEGACY) return(grant_null);
    else return(grant_senior);
}

void boss_end_packet_actions(boolean grant_received_from_senior, ArbState grant_type) {

  // make sure that any Legacy packet which is non-s100 is terminated with DATA_END
  // otherwise it is possible to generate a Legacy >s100 concatenated by a S100 Legacy, 
  // which is prohibited.
  // cannot terminate with DATA_NULL

  // End of packet, determine if the local node is boss and should grant a pending request
  // If so, the grant_port = favorite in-phase request
  
  // If granting senior port, don't deny junior ports.
  // If granting a junior port
  // send GNT GNT on grant port, or nothing if granting the link (grant port = NPORT)
  // send data_null on other Beta ports
  // send DP on DS ports
  // wait 2 x max (grant port time or packet time)

  // if granting the link or a PHY action (grant port is NPORT) then set grant_self 
  // in order to 
  // loop back on exit from TX actions to get the next packet from the link.
  
  // if BOSS and no port to grant, and hybrid bus, then pass a grant to senior port if suitable
  // otherwise send a token if possible

  int i;
  boss_eop_status req_status;

  // find best isoch and async request
  req_status = test_requests(&requesting_port, grant_received_from_senior, &grant_type);
  switch (req_status) {
    case grant_senior:
      if (grant_received_from_senior) {     // can't grant back to senior,
        stop_tx_packet(grant_type, NPORT);  // so grant self and 
        send_null_packet = TRUE;            // schedule TX of a null packet
        grant_self = TRUE;
      } else {
        stop_tx_packet(grant_type, senior_port);
        requesting_port = NPORT;

        // set OK_to_grant for proxy_root's use in A0:Idle if end of subaction detected
        // Special care is taken at the proxy_root during the asynchronous interval if
        // attached to a Legacy link to ensure that a priority bus request for the cycle
        // start packet can be issued if required 
        if (grant_type == DATA_END) {  // still expecting end of subaction
          OK_to_grant = FALSE;
          defer_grant = FALSE;
        } else if (!iso_cycle && (link == Legacy_Link)) { // await possible PriReq for cycle start
          OK_to_grant = FALSE;
          defer_grant = TRUE;
        } else {  // explicit end of subaction, no need to defer grant in A0:Idle 
          OK_to_grant = TRUE;
          defer_grant = FALSE;
        }
      }
      return;
    case grant_link:
      stop_tx_packet(grant_type, NPORT);
      grant_to_give = grant_type;
      grant_self = TRUE;
      if (!iso_cycle) isbr_OK = isbr;
      return;
    case grant_null:
      stop_tx_packet(grant_type, NPORT);
      send_null_packet = TRUE;          // schedule TX of a null packet
      grant_self = TRUE;
      return;
    case grant_junior:
      stop_tx_packet(grant_type, requesting_port);
      grant_to_give = grant_type;
      return;
    case boss_management_actions:
      if (receive_port != NPORT) {
        portTarb(receive_port, DATA_NULL);  // need to send DN on the receive port
        // and the s_t_p (below) will spend time which will ensure it gets out
      }
      stop_tx_packet(grant_type, NPORT); 
      do {                              // repeat gaps
        gap_repeat_actions(FALSE, NPORT);
        // having advanced the gap, now look for a favorite request again
        grant_type = GRANT;
        req_status = test_requests(&requesting_port, grant_received_from_senior, &grant_type);
        switch (req_status) {
          case grant_senior:
            if (!proxy_root) {
              portTarb(senior_port, GRANT);
              wait_symbol_time(port_speed[senior_port]);
              wait_symbol_time(port_speed[senior_port]);
              did_arbrst = FALSE;         // only relevant to a B_bus
            }
            OK_to_grant = TRUE;         // for proxy_root's use in A0:Idle
            defer_grant = FALSE;
            requesting_port = NPORT;    // so that node goes to Idle
            return;
          case grant_link:
            grant_to_give = grant_type;
            grant_self = TRUE;
            if (!iso_cycle) isbr_OK = isbr;
            did_arbrst = FALSE;         // only relevant to a B_bus
            return;
          case grant_null:
            send_null_packet = TRUE;    // schedule TX of a null packet
            grant_self = TRUE;
            did_arbrst = FALSE;         // only relevant to a B_bus
            return;
          case grant_junior:
            did_arbrst = FALSE;         // only relevant to a B_bus
            return;
          case boss_management_actions:
            break;                      // to go round the loop again - won't happen more than twice!
        }
      } while (TRUE);
  }
}

void gap_repeat_actions(boolean out_of_packet, int token_receive_port) {

  if (send_async_start_token) {
    if (link == B_Link)  { 
      PH_DATA_indication(PH_SUBACTION_GAP, 0, 0, 0);
      if (bus_initialize_active) {        // End of self_ID process for whole bus?    
        PH_EVENT_indication(PH_BUS_RESET_COMPLETE, 0, 0);
        bus_initialize_active = FALSE;
      }
    }
    send_control(ASYNC_START, out_of_packet, token_receive_port);
    send_async_start_token = FALSE;
    sent_async_start_token = TRUE;
    if (iso_cycle) {
      iso_cycle = FALSE;
      odd_isoch_phase = !odd_isoch_phase;  // Advance phase at end of isoch interval
    }
  }
  if (arb_reset) {
    if (odd_async_phase) {
      if (link == B_Link) PH_DATA_indication(PH_ARB_RESET_ODD, 0, 0, 0);
      send_control(ARB_RESET_ODD, out_of_packet, token_receive_port);
    }
    else {
      if (link == B_Link) PH_DATA_indication(PH_ARB_RESET_EVEN, 0, 0, 0);
      send_control(ARB_RESET_EVEN, out_of_packet, token_receive_port);
    }
    odd_async_phase = !odd_async_phase;
    arb_enable = TRUE;         // Reenable fair arbitration for Legacy Links
    arb_reset = FALSE;
    sent_arb_reset = TRUE;
  }
}
