// node.c

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

timer bias_timer;                       // Timer for bias filter

int loop_test_random();                 // not defined by C code, see spec for requirements

void node_status_monitor() {            // Continuously monitor node status in all states
  int active_ports = 0;                 // count of active ports, including ports in standby
  int i, suspended_ports = 0;
  boolean border = link == Legacy_Link; // starting assumption
  boolean l_isolated_node = TRUE;
  speedCode l_min_operating_speed = PHY_SPEED; // starting assumption          
  if (!power_reset) {
    for (i = 0; i < NPORT; i++) {
      if ((active[i] || suspend_request[i] || do_standby[i]) && 
        Beta_mode[i] && (port_speed[i] < l_min_operating_speed))
        l_min_operating_speed = port_speed[i];
      if (active[i] || in_standby[i]) {
        active_ports++;                 // Necessary to deduce boundary node status
        l_isolated_node = FALSE;
        if (!Beta_mode[i])              // active port is a 1394a port
          border = TRUE;
      } else if (connected[i] && !disabled[i] && !in_standby[i] && 
          !loop_disabled[i] && !untested_state[i])
        suspended_ports++;              // Other part of boundary node definition
    }
  }
  border_node = border;                 // the node is a border or otherwise
  boundary_node = (active_ports > 0 && suspended_ports > 0);
  all_ports_suspended = (active_ports == 0) && (suspended_ports > 0);
  isolated_node = l_isolated_node ;     // Remains TRUE if no active port(s) found
  min_operating_speed = l_min_operating_speed;    
}

boolean suspend_in_progress() {         // TRUE if any port suspending
   int i;
   for (i = 0; i < NPORT; i++)
      if (suspend_request[i])
         return(TRUE);
   return(FALSE);
}

boolean resume_in_progress() {          // TRUE if any port resuming
   int i;
   for (i = 0; i < NPORT; i++)
      if (resume[i])
         return(TRUE);
   return(FALSE);
}

void suspend_all_active_ports() {
  int j;
  for (j = 0; j < NPORT; j++)
    if (active[j])                      // Otherwise all active ports become suspend initiators
      suspend_request[j] = TRUE;
}

void resume_all_ports() {
  int j;
  for (j = 0; j < NPORT; j++)
    if ((port_state[j] == P3) || (port_state[j] == P4) || (port_state[j] == P5))
      resume[j] = TRUE;                 // Resume all suspended ports
}

boolean resume_rx_OK() {
  int j;
  boolean resume_rx_OK_val = TRUE;      // Assume this is true until proven false
  for (j=0; j < NPORT; j++) 
    resume_rx_OK_val &= (!resume[j] || rx_ok[j]);
     // Note that for resuming Beta ports presence of receive_ok is not
     // enough, need to allow sufficient time for port sync also. For
     // DS ports, rx_ok indicates state of detected bias

  return (resume_rx_OK_val);
}

boolean restore_in_progress() {         // TRUE if any port restoring
   int i;
   for (i = 0; i < NPORT; i++)
      if (restore[i])
         return(TRUE);
   return(FALSE);
}

void restore_port() {
  int j;
  for (j = 0; j < NPORT; j++)
    if (in_standby[j] && connected[j] && !proxy[j])
      restore[j] = TRUE;                // Restore port in standby (if any) if on leaf
}


void bias_status() {                    // Continuously running bias update code 
  static boolean bias_filter[NPORT];    // TRUE when applying hysteresis to bias_detect circuit
  int i;
  if (power_reset) { 
    for (i = 0; i < NPORT; i++) 
      bias[i] = bias_filter[i] = FALSE;
    while (power_reset)
      ;
    return;
  }
  for (i = 0; i < NPORT; i++) {
    if (!Beta_mode_only_port[i]) {
	    if (sending_tone[i]) {
	      bias_filter[i] = FALSE;           // abandon bias filtering on this port whilst sending a tone
	      continue;                         // and move on to next port
	    }
	    if (!(PMD_STATUS_request(i) & PMD_BIAS_DETECT)) {
	      bias_filter[i] = FALSE;
	      bias[i] = FALSE     ;             // Report immediately
	    } else if (bias_filter[i]) {        // Filtering positive bias transition?
	      if (bias_timer >= BIAS_FILTER_TIME) {
	        bias_filter[i] = FALSE;         // Done filtering
	        bias[i] = TRUE;                 // Confirm new value in PHY register bit
	      }
	    } else if ( !disabled[i]            // detected and reported bias differ on enabled port?
	          && (PMD_STATUS_request(i) & PMD_BIAS_DETECT) != bias[i]) {
	      bias_filter[i] = TRUE;            // Yes, start a filtering period
	      bias_timer = 0;
	    }
	  }
  }
}

