/* SPDX-License-Identifier: Apache-2.0
 * Copyright (c) 2023 Cisco Systems, Inc.
 */

#include <vnet/vnet.h>
#include <vnet/ethernet/ethernet.h>
#include <vnet/dev/dev.h>
#include <vnet/dev/counters.h>
#include <vnet/dev/bus/platform.h>
#include <vppinfra/ring.h>
#include <dev_armada/musdk.h>
#include <dev_armada/pp2/pp2.h>

VLIB_REGISTER_LOG_CLASS (mvpp2_log, static) = {
  .class_name = "armada",
  .subclass_name = "pp2-port",
};

vnet_dev_rv_t
mvpp2_port_init (vlib_main_t *vm, vnet_dev_port_t *port)
{
  vnet_dev_t *dev = port->dev;
  mvpp2_device_t *md = vnet_dev_get_data (dev);
  mvpp2_port_t *mp = vnet_dev_get_port_data (port);
  vnet_dev_rv_t rv = VNET_DEV_OK;
  vnet_dev_rx_queue_t *rxq0 = vnet_dev_get_port_rx_queue_by_id (port, 0);
  struct pp2_ppio_link_info li;
  char match[16];
  int mrv;

  log_debug (port->dev, "");

  snprintf (match, sizeof (match), "ppio-%d:%d", md->pp_id, port->port_id);

  struct pp2_ppio_params ppio_params = {
    .match = match,
    .type = PP2_PPIO_T_NIC,
    .eth_start_hdr = mp->is_dsa ? PP2_PPIO_HDR_ETH_DSA : PP2_PPIO_HDR_ETH,
    .inqs_params = {
      .num_tcs = 1,
      .tcs_params[0] = {
        .pkt_offset = 0,
	.num_in_qs = 1,
	.inqs_params = &(struct pp2_ppio_inq_params) { .size = rxq0->size },
	.pools[0][0] = md->thread[rxq0->rx_thread_index].bpool,
      },
    },
  };

  foreach_vnet_dev_port_tx_queue (q, port)
    {
      struct pp2_ppio_outqs_params *oqs = &ppio_params.outqs_params;
      oqs->outqs_params[q->queue_id].weight = 1;
      oqs->outqs_params[q->queue_id].size = q->size;
      oqs->num_outqs++;
    }

  mrv = pp2_ppio_init (&ppio_params, &mp->ppio);
  if (mrv)
    {
      rv = VNET_DEV_ERR_INIT_FAILED;
      log_err (dev, "port %u ppio '%s' init failed, rv %d", port->port_id,
	       match, mrv);
      goto done;
    }
  log_debug (dev, "port %u ppio '%s' init ok", port->port_id, match);

  mrv = pp2_ppio_get_link_info (mp->ppio, &li);
  if (mrv)
    {
      rv = VNET_DEV_ERR_INIT_FAILED;
      log_err (dev, "failed to get link info for port %u, rv %d",
	       port->port_id, mrv);
      goto done;
    }

  log_debug (dev, "port %u %U", port->port_id, format_pp2_ppio_link_info, &li);

  for (u32 i = 0; i < VLIB_FRAME_SIZE; i++)
    mp->desc_ptrs[i] = mp->descs + i;

  mvpp2_port_add_counters (vm, port);

done:
  if (rv != VNET_DEV_OK)
    mvpp2_port_stop (vm, port);
  return rv;
}

void
mvpp2_port_deinit (vlib_main_t *vm, vnet_dev_port_t *port)
{
  mvpp2_port_t *mp = vnet_dev_get_port_data (port);

  log_debug (port->dev, "");

  if (mp->ppio)
    {
      pp2_ppio_deinit (mp->ppio);
      mp->ppio = 0;
    }
}

