//
// Copyright (c) 2010-2025 Antmicro
//
// This file is licensed under the MIT License.
// Full license text is available in 'licenses/MIT.txt'.
//
#include "renode_bridge.h"

#ifdef RENODE_NATIVE_INTERFACE
// librenode.h should be included after renode_bridge.h
// to have renode_message struct already defined.
#define _RENODE_BRIDGE_H
#include "librenode.h"
#endif
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <chrono>
#include <thread>
#include <cinttypes>
#include <array>
#ifdef __linux__
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#endif

#include "socket-cpp/Socket/TCPClient.h"

// ================================================================================
//  > Debug printing
// ================================================================================

static void print_renode_message(renode_message *message) {
  if (message->action == TIMESYNC)
    return;
  uint64_t thread_id = 0;
  { // Get a cross-platform thread identifier
    std::hash<std::thread::id> hasher;
    thread_id = hasher(std::this_thread::get_id());
  }
  printf("[0x%" PRIX64 "][RENODE MESSAGE] Action: ", thread_id);
  switch (message->action) {
  case INIT:
    printf("INIT");
    break;
  case READ:
    printf("READ");
    break;
  case WRITE:
    printf("WRITE");
    break;
  case TIMESYNC:
    printf("TIMESYNC");
    break;
  case GPIOWRITE:
    printf("GPIOWRITE");
    break;
  case RESET:
    printf("RESET");
    break;
  default:
    printf("INVALID");
  }
  printf(" | Address: 0x%" PRIX64, message->address);
  printf(" | Payload: 0x%" PRIX64, message->payload);
  printf(" | ConnIdx: %u\n", message->connection_index);
}

static void print_transaction_status(tlm::tlm_generic_payload *payload) {
  tlm::tlm_response_status status = payload->get_response_status();
  std::string response_string = payload->get_response_string();
  printf("Renode transport status: %s\n", response_string.c_str());
}

// ================================================================================
//  > Renode Bridge SystemC module
// ================================================================================

static void initialize_payload(tlm::tlm_generic_payload *payload,
                               const renode_message *message, uint8_t *data) {
  tlm::tlm_command command = tlm::TLM_IGNORE_COMMAND;
  switch (message->action) {
  case WRITE:
  case WRITE_REGISTER:
    command = tlm::TLM_WRITE_COMMAND;
    break;
  case READ:
  case READ_REGISTER:
    command = tlm::TLM_READ_COMMAND;
    break;
  default:
    assert(!"Only WRITE and READ messages should initialize TLM payload");
  }

  payload->set_command(command);
  // Right now the address visible to SystemC is directly the offset
  // from Renode; i. e. if we write to address 0x9000100 and the peripheral
  // address is 0x9000000, then address in SystemC will be 0x100.
  payload->set_address(message->address);
  payload->set_data_ptr(data);
  payload->set_data_length(message->data_length);
  payload->set_byte_enable_ptr(nullptr);
  payload->set_byte_enable_length(0);
  payload->set_streaming_width(message->data_length);
  payload->set_dmi_allowed(false);
  payload->set_response_status(tlm::TLM_INCOMPLETE_RESPONSE);
}

static uint64_t sc_time_to_us(sc_core::sc_time time) {
  // Converts sc_time to microseconds count.
  return static_cast<int64_t>(time.to_seconds() * 1000000.0);
}

static uint64_t
perform_transaction(renode_bridge::renode_bus_initiator_socket &socket,
                    tlm::tlm_generic_payload *payload) {
  sc_core::sc_time delay = sc_core::SC_ZERO_TIME;
  socket->b_transport(*payload, delay);
#ifdef VERBOSE
  print_transaction_status(payload);
#endif
  return sc_time_to_us(delay);
}

static void terminate_simulation(int exitstatus) {
  sc_core::sc_stop();
  exit(exitstatus);
}

