//
// Copyright(c)2006, James Peacock
// Allrightsreserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//     * Neither the name of RISC OS Open Ltd nor the names of its contributors
//       may be used to endorse or promote products derived from this software
//       without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//

/* Created XX.XX.2006 J. Peacock
   Changed 09.12.2022 T. Milius  Make CDC useable
   Changed 09.03.2023 T. Milius  usbcdc included with adaptations
   Changed 20.03.2023 T. Milius  superflous config from ws_t and usb_set_config inside device_start removed
*/

/*
 * Backend for devices compatible with the USB communications device class.
 */

#include <stddef.h>
#include <assert.h>
#include <string.h>
#include <stdio.h>

#include "backends.h"
#include "module.h"
#include "utils.h"
#include "USB/USBDevFS.h"
#include "usb.h"

/* This is definitely not optimal, but we can use usbcdc.h from BSD
   as it is */
#if defined(__CC_NORCROFT) && !defined(DISABLE_PACKED)
#define UPACKED __packed
#else
#define UPACKED __attribute__((__packed__))
#endif
typedef uint8_t uByte;
typedef uint16_t uWord;
typedef uint32_t uDWord;
#include "usbcdc.h"

#include "net.h"
#include "swis.h"

#define USB_DEVICE_CLASS_CDC 0x02
#define USB_INTERFACE_CLASS_CDC 0x02
#define USB_INTERFACE_CLASS_CDC_ETHERNET 0x06
#define USB_INTERFACE_CLASS_DATA 0x0a

/* ??? Double declaration !!! usb.c */
struct usb_pipe
{
  usb_pipe_t       prev;
  usb_pipe_t       next;
  usb_open_t       direction;
  uint32_t         file_handle;
  uint32_t         buffer_handle;
  uint32_t         buffer_internal;
  uint32_t         buffer_code;
  uint32_t         buffer_r12;
  uint32_t         usb_handle;
  usb_handler_fn*  handler_fn;
  void*            handler_ws;
  char             device_name[20];
  char             file_name[];
};

/* Receive buffer registered with DeviceFS.
   16384 has the advantage that we may be able to cope with jumbo frames */
#define RX_BUF_SIZE      16384

typedef struct
{
  uint8_t  master_if;
  uint8_t  master_alt;
  uint8_t  slave_if;
  uint8_t  slave_alt;
  uint32_t max_segment_size;
  usb_pipe_t rx_pipe;
  usb_pipe_t tx_pipe;
  usb_pipe_t interrupt_pipe;
  net_device_t*   dev;
  uint8_t  index_mac_string;
  uint32_t rx_buf_length;
  uint32_t rx_buf[RX_BUF_SIZE/sizeof(uint32_t)];
  uint32_t usb_stream_handle;
} ws_t;

//---------------------------------------------------------------------------
// USB pipe handlers
//---------------------------------------------------------------------------
static void read_packet(usb_pipe_t pipe, void* dev_pw)
{
  uint32_t finished;
  ws_t* ws = dev_pw;
  uint32_t* buf = ws->rx_buf;
  net_header_t* hdr = (net_header_t*) buf;
  size_t size;
  _kernel_swi_regs regs;
  _kernel_oserror* e;

  finished=0;
  for (;;)
  {
    size = sizeof(ws->rx_buf) - ws->rx_buf_length;

    e = usb_read(ws->rx_pipe, buf + ws->rx_buf_length, &size);
    if (!e)
    {
      regs.r[0]=1<<31 | 6;
      regs.r[1]=(int) ws->dev->name;
      regs.r[2]=pipe->usb_handle;
      e = _kernel_swi(DeviceFS_CallDevice, &regs, &regs);
      if (!e)
      {
        finished=regs.r[3];
      }
    }
    if (e || size == 0) break;
    ws->rx_buf_length+=size;
    if (finished != 0 || ws->rx_buf_length >= ETHER_MAX_LEN) break;
  }
  if (e || ws->rx_buf_length > ETHER_MAX_LEN || finished == -1)
  {
    syslog("cdc read data problems!");
    ws->rx_buf_length=0;
    usb_start_read(ws->rx_pipe);
  }
  else
  {
    if (finished == 0)
    {
      syslog("cdc read wrong place!");
    }
    else
    {
      if (ws->rx_buf_length > 0)
      {
        syslog("cdc read type %02x%02x size %d %02x:%02x:%02x:%02x:%02x:%02x -> %02x:%02x:%02x:%02x:%02x:%02x",
                hdr->type & 0x000000FF,
                hdr->type>>8 & 0x000000FF,
                ws->rx_buf_length,
                hdr->src_addr[0], hdr->src_addr[1], hdr->src_addr[2],
                hdr->src_addr[3], hdr->src_addr[4], hdr->src_addr[5],
                hdr->dst_addr[0], hdr->dst_addr[1], hdr->dst_addr[2],
                hdr->dst_addr[3], hdr->dst_addr[4], hdr->dst_addr[5]);
        net_receive(ws->dev, buf, ws->rx_buf_length, 0);
        ws->rx_buf_length=0;
      }
    usb_start_read(ws->rx_pipe);
    }
  }
}

