/*-
 * Portions of this file are based on USBDescriptors:
 *
 * Copyright (c) 2015 Colin Granville. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
 */


#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stddef.h>

#include "swis.h"
#include "kernel.h"


#include "debugrep.h"



#include "usb.h"



#define \
  usb_control(device, bmRequestType, bRequest, wValue,wIndex,wLength,dataout)\
     _swix(DeviceFS_CallDevice,_INR(0,6),DeviceCall_USB_USBRequest,(device),0,((bmRequestType) | ((bRequest)<<8) | ((wValue)<<16)),\
                                                                 ((wIndex) | ((wLength)<<16)),(dataout),0)



_kernel_oserror* usb_get_device_handle(const usb_device_t* device, int fs_handle, int* buffer_handle, unsigned int *devfs_handle)
{
  // in
  // r0: DeviceCall_USB_GetHandles   return buffer handle and devicefs stream handle
  // r1: device name (e.g. usbd)
  // r2: fileswitch stream handle
  //
  // out
  // r3: buffer handle
  // r4: devicefs stream handle

  return _swix(DeviceFS_CallDevice, _INR(0,2) | _OUTR(3,4),
               DeviceCall_USB_GetHandles,
               (device),
               fs_handle,
               buffer_handle,
               devfs_handle);
}



_kernel_oserror* usb_scan_devices(usb_scan_devices_fn* fn, void* handle)
{
  USBServiceAnswer* next = NULL;

  debug_printf("usb_scan_devices: begin\n");

  _kernel_oserror* e = _swix(OS_ServiceCall, _INR(0,2)|_OUT(2),
                             Service_USB_Connected,
                             Service_USB,
                             0,
                             &next);

  debug_printf("usb_scan_devices: enumerating devices\n");
  while (next)
  {
    const USBServiceAnswer* current = next;
    next = next->link;

    USBServiceCall d = current->svc;
    debug_printf("usb_scan_devices: calling handler for device '%s'\n", d.devname);

    if (!e)
      e = fn(&current->svc, handle);

    _swix(OS_Module, _IN(0)|_IN(2), 7, current);
  }

  debug_printf("usb_scan_devices: end\n");

  return e;
}





usb_descriptor_t* usb_get_descriptor_list(const USBServiceCall* device)
{
  debug_printf("usb_get_descriptor_list: device '%s'\n", device->devname);
  return (usb_descriptor_t*)(((int)device)+device->descoff);
}




// The Xbox 360 wireless receiver refuses control requests and stalls OUT
// writes at random (USBDriver sees the same), so a failed transfer is tried
// again. Every attempt is logged.
#define USB_ATTEMPTS         3
#define USB_RETRY_DELAY_CS   5


static void delay_cs(uint32_t cs)
{
  uint32_t start = 0, now = 0;

  _swix(OS_ReadMonotonicTime, _OUT(0), &start);
  do {
    _swix(OS_ReadMonotonicTime, _OUT(0), &now);
  } while (now - start < cs);
}


// what names the request in the log. Up to attempts tries are made.
static _kernel_oserror* control_retry(const usb_device_t* device, const char* what, int attempts, uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, uint16_t wLength, void* buf)
{
  _kernel_oserror* e = NULL;
  uint32_t start = debug_now_cs();

  for (int attempt=1; attempt<=attempts; attempt++) {
    e = usb_control(device, bmRequestType, bRequest, wValue, wIndex, wLength, buf);
    if (e == NULL) {
      debug_printf("usb_control: %s: %s: attempt %d of %d succeeded, %u cs in all\n", device, what, attempt, attempts, debug_now_cs() - start);
      return NULL;
    }

    debug_printf("usb_control: %s: %s: attempt %d of %d failed after %u cs: %s\n", device, what, attempt, attempts, debug_now_cs() - start, e->errmess);
    if (attempt < attempts)
      delay_cs(USB_RETRY_DELAY_CS);
  }

  debug_printf("usb_control: %s: %s: gave up after %d attempts, %u cs in all\n", device, what, attempts, debug_now_cs() - start);
  return e;
}


// A stalled write leaves the stream's transfer status in error, so every
// later write fails without reaching the device until the stall is cleared.
// Control requests are not affected: USBDriver clears those itself.
static _kernel_oserror* clear_stall(const usb_device_t* device, int fs_handle)
{
  return _swix(DeviceFS_CallDevice, _INR(0,2), DeviceCall_USB_ClearStall, device, fs_handle);
}