static void connect_with_retry(CTCPClient* socket, const char* address, const char* port) {
  constexpr uint32_t max_retry_s = 10;
  constexpr uint32_t retry_interval_s = 2;

  uint32_t retry_s = 0;
  while (!socket->Connect(address, port)) {
    fprintf(stderr, "Failed to connect to Renode, retrying in %us...\n", retry_interval_s);
    std::this_thread::sleep_for(std::chrono::seconds(retry_interval_s));
    retry_s += retry_interval_s;
    if(retry_s >= max_retry_s) {
        fprintf(stderr, "Maximum timeout reached. Failed to initialize Renode connection. Aborting.\n");
        terminate_simulation(1);
    }
  }
}

static void handle_backward_response_native(void* opaque_ptr, renode_message message) {
    renode_bridge* bridge = (renode_bridge*)opaque_ptr;
    bridge->handle_backward_response_from_native(message);
}

static void handle_backward_response_dmi_native(void* opaque_ptr, dmi_message message) {
  renode_bridge* bridge = (renode_bridge*)opaque_ptr;
  bridge->handle_backward_response_dmi_from_native(message);
}

static void handle_forward_request_native(void* opaque_ptr, renode_message message) {
  renode_bridge* bridge = (renode_bridge*)opaque_ptr;
  bridge->handle_forward_request_from_native(message);
}

void renode_bridge::handle_backward_response_from_native(renode_message message)
{
  bw_response.add(message);
}

void renode_bridge::handle_backward_response_dmi_from_native(dmi_message message)
{
  dmi_response.add(message);
}

void renode_bridge::handle_forward_request_from_native(renode_message message)
{
  fw_request.add(message);
}

renode_message renode_bridge::receive_backward_response()
{
  if(native) {
    return bw_response.take();
  } else {
    renode_message message;
    backward_connection->Receive((char *)&message, sizeof(renode_message));
    return message;
  }
}

dmi_message renode_bridge::receive_backward_response_dmi()
{
  if(native) {
    return dmi_response.take();
  } else {
    dmi_message response;
    backward_connection->Receive((char *)&response, sizeof(dmi_message));
    return response;
  }
}

renode_message renode_bridge::receive_forward_request(bool* closed)
{
  if(native) {
    *closed = false;
    return fw_request.take();
  } else {
    renode_message message;
    int nread =
        forward_connection->Receive((char *)&message, sizeof(renode_message));
    *closed = nread <= 0;
    return message;
  }
}

void renode_bridge::send_backward_request(renode_message *message) {
  if (native) {
#ifdef RENODE_NATIVE_INTERFACE
    renode_systemc_send_backward_request(*message, mach.c_str(), peri.c_str());
#endif
  } else {
    backward_connection->Send((char *)message, sizeof(renode_message));
  }
}

void renode_bridge::send_forward_response(renode_message *message) {
  if (native) {
#ifdef RENODE_NATIVE_INTERFACE
    renode_systemc_send_forward_response(*message, mach.c_str(), peri.c_str());
#endif
  } else {
    forward_connection->Send((char *)message, sizeof(renode_message));
  }
}