static void write_packet(usb_pipe_t pipe, void* dev_pw)
{
  ws_t* ws = dev_pw;
  net_attempt_transmit(ws->dev);
  UNUSED(pipe);
}

static void read_interrupt_packet(usb_pipe_t pipe, void* dev_pw)
{
  ws_t* ws = dev_pw;
  usb_cdc_notification_t notification;
  size_t size;
  uint32_t *transmission_rate;
  _kernel_oserror* e;

  size=sizeof(usb_cdc_notification_t);
  e = usb_read(ws->interrupt_pipe, (void *) &notification, &size);
  if (!e && size >= 8)
  {
    switch (notification.bNotification)
    {
      case UCDC_N_NETWORK_CONNECTION:
        if (notification.wValue == 0)
        {
          if (ws->dev->status.up != 0)
          {
            syslog("cdc interface %d down", notification.wIndex);
            ws->dev->status.up = 0;
          }
        }
        else
        {
          if (ws->dev->status.up == 0)
          {
            syslog("cdc interface %d up", notification.wIndex);
            ws->dev->status.up = 1;
            usb_start_read(ws->rx_pipe);
          }
        }
        break;
      case UCDC_N_CONNECTION_SPEED_CHANGE:
        transmission_rate=(uint32_t*) notification.data;
        syslog("cdc interface %d speed up %d down %d", notification.wIndex, transmission_rate[0], transmission_rate[1]);
        switch (transmission_rate[0])
        {
          case 10000000:
            ws->dev->status.speed=net_speed_10Mb;
            break;
          case 100000000:
            ws->dev->status.speed=net_speed_100Mb;
            break;
          case 1000000000:
            ws->dev->status.speed=net_speed_1000Mb;
            break;
          default:
            ws->dev->status.speed=net_speed_unknown;
        }
        break;
    }
  }
  usb_start_read(ws->interrupt_pipe);
  UNUSED(pipe);
}