_kernel_oserror* usb_write(const usb_device_t* device, int fs_handle, const uint8_t* buf, uint32_t len, uint32_t* not_written)
{
  _kernel_oserror* e = NULL;
  int left = 0;
  uint32_t start = debug_now_cs();
  char what[40];

  sprintf(what, "usb_write: %s: sending", device);
  debug_hexdump(what, buf, len);

  for (int attempt=1; attempt<=USB_ATTEMPTS; attempt++) {
    e = _swix(OS_GBPB, _INR(0,3) | _OUT(3), 2, fs_handle, buf, len, &left);
    if (e == NULL) {
      debug_printf("usb_write: %s: %u bytes, attempt %d of %d succeeded, %u cs in all\n", device, len, attempt, USB_ATTEMPTS, debug_now_cs() - start);
      break;
    }

    debug_printf("usb_write: %s: attempt %d of %d failed after %u cs: %s\n", device, attempt, USB_ATTEMPTS, debug_now_cs() - start, e->errmess);

    _kernel_oserror* ce = clear_stall(device, fs_handle);
    if (ce) {
      debug_printf("usb_write: %s: clear stall failed: %s\n", device, ce->errmess);
      break;
    }
    if (attempt < USB_ATTEMPTS)
      delay_cs(USB_RETRY_DELAY_CS);
  }

  if (e)
    debug_printf("usb_write: %s: gave up after %d attempts, %u cs in all\n", device, USB_ATTEMPTS, debug_now_cs() - start);

  if (not_written)
    *not_written = (uint32_t)left;
  return e;
}


// Read a string descriptor as USBDriver does: its length first, then the
// whole descriptor. The caller frees the result.
static usb_string_descriptor_t* get_string_descriptor(const usb_device_t* device, uint8_t index, uint16_t langid, const char* what)
{
  uint16_t wValue = (UDESC_STRING << 8) + index;
  uint8_t head[2];
  char label[40];

  sprintf(label, "%s, length", what);
  if (control_retry(device, label, USB_ATTEMPTS, UT_READ_DEVICE, UR_GET_DESCRIPTOR, wValue, langid, sizeof(head), head))
    return 0;

  if (head[0] < 2 || head[1] != UDESC_STRING) {
    debug_printf("usb_get_device_string: %s: %s: not a string descriptor (length %u, type %u)\n", device, what, head[0], head[1]);
    return 0;
  }

  uint8_t length = head[0];
  usb_string_descriptor_t* str = calloc(1, length);
  if (str == NULL) {
    debug_printf("usb_get_device_string: %s: %s: cannot allocate %u bytes\n", device, what, length);
    return 0;
  }

  sprintf(label, "%s, descriptor", what);
  if (control_retry(device, label, USB_ATTEMPTS, UT_READ_DEVICE, UR_GET_DESCRIPTOR, wValue, langid, length, (void*)str)) {
    free((void*)str);
    return 0;
  }

  if (str->bLength < 2 || str->bDescriptorType != UDESC_STRING) {
    debug_printf("usb_get_device_string: %s: %s: not a string descriptor (length %u, type %u)\n", device, what, str->bLength, str->bDescriptorType);
    free((void*)str);
    return 0;
  }

  if (str->bLength > length)
    str->bLength = length;

  return str;
}


// Gets the device's first language id into *langid, 0 if its table is empty
// (as USBDriver does). false if the table cannot be read, when no string can
// be either.
bool usb_get_language(const usb_device_t* device, uint16_t* langid)
{
  usb_string_descriptor_t* lang = get_string_descriptor(device, 0, 0, "language table");

  *langid = 0;

  if (lang == NULL) {
    debug_printf("usb_get_language: %s: no language table\n", device);
    return false;
  }

  if (lang->bLength >= 4)
    *langid = UGETW(lang->bString[0]);
  else
    debug_printf("usb_get_language: %s: empty language table, using 0\n", device);

  free((void*)lang);
  debug_printf("usb_get_language: %s: language 0x%04x\n", device, *langid);
  return true;
}


usb_string_descriptor_t* usb_get_device_string_descriptor(const usb_device_t* device, uint16_t langid, int index)
{
  char what[24];

  if (index < 1 || index >= 0x100)
    return 0;

  sprintf(what, "string %d", index);
  return get_string_descriptor(device, (uint8_t)index, langid, what);
}