void
mvpp2_port_poll (vlib_main_t *vm, vnet_dev_port_t *port)
{
  mvpp2_port_t *mp = vnet_dev_get_port_data (port);
  vnet_dev_t *dev = port->dev;
  vnet_dev_port_state_changes_t changes = {};
  struct pp2_ppio_link_info li;
  int mrv;

  mrv = pp2_ppio_get_link_info (mp->ppio, &li);

  if (mrv)
    {
      log_debug (dev, "pp2_ppio_get_link_info: failed, rv %d", mrv);
      return;
    }

  if (mp->last_link_info.up != li.up)
    {
      changes.change.link_state = 1;
      changes.link_state = li.up != 0;
      log_debug (dev, "link state changed to %u", changes.link_state);
    }

  if (mp->last_link_info.duplex != li.duplex)
    {
      changes.change.link_duplex = 1;
      changes.full_duplex = li.duplex != 0;
      log_debug (dev, "link full duplex changed to %u", changes.full_duplex);
    }

  if (mp->last_link_info.speed != li.speed)
    {
      u32 speeds[] = {
	[MV_NET_LINK_SPEED_AN] = 0,
	[MV_NET_LINK_SPEED_10] = 10000,
	[MV_NET_LINK_SPEED_100] = 100000,
	[MV_NET_LINK_SPEED_1000] = 1000000,
	[MV_NET_LINK_SPEED_2500] = 2500000,
	[MV_NET_LINK_SPEED_10000] = 10000000,
      };

      if (li.speed < ARRAY_LEN (speeds))
	{
	  changes.change.link_speed = 1;
	  changes.link_speed = speeds[li.speed];
	  log_debug (dev, "link speed changed to %u", changes.link_speed);
	}
    }

  if (changes.change.any)
    {
      mp->last_link_info = li;
      vnet_dev_port_state_change (vm, port, changes);
    }

  mvpp2_port_get_stats (vm, port);
}

vnet_dev_rv_t
mvpp2_port_start (vlib_main_t *vm, vnet_dev_port_t *port)
{
  mvpp2_port_t *mp = vnet_dev_get_port_data (port);
  int mrv;

  log_debug (port->dev, "");

  mrv = pp2_ppio_enable (mp->ppio);
  if (mrv)
    {
      log_err (port->dev, "pp2_ppio_enable() failed, rv %d", mrv);
      return VNET_DEV_ERR_NOT_READY;
    }

  mp->is_enabled = 1;

  vnet_dev_poll_port_add (vm, port, 0.5, mvpp2_port_poll);

  return VNET_DEV_OK;
}

void
mvpp2_port_stop (vlib_main_t *vm, vnet_dev_port_t *port)
{
  int rv;
  mvpp2_port_t *mp = vnet_dev_get_port_data (port);

  log_debug (port->dev, "");

  if (mp->is_enabled)
    {
      vnet_dev_poll_port_remove (vm, port, mvpp2_port_poll);

      rv = pp2_ppio_disable (mp->ppio);
      if (rv)
	log_err (port->dev, "pp2_ppio_disable() failed, rv %d", rv);

      vnet_dev_port_state_change (vm, port,
				  (vnet_dev_port_state_changes_t){
				    .change.link_state = 1,
				    .change.link_speed = 1,
				    .link_speed = 0,
				    .link_state = 0,
				  });
      mp->is_enabled = 0;
    }
}

vnet_dev_rv_t
mvpp2_port_add_sec_if (vlib_main_t *vm, vnet_dev_port_t *port, void *p)
{
  vnet_dev_port_interface_t *sif = p;
  mvpp2_port_t *mp = vnet_dev_get_port_data (port);
  u32 port_id = CLIB_U32_MAX, switch_id = 0, index;

  if (mp->is_dsa == 0)
    return VNET_DEV_ERR_NOT_SUPPORTED;

  foreach_vnet_dev_args (a, sif)
    {
      switch (a->id)
	{
	case MVPP2_SEC_IF_ARG_DSA_PORT:
	  if (a->val_set)
	    port_id = vnet_dev_arg_get_uint32 (a);
	  break;
	case MVPP2_SEC_IF_ARG_DSA_SWITCH:
	  switch_id = vnet_dev_arg_get_uint32 (a);
	  break;
	default:
	  break;
	}
    }

  if (port_id == CLIB_U32_MAX)
    {
      log_err (port->dev, "missing dsa_port argument");
      return VNET_DEV_ERR_INVALID_ARG;
    }

  log_debug (port->dev, "switch %u port %u", switch_id, port_id);

  mv_dsa_tag_t tag = {
    .tag_type = MV_DSA_TAG_TYPE_FROM_CPU,
    .src_port_or_lag = port_id,
    .src_dev = switch_id,
  };

  index = switch_id << 5 | port_id;

  sif->user_data = tag.as_u32;
  uword_bitmap_set_bits_at_index (mp->valid_dsa_src_bitmap, index, 1);
  mp->dsa_to_sec_if[index] = sif->index;
  return VNET_DEV_OK;
}