//---------------------------------------------------------------------------
// Backend functions.
//---------------------------------------------------------------------------
static _kernel_oserror* device_open(const USBServiceCall* usb,
                                    const char*          options,
                                    void**               private)
{
  syslog("cdc open");

  if ((usb->ddesc.bDeviceClass!=USB_DEVICE_CLASS_CDC &&
       usb->ddesc.bDeviceClass!=0) ||
       usb->ddesc.bDeviceSubClass!=0 ||
       usb->ddesc.bDeviceProtocol!=0)
  {
    syslog("cdc open no cdc device");
    return err_translate(ERR_UNSUPPORTED);
  }

  const usb_cdc_header_descriptor_t*   cdc_header   = NULL;
  const usb_cdc_union_descriptor_t*    cdc_union    = NULL;
  const usb_cdc_ethernet_descriptor_t* cdc_ethernet = NULL;
  const usb_config_descriptor_t*       config       = NULL;

  // Scan the descriptors to pick out the ones we are interested in.
  const void* descriptor = usb_descriptor_list(usb);
  while (descriptor)
  {
    const unsigned type = usb_descriptor_type(descriptor);
    const size_t   size = usb_descriptor_size(descriptor);

    if (type==UDESC_CONFIG && size>=sizeof(usb_config_descriptor_t))
    {
      config = descriptor;
      cdc_header = NULL;
      cdc_union = NULL;
      cdc_ethernet = NULL;
    }
    else if (config && type==UDESC_CS_INTERFACE && size>=3)
    {
      const uint8_t* p = (const uint8_t*)descriptor;
      switch (p[2])
      {
        case 0x00:
          // Header - indicates a concatenated set of functional descriptors
          // for a interface.
          if (size>=sizeof(usb_cdc_header_descriptor_t))
          {
            cdc_header = descriptor;
            cdc_union = NULL;
            cdc_ethernet = NULL;
          }
          break;

        case 0x06:
          // Union: Specifies master and at least one slave interface
          // numbers as 0 based index in this configuration.
          // The master is the controlling interface.
          // The slave is the data inteface.
          if (cdc_header &&
              !cdc_union &&
              size>=sizeof(usb_cdc_union_descriptor_t) &&
              size>=5)
          {
            cdc_union = descriptor;
          }
          break;

        case 0x0f:
          // Ethernet:
          if (cdc_header &&
              !cdc_ethernet &&
              size>=sizeof(usb_cdc_ethernet_descriptor_t))
          {
            cdc_ethernet = descriptor;
          }
          break;
      }
    }
    descriptor = usb_descriptor_next(descriptor);
  }

  if (!(config      &&
        cdc_header  &&
        cdc_union   &&
        cdc_ethernet))
  {
    syslog("cdc open cdc device info missing");
    return err_translate(ERR_UNSUPPORTED);
  }

  const usb_interface_descriptor_t* master =
    usb_find_interface(usb, config->bConfigurationValue,
                       cdc_union->bMasterInterface, 0);

  if (!master)
  {
    syslog("cdc open no master interface");
    return err_translate(ERR_UNSUPPORTED);
  }

  // Find slave interface alternative with suitable looking endpoints.
  // Some devices, including my cable modem, seem to get these the wrong
  // way round...
  const usb_interface_descriptor_t* slave = NULL;
  const usb_interface_descriptor_t* reversed_slave = NULL;

  for (unsigned alt=0; alt!=256; ++alt)
  {
    const usb_interface_descriptor_t* s
      = usb_find_interface(usb, config->bConfigurationValue,
                           cdc_union->bSlaveInterface[0], alt);

    if (!s) break;
    if (s->bInterfaceClass==USB_INTERFACE_CLASS_DATA &&
        s->bNumEndpoints>=2)
    {
      slave = s;
      break;
    }

    if (!reversed_slave &&
        s->bInterfaceClass       ==USB_INTERFACE_CLASS_CDC &&
        s->bInterfaceSubClass    ==USB_INTERFACE_CLASS_CDC_ETHERNET &&
        master->bInterfaceClass  ==USB_INTERFACE_CLASS_DATA &&
        master->bNumEndpoints    >=2)
    {
      reversed_slave = s;
    }
  }

  if (!slave && reversed_slave)
  {
    slave = master;
    master = reversed_slave;
  }

  if (!slave)
  {
    syslog("cdc open no slave interface");
    return err_translate(ERR_UNSUPPORTED);
  }

  ws_t* ws = xalloc(sizeof(ws_t));
  if (!ws)
  {
    syslog("cdc open no memory");
    return err_translate(ERR_NO_MEMORY);
  }

  ws->index_mac_string = cdc_ethernet->iMacAddress;
  ws->max_segment_size=cdc_ethernet->wMaxSegmentSize;
  ws->master_if  = master->bInterfaceNumber;
  ws->master_alt = master->bAlternateSetting;
  ws->slave_if   = slave->bInterfaceNumber;
  ws->slave_alt  = slave->bAlternateSetting;
  ws->rx_pipe        = 0;
  ws->tx_pipe        = 0;
  ws->interrupt_pipe = 0;
  ws->dev            = NULL;
  ws->rx_buf_length  = 0;
  /* other values initialized during device_start */

  syslog("cdc open ok");
  *private = ws;
  UNUSED(options);
  return NULL;
}