void send_LTP() {
  PHY_PKT tx_phy_pkt;

  tx_phy_pkt.dataQuadlet = 0;
  tx_phy_pkt.phy_ID = 0x3F;
  tx_phy_pkt.ext_type = 0xE;            // LTP packet;
  tx_phy_pkt.mode = HR_mode;
  tx_phy_pkt.G = HR_G;
  tx_phy_pkt.test_value = HR_test_value;
  PH_DATA_indication(PH_DATA_START, S100, 0, LEGACY);
  tx_quadlet(tx_phy_pkt.dataQuadlet);
  tx_quadlet(~tx_phy_pkt.dataQuadlet);
  // Keep bus for concatenation
  PH_DATA_indication(PH_DATA_PREFIX, 0, 0, 0);
  stop_tx_packet(DATA_PREFIX, NPORT);
  start_tx_packet(S100, LEGACY);        // Send data prefix & speed signal
}

void tx_PHY_packet(PHY_PKT packet_to_send, int port_num) {
  int i;
  wait_symbol_time(S100);               // don't assume alignment
  portTspeed(port_num, S100, LEGACY);   // S100 LEGACY format speed code
                                        // Beta format is also compliant
  wait_symbol_time(S100);
  portTarb(port_num, DATA_PREFIX);
  wait_symbol_time(S100);
  for (i = 0; i < 4; i++) {             // ...a byte at a time
    portT[port_num].speed = S100;
    portT[port_num].data = (packet_to_send.dataQuadlet >> 8*(3-i)) & 0xFF;
    portT[port_num].tag = DATA;
    wait_symbol_time(S100);
  }
  for (i = 0; i < 4; i++) {             // ...a byte at a time
    portT[port_num].speed = S100;
    portT[port_num].data = ((~packet_to_send.dataQuadlet) >> 8*(3-i)) & 0xFF;
    portT[port_num].tag = DATA;
    wait_symbol_time(S100);
  }
  portTarb(port_num, DATA_END);
  wait_symbol_time(S100);
  wait_symbol_time(S100);
}

PHY_PKT rx_PHY_packet(int port_num) {
  PHY_PKT packet_received;
  int byte_count;
  ArbState rx_arb_state;
  byte rx_data_byte;
  byte_count = 0;
  rx_arb_state = portRarb[port_num];
  // wait for start of packet
  while (connected[port_num] && !((rx_arb_state == SPEED) || (rx_arb_state == DATA_PREFIX))) 
    rx_arb_state = portRarb[port_num]; 
  while (connected[port_num] && (rx_arb_state != DATA_BYTE)) 
    rx_arb_state = portR_next_arb(port_num); // skip DATA_PREFIXes
  while (rx_arb_state == DATA_BYTE) {
    rx_data_byte = current_data[port_num];
    if (byte_count < 8)
      packet_received.dataBytes[byte_count] = rx_data_byte;
    byte_count++;
    rx_arb_state = portR_next_arb(port_num);
  }
  if ((byte_count != 8) ||
    (packet_received.dataQuadlet != ~packet_received.checkQuadlet))
    packet_received.dataQuadlet = 0;    // kill bad packets
  return (packet_received);
}