SC_HAS_PROCESS(renode_bridge);
renode_bridge::renode_bridge(sc_core::sc_module_name name, const char *address,
                             const char *port, bool native, std::string mach, std::string peri)
    : sc_module(name),
      initiator_socket("initiator_socket"),
      register_initiator_socket("register_initiator_socket"),
      fw_connection_initialized(false),
      native(native),
      mach(mach),
      peri(peri) {
  if (native) {
#ifdef RENODE_NATIVE_INTERFACE
    auto rc = renode_systemc_setup_renode_bridge((void*)this, (void*)handle_backward_response_native, (void*)handle_backward_response_dmi_native, (void*)handle_forward_request_native, mach.c_str(), peri.c_str());
    if (rc != RENODE_SUCCESS) {
      fprintf(stderr, "Failed to initialize native interface. Aborting.\n");
      terminate_simulation(1);
    }
#else
    fprintf(stderr, "Failed to initialize native interface. Aborting.\n");
    terminate_simulation(1);
#endif
  }
  SC_THREAD(forward_loop);
  SC_THREAD(on_port_gpio);
  for (int i = 0; i < NUM_GPIO; ++i) {
    sensitive << gpio_ports_in[i];
  }

  SC_METHOD(on_init_ns_vtor);
  sensitive << init_vtor_ns_in;
  SC_METHOD(on_init_s_vtor);
  sensitive << init_vtor_s_in;

  bus_target_fw_handler.initialize(this, 0);
  cpu_target_fw_handler.initialize(this, 0);

  target_socket.bind(bus_target_fw_handler.socket);
  for (int i = 0; i < NUM_DIRECT_CONNECTIONS; ++i) {
    dc_initiators[i].initialize(this);
    dc_targets[i].initialize(this, i + 1);
    direct_connection_targets[i].bind(dc_targets[i]);
    direct_connection_initiators[i].bind(dc_initiators[i]);
  }

  bus_initiator_bw_handler.initialize(this);
  cpu_initiator_bw_handler.initialize(this);
  initiator_socket.bind(bus_initiator_bw_handler);
  register_initiator_socket.bind(cpu_initiator_bw_handler);

  payload.reset(new tlm::tlm_generic_payload());

  if(native) {
#ifdef RENODE_NATIVE_INTERFACE
    // Execute on a background thread.
    // Renode is going to send a forward request with INIT message
    // and wait for response from us in a blocking way.
    // We need to receive that message in forward_loop SC_THREAD
    // and send response to unblock Renode.
    std::thread run_resc
    {
      [mach, peri]
      ()-> void
      {
        // Execute on a background thread as Renode waits for the renode_bridge connection.
        renode_systemc_init_native_connection(mach.c_str(), peri.c_str());
        renode_exec_command("start");
      }
    };
    run_resc.detach();
#endif
  } else {
    forward_connection.reset(new CTCPClient(NULL, ASocket::NO_FLAGS));
    connect_with_retry(forward_connection.get(), address, port);
  
    backward_connection.reset(new CTCPClient(NULL, ASocket::NO_FLAGS));
    connect_with_retry(backward_connection.get(), address, port);
  }
}

renode_bridge::~renode_bridge() {
  if (native) {
#ifdef RENODE_NATIVE_INTERFACE
    renode_systemc_teardown_native_connection(mach.c_str(), peri.c_str());
#endif
  } else {
    forward_connection->Disconnect();
    backward_connection->Disconnect();
  }
}

bool renode_bridge::initialize_connection(renode_message *message, int64_t *out_max_desync_us) {
  // Receive INIT message from Renode and use it to setup connection, e. g.
  // time synchronization period.
  // This is done during SystemC elaboration, once per lifetime of the module.
  bool closed;
  *message = receive_forward_request(&closed);
  if (closed) {
    return false;
  }

#ifdef VERBOSE
  print_renode_message(message);
#endif

  if (message->action != renode_action::INIT) {
    fprintf(stderr, "Renode bridge connection error: missing INIT action.\n");
    return false;
  }
  *out_max_desync_us = static_cast<int64_t>(message->payload);

  // Acknowledge initialization is done.
  send_forward_response(message);

#ifdef VERBOSE
  printf("Connection to Renode initialized with timesync period %" PRId64 " us.\n",
  *out_max_desync_us);
#endif
  return true;
}