static _kernel_oserror* device_start(net_device_t* dev, const char* options)
{
  ws_t* ws = dev->private;

  ws->dev  = dev;

  syslog("cdc start");
  // Setting the slave interface should kick the device into life.
  _kernel_oserror* e = usb_set_interface(dev->name, ws->master_if, ws->master_alt);
  if (!e) e = usb_set_interface(dev->name, ws->slave_if, ws->slave_alt);
   /* Kind of packets we are interest in (taken from Raspian usbmon log).
      Early as eg. Huawei E8372 tends to continue resesting device
      with class swapping if it is not getting any actions from USB host.
      See status below */
  if (!e) e = usb_control(dev->name,
                          0x21,
                          0x43,
                          0x000E,
                          0x0000,
                          0,
                          NULL);
  if (e)
  {
    syslog("cdc packet seletion failed");
  }
  syslog("cdc Master IF %d Setting %d", ws->master_if, ws->master_alt);
  syslog("cdc Slave IF %d Setting %d", ws->slave_if, ws->slave_alt);
  if (!e && ws->index_mac_string != 0)
  {
    usb_string_descriptor_t info_string;
    uint16_t string_language = 0x0000;

    /* We are requiring the supported string languages.
       String is 16 Bit Unicode and the supported languages are
       also 16 Bit values we can use the string_descriptor structure. */
    if (usb_control(dev->name,
                    0x80,
                    0x06,
                    UDESC_STRING<<8 | 0,
                    string_language,
                    sizeof(usb_string_descriptor_t),
                    (void *) &info_string) == NULL)
    {
      if (info_string.bLength >= 4)
      {
      /* Take the first langauage.
         The result should be indepedent of the language */
      string_language=info_string.bString[0];
      }
    }
    syslog("cdc mac index %d language %x",
            ws->index_mac_string,
            string_language);
    /* Determine MAC from string */
    e = usb_control(dev->name,
                    0x80,
                    0x06,
                    UDESC_STRING<<8 | ws->index_mac_string,
                    string_language,
                    sizeof(usb_string_descriptor_t),
                    (void *) &info_string);
    if (e)
    {
      syslog("cdc determination of MAC failed");
    }
    else
    {
      char conv_string[32];
      unsigned long var_mac[ETHER_ADDR_LEN];
      int i;

      sprintf(conv_string, "%c%c:%c%c:%c%c:%c%c:%c%c:%c%c",
              info_string.bString[0],
              info_string.bString[1],
              info_string.bString[2],
              info_string.bString[3],
              info_string.bString[4],
              info_string.bString[5],
              info_string.bString[6],
              info_string.bString[7],
              info_string.bString[8],
              info_string.bString[9],
              info_string.bString[10],
              info_string.bString[11]);
      syslog("cdc MAC %s", conv_string);
      if (sscanf(conv_string,"%lx:%lx:%lx:%lx:%lx:%lx",
                &var_mac[0],
                &var_mac[1],
                &var_mac[2],
                &var_mac[3],
                &var_mac[4],
                &var_mac[5]) == ETHER_ADDR_LEN)
      {
        for(i=0;i<ETHER_ADDR_LEN;i++)
        {
          dev->status.mac[i] = (uint8_t)var_mac[i];
        }
      }
    }
  }
  /* Flush log */
  syslog_flush();

  /* RISC OS USB part */
  if (!e) syslog("cdc start RISC OS USB");

  if (!e) e = usb_open(dev->name,
                       &ws->rx_pipe,
                       USBRead,
                       &read_packet,
                       ws,
                       "Devices#bulk;size" STR(RX_BUF_SIZE) ":$.%s",
                       dev->name);
  if (!e) e = usb_open(dev->name,
                       &ws->tx_pipe,
                       USBWrite,
                       &write_packet,
                       ws,
                       "Devices#bulk;size16384:$.%s",
                       dev->name);
  if (!e && ws->tx_pipe) e = usb_force_short_xfer(ws->tx_pipe);
  if (!e) e = usb_open(dev->name,
                       &ws->interrupt_pipe,
                       USBRead,
                       &read_interrupt_packet,
                       ws,
                       "Devices#interrupt;size64:$.%s",
                       dev->name);

  if (e)
  {
    syslog("cdc start failure %s", e->errmess);
    usb_close(&ws->rx_pipe);
    usb_close(&ws->tx_pipe);
    usb_close(&ws->interrupt_pipe);
    return e;
  }

  /* Adjust MTU */
  dev->mtu=ws->max_segment_size - sizeof(net_header_t);
/* ??? Usb commands to obtain information? */
  dev->abilities.half_duplex=0;
  dev->abilities.full_duplex=1;
  dev->abilities.speed_10Mb=1;
  dev->abilities.speed_100Mb=1;
  dev->abilities.speed_1000Mb=1;
  dev->abilities.autoneg=1;
  dev->abilities.loopback = 0;
  dev->abilities.multicast   = 1;
  dev->abilities.promiscuous = 0;
  dev->abilities.tx_rx_loopback = 0;
  dev->abilities.rx_pause = dev->abilities.tx_pause = dev->abilities.symmetric_pause = 0;
  dev->abilities.mutable_mac = 0;
  dev->status.speed=net_speed_unknown;
  dev->status.duplex=net_duplex_unknown;
  dev->status.link=net_link_unknown;
  dev->status.autoneg=net_autoneg_none;
  dev->status.remote_faults=0;
  dev->status.jabbers=0;
  dev->status.up = 0;
  dev->status.ok = 1;
  /* Must match SET_ETHERNET_PAKET_FILTER */
  /* Bit 3 */
  dev->status.broadcast   = 1;
  /* Bit 2 */
  dev->status.multicast   = 1;
  /* Bit 0 */
  dev->status.promiscuous = 0;
  dev->status.polarity_incorrect = 0;
  dev->status.tx_pause = 0;
  dev->status.rx_pause = 0;

  // Request initial packet.
  usb_start_read(ws->interrupt_pipe);
  if (dev->status.up == 1)
  {
    usb_start_read(ws->rx_pipe);
  }

  UNUSED(options);
  return NULL;
}