void con_mgmnt_granted() { 
                                        // called from tx_actions once arbitration has been won
  int i;
  PHY_PKT config_pkt;
  if (restore_request) {
    for (i = 0; i < NPORT; i++) {
      if (restore[i] && bport_sync_ok[i]) {  // find a restoring port (do restores one at a time)
        if (proxy[i]) {                 // uncle actions
          // send updated information to peer port
          config_pkt.dataQuadlet = 0;
          config_pkt.root_ID = standby_phy_ID[i];
          config_pkt.R = 1; // to avoid both R and T being zero
          config_pkt.T = gap_count_reset_disable ? 1: 0;
          config_pkt.gap_count = gap_count;
          config_pkt.Q = root ? 1: 0;   // included for use by PIL_FOP
          config_pkt.P = PMD_PS_request() ? 1: 0;   // included for use by PIL_FOP
          config_pkt.notify_odd_async_phase = odd_async_phase ? 1: 0;
          config_pkt.notify_reset = reset_notify[i] ? 1: 0;
          if ((breq == FAIR_REQ) || (breq == PRIORITY_REQ)) {
            breq = NO_REQ;                      // Cancel the request
            PH_ARB_confirmation(PH_LOST, 0, 0); // And let the link know
          }
          PH_DATA_indication(PH_DATA_PREFIX, 0, 0, 0); // Send notification of bus activity
          start_tx_packet(S100, BETA);  // Send null packet on active ports
          tx_PHY_packet(config_pkt, i); // and the special configuration packet on this port
                                        // packet ends with a quiet grant to nephew
          PH_DATA_indication(PH_DATA_END, 0, 0, 0);
          proxy[i] = FALSE;
        } else {                        // nephew actions
          // restored leaf node - receive updated information from peer port
          config_pkt = rx_PHY_packet(i);
          start_tx_packet(S100, BETA);   // send null packet
          physical_ID = config_pkt.root_ID;
          gap_count = config_pkt.gap_count;
          gap_count_reset_disable = config_pkt.T == 1;
          odd_async_phase = config_pkt.notify_odd_async_phase;
          if (config_pkt.notify_reset == 1) { // inform nephew of a previous reset on the uncle's bus
            PH_EVENT_indication(PH_RESTORE_RESET, 0, 0);
            waitPH_EVENT_response();
          }
          PH_EVENT_indication(PH_SELF_IDENTITY, physical_ID, root); // Unsolicited Register 0
          PH_DATA_indication(odd_async_phase ? PH_ARB_RESET_ODD : PH_ARB_RESET_EVEN, 0, 0, 0);
          immediate_restore_request = FALSE;
          boss_end_packet_actions(FALSE, GRANT);  // not in iso cycle
        }   
        restore[i] = FALSE;             // this lets the port transition to active
        break;                          // done this port, don't do any more
      }
    }
    restore_request = FALSE;
    for (i = 0; i < NPORT; i++)         // check if any more to do
      if (restore[i] && bport_sync_ok[i]) restore_request = TRUE;

  } else {                              // request for loop test
    max_occupancy_timer = 0; // Start the occupancy timer.
    in_control = TRUE;

    if ((breq == FAIR_REQ) || (breq == PRIORITY_REQ)) {
      breq = NO_REQ;                      // Cancel the request
      PH_ARB_confirmation(PH_LOST, 0, 0); // And let the link know
    }
    PH_DATA_indication(PH_DATA_PREFIX, 0, 0, 0); // Send notification of bus activity
    start_tx_packet(S100, LEGACY); // Send data prefix & speed signal

    while (loop_test_request)
      if (need_new_LTP) {
        send_LTP();
        test_interval_timer = 0;
        need_new_LTP = FALSE;
      } 
    in_control = FALSE; // then release the bus.
    isbr_OK = isbr;  // While the local node was a controlling node, any port which
                     // received an attach request or a test port which
                     // received a bus reset will have set isbr
    if (!isbr_OK) {
      PH_DATA_indication(PH_DATA_END, 0, 0, 0);
      stop_tx_packet(DATA_END, NPORT);
    }
  }
}

void loop_detector() {             // continuously running
  int i;
  if (power_reset) {
    while (power_reset)
      ; 
    return;
  }
  while (loop_to_detect) {              // exit from here if tree_ID is successful, possibly only after 
                                        // the local beta ports have been put into the untested state
    if (T0_timeout || (max_arb_state_timeout()) || (reset_count > 3)) { 
      // tree_ID has detected a loop
      T0_timeout = FALSE;
      for (i = 0; i < NPORT; i++)
        if (Beta_mode[i] && active[i]) {
          // note that the transition out of active might now allow an exit from T0
          force_disconnect[i] = TRUE;
        }
    }
  }
}

boolean attach_pending() {         // TRUE if any port set to attach
   int i;
   for (i = 0; i < NPORT; i++)
      if (attach[i])
         return(TRUE);
   return(FALSE);
}

int new_test_value() {
  return (loop_test_random());   // see specification for requirements
}