void renode_bridge::forward_loop() {
  // Processing of requests initiated by Renode.
  uint8_t data[8] = {};

  renode_message message;
  bool closed;

  int64_t max_desync_us;
  if (!initialize_connection(&message,
                             &max_desync_us)) {
    fprintf(stderr, "Failed to initialize Renode connection. Aborting.\n");
    terminate_simulation(1);
    return;
  }
  fw_connection_initialized = true;

  while (true) {
    memset(data, 0, sizeof(data));

    message = receive_forward_request(&closed);
    if (closed) {
#ifdef VERBOSE
      printf("Connection to Renode closed.\n");
#endif
      break;
    }

#ifdef VERBOSE
    print_renode_message(&message);
#endif

    // Choose the appropriate initiator socket to initiate the transaction with.
    renode_bus_initiator_socket *initiator_socket = nullptr;
    if (message.connection_index > NUM_DIRECT_CONNECTIONS) {
      fprintf(stderr,
              "Invalid connection_index %u, exceeds available number of direct "
              "connections (%u)\n",
              message.connection_index, NUM_DIRECT_CONNECTIONS);
      return;
    }

    if (message.connection_index == 0) {
      initiator_socket = &this->initiator_socket;
    } else {
      initiator_socket =
          &this->direct_connection_initiators[message.connection_index - 1];
    }

    switch (message.action) {
    case renode_action::WRITE: {
      handle_write(*initiator_socket, message, data);
    } break;
    case renode_action::READ: {
      handle_read(*initiator_socket, message, data);
    } break;
     case renode_action::WRITE_REGISTER: {
      handle_write(register_initiator_socket, message, data);
    } break;
     case renode_action::READ_REGISTER: {
      handle_read(register_initiator_socket, message, data);
    } break;
    case renode_action::TIMESYNC: {
      // Renode drives the simulation time. This module never leaves the delta
      // cycle loop until a TIMESYNC with future time is received. It then waits
      // for the time difference between current virtual time and time from
      // TIMESYNC, allowing the SystemC simulation to progress in time. This is
      // effectively a synchronization barrier.
      int64_t systemc_time_us = sc_time_to_us(sc_core::sc_time_stamp());
      int64_t renode_time_us = (int64_t)message.payload;

      int64_t dt = renode_time_us - systemc_time_us;
      message.payload = systemc_time_us;
      if (dt > max_desync_us) {
        wait(dt, sc_core::SC_US);
      }
      message.payload = sc_time_to_us(sc_core::sc_time_stamp());
      send_forward_response(&message);
    } break;
    case renode_action::GPIOWRITE: {
      auto number = message.address;
      auto value = message.payload;
      sc_core::sc_interface *iface = gpio_ports_out[number].get_interface();
      if (iface == nullptr) {
        break;
      }
      gpio_ports_out[number]->write(value == 1);
      send_forward_response(&message);
    } break;
    case renode_action::INIT: {
      if (native) {
#ifdef RENODE_NATIVE_INTERFACE
        renode_systemc_teardown_native_connection(mach.c_str(), peri.c_str());
#endif
      } else {
        forward_connection->Disconnect();
        backward_connection->Disconnect();
      }
      terminate_simulation(0);
    } break;
    case renode_action::RESET: {
      sc_core::sc_interface *iface = reset.get_interface();
      if (iface != nullptr) {
        reset->write(true);
      }
      send_forward_response(&message);
    } break;
    default:
      fprintf(stderr, "Malformed message received from Renode - terminating simulation.\n");
      terminate_simulation(1);
    }
  }
}

void renode_bridge::invalidate_translation_blocks(uint64_t start_address, uint64_t end_address) {
    renode_message message = {};
    message.action = renode_action::TBSINVALID;
    message.address = start_address;
    message.payload = end_address;

    send_backward_request(&message);
    // Response is ignored.
    receive_backward_response();
}

void renode_bridge::handle_read(renode_bus_initiator_socket &socket, renode_message &message, uint8_t data[8]) {
  initialize_payload(payload.get(), &message, data);

  uint64_t delay = perform_transaction(socket, payload.get());

  // NOTE: address field is re-used here to pass timing information.
  message.address = delay;
  message.payload = *((uint64_t *)data);
  send_forward_response(&message);
  wait(sc_core::SC_ZERO_TIME);
}

void renode_bridge::handle_write(renode_bus_initiator_socket &socket, renode_message &message, uint8_t data[8]) {
  initialize_payload(payload.get(), &message, data);

  *((uint64_t *)data) = message.payload;

  uint64_t delay = perform_transaction(socket, payload.get());

  // NOTE: address field is re-used here to pass timing information.
  message.address = delay;
  send_forward_response(&message);

  wait(sc_core::SC_ZERO_TIME);
}

enum gpio_state {
  GPIO_NOT_SENT = -1,
  GPIO_LOW = 0,
  GPIO_HIGH = 1
};