static _kernel_oserror* device_stop(net_device_t* dev)
{
  syslog("cdc stop");
  ws_t* ws = dev->private;
  usb_close(&ws->rx_pipe);
  usb_close(&ws->tx_pipe);
  usb_close(&ws->interrupt_pipe);
  return NULL;
}

static _kernel_oserror* device_close(void** private)
{
  syslog("cdc close");
  xfree(*private);
  return NULL;
}

static _kernel_oserror* device_transmit(net_device_t* dev,
                                        const net_tx_t* pkt)
{
  ws_t* ws = dev->private;
  size_t to_write = pkt->size;

  syslog("cdc send type %02x%02x size %d %02x:%02x:%02x:%02x:%02x:%02x -> %02x:%02x:%02x:%02x:%02x:%02x",
         pkt->header.type & 0x000000FF,
         pkt->header.type>>8 & 0x000000FF,
         to_write,
         pkt->header.src_addr[0], pkt->header.src_addr[1], pkt->header.src_addr[2],
         pkt->header.src_addr[3], pkt->header.src_addr[4], pkt->header.src_addr[5],
         pkt->header.dst_addr[0], pkt->header.dst_addr[1], pkt->header.dst_addr[2],
         pkt->header.dst_addr[3], pkt->header.dst_addr[4], pkt->header.dst_addr[5]);
  usb_write(ws->tx_pipe, (void*) &pkt->header, &to_write);
  return (to_write==0) ? err_translate(ERR_TX_BLOCKED) : NULL;
}


static const net_backend_t s_backend = {
  .name        = N_CDC,
  .description = "DescCDC",
  .open        = device_open,
  .start       = device_start,
  .stop        = device_stop,
  .close       = device_close,
  .transmit    = device_transmit,
  .info        = NULL,
  .config      = NULL,
  .status      = NULL
};

_kernel_oserror* cdc_register(void)
{
  syslog("cdc register");
  return net_register_backend(&s_backend);
}