void loop_test_interval_actions() { // continuously running
  static int collision_count;
  byte received_LTS;
  int i;
  if (power_reset) {
    collision_count = 0;
    HR_test_value = new_test_value();
    for (i = 0; i < NPORT; i++)
      port_under_test[i] = FALSE;
    send_attach = FALSE;
    found_loop = FALSE;
    loop_test_request = FALSE;
    while (power_reset)
      ;
    return;
  }

  if (NPORT==1) return;  // single port phy doesn't require loop testing

  test_port = NPORT;            // no port is selected (yet) for testing

  // First, complete any unfinished attaches, caused by reception of ATTACH_REQUEST
  // during a period when no local port was eligible for testing.  Also, wait for any
  // controlling node on the local segment to complete it's ATTACH attempt.
  while (attach_pending() || (HR_mode == ATTACH_IN_PROGRESS))
    ;

  // Now select a unique port to test
  // First try to find a port which is receiving an LTS lower than the test value
  // If unsuccessful, then choose a port which is receiving an LTS equal to the test value
  // Never select a port which is receiving an LTS higher than the test value
  for (i = 0; i < NPORT; i++)
    if (untested[i] && (portRarb[i] == DATA_BYTE)) {
      if ((HR_mode << 7 | HR_G << 6 | HR_test_value) > current_data[i]) {
        test_port = i;  // found a port to test with an LTS lower than the
        break;          // test value, so test immediately
      } 
      else if ((HR_mode << 7 | HR_G << 6 | HR_test_value) == current_data[i])
        test_port = i;  // found a colliding port, remember for testing in case
                        // another preferred port is not found
    }

  if (test_port != NPORT) {
    collision_count = 0; // Clear any old collisions
    port_under_test[test_port] = TRUE;        
    loop_test_request = TRUE;         // and request the bus
    while (port_under_test[test_port]) {

      // First, get the latest LTS received on the test port
      if (portRarb[test_port] == DATA_BYTE)
        received_LTS = current_data[test_port];
          
      // Check if need to release bus to complete ATTACH_REQUESTS received on other ports
      // Also check if another controlling node on the local segment has initiated an attach
      // in which case an immediate abort is appropriate so that a new test port can be
      // selected
      if (attach_pending() || (HR_mode == ATTACH_IN_PROGRESS)) {
        port_under_test[test_port] = FALSE;
      }

      else if (!untested[test_port]) {  // Loss of sync?
        port_under_test[test_port] = FALSE;
      }

      // Check if the test-port is receiving a dominant LTS from its peer port.
      else if (((received_LTS & 0x80) != 0) ||        // Attach in progress, immediate abort ...
          (!need_new_LTP && test_interval_timer >= TEST_INTERVAL && 
           received_LTS > (HR_mode << 7 | HR_G << 6 | HR_test_value)))  {
        // ... or the node has waited long enough for the last xmitted LTP to reach all
        // nodes in the network, and the received LTS is dominant, so abort
        // testing on this port (go to next).
        port_under_test[test_port] = FALSE;
      }

      // Check if there's a collision between the xmitted LTS and the rcv'd LTS
      else if (in_control && (received_LTS == (HR_mode << 7 | HR_G << 6 | HR_test_value))) {
        // There's a collision between xmitted LTS and received LTS.
        collision_count++;
        if (collision_count == COLLISION_LIMIT) {
          found_loop = TRUE; // flag to send port_under_test to loop disabled state
          while (untested[test_port])
            ;  // Wait for port to acknowledge command to enter loop_disabled state
          port_under_test[test_port] = FALSE;
        } else {
          HR_test_value = new_test_value();
          HR_G = (HR_G == 0) ? 1 : 0;
          need_new_LTP = TRUE;
        }
      }

      // Check if the test-port can be attached.
      else if (in_control && test_interval_timer >= TEST_INTERVAL && !need_new_LTP) {
        // Test interval has expired, and the local node is in control, without a collision
        HR_mode = ATTACH_IN_PROGRESS; // Should not receive new attach-requests
                                      // on any port once this is set.
        need_new_LTP = TRUE;
        send_attach = TRUE; // Flag for the test port to attempt attach
        while (!attach[test_port] && untested[test_port])
          ;  // Wait for port to prepare for bus reset (attach) or
             // to "complete" testing due to loss of sync
        if (!untested[test_port]) {  // did the port lose sync?
          HR_mode = LTP_TEST;  // need to flush out the ATTACH_IN_PROGRESS
          need_new_LTP = TRUE;
          while (need_new_LTP)
            ;
        }
        port_under_test[test_port] = FALSE;
      }
    }  // end of while port_under_test loop
    loop_test_request = FALSE;  // stop requesting the bus and release if necessary
    while (in_control || attach_pending())
      ;  // Wait until controlling node has released the bus and any outstanding attaches
         // (including any on the test_port) have completed
    if (HR_mode != LTP_TEST) {
      // On an isolated node, an attach request sent to a test port which subsequently
      // loses synchronization can leave HR_mode set to ATTACH_IN_PROGRESS after clearing
      // the attach flag.  As such, the mode needs to be reset as if a bus_reset had occured,
      // but does not need to be sent immediately in an LTP since the node is isolated
      HR_mode = LTP_TEST;
      need_new_LTP = TRUE;
    }
    found_loop = FALSE;
    send_attach = FALSE;  // No longer waiting for an attach, okay to move onto another port
  }  // finished with current test_port, allow next test_port to be selected
}