void renode_bridge::on_port_gpio() {
  std::array<gpio_state, NUM_GPIO> last_sent_state;
  last_sent_state.fill(GPIO_NOT_SENT);
  while (true) {
    // Wait for a change in any of the GPIO ports.
    wait();

    for (int i = 0; i < NUM_GPIO; ++i) {
      if (gpio_ports_in[i].get_interface() == nullptr) {
        continue;
      }
      gpio_state current = (gpio_state)gpio_ports_in[i]->read();
      if (current == last_sent_state[i]) {
        continue; // no change, skip
      }
      last_sent_state[i] = current;

      renode_message message = {};
      message.action = renode_action::GPIOWRITE;
      message.address = i;
      message.payload = current;

      send_backward_request(&message);
      // Response is ignored.
      receive_backward_response();
    }
  }
}

void renode_bridge::service_backward_request(tlm::tlm_generic_payload &payload,
                                             uint8_t connection_idx,
                                             sc_core::sc_time &delay) {
  unsigned int bytes_done = 0;
  unsigned int bytes_remaining = payload.get_data_length();
  renode_message message = {};
  if (payload.is_read()) {
    message.action = renode_action::READ;
  } else if (payload.is_write()) {
    message.action = renode_action::WRITE;
  } else {
    return;
  }

  while (bytes_remaining) {
    message.address = payload.get_address() + bytes_done;
    message.connection_index = connection_idx;
    message.data_length = bytes_remaining > 8 ? 8 : bytes_remaining;
    bytes_remaining -= message.data_length;
    if (payload.is_write()) {
      memcpy(&message.payload, payload.get_data_ptr() + bytes_done, message.data_length);
    }

    send_backward_request(&message);
    message = receive_backward_response();

    if (payload.is_read()) {
      memcpy(payload.get_data_ptr() + bytes_done, &message.payload, message.data_length);
    }

    bytes_done += 8;
  }

  if (connection_idx == 0 && message.connection_index == 1) {
    payload.set_dmi_allowed(true);
  }

  payload.set_response_status(tlm::TLM_OK_RESPONSE);
}

void renode_bridge::init_vtor(renode_action action, vtor_in_port &port) {
  if (port.get_interface() == nullptr) {
    return;
  }

  renode_message msg = {};
  msg.action = action;
  msg.address = port->read();
  send_backward_request(&msg);
  // Response is ignored.
  msg = receive_backward_response();
}

void renode_bridge::on_init_s_vtor() {
  init_vtor(INIT_SECURE_VTOR, init_vtor_s_in);
}

void renode_bridge::on_init_ns_vtor() {
  init_vtor(INIT_NON_SECURE_VTOR, init_vtor_ns_in);
}

// ================================================================================
//   target_fw_handler
// ================================================================================

void renode_bridge::target_fw_handler::initialize(
    renode_bridge *renode_bridge, uint8_t conn_idx) {
  bridge = renode_bridge;
  connection_idx = conn_idx;
  socket.bind(*this);
}

void renode_bridge::target_fw_handler::b_transport(
    tlm::tlm_generic_payload &payload, sc_core::sc_time &delay) {
  bridge->service_backward_request(payload, connection_idx, delay);
}

tlm::tlm_sync_enum
renode_bridge::target_fw_handler::nb_transport_fw(
    tlm::tlm_generic_payload &trans, tlm::tlm_phase &phase,
    sc_core::sc_time &t) {
  bridge->service_backward_request(trans, connection_idx, t);
  return tlm::TLM_COMPLETED;
}

tlm::tlm_sync_enum
renode_bridge::target_fw_handler::nb_transport_bw(
    tlm::tlm_generic_payload &, tlm::tlm_phase &, sc_core::sc_time &) {
  fprintf(stderr, "[ERROR] nb_transport_bw not implemented for "
                  "target_fw_handler.\n");
  return tlm::TLM_COMPLETED;
}

void renode_bridge::target_fw_handler::invalidate_direct_mem_ptr(
  sc_dt::uint64, sc_dt::uint64) {
  fprintf(stderr, "[ERROR] invalidate_direct_mem_ptr not implemented for "
                    "target_fw_handler.\n");
}