vnet_dev_rv_t
mvpp2_port_del_sec_if (vlib_main_t *vm, vnet_dev_port_t *port, void *p)
{
  vnet_dev_port_interface_t *sif = p;
  mvpp2_port_t *mp = vnet_dev_get_port_data (port);
  mv_dsa_tag_t tag = { .as_u32 = sif->user_data };
  u32 index = tag.src_dev << 5 | tag.src_port_or_lag;

  log_debug (port->dev, "switch %u port %u", tag.src_dev, tag.src_port_or_lag);

  uword_bitmap_clear_bits_at_index (mp->valid_dsa_src_bitmap, index, 1);
  return VNET_DEV_OK;
}

vnet_dev_rv_t
mvpp2_port_cfg_change_validate (vlib_main_t *vm, vnet_dev_port_t *port,
				vnet_dev_port_cfg_change_req_t *req)
{
  vnet_dev_rv_t rv = VNET_DEV_OK;

  switch (req->type)
    {
    case VNET_DEV_PORT_CFG_PROMISC_MODE:
    case VNET_DEV_PORT_CFG_CHANGE_PRIMARY_HW_ADDR:
    case VNET_DEV_PORT_CFG_ADD_SECONDARY_HW_ADDR:
    case VNET_DEV_PORT_CFG_REMOVE_SECONDARY_HW_ADDR:
      break;

    default:
      rv = VNET_DEV_ERR_NOT_SUPPORTED;
    };

  return rv;
}

vnet_dev_rv_t
mvpp2_port_cfg_change (vlib_main_t *vm, vnet_dev_port_t *port,
		       vnet_dev_port_cfg_change_req_t *req)
{
  mvpp2_port_t *mp = vnet_dev_get_port_data (port);
  vnet_dev_rv_t rv = VNET_DEV_OK;
  eth_addr_t addr;
  int mrv;

  switch (req->type)
    {

    case VNET_DEV_PORT_CFG_PROMISC_MODE:
      mrv = pp2_ppio_set_promisc (mp->ppio, req->promisc);
      if (mrv)
	{
	  log_err (port->dev, "pp2_ppio_set_promisc: failed, rv %d", mrv);
	  rv = VNET_DEV_ERR_INTERNAL;
	}
      else
	log_debug (port->dev, "pp2_ppio_set_promisc: promisc %u",
		   req->promisc);
      break;

    case VNET_DEV_PORT_CFG_CHANGE_PRIMARY_HW_ADDR:
      clib_memcpy (&addr, req->addr.eth_mac, sizeof (addr));
      mrv = pp2_ppio_set_mac_addr (mp->ppio, addr);
      if (mrv)
	{
	  log_err (port->dev, "pp2_ppio_set_mac_addr: failed, rv %d", mrv);
	  rv = VNET_DEV_ERR_INTERNAL;
	}
      else
	log_debug (port->dev, "pp2_ppio_set_mac_addr: %U added",
		   format_ethernet_address, &addr);
      break;

    case VNET_DEV_PORT_CFG_ADD_SECONDARY_HW_ADDR:
      clib_memcpy (&addr, req->addr.eth_mac, sizeof (addr));
      mrv = pp2_ppio_add_mac_addr (mp->ppio, addr);
      if (mrv)
	{
	  log_err (port->dev, "pp2_ppio_add_mac_addr: failed, rv %d", mrv);
	  rv = VNET_DEV_ERR_INTERNAL;
	}
      else
	log_debug (port->dev, "pp2_ppio_add_mac_addr: %U added",
		   format_ethernet_address, &addr);
      break;

    case VNET_DEV_PORT_CFG_REMOVE_SECONDARY_HW_ADDR:
      clib_memcpy (&addr, req->addr.eth_mac, sizeof (addr));
      mrv = pp2_ppio_remove_mac_addr (mp->ppio, addr);
      if (mrv)
	{
	  log_err (port->dev, "pp2_ppio_remove_mac_addr: failed, rv %d", mrv);
	  rv = VNET_DEV_ERR_INTERNAL;
	}
      else
	log_debug (port->dev, "pp2_ppio_remove_mac_addr: %U added",
		   format_ethernet_address, &addr);
      break;

    default:
      return VNET_DEV_ERR_NOT_SUPPORTED;
    };

  return rv;
}