_kernel_oserror* usb_get_descriptor(const usb_device_t* dev, uint8_t type, uint16_t length, uint8_t* buf)
{
  uint16_t wValue = (type << 8);
  uint16_t wIndex = 0;
  uint16_t wLength = length;

  debug_printf("usb_get_descriptor: dev '%s' wValue 0x%x, wIndex %u, wLength %u\n", dev, wValue, wIndex, wLength);

  return control_retry(dev, "descriptor", USB_ATTEMPTS, UT_READ_INTERFACE, UR_GET_DESCRIPTOR, wValue, wIndex, wLength, buf);
}






// A non-zero report_id goes in the low byte of wValue and must also be the
// first byte of buf: the data stage of a numbered report carries it. Up to
// attempts tries are made.
static _kernel_oserror* hid_set_report(const usb_device_t* dev, uint8_t interface, uint8_t report_type, uint8_t report_id, uint8_t* buf, uint16_t length, int attempts)
{
  uint16_t wValue = (report_type << 8) | report_id;
  uint16_t wIndex = interface;
  uint16_t wLength = length;

  char what[40];

  debug_printf("usb_hid_set_report: dev '%s' wValue 0x%x, wIndex %u, wLength %u\n", dev, wValue, wIndex, wLength);
  sprintf(what, "usb_hid_set_report: %s: sending", dev);
  debug_hexdump(what, buf, length);

  return control_retry(dev, "set report", attempts, UT_WRITE_CLASS_INTERFACE, UR_SET_REPORT, wValue, wIndex, wLength, buf);
}


_kernel_oserror* usb_hid_set_report(const usb_device_t* dev, uint8_t interface, uint8_t report_type, uint8_t report_id, uint8_t* buf, uint16_t length)
{
  return hid_set_report(dev, interface, report_type, report_id, buf, length, USB_ATTEMPTS);
}


// For a request a device is allowed to refuse.
_kernel_oserror* usb_hid_set_report_once(const usb_device_t* dev, uint8_t interface, uint8_t report_type, uint8_t report_id, uint8_t* buf, uint16_t length)
{
  return hid_set_report(dev, interface, report_type, report_id, buf, length, 1);
}


_kernel_oserror* usb_hid_get_report(const usb_device_t* dev, uint8_t interface, uint8_t report_type, uint8_t report_id, uint8_t* buf, uint16_t length)
{
  uint16_t wValue = (report_type << 8) | report_id;
  uint16_t wIndex = interface;
  uint16_t wLength = length;

  char what[40];

  debug_printf("usb_hid_get_report: dev '%s' wValue 0x%x, wIndex %u, wLength %u\n", dev, wValue, wIndex, wLength);

  _kernel_oserror* e = control_retry(dev, "get report", USB_ATTEMPTS, UT_READ | UT_CLASS | UT_INTERFACE, UR_GET_REPORT, wValue, wIndex, wLength, buf);
  if (e == NULL) {
    sprintf(what, "usb_hid_get_report: %s: received", dev);
    debug_hexdump(what, buf, length);
  }
  return e;
}





uint32_t usb_get_hid_u_value(uint8_t* buf)
{
    uint8_t size = *buf & 3;
    buf++;
    switch (size)
    {
        case 1: return *buf;
        case 2: return buf[0] + (buf[1] << 8);
        case 3: return buf[0] + (buf[1] << 8) + (buf[2] << 16) + (buf[3] << 24);
    }
    return 0;  
}





int32_t usb_get_hid_s_value(uint8_t* buf)
{
    uint8_t size = *buf & 3;
    buf++;
    switch (size)
    {
        case 1: return (*buf << 24) >> 24;
        case 2: return ((buf[0] + (buf[1] << 8)) << 16) >> 16;
        case 3: return buf[0] + (buf[1] << 8) + (buf[2] << 16) + (buf[3] << 24);
    }
    return 0;  
}





uint32_t usb_string_descriptor_to_ascii(const usb_string_descriptor_t *str, char *buffer)
{
  uint32_t len = 0;
  if (str && str->bLength >= 2) {
    for (len=0; len < ((uint32_t)(str->bLength-2)/2); len+=1) {
      char c = UGETW(str->bString[len]);
      if (c >= ' ' && c < 0x7f)
        buffer[len] = c;
    }
  }
  return len;
}