bool renode_bridge::target_fw_handler::get_direct_mem_ptr(
    tlm::tlm_generic_payload &trans, tlm::tlm_dmi &dmi_data) {
  if (connection_idx != 0) {
    fprintf(stderr, "[ERROR] get_direct_mem_ptr not implemented for "
                    "target_fw_handler.\n");
    return false;
  } else {
    return bridge->service_backward_request_dmi(trans, dmi_data);
  }
}

bool renode_bridge::service_backward_request_dmi(tlm::tlm_generic_payload &payload, tlm::tlm_dmi &dmi_data) {

#ifdef __linux__
  renode_message message = {};
  message.address = payload.get_address();
  message.data_length = payload.get_data_length();
  message.connection_index = 0;
  message.action = renode_action::DMIREQ;

  dmi_message response;

  send_backward_request(&message);
  response = receive_backward_response_dmi();

  bool dmi_allowed = response.allowed;

  if (dmi_allowed && response.mmf_offset % sysconf(_SC_PAGESIZE)) {
      fprintf(stderr, "[ERROR] invalid page-unaligned offset 0x%" PRIx64 " for MMF %s\n",
        response.mmf_offset,
        response.mmf_path
      );
      dmi_allowed = false;
  }

  if (dmi_allowed) {
    dmi_data.allow_read_write();
    dmi_data.set_start_address(response.start_address);
    dmi_data.set_end_address(response.end_address);
    int mmf_fd = open(response.mmf_path, O_RDWR);
    if (mmf_fd != -1) {
      unsigned char* mmf_base = static_cast<unsigned char*>(mmap(
        nullptr,
        static_cast<size_t>(dmi_data.get_end_address() - dmi_data.get_start_address() + 1),
        PROT_WRITE|PROT_READ,
        MAP_SHARED,
        mmf_fd,
        response.mmf_offset
      ));
      if (mmf_base != MAP_FAILED) {
        dmi_data.set_dmi_ptr(mmf_base);
        return true;
      }
    }
  }
#else
  // at present DMI support has been implemented for linux only.
  // print a one-time warning for DMI request on another operating system
  static bool dmi_disabled_warned = false;
  if (!dmi_disabled_warned) {
    fprintf(stderr, "[WARNING] DMI support is unimplemented on this operating system\n");
  }
  dmi_disabled_warned = true;
#endif

  return false;
}

unsigned int renode_bridge::target_fw_handler::transport_dbg(
    tlm::tlm_generic_payload &trans) {

  // The SystemC simulation can begin before the connection with Renode is
  // initialized. Reject any transactions during this interval.
  if (!bridge->is_initialized()) return 0;

  sc_core::sc_time delay = sc_core::SC_ZERO_TIME;
  bridge->service_backward_request(trans, connection_idx, delay);
  return trans.is_response_ok() ? trans.get_data_length() : 0;
}

// ================================================================================
//  initiator_bw_handler
// ================================================================================

void renode_bridge::initiator_bw_handler::initialize(
    renode_bridge *renode_bridge) {
  bridge = renode_bridge;
}

tlm::tlm_sync_enum renode_bridge::initiator_bw_handler::nb_transport_bw(
    tlm::tlm_generic_payload &trans, tlm::tlm_phase &phase,
    sc_core::sc_time &t) {
  fprintf(stderr, "[ERROR] nb_transport_bw not implemented for "
                  "initiator_bw_handler- this should never be called, "
                  "as Renode integration only uses b_transfer.\n");
  return tlm::TLM_COMPLETED;
}

void renode_bridge::initiator_bw_handler::invalidate_direct_mem_ptr(
    sc_dt::uint64 start_range, sc_dt::uint64 end_range) {
  fprintf(stderr, "[ERROR] invalidate_direct_mem_ptr not implemented for "
                  "initiator_bw_handler - this should never be called, "
                  "as Renode integration only uses b_transfer.\n");
}

// ================================================================================
