
/*

USBJoystick - a RISC OS USB HID Joystick driver

Based on NetBSD USB joystick driver in X-MAME by Krister Walfridsson <cato@df.lth.se> and Dieter Baron <dillo@giga.or.at>
Uses some NetBSD USB HID parsing functions (see headers in data/descr/parse C files)
Uses a FreeBSD USB HID report descriptor for XBOX360 controller (see usbrdesc header file)
Inspiration taken from EtherUSB by James Peacock (Copyright (C) 2012, James Peacock)
RISC OS specific USB code taken from Colin Granville's USBDescriptors (Copyright (c) 2015 Colin Granville. All rights reserved.)
C function to interface to Reporter is from Steve Fryatt's online series

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.

*/

// C library
#include "swis.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "Global/NewErrors.h"
#include "Global/RISCOS.h"
#include "Global/Keyboard.h"
#include "Global/Pointer.h"

// OSLib
#include "oslib/os.h"
#include "oslib/osfind.h"
#include "oslib/osgbpb.h"
#include "oslib/osbyte.h"
#include "oslib/devicefs.h"
#include "oslib/resourcefs.h"

// our debugging utility
#include "debugrep.h"

// our usb service defs
#include "usbservice.h"

// include our CMHG header
#include "usbjoystickHdr.h"

// include our header
#include "usbjoystick.h"

// resourcefs
extern void *Resources(void);

// silence compiler warnings about unused parameters
#define IGNORE(x) do { (void)(x); } while(0)

// Vector readability
#define VECTOR_PASSON 1
#define VECTOR_CLAIM 0




// Module state

extern void* g_module_pw = NULL;



// Module errors
// todo: This will be improved so we internationalise them

extern _kernel_oserror err_syntax = {
  ErrorNumber_Syntax,
  Module_Title ": Syntax error"
};

extern _kernel_oserror err_badnoparms = {
  ErrorNumber_BadNoParms,
  "Bad parameters"
};

extern _kernel_oserror err_toomanyparms = {
  ErrorNumber_TooManyParms,
  "Too many parameters"
};

extern _kernel_oserror err_nosuchswi = {
  ErrorNumber_NoSuchSWI,
  "No such SWI"     // ErrorString_ModuleBadSWI
};

extern _kernel_oserror err_modulebadswi = {
  ErrorNumber_ModuleBadSWI,
  "SWI value out of range for module " Module_Title
};

extern _kernel_oserror err_badjoy = {
  ErrorBase_Joystick + 1,
  "Bad joystick number"
};

extern _kernel_oserror err_nousb = {
  ErrorBase_Joystick + 2,
  Module_Title " requires USBDriver"
};

extern _kernel_oserror err_joynotactive = {
  ErrorBase_Joystick + 3,
  "Joystick not active"
};

extern _kernel_oserror err_joynotopen = {
  ErrorBase_Joystick + 4,
  "Joystick not open"
};





// Joystick data
struct joydata_struct joy_data[JOY_MAX];
struct adc_data_struct adc_data;
struct adc_map_struct adc_map;

// which joystick are we currently setting up?
unsigned int joy_index = 0;

// which joystick, if any, is performing mouse duties?
int mouse_joy = NOT_MAPPED;
os_pointer_type initial_pointer_type;
int mouse_rel_x = 0;
int mouse_rel_y = 0;
int mouse_select = 0;
int mouse_menu = 0;
int mouse_adjust = 0;
int mouse_previous_select = 0;
int mouse_previous_menu = 0;
int mouse_previous_adjust = 0;
int mouse_previous_pc_x = 0;
int mouse_previous_pc_y = 0;

// supported usb hid axes codes (not including HAT or DPAD)
// also note mapping to our own type codes
static const unsigned int supported_axes_usage[] = {
  [HUG_X] = AXIS_X,
  [HUG_Y] = AXIS_Y,
  [HUG_Z] = AXIS_Z,
  [HUG_RX] = AXIS_RX,
  [HUG_RY] = AXIS_RY,
  [HUG_RZ] = AXIS_RZ,
  [HUG_SLIDER] = AXIS_SLIDER,
};

static const char *axes_names[] = {
  [AXIS_HATX] = "HATX",
  [AXIS_HATY] = "HATY",
  [AXIS_DPADX] = "DPADX",
  [AXIS_DPADY] = "DPADY",
  [AXIS_X] = "X",
  [AXIS_Y] = "Y",
  [AXIS_Z] = "Z",
  [AXIS_RX] = "RX",
  [AXIS_RY] = "RY",
  [AXIS_RZ] = "RZ",
  [AXIS_SLIDER] = "S",
};


// todo: can we get away with assuming protocol==0 for standard HID devices?  keyboard is 2
static const struct joy_device_struct joy_devices[] = {
  { 0xFF, 0x5D,   1, 0x45E, 0x028E, "XBOX360 wired controller"         , JOY_TYPE_XBOX360  , (void*)&uhid_xb360gp_report_descr, sizeof(uhid_xb360gp_report_descr), 0, { 0x01, 0x03, 0x00 }                                                      ,  3, { AXIS_Y, AXIS_RY }, 2, 0 },
  { 0xFF, 0x5D, 129, 0x45E, 0x0719, "XBOX360 wireless controller"      , JOY_TYPE_XBOX360WL, (void*)&uhid_xb360gp_report_descr, sizeof(uhid_xb360gp_report_descr), 4, { 0x00, 0x00, 0x08, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 12, { AXIS_Y, AXIS_RY }, 2, &xbox360wireless_data_validator },
  { 0x03, 0x00,   0,     0,     0 , "Standard HID controller (no boot)", JOY_TYPE_STANDARD ,                                 0,                                 0, 0, { 0x00 }                                                                  ,  0, { 0 }              , 0, 0 },
  { 0x03, 0x01,   0,     0,     0 , "Standard HID controller (boot)"   , JOY_TYPE_STANDARD ,                                 0,                                 0, 0, { 0x00 }                                                                  ,  0, { 0 }              , 0, 0 }
};

unsigned int joy_devices_count = sizeof(joy_devices)/sizeof(joy_devices[0]);







osbool emulate_acorn_on = TRUE;
osbool emulate_serial_port_on = TRUE;
osbool emulate_joy_on = TRUE;
osbool emulate_adc_on = TRUE;
osbool upcall_claimed = FALSE;
osbool ukswiv_claimed = FALSE;
osbool bytev_claimed = FALSE;
osbool pointerv_claimed = FALSE;






osbool xbox360wireless_data_validator(char *d)
{
  return (osbool)(d[0] == 0 && d[1] == 1);
}





_kernel_oserror *module_initialise(const char *cmd_tail, int podule_base, void *pw)
{
  os_error *error;

  IGNORE(cmd_tail);
  IGNORE(podule_base);

  debug_printf("module_initialise: begin\n");
  g_module_pw = pw;

  // clean up joysticks data structure
  clean_joystick_data();

  // we must have USB
  // num_from_string will give 'swi not known', so swap this for our own error
  int usbdriver_version = 0;
  error = xos_swi_number_from_string("USBDriver_Version", &usbdriver_version);
  if (error) error = (os_error*) &err_nousb;

  // claim upcall vector for acting on usb data received
  if (!error) {
    error = xos_claim(UpCallV, &upcallv_hook, pw);
    if (error)
      debug_printf("module_initialise: Cannot claim UpCallV: %u %s\n", error->errnum, error->errmess);
    else
      upcall_claimed = TRUE;
  }

  // claim ukswi vector for serial port api cmpatibility
  if (!error) {
    error = xos_claim(UKSWIV, (asm_routine)ukswiv_pre_hook, pw);
    if (error)
      debug_printf("module_initialise: Cannot claim UkSWIV: %u %s\n", error->errnum, error->errmess);
    else
     ukswiv_claimed = TRUE;
  }

  // claim byte vector for emulating Acorn I/O podule ADC
  if (!error) {
    error = xos_claim(ByteV, &bytev_hook, pw);
    if (error)
      debug_printf("module_initialise: Cannot claim ByteV: %u %s\n", error->errnum, error->errmess);
    else
      bytev_claimed = TRUE;
  }

  // claim pointer vector for emulating a mouse
  if (!error) {
    error = xos_claim(PointerV, &pointerv_hook, pw);
    if (error)
      debug_printf("module_initialise: Cannot claim PointerV: %u %s\n", error->errnum, error->errmess);
    else
      pointerv_claimed = TRUE;
  }

  // register resources
  #ifndef ROM_MODULE
  if (!error)
    error = xresourcefs_register_files((resourcefs_file_list *) Resources());
  #else
  /* In ROM builds, all resource files live in the Messages module instead */
  #endif

  // build a list of connected joysticks
  // each usb device will be queried to check for suitability
  if (!error) {
    debug_printf("module_initialise: Scanning USB devices...\n");
    usb_scan_devices(&joy_check_device, NULL);
    debug_printf("module_initialise: Scan complete\n");
  }

  /*
    TODO!!!!!!
  if (!error)
    error = free memory from scan-devices
  */

  debug_printf("module_initialise: end\n");

  return (_kernel_oserror *) error;
}





_kernel_oserror *module_swi(int swi_offset, _kernel_swi_regs *r, void *pw)
{
  IGNORE(pw);

  switch(swi_offset)
  {
    case MODULE_SWI(Joystick_Read):
    {
      if (emulate_acorn_on)
        return swi_joystick_read(r);
      else
        return &err_nosuchswi;
      break;
    }

    case MODULE_SWI(Joystick_CalibrateTopRight):
    case MODULE_SWI(Joystick_CalibrateBottomLeft):
    {
      if (emulate_acorn_on)
        return NULL;
      else
        return &err_nosuchswi;
      break;
    }

    case MODULE_SWI(Joystick_Status):
    {
      if (emulate_serial_port_on)
        return swi_joystick_status(r);
      else
        return &err_nosuchswi;
      break;
    }
  }

  // SWI value out of range for module USBJoystick
  return &err_modulebadswi;
}





void open_joystick(unsigned int i)
{
  if (joy_data[i].in_use == FALSE)
    return;

  // open the usb data stream for reading
  os_error* error = xosfind_openinw(osfind_NO_PATH | osfind_ERROR_IF_ABSENT, joy_data[i].usb_path, NULL, &(joy_data[i].fp));

  if (error)
  {
    debug_printf("open_joystick: Cannot open fp %u on %s: %s\n", joy_data[i].fp, joy_data[i].usb_path, error->errmess);
    joy_data[i].fp = 0;
  }
  else if (joy_data[i].fp == 0) {
    debug_printf("open_joystick: Cannot open (fp is 0) on %s\n", joy_data[i].usb_path);
  }
  else
  {
    // find out the buffer handle and devicefs handle
    error = (os_error*) usb_get_device_handle((usb_device_t*)joy_data[i].device_name, joy_data[i].fp, &(joy_data[i].buffer_handle), &(joy_data[i].devicefs_handle));
    if (error) {
      debug_printf("open_joystick: Cannot get device/buffer handles: %s\n", error->errmess);
    }
    else {
      // get the internal buffer info
      error = xbuffer_internal_info(joy_data[i].buffer_handle, &(joy_data[i].buffer_internal_id), &(joy_data[i].buffer_service_routine), &(joy_data[i].buffer_workspace));
      if (error) {
        debug_printf("open_joystick: Cannot get buffer internal info: %s\n", error->errmess);
      }
      else {
        joy_data[i].open = TRUE;
        auto_map(i);
      }
    }
  }
}





void auto_map(unsigned int i)
{
  if (joy_data[i].in_use == FALSE || joy_data[i].open == FALSE)
    return;

  // 8-bit axes mapping
  osbool mapped8 = find_and_map_x_y_8(i, AXIS_DPADX, AXIS_DPADY);

  if (!mapped8)
    mapped8 = find_and_map_x_y_8(i, AXIS_HATX, AXIS_HATY);

  if (!mapped8)
    mapped8 = find_and_map_x_y_8(i, AXIS_X, AXIS_Y);

  // 16-bit axes mapping
  osbool mapped16 = find_and_map_x_y_16(i, AXIS_X, AXIS_Y);

  if (!mapped16)
    mapped16 = find_and_map_x_y_8(i, AXIS_DPADX, AXIS_DPADY);

  if (!mapped16)
    mapped16 = find_and_map_x_y_8(i, AXIS_HATX, AXIS_HATY);

  // Auto flip any axes that this 'driver' wants us to
  for (unsigned int a=0; a<joy_data[i].device->auto_flip_axes_length; a++) {
    for (unsigned int j=0; j<joy_data[i].num_axes; j++) {
      if (joy_data[i].axes[j].type == joy_data[i].device->auto_flip_axes[a])
        joy_data[i].axes[j].flip = TRUE;
    }
  }

  // Automatically map the buttons
  unsigned int maxbutton = joy_data[i].num_buttons;

  if (joy_data[i].num_buttons > ACORN_BUTTONS)
    maxbutton = ACORN_BUTTONS;

  for (unsigned int b=0; b<maxbutton; b++)
    joy_data[i].mapped_buttons[b] = b;

  // Automatically assign a 'stick number' based on the highest used so far
  int highest_stick = NOT_MAPPED;
  for (unsigned int j=0; j<JOY_MAX; j++) {
    if (joy_data[j].mapped_number > highest_stick)
      highest_stick = joy_data[j].mapped_number;
  }

  if (highest_stick == NOT_MAPPED)
    joy_data[i].mapped_number = 0;
  else
    joy_data[i].mapped_number = highest_stick + 1;

  // Auto map the mouse axes and buttons
  joy_data[i].mapped_mouse_x = joy_data[i].mapped_x_16;
  joy_data[i].mapped_mouse_y = joy_data[i].mapped_y_16;
  joy_data[i].mapped_mouse_select = joy_data[i].mapped_buttons[0];
  joy_data[i].mapped_mouse_menu = joy_data[i].mapped_buttons[1];
  joy_data[i].mapped_mouse_adjust = joy_data[i].mapped_buttons[2];

}





void unmap(unsigned int i)
{
  // don't need to do anything if we were not mapped
  if (joy_data[i].mapped_number == NOT_MAPPED)
    return;

  // decrement any higher mapped stick numbers
  int old_map = joy_data[i].mapped_number;
  joy_data[i].mapped_number = NOT_MAPPED;

  for (unsigned int j=0; j<JOY_MAX; j++) {
    if (joy_data[j].mapped_number > old_map)
      joy_data[j].mapped_number--;
  }

}





osbool find_and_map_x_y_8(unsigned int i, unsigned int x_type, unsigned int y_type)
{
  int x = NOT_MAPPED;
  int y = NOT_MAPPED;

  for (unsigned int j=0; j<joy_data[i].num_axes; j++) {
    if (joy_data[i].axes[j].type == x_type)
      x = j;
    else if (joy_data[i].axes[j].type == y_type)
      y = j;
  }

  if (x != NOT_MAPPED && y != NOT_MAPPED) {
      joy_data[i].mapped_x_8 = x;
      joy_data[i].mapped_y_8 = y;
      return true;
  }
  else {
    return false;
  }
}




osbool find_and_map_x_y_16(unsigned int i, unsigned int x_type, unsigned int y_type)
{
  int x = NOT_MAPPED;
  int y = NOT_MAPPED;

  for (unsigned int j=0; j<joy_data[i].num_axes; j++) {
    if (joy_data[i].axes[j].type == x_type)
      x = j;
    else if (joy_data[i].axes[j].type == y_type)
      y = j;
  }

  if (x != NOT_MAPPED && y != NOT_MAPPED) {
      joy_data[i].mapped_x_16 = x;
      joy_data[i].mapped_y_16 = y;
      return true;
  }
  else {
    return false;
  }
}





void joy_remove_device(const char* device_name)
{
  debug_printf("joy_remove_device: Removing device %s\n", device_name);
  for (unsigned int i=0; i<JOY_MAX; i++)
  {
    if (strcmp(joy_data[i].device_name, device_name) == 0) {
      if (joy_data[i].fp > 0) {
        os_error* error = xosfind_closew(joy_data[i].fp);
        if (error) {
          debug_printf("joy_remove_device: Cannot close fp %d when device %s is removed: %s", joy_data[i].fp, device_name, error->errmess);
        }
      }
      if (mouse_joy == i) {
        debug_printf("joy_remove_device: switching off mouse control (was on stick id %u)\n", i);
        mouse_joy = NOT_MAPPED;
      }
      unmap(i);
      clean_joystick_data_item(i);
      debug_printf("joy_remove_device: Removed device %s\n", device_name);
    }
  }
}




unsigned int next_available_joystick_index(void)
{
  for (unsigned int i=0; i<JOY_MAX; i++)
  {
    if (joy_data[i].in_use == FALSE)
      return i;
  }
  return NONE_AVAILABLE;
}




void clean_joystick_data(void)
{
  for (unsigned int i=0; i<JOY_MAX; i++)
    clean_joystick_data_item(i);

  for (unsigned int c=0; c<ADC_CHANNELS; c++) {
    adc_map.channels[c].joy_id = NOT_MAPPED;
    adc_map.channels[c].axis = NOT_MAPPED;
    adc_map.channels[c].start = 0;
    adc_map.channels[c].end = 0;
    adc_map.channels[c].slope = 1;
    adc_map.channels[c].flip = FALSE;
    adc_map.channels[c].slope_div = FALSE;
    adc_data.channel_values[c] = 0;
  }

  for (unsigned int b=0; b<ADC_BUTTONS; b++) {
    adc_map.buttons[b].joy_id = NOT_MAPPED;
    adc_map.buttons[b].button = NOT_MAPPED;
    adc_data.button_values[b] = 0;
  }

  adc_data.channels_to_be_sampled = ADC_CHANNELS;
  adc_data.current_channel = 1;   // todo: is this a sensible initial state???
}





void clean_joystick_data_item(unsigned int i)
{
  unsigned int j;

  joy_data[i].report_length = 0;

  joy_data[i].has_report_id = FALSE;

  joy_data[i].device = NULL;

  free(joy_data[i].data_buf);
  joy_data[i].data_buf = NULL;

  for (j=0; j<JOY_AXES; j++) {
    free(joy_data[i].axes_items[j]);
    joy_data[i].axes_items[j] = NULL;
  }

  for (j=0; j<JOY_BUTTONS; j++) {
    free(joy_data[i].button_items[j]);
    joy_data[i].button_items[j] = NULL;
  }

  free(joy_data[i].hat_item);
  free(joy_data[i].dpad_up_item);
  free(joy_data[i].dpad_down_item);
  free(joy_data[i].dpad_left_item);
  free(joy_data[i].dpad_right_item);
  joy_data[i].hat_item = NULL;
  joy_data[i].dpad_up_item = NULL;
  joy_data[i].dpad_down_item = NULL;
  joy_data[i].dpad_left_item = NULL;
  joy_data[i].dpad_right_item = NULL;

  // these will only be valid if we actually have a HAT/DPAD
  joy_data[i].hat_axis = 0;
  joy_data[i].dpad_axis = 0;

  joy_data[i].has_dpad = FALSE;
  joy_data[i].has_hat = FALSE;

  joy_data[i].num_axes = 0;
  joy_data[i].num_buttons = 0;

  for (j=0; j<JOY_AXES; j++) {
    joy_data[i].axes[j].val = 0;
    joy_data[i].axes[j].min = 0;
    joy_data[i].axes[j].mid = 0;
    joy_data[i].axes[j].max = 0;
    joy_data[i].axes[j].acorn_slope_8 = 1;
    joy_data[i].axes[j].acorn_slope_div_8 = FALSE;
    joy_data[i].axes[j].acorn_slope_16 = 1;
    joy_data[i].axes[j].acorn_slope_div_16 = FALSE;
    joy_data[i].axes[j].pc_slope = 1;
    joy_data[i].axes[j].pc_slope_div = FALSE;
    joy_data[i].axes[j].mouse_factor = 1;
    joy_data[i].axes[j].mouse_sensitivity = 1;
    joy_data[i].axes[j].serial_port_upper_bound = 0;
    joy_data[i].axes[j].serial_port_lower_bound = 0;
    joy_data[i].axes[j].is_digital = FALSE;
    joy_data[i].axes[j].name = NULL;
    joy_data[i].axes[j].flip = FALSE;
  }

  for (j=0; j<JOY_BUTTONS; j++)
    joy_data[i].buttons[j] = 0;

  joy_data[i].in_use = FALSE;

  joy_data[i].open = FALSE;

  for (j=0; j<USB_DEVICE_NAME_LENGTH; j++)
    joy_data[i].device_name[j] = NULL;

  joy_data[i].interface = 0;
  joy_data[i].endpoint = 0;

  for (j=0; j<USB_DEVICEFS_PATH_MAX_LENGTH; j++)
    joy_data[i].usb_path[j] = NULL;

  joy_data[i].fp = NULL;
  joy_data[i].devicefs_handle = NULL;
  joy_data[i].buffer_handle = NULL;
  joy_data[i].buffer_internal_id = NULL;
  joy_data[i].buffer_service_routine = NULL;
  joy_data[i].buffer_workspace = NULL;

  joy_data[i].upcalls = 0;
  joy_data[i].upcalls_delta = 0;

  for (j=0; j<USB_MAX_STRING_LEN; j++) {
    joy_data[i].manufacturer[j] = NULL;
    joy_data[i].product[j] = NULL;
    joy_data[i].serial[j] = NULL;
  }

  joy_data[i].vendor_id = 0;
  joy_data[i].product_id = 0;
  joy_data[i].device_id = 0;

  joy_data[i].good_reads = 0;
  joy_data[i].good_reads_delta = 0;

  joy_data[i].bad_reads = 0;
  joy_data[i].bad_reads_delta = 0;

  joy_data[i].mapped_number = NOT_MAPPED;

  joy_data[i].mapped_x_8 = NOT_MAPPED;
  joy_data[i].mapped_y_8 = NOT_MAPPED;
  joy_data[i].mapped_x_16 = NOT_MAPPED;
  joy_data[i].mapped_y_16 = NOT_MAPPED;

  joy_data[i].mapped_mouse_x = NOT_MAPPED;
  joy_data[i].mapped_mouse_y = NOT_MAPPED;
  joy_data[i].mapped_mouse_select = NOT_MAPPED;
  joy_data[i].mapped_mouse_menu = NOT_MAPPED;
  joy_data[i].mapped_mouse_adjust = NOT_MAPPED;

  joy_data[i].key_up = NOT_MAPPED;
  joy_data[i].key_down = NOT_MAPPED;
  joy_data[i].key_left = NOT_MAPPED;
  joy_data[i].key_right = NOT_MAPPED;

  for (j=0; j<ACORN_BUTTONS; j++) {
    joy_data[i].key_buttons[j] = NOT_MAPPED;
    joy_data[i].mapped_buttons[j] = NOT_MAPPED;
  }

}







_kernel_oserror* joy_check_device(const usb_service_t* dev, void* handle)
{
  // check this usb device (dev is a connected usb device) to see if it looks like a joystick
  IGNORE(handle);

  debug_printf("joy_check_device: (usb service call) +++++++++++++++++++++++++++++++++++\n");
  debug_printf("joy_check_device: Checking device name '%s' (sum_length %u, descriptors_offset %u, class %02x, subclass %02x, protocol %02x)\n",
    dev->name,
    dev->sum_length,
    dev->descriptors_offset,
    dev->device.bDeviceClass,
    dev->device.bDeviceSubClass,
    dev->device.bDeviceProtocol);

  // 'dev->name' is 'USB4' for example, which is our 'connected USB device' handle
  usb_device_t* device = (usb_device_t*) dev->name;

  usb_device_descriptor_t* ddesc = NULL;
  usb_config_descriptor_t* cdesc = NULL;
  usb_string_descriptor_t* sdesc = NULL;
  usb_interface_descriptor_t* idesc = NULL;
  usb_endpoint_descriptor_t* edesc = NULL;
  uint32_t csDeviceClass = 0;
  uint32_t csInterfaceClass = 0;

  debug_printf("joy_check_device: Getting USB descriptors\n");
  usb_descriptor_t *des = usb_get_descriptor_list(dev);

  // track if we've parsed something successfully - if so, we back-fill interface+endpoint info
  osbool got_valid_joystick = FALSE;

  debug_printf("joy_check_device: Parsing USB descriptors\n");
  for (char* pos=(char*)des; des->bLength; pos+=des->bLength,des=(usb_descriptor_t*)pos)
  {
    osbool unknown = FALSE;
    switch (des->bDescriptorType)
    {
      case USB_DEVICE_DESCRIPTOR:
        {
          ddesc = (usb_device_descriptor_t*)pos;
          csDeviceClass = ((ddesc->bDeviceClass << 16) | (ddesc->bDeviceSubClass << 8) | ddesc->bDeviceProtocol);
          debug_printf("joy_check_device: Device descriptor: Length %d, Type %d, Configurations %d, csDeviceClass 0x%x\n", ddesc->bLength, ddesc->bDescriptorType, ddesc->bNumConfigurations, csDeviceClass);
          debug_printf("joy_check_device: Device descriptor: iManufacturer %d, iProduct %d, iSerialNumber %d\n", ddesc->iManufacturer, ddesc->iProduct, ddesc->iSerialNumber);
          debug_printf("joy_check_device: Device descriptor: Vendor 0x%04x, Product 0x%04x, Device 0x%04x\n", ddesc->idVendor, ddesc->idProduct, ddesc->bcdDevice);
        }
        break;

      case USB_CONFIGURATION_DESCRIPTOR:
        {
          cdesc = (usb_config_descriptor_t*)pos;
          debug_printf("joy_check_device: Configuration descriptor: Length %u, Type %u, TotalLength %u, NumInterfaces %d \n", cdesc->bLength, cdesc->bDescriptorType, cdesc->wTotalLength, cdesc->bNumInterface);
        }
        break;

      case USB_STRING_DESCRIPTOR:
        {
          sdesc = (usb_string_descriptor_t*)pos;
          debug_printf("joy_check_device: String descriptor: Length %d, Type %d, String %d \n", sdesc->bLength, sdesc->bDescriptorType, sdesc->bString);
        }
        break;

      case USB_INTERFACE_DESCRIPTOR:
        {
          idesc = (usb_interface_descriptor_t*)pos;
          csInterfaceClass = ((idesc->bInterfaceClass << 16) | (idesc->bInterfaceSubClass << 8) | idesc->bInterfaceProtocol);
          debug_printf("joy_check_device: Interface descriptor: Length %u, DescriptorType %u, InterfaceNumber %d, InterfaceClass %d (3 is HID), iInterface %d, csInterfaceClass 0x%x\n", idesc->bLength, idesc->bDescriptorType, idesc->bInterfaceNumber, idesc->bInterfaceClass, idesc->iInterface, csInterfaceClass);
        }
        break;

      case USB_ENDPOINT_DESCRIPTOR:
        {
          edesc = (usb_endpoint_descriptor_t*)pos;
          debug_printf("joy_check_device: Endpoint descriptor: EndpointAddress %d, (on InterfaceNumber %d)\n", edesc->bEndpointAddress, idesc->bInterfaceNumber);
          if (got_valid_joystick == TRUE)
          {
            debug_printf("joy_check_device: Endpoint descriptor: We think this is a joystick (got HID report descriptor and it looks good), so set up\n");
            set_up_joystick(dev, device, ddesc, idesc, edesc);
            // reset validity so we can re-test the next endpoint
            got_valid_joystick = FALSE;
          }
          else
          {
            debug_printf("joy_check_device: Endpoint descriptor: We think this isn't a joystick - ignore this endpoint\n");
          }
        }
        break;

      default:
        {
          debug_printf("joy_check_device: Other descriptor: type is %u - not Device/Config/Interface/Endpoint\n (type 33 is HID device descriptor)", des->bDescriptorType);
          debug_printf("joy_check_device: csDeviceClass 0x%x, csInterfaceClass 0x%x\n", csDeviceClass, csInterfaceClass);
          debug_printf("joy_check_device: InterfaceClass 0x%x, InterfaceSubClass 0x%x, InterfaceProtocol %u, Vendor 0x%04x, Product 0x%04x\n", idesc->bInterfaceClass, idesc->bInterfaceSubClass, idesc->bInterfaceProtocol, ddesc->idVendor, ddesc->idProduct);

          struct joy_device_struct *joy_device = lookup_joy_device(idesc->bInterfaceClass, idesc->bInterfaceSubClass, idesc->bInterfaceProtocol, ddesc->idVendor, ddesc->idProduct);
          if (joy_device) {
            debug_printf("joy_check_device: Supported device: %s\n", joy_device->name);
            void *report_descriptor = joy_device->report_descriptor;
            uint8_t report_descriptor_length = joy_device->report_descriptor_length;
            if (report_descriptor != 0 && report_descriptor_length > 0) {
              debug_printf("joy_check_device: Using baked-in HID report descriptor\n");
            }
            else {
              // this device doesn't need a baked-in descriptor, so get it from the device
              debug_printf("joy_check_device: Assume we are looking at a HID device descriptor\n");
              usb_hid_device_descriptor_t* hdesc = (usb_hid_device_descriptor_t*)pos;
              debug_printf("joy_check_device: Getting HID report descriptor from device...\n");
              report_descriptor_length = hdesc->wReportDescLength;
              report_descriptor = get_report_descriptor(device, hdesc);
            }
            if (report_descriptor == 0 || report_descriptor_length == 0) {
              debug_printf("joy_check_device: Cannot get HID report descriptor for device - aborting\n");
            }
            else {
              // now report_descriptor is a valid HID report descriptor (baked -in or read from device)
              // note that joy_index will be moved on by this next function
              got_valid_joystick = parse_usb_hid_device_descriptor(report_descriptor_length, report_descriptor);
              debug_printf("joy_check_device: Parsed HID report descriptor, got_valid_joystick is %d\n", got_valid_joystick);
              // perform custom init sequence
              if (got_valid_joystick && joy_device->init_command_length > 0) {
                debug_printf("joy_check_device: Sending custom initialisation command\n");
                _kernel_oserror* e = usb_hid_set_output_report(device, idesc->bInterfaceNumber, joy_device->init_command, joy_device->init_command_length);
                if (e) {
                  debug_printf("joy_check_device: Unable to initialise device %s: %s\n", device, e->errmess);
                }
              }
              // note device driver info
              if (got_valid_joystick)
                joy_data[joy_index].device = joy_device;
                // we need to free the memory used by getting our own descriptor
                // todo: do we?  is this right?
              if (joy_device->report_descriptor == 0) {
                free(report_descriptor);
              }
            }
          }
          else {
            debug_printf("joy_check_device: Not a supported device - aborting\n");
          }

          if (unknown)
            debug_printf("joy_check_device: Unknown descriptor\n");
        }
    }
  }

  // This will never return an error (we only have the return type setup to satisfy usb.c)
  return NULL;
}





struct joy_device_struct *lookup_joy_device(uint8_t iClass, uint8_t iSubClass, uint8_t iProtocol, uint16_t vendor, uint16_t product)
{
  // look for a full match
  for (unsigned int i=0; i<joy_devices_count; i++) {
    if (joy_devices[i].interface_class == iClass &&
      joy_devices[i].interface_subclass == iSubClass &&
      joy_devices[i].interface_protocol == iProtocol &&
      joy_devices[i].vendor_id == vendor &&
      joy_devices[i].product_id == product)
      return (struct joy_device_struct*)&(joy_devices[i]);
  }

  // look for a match without vendor and product ids
  for (unsigned int i=0; i<joy_devices_count; i++) {
    if (joy_devices[i].interface_class == iClass &&
      joy_devices[i].interface_subclass == iSubClass &&
      joy_devices[i].interface_protocol == iProtocol &&
      joy_devices[i].vendor_id == 0 &&
      joy_devices[i].product_id == 0)
      return (struct joy_device_struct*)&(joy_devices[i]);
  }

  return 0;
}






uint8_t *get_report_descriptor(usb_device_t* device, usb_hid_device_descriptor_t *hdesc)
{
  int report_descriptor_type = UT_GET_TYPE(hdesc->bReportDescType);
  int report_descriptor_id = (hdesc->bReportDescType & 0xf);

  debug_printf("get_report_descriptor: HID device descriptor: Length %u, DescriptorType %u, HID %u, country %u, NumDescriptors %u, ReportDescType %u, ReportDescLength %u\n", hdesc->bLength, hdesc->bDescriptorType, hdesc->bcdHID, hdesc->bCountryCode, hdesc->bNumDescriptors, hdesc->bReportDescType, hdesc->wReportDescLength);

  debug_printf("get_report_descriptor: Calculated ReportDescriptorType %d, ReportDescriptorId %d\n", report_descriptor_type, report_descriptor_id);

  if (report_descriptor_type == UT_CLASS && report_descriptor_id == 2) {
    uint8_t *hidrepdesc = calloc(1, hdesc->wReportDescLength);
    if (hidrepdesc != NULL)
    {
      debug_printf("get_report_descriptor: Getting HID report descriptor\n");
      // is it correct to pass in hdesc->bReportDescType here for the type?  or should it be report_descriptor_type?
      _kernel_oserror* e = usb_get_descriptor(device, hdesc->bReportDescType, hdesc->wReportDescLength, hidrepdesc);
      if (e)
      {
        debug_printf("get_report_descriptor: Unable to get HID report descriptor: %s\n", e->errmess);
        free(hidrepdesc);
      }
      else
      {
        return hidrepdesc;
      }
    }
    else {
      debug_printf("get_report_descriptor: Failed to allocate memory for getting HID report descriptor - aborting\n");
    }
  }
  else {
    debug_printf("get_report_descriptor: report_descriptor_type is wrong (must be %u) and/or report_descriptor_id is wrong (must be 2) - aborting\n", UT_CLASS);
  }

  return 0;

}





void set_up_joystick(const usb_service_t* dev, usb_device_t* device, usb_device_descriptor_t* ddesc, usb_interface_descriptor_t* idesc, usb_endpoint_descriptor_t* edesc)
{
  debug_printf("set_up_joystick: Fill up joystick info for device '%s', interface %u, endpoint %u\n", device, idesc->bInterfaceNumber, edesc->bEndpointAddress);

  // ids
  joy_data[joy_index].vendor_id = ddesc->idVendor;
  joy_data[joy_index].product_id = ddesc->idProduct;
  joy_data[joy_index].device_id = ddesc->bcdDevice;

  // grab product/manufacturer/serial strings
  if (ddesc->iManufacturer > 0) {
    usb_string_descriptor_t *man = usb_get_device_string_descriptor(device, ddesc->iManufacturer);
    if (man) {
      usb_string_descriptor_to_ascii(man, joy_data[joy_index].manufacturer);
      debug_printf("set_up_joystick: String index %u, Manufacturer: '%s'\n", ddesc->iManufacturer, joy_data[joy_index].manufacturer);
      free((void*)man);
    }
  }
  else {
    debug_printf("set_up_joystick: No manufacturer string\n");
  }

  if (ddesc->iProduct > 0) {
    usb_string_descriptor_t *product = usb_get_device_string_descriptor(device, ddesc->iProduct);
    if (product) {
      usb_string_descriptor_to_ascii(product, joy_data[joy_index].product);
      debug_printf("set_up_joystick: String index %u, Product: '%s'\n", ddesc->iProduct, joy_data[joy_index].product);
      free((void*)product);
    }
  }
  else {
    debug_printf("set_up_joystick: No product string\n");
  }

  if (ddesc->iSerialNumber > 0) {
    usb_string_descriptor_t *serial = usb_get_device_string_descriptor(device, ddesc->iSerialNumber);
    if (serial) {
      usb_string_descriptor_to_ascii(serial, joy_data[joy_index].serial);
      debug_printf("set_up_joystick: String index %u, Serial: '%s'\n", ddesc->iSerialNumber, joy_data[joy_index].serial);
      free((void*)serial);
    }
  }
  else {
    debug_printf("set_up_joystick: No serial number string\n");
  }

  // construct devicefs path
  memcpy(joy_data[joy_index].device_name, dev->name, USB_DEVICE_NAME_LENGTH);
  joy_data[joy_index].interface = idesc->bInterfaceNumber;
  joy_data[joy_index].endpoint = edesc->bEndpointAddress & 0x1f;
  sprintf(joy_data[joy_index].usb_path, USB_PATH_SPECIFIER, joy_data[joy_index].interface, joy_data[joy_index].endpoint, joy_data[joy_index].device_name);
  debug_printf("set_up_joystick: Constructed DeviceFS path for stick id %u, path is: %s\n", joy_index, joy_data[joy_index].usb_path);

  // start talking to it
  debug_printf("set_up_joystick: Open a connection to the joystick (id is %u)\n", joy_index);
  open_joystick(joy_index);
}






osbool parse_usb_hid_device_descriptor(unsigned int length, uint8_t* buf)
{
  osbool is_joystick = FALSE;
  osbool got_something = FALSE;
  report_desc_t rd = NULL;

  joy_index = next_available_joystick_index();
  if (joy_index == NONE_AVAILABLE)
  {
    debug_printf("parse_usb_hid_device_descriptor: Cannot parse this HID device descriptor - we have run out of joysticks!\n");
    return FALSE;
  }
  debug_printf("parse_usb_hid_device_descriptor: Will use joystick number %d (next available) for this HID parse\n", joy_index);

  joy_data[joy_index].in_use = TRUE;

  // share HID report descriptor data with libusbhid
  rd = hid_use_report_desc(buf, length);

  if (rd == NULL) {
    debug_printf("parse_usb_hid_device_descriptor: Failed to share report descriptor with libusbhid - aborting parse\n");
    return FALSE;
  }

  debug_printf("parse_usb_hid_device_descriptor: Shared report descriptor 'rd' (size %u) with libusbhid\n", rd->size);

  // report_id is 0 by default, but some devices need a different value
  // todo: this could be dubious: what if we need to support MULTIPLE reportids?  Would they be two sticks?!
  joy_data[joy_index].has_report_id = FALSE;
  int report_id = NO_REPORT_ID;
  int custom_report_id = hid_get_report_id(rd);       // returns -1 for 'not used/applicable'
  if (custom_report_id > 0) {
    debug_printf("parse_usb_hid_device_descriptor: HID report descriptors use a reportid, so switching from %d to %d\n", report_id, custom_report_id);
    report_id = custom_report_id;
    joy_data[joy_index].has_report_id = TRUE;
  }

  // we only want to see inputs
  unsigned int size = hid_report_size(rd, hid_input, report_id);

  // todo: does this size include the 1st byte that might be a reportid?  
  joy_data[joy_index].report_length = size;

  if (size > 0) {
    debug_printf("parse_usb_hid_device_descriptor: Got hid report size %d for reportid %d, has_report_id is %d\n", size, report_id, joy_data[joy_index].has_report_id);
  }
  else {
    debug_printf("parse_usb_hid_device_descriptor: Error: HID report size is zero, abandoning parse\n");
    clean_joystick_data_item(joy_index);
    return FALSE;
  }

  // reserve space for storing raw data (now we know the required size)
  joy_data[joy_index].data_buf = malloc(size);
  if (joy_data[joy_index].data_buf == NULL)
  {
    debug_printf("parse_usb_hid_device_descriptor: Error: Not enough memory for joystick raw report data buffer.\n");
    clean_joystick_data_item(joy_index);
    return FALSE;
  }

  // initialise parsing data

  struct hid_data *d;
  struct hid_item h;

  // start parsing

  //debug_printf("parse_usb_hid_device_descriptor: Starting parse of HID report descriptor\n");
  unsigned int l = 1;
  IGNORE(l);
  for (d = hid_start_parse(rd, 1 << hid_input, report_id); hid_get_item(d, &h); )
  {
    int page = HID_PAGE(h.usage);
    int usage = HID_USAGE(h.usage);

    //debug_printf("parse_usb_hid_device_descriptor: HID parser 1: l %u, pos %u, h.usage=0x%x, page=0x%x, usage=0x%x, kind %d, reportid %d\n", l++, h.pos, h.usage, page, usage, h.kind, h.report_ID);

    is_joystick = is_joystick ||
        (
            h.kind == hid_collection &&
            page == HUP_GENERIC_DESKTOP &&
            (usage == HUG_JOYSTICK || usage == HUG_GAME_PAD)
        );

    if (h.kind != hid_input) continue;

    if (!is_joystick) continue;

    //debug_printf("parse_usb_hid_device_descriptor: HID parser 2: is_joystick %d\n", is_joystick);

    if (page == HUP_GENERIC_DESKTOP)
    {
      if (usage == HUG_HAT_SWITCH)
      {
        got_something = TRUE;
        if (joy_data[joy_index].hat_item == NULL) {
          //debug_printf("parse_usb_hid_device_descriptor: HID parser:   reportid %d, got HAT switch, copying\n", h.report_ID);
          joy_data[joy_index].hat_item = itemdup(&h);
        }
      }

      else if (usage == HUG_DPAD_UP)
      {
        got_something = TRUE;
        if (joy_data[joy_index].dpad_up_item == NULL) {
          //debug_printf("parse_usb_hid_device_descriptor: HID parser:   reportid %d, got dpad up, copying\n", h.report_ID);
          joy_data[joy_index].dpad_up_item = itemdup(&h);
        }
      }

      else if (usage == HUG_DPAD_DOWN)
      {
        got_something = TRUE;
        if (joy_data[joy_index].dpad_down_item == NULL) {
          //debug_printf("parse_usb_hid_device_descriptor: HID parser:   reportid %d, got dpad down, copying\n", h.report_ID);
          joy_data[joy_index].dpad_down_item = itemdup(&h);
        }
      }

      else if (usage == HUG_DPAD_LEFT)
      {
        got_something = TRUE;
        if (joy_data[joy_index].dpad_left_item == NULL) {
          //debug_printf("parse_usb_hid_device_descriptor: HID parser:   reportid %d, got dpad left, copying\n", h.report_ID);
          joy_data[joy_index].dpad_left_item = itemdup(&h);
        }
      }

      else if (usage == HUG_DPAD_RIGHT)
      {
        got_something = TRUE;
        if (joy_data[joy_index].dpad_right_item == NULL) {
          //debug_printf("parse_usb_hid_device_descriptor: HID parser:   reportid %d, got dpad right, copying\n", h.report_ID);
          joy_data[joy_index].dpad_right_item = itemdup(&h);
        }
      }

      else if (supported_axes_usage[usage])
      {
        unsigned int type = supported_axes_usage[usage];
        IGNORE(type);
        //debug_printf("parse_usb_hid_device_descriptor: HID parser:   reportid %d, got supported axis, usage is 0x%x, type is %u\n", h.report_ID, usage, type);
        got_something = TRUE;
        // see if we can find somewhere to copy the axis item
        for (unsigned int j=0; j<JOY_USAGE_AXES; j++) {
          if (joy_data[joy_index].axes_items[j] == NULL) {
            //debug_printf("HID parser:   copying axis HID item to index %u\n", j);
            joy_data[joy_index].axes_items[j] = itemdup(&h);
            break;
          }
          else {
            //debug_printf("parse_usb_hid_device_descriptor: HID parser:   index %u is full, will try next index\n", j);
          }
        }
      }

      else
      {
        debug_printf("HID parser:   reportid %d, usage is unsupported (not axis/dpad/hat, code 0x%x)\n", h.report_ID, usage);
      }

    }

    else if (page == HUP_BUTTON)
    {
      if ((usage > 0) && (usage <= JOY_BUTTONS))
      {
        got_something = TRUE;
        //debug_printf("parse_usb_hid_device_descriptor: HID parser:   reportid %d, got button, setting up button %d\n", h.report_ID, usage-1);

        if (joy_data[joy_index].button_items[usage-1] == NULL)
          joy_data[joy_index].button_items[usage-1] = itemdup(&h);

        if (usage > joy_data[joy_index].num_buttons)
          joy_data[joy_index].num_buttons = usage;
      }
    }

    else
    {
      //debug_printf("parse_usb_hid_device_descriptor: HID parser:   reportid %d, ignoring item where page %u and usage %u\n", h.report_ID, page, usage);
    }

  }  // end of for (each hid_item)

  hid_end_parse(d);
  debug_printf("parse_usb_hid_device_descriptor: Parse completed - freeing libusbhid report descriptor memory\n");
  hid_dispose_report_desc(rd);

  if (!got_something)
  {
    // nothing useful in this hid descriptor
    debug_printf("parse_usb_hid_device_descriptor: Got nothing - free raw data buffer memory and exit\n");
    clean_joystick_data_item(joy_index);
    return FALSE;
  }

  debug_printf("parse_usb_hid_device_descriptor: Analysing parse output to build axes info\n");

  // set up analogue axes
  for (unsigned int j=0; j<JOY_USAGE_AXES; j++)
  {
    struct hid_item *hid_item = joy_data[joy_index].axes_items[j];
    if (hid_item != NULL)
    {
      // we have a HID item for an axis, so build an axisdata_struct for it
      struct axisdata_struct *axis = &(joy_data[joy_index].axes[j]);
      joy_data[joy_index].num_axes++;

      unsigned int item_usage = HID_USAGE(hid_item->usage);

      axis->type = supported_axes_usage[item_usage];
      axis->name = (char*)axes_names[axis->type];
      axis->min = hid_item->logical_minimum;
      axis->max = hid_item->logical_maximum;
      axis->mid = ((axis->max - axis->min + 1) / 2) + axis->min;
      axis->val = axis->mid;
      axis->is_digital = FALSE;

      debug_printf("parse_usb_hid_device_descriptor: Axis %d, item_usage is %u, type is %u, name is %s\n", j, item_usage, axis->type, axis->name);
      
      unsigned int range = (axis->max) - (axis->min);
      unsigned int deadzone = (range) / 8;

      axis->serial_port_lower_bound = axis->mid - deadzone;
      axis->serial_port_upper_bound = axis->mid + deadzone;

      axis->acorn_slope_div_8 = (range > ACORN_AXIS_VALUES_8);
      if (axis->acorn_slope_div_8)
        axis->acorn_slope_8 = round_up_down((float)range / (float)ACORN_AXIS_VALUES_8);
      else
        axis->acorn_slope_8 = round_up_down((float)ACORN_AXIS_VALUES_8 / (float)range);

      axis->acorn_slope_div_16 = (range > ACORN_AXIS_VALUES_16);
      if (axis->acorn_slope_div_16)
        axis->acorn_slope_16 = round_up_down((float)range / (float)ACORN_AXIS_VALUES_16);
      else
        axis->acorn_slope_16 = round_up_down((float)ACORN_AXIS_VALUES_16 / (float)range);

      axis->pc_slope_div = (range > 100);
      if (axis->pc_slope_div)
        axis->pc_slope = round_up_down((float)range / (float)100);
      else
        axis->pc_slope = round_up_down((float)100 / (float)range);

    }
  }

  // if we have a HAT switch, then add two axes for it (x and y), if we have spare slots
  if (joy_data[joy_index].hat_item)
  {
    if (joy_data[joy_index].num_axes < JOY_AXES-2)
    {
      debug_printf("parse_usb_hid_device_descriptor: Stick has HAT switch and we have two axes spare to use\n");
      joy_data[joy_index].has_hat = TRUE;
      unsigned int n = joy_data[joy_index].num_axes;
      joy_data[joy_index].num_axes += 2;
      joy_data[joy_index].hat_axis = n;
      joy_data[joy_index].axes[n].type = AXIS_HATX;
      joy_data[joy_index].axes[n].name = (char*)axes_names[AXIS_HATX];
      joy_data[joy_index].axes[n+1].type = AXIS_HATY;
      joy_data[joy_index].axes[n+1].name = (char*)axes_names[AXIS_HATY];
      for (unsigned int j=0; j<2; j++)
      {
        joy_data[joy_index].axes[n+j].min = -1;
        joy_data[joy_index].axes[n+j].max = 1;
        joy_data[joy_index].axes[n+j].mid = 0;
        joy_data[joy_index].axes[n+j].val = 0;
        joy_data[joy_index].axes[n+j].serial_port_lower_bound = 0;
        joy_data[joy_index].axes[n+j].serial_port_upper_bound = 0;
        joy_data[joy_index].axes[n+j].acorn_slope_8 = 1;
        joy_data[joy_index].axes[n+j].acorn_slope_div_8 = FALSE;
        joy_data[joy_index].axes[n+j].acorn_slope_16 = 1;
        joy_data[joy_index].axes[n+j].acorn_slope_div_16 = FALSE;
        joy_data[joy_index].axes[n+j].pc_slope = 1;
        joy_data[joy_index].axes[n+j].pc_slope_div = FALSE;
        joy_data[joy_index].axes[n+j].is_digital = TRUE;
      }
    }
    else
    {
      // too many axes to support hat
      debug_printf("parse_usb_hid_device_descriptor: Too many axis to support HAT (got %d, max is %d ) - exiting\n", joy_data[joy_index].num_axes, JOY_AXES-1);
      clean_joystick_data_item(joy_index);
    }
  }

  // if we have a dpad, then add two axes for it (x and y), if we have spare slots
  if (joy_data[joy_index].dpad_up_item &&
      joy_data[joy_index].dpad_down_item &&
      joy_data[joy_index].dpad_left_item &&
      joy_data[joy_index].dpad_right_item)
  {
    if (joy_data[joy_index].num_axes < JOY_AXES-2)
    {
      debug_printf("parse_usb_hid_device_descriptor: Stick has DPAD and we have two axes spare to use\n");
      joy_data[joy_index].has_dpad = TRUE;
      unsigned int n = joy_data[joy_index].num_axes;
      joy_data[joy_index].num_axes += 2;
      joy_data[joy_index].dpad_axis = n;
      joy_data[joy_index].axes[n].type = AXIS_DPADX;
      joy_data[joy_index].axes[n].name = (char*)axes_names[AXIS_DPADX];
      joy_data[joy_index].axes[n+1].type = AXIS_DPADY;
      joy_data[joy_index].axes[n+1].name = (char*)axes_names[AXIS_DPADY];
      for (unsigned int j=0; j<2; j++)
      {
        joy_data[joy_index].axes[n+j].min = -1;
        joy_data[joy_index].axes[n+j].max = 1;
        joy_data[joy_index].axes[n+j].mid = 0;
        joy_data[joy_index].axes[n+j].val = 0;
        joy_data[joy_index].axes[n+j].serial_port_lower_bound = 0;
        joy_data[joy_index].axes[n+j].serial_port_upper_bound = 0;
        joy_data[joy_index].axes[n+j].acorn_slope_8 = 1;
        joy_data[joy_index].axes[n+j].acorn_slope_div_8 = FALSE;
        joy_data[joy_index].axes[n+j].acorn_slope_16 = 1;
        joy_data[joy_index].axes[n+j].acorn_slope_div_16 = FALSE;
        joy_data[joy_index].axes[n+j].pc_slope = 1;
        joy_data[joy_index].axes[n+j].pc_slope_div = FALSE;
        joy_data[joy_index].axes[n+j].is_digital = TRUE;
      }
    }
    else
    {
      // too many axes to support dpad
      debug_printf("parse_usb_hid_device_descriptor: Too many axis to support DPAD (got %d, max is %d ) - exiting\n", joy_data[joy_index].num_axes, JOY_AXES-1);
      clean_joystick_data_item(joy_index);
    }
  }

  debug_printf("parse_usb_hid_device_descriptor: Joystick/gamepad parsed and configured successfully (joy_index is %d)\n", joy_index);

  return TRUE;
}





void joystick_decode(unsigned int i)
{
  /* 0   1   2   3   4   5   6   7 */
  /* u  ru  r   rd   d  ld  l   lu */

  const int hat_x[] = {
     0,  1,  1,  1,  0, -1, -1, -1
  };
  const int hat_y[] = {
    -1, -1,  0,  1,  1,  1,  0, -1
  };

  int d;
  unsigned int j;
  char *p = joy_data[i].data_buf;

  // extract the reportid byte (if there is one), falling back to 0 as a default for ('no report id')
  unsigned int reportid = 0;
  if (joy_data[i].has_report_id) {
    reportid = joy_data[i].data_buf[0];
    p++;
  }

  // run any custom validators on the data packet
  osbool valid = FALSE;
  if (joy_data[i].device->data_valid)
    valid = joy_data[i].device->data_valid(p);
  else
    valid = TRUE;

  if (!valid)
    return;

  // move past the data offset (if there is one - it may be zero)
  p = p + joy_data[i].device->report_data_offset;

  // now parse the HID report data (currently pointed to by p)

  // standard axes
  for (j=0; j<joy_data[i].num_axes; j++)
  {
    if (joy_data[i].axes_items[j] && joy_data[i].axes_items[j]->report_ID == reportid) {
      joy_data[i].axes[j].val = hid_get_data(p, joy_data[i].axes_items[j]);
    }
  }

  // HAT switch
  if (joy_data[i].has_hat && joy_data[i].hat_item->report_ID == reportid)
  {
    d = hid_get_data(p, joy_data[i].hat_item) - joy_data[i].hat_item->logical_minimum;
    j = joy_data[i].hat_axis;
    if (d < 0 || d >= 8)
    {
      joy_data[i].axes[j].val = 0;
      joy_data[i].axes[j+1].val = 0;
    }
    else
    {
      joy_data[i].axes[j].val = hat_x[d];
      joy_data[i].axes[j+1].val = hat_y[d];
    }
  }

  // DPAD
  if (joy_data[i].has_dpad)
  {
    j = joy_data[i].dpad_axis;

    // left/right (x axis)
    if (joy_data[i].dpad_left_item->report_ID == reportid && joy_data[i].dpad_right_item->report_ID == reportid) {
      d = hid_get_data(p, joy_data[i].dpad_left_item) - joy_data[i].dpad_left_item->logical_minimum;
      if (d) {
        joy_data[i].axes[j].val = -1;
      }
      else {
        d = hid_get_data(p, joy_data[i].dpad_right_item) - joy_data[i].dpad_right_item->logical_minimum;
        if (d) {
          joy_data[i].axes[j].val = 1;
        }
        else {
          joy_data[i].axes[j].val = 0;
        }
      }
    }

    // up/down (y axis)
    if (joy_data[i].dpad_up_item->report_ID == reportid && joy_data[i].dpad_down_item->report_ID == reportid) {
      d = hid_get_data(p, joy_data[i].dpad_up_item) - joy_data[i].dpad_up_item->logical_minimum;
      if (d) {
        joy_data[i].axes[j+1].val = -1;
      }
      else {
        d = hid_get_data(p, joy_data[i].dpad_down_item) - joy_data[i].dpad_down_item->logical_minimum;
        if (d) {
          joy_data[i].axes[j+1].val = 1;
        }
        else {
          joy_data[i].axes[j+1].val = 0;
        }
      }
    }
  }

  // buttons
  for (j=0; j<joy_data[i].num_buttons; j++)
  {
    if (joy_data[i].button_items[j] && joy_data[i].button_items[j]->report_ID == reportid)
      joy_data[i].buttons[j] = (hid_get_data(p, joy_data[i].button_items[j]) == joy_data[i].button_items[j]->logical_maximum);
  }

  // do we need to update the mouse?
  // todo: implement acceleration
  //
  // this is called on every datarx, so we can note previous position
  // need to convert relative movement into scaled factor

  if (i == mouse_joy && joy_data[i].mapped_mouse_x != NOT_MAPPED && joy_data[i].mapped_mouse_y != NOT_MAPPED) {
    // get mouse x update
    struct axisdata_struct mx = joy_data[i].axes[joy_data[i].mapped_mouse_x];
    //int pc_x = scaled_value_8(&mx);
    //int dx = abs(pc_x-mouse_previous_pc_x) + 1;    // plus step?
    int x = 0;
    if (mx.val > mx.mid)
      x = mx.mouse_factor;
    else if (mx.val < mx.mid)
      x = -mx.mouse_factor;
    if (mx.flip)
      x = x * -1;
    mouse_rel_x = mouse_rel_x + x;
    //mouse_previous_pc_x = pc_x;
    // get mouse y update
    struct axisdata_struct my = joy_data[i].axes[joy_data[i].mapped_mouse_y];
    int y = 0;
    if (my.val > my.mid)
      y = -my.mouse_factor;
    else if (my.val < my.mid)
      y = my.mouse_factor;
    if (my.flip)
      y = y * -1;
    mouse_rel_y = mouse_rel_y + y;
    // update pointerv if there is pointer movement
    if (x || y) {
      // position
      _swix(OS_CallAVector, _INR(0,4) | _IN(9), PointerReason_Report, initial_pointer_type, x, y, 0, PointerV);
    }
    // select button
    if (joy_data[i].mapped_mouse_select != NOT_MAPPED) {
      int select = joy_data[i].buttons[joy_data[i].mapped_mouse_select];
      osbool changed = (select != mouse_previous_select);
      mouse_previous_select = select;
      if (changed)
        _swix(OS_CallAVector, _INR(0,1) | _IN(9), (select)? KeyV_KeyDown: KeyV_KeyUp, KeyNo_LeftMouse, KEYV);
    }
    // menu button
    if (joy_data[i].mapped_mouse_menu != NOT_MAPPED) {
      int menu = joy_data[i].buttons[joy_data[i].mapped_mouse_menu];
      osbool changed = (menu != mouse_previous_menu);
      mouse_previous_menu = menu;
      if (changed)
        _swix(OS_CallAVector, _INR(0,1) | _IN(9), (menu)? KeyV_KeyDown: KeyV_KeyUp, KeyNo_CentreMouse, KEYV);
    }
    // adjust button
    if (joy_data[i].mapped_mouse_adjust != NOT_MAPPED) {
      int adjust = joy_data[i].buttons[joy_data[i].mapped_mouse_adjust];
      osbool changed = (adjust != mouse_previous_adjust);
      mouse_previous_adjust = adjust;
      if (changed)
        _swix(OS_CallAVector, _INR(0,1) | _IN(9), (adjust)? KeyV_KeyDown: KeyV_KeyUp, KeyNo_RightMouse, KEYV);
    }
  }

  /*
  // todo: keypresses: how do we know if stick has moved u/d/l/r?
  // need to check acorn mapping, then read previous value to detect change.
  if (joy_data[i].key_up != NOT_MAPPED) {
  }*/

}





int pointerv_hook_handler(_kernel_swi_regs *r, void *pw)
{
  IGNORE(pw);

  if (r->r[0] == PointerReason_Request && mouse_joy != NOT_MAPPED) {
    // we need to update pointer position
    int irqs = _kernel_irqs_disabled();
    if (!irqs)
      _kernel_irqs_off();
    r->r[2] = mouse_rel_x;
    r->r[3] = mouse_rel_y;
    mouse_rel_x = mouse_rel_y = 0;
    if (!irqs)
      _kernel_irqs_on();
    return VECTOR_CLAIM;      // todo: is this sensible?!  we are not the only pointer device, are we?
  }

  return VECTOR_PASSON;
}





static struct hid_item *itemdup(struct hid_item *s)
{
  struct hid_item *t;

  t = malloc(sizeof(*t));
  if (t == NULL)
  {
    debug_printf("itemdup: Error: Not enough memory for joystick\n");
    return NULL;
  }

  memcpy(t, s, sizeof(*t));

  return t;
}




_kernel_oserror *module_finalise(int fatal, int podule, void *pw)
{
  IGNORE(fatal);
  IGNORE(podule);

  os_error *error = NULL;

  debug_printf("module_finalise: begin");

  if (upcall_claimed == TRUE) {
    error = xos_release(UpCallV, &upcallv_hook, pw);
    if (error) {
      debug_printf("module_finalise: failed to release upcallv, error number %d: %s\n", error->errnum, error->errmess);
    }
  }

  if (ukswiv_claimed == TRUE) {
    error = xos_release(UKSWIV, (asm_routine) ukswiv_pre_hook, pw);
    if (error) {
      debug_printf("module_finalise: failed to release ukswiv, error number %d: %s\n", error->errnum, error->errmess);
    }
  }

  if (bytev_claimed == TRUE) {
    error = xos_release(ByteV, &bytev_hook, pw);
    if (error) {
      debug_printf("module_finalise: failed to release bytev, error number %d: %s\n", error->errnum, error->errmess);
    }
  }

  if (pointerv_claimed == TRUE) {
    error = xos_release(PointerV, &pointerv_hook, pw);
    if (error) {
      debug_printf("module_finalise: failed to release pointerv, error number %d: %s\n", error->errnum, error->errmess);
    }
  }

  // close down any USB file handles we had active
  for (unsigned int i=0; i<JOY_MAX; i++) {
    if (joy_data[i].fp != NULL) {
      error = xosfind_closew(joy_data[i].fp);
      if (error) {
        debug_printf("module_finalise: failed to close joystick %d fp, error number %d: %s\n", i, error->errnum, error->errmess);
      }
      joy_data[i].fp = 0;
    }
  }

  #ifndef ROM_MODULE
  error = xresourcefs_deregister_files((resourcefs_file_list *) Resources());
  if (error) {
    debug_printf("module_finalise: failed to degreister resources, error number %d: %s\n", error->errnum, error->errmess);
  }
  #endif

  // allow module to die
  debug_printf("module_finalise: end (module can die)");
  return NULL;
}




_kernel_oserror *command_read(const char *args, int argc)
{
  if (argc != 1) {
    debug_printf("command_read: Bad arguments, argc isn't 1 (it is %d)\n", argc);
    return &err_badnoparms;
  }

  os_error *error = NULL;
  unsigned int i;
  unsigned int ret = sscanf(args, "%d", &i);
  if (ret != 1) {
    debug_printf("command_read: Bad arguments, didn't read 1 arg (read %d)\n", ret);
    return &err_badnoparms;
  }

  if (i >= JOY_MAX) {
    debug_printf("command_read: Bad arguments, id (%d) is out of range\n", i);
    return &err_badnoparms;
  }

  if (!joy_data[i].in_use) {
    debug_printf("command_read: Joystick id (%d) isn't active\n", i);
    return &err_joynotactive;
  }

  if (!joy_data[i].open) {
    debug_printf("command_read: Joystick id (%d) isn't open\n", i);
    return &err_joynotopen;
  }

  debug_printf("command_read: i %u\n", i);

  printf("Joytstick id %u (%s - %s)\n", i, joy_data[i].manufacturer, joy_data[i].product);

  for (unsigned int a=0; a<joy_data[i].num_axes; a++) {
    printf("Axis %02d %6s : ", a, joy_data[i].axes[a].name);
    printf("Val %8d  ", joy_data[i].axes[a].val);
    printf("Min %8d  ", joy_data[i].axes[a].min);
    printf("Mid %8d  ", joy_data[i].axes[a].mid);
    printf("Max %8d  ", joy_data[i].axes[a].max);
    printf("Flip=");
    if (joy_data[i].axes[a].flip)
      printf("on");
    else
      printf("off");

    printf("\n");
  }

  printf("Buttons: ");
  for (unsigned int b=0; b<joy_data[i].num_buttons; b++) {
    printf("%u=%u ", b, joy_data[i].buttons[b]);
  }
  printf("\n");

  IGNORE(error);

  return NULL;
}




_kernel_oserror *command_key_controls(const char *args, int argc)
{
  const char s[2] = " ";
  char *t = NULL;

  unsigned int id = JOY_MAX;      // the usb joystick id
  int up = NOT_MAPPED;
  int down = NOT_MAPPED;
  int left = NOT_MAPPED;
  int right = NOT_MAPPED;
  int b[ACORN_BUTTONS];

  t = strtok((char*)args, s);
  if (t) id = atoi(t);
  if (id >= JOY_MAX) {
    debug_printf("command_key_controls: Bad arguments, id (%d) is out of range\n", id);
    return &err_badnoparms;
  }

  t = strtok(NULL, s);
  if (t) up = atoi(t);

  t = strtok(NULL, s);
  if (t) down = atoi(t);

  t = strtok(NULL, s);
  if (t) left = atoi(t);

  t = strtok(NULL, s);
  if (t) right = atoi(t);

  unsigned int cmd_buttons = argc - 5;

  for (unsigned int i=0; i<ACORN_BUTTONS; i++) {
    if (i < cmd_buttons) {
      t = strtok(NULL, s);
      if (t) {
        b[i] = atoi(t);
      }
    }
    else {
      b[i] = NOT_MAPPED;
    }
  }
  if (!joy_data[id].in_use) {
    debug_printf("command_key_controls: Joystick id (%d) isn't active\n", id);
    return &err_joynotactive;
  }

  if (!joy_data[id].open) {
    debug_printf("command_key_controls: Joystick id (%d) isn't open\n", id);
    return &err_joynotopen;
  }

  // set up key mappings for this stick id
  joy_data[id].key_up = up;
  joy_data[id].key_down = down;
  joy_data[id].key_left = left;
  joy_data[id].key_right = right;

  for (int j=0; j<ACORN_BUTTONS; j++) {
    joy_data[id].key_buttons[j] = b[j];
  }

  return NULL;
}






_kernel_oserror *command_mouse_control(const char *args, int argc)
{
  IGNORE(argc);
  unsigned int i = JOY_MAX;

  osbool off = (strncmp(args, "off", 3) == 0);

  if (off) {
    // switch off control
    debug_printf("command_mouse_control: Switching off\n");
    mouse_joy = NOT_MAPPED;

    // ensure all mouse buttons are 'up'
    if (mouse_select)
      _swix(OS_CallAVector, _INR(0,1) | _IN(9), KeyV_KeyUp, KeyNo_LeftMouse, KEYV);

    if (mouse_menu)
      _swix(OS_CallAVector, _INR(0,1) | _IN(9), KeyV_KeyUp, KeyNo_CentreMouse, KEYV);

    if (mouse_adjust)
      _swix(OS_CallAVector, _INR(0,1) | _IN(9), KeyV_KeyUp, KeyNo_RightMouse, KEYV);
  }
  else {
    unsigned int ret = sscanf(args, "%d", &i);

    if (ret != 1) {
      debug_printf("command_mouse_control: Bad arguments, didn't read stick id\n", ret);
      return &err_badnoparms;
    }

    if (i >= JOY_MAX) {
      debug_printf("command_mouse_control: Bad arguments, id (%d) is out of range\n", i);
      return &err_badnoparms;
    }

    if (!joy_data[i].in_use) {
      debug_printf("command_mouse_control: Joystick id (%d) isn't active\n", i);
      return &err_joynotactive;
    }

    if (!joy_data[i].open) {
      debug_printf("command_mouse_control: Joystick id (%d) isn't open\n", i);
      return &err_joynotopen;
    }

    // switch on control

    // note current pointer type (to use in future pointer updates)
    os_error *getptr_e = xospointer_get(&initial_pointer_type);
    if (getptr_e)
      debug_printf("command_mouse_control: Error getting pointer type: %s\n", getptr_e->errmess);

    // todo: ONLY do this if the mouse x+y axes are mapped? buttons?
    debug_printf("command_mouse_control: Switching on (stick %u)\n", i);
    mouse_joy = i;
  }

  return NULL;
}






_kernel_oserror *command_map_mouse(const char *args, int argc)
{
  const char s[2] = " ";
  char *t = NULL;

  unsigned int id = JOY_MAX;      // the usb joystick id
  unsigned int x = JOY_AXES;
  unsigned int y = JOY_AXES;
  unsigned int select = JOY_BUTTONS;
  unsigned int menu = JOY_BUTTONS;
  unsigned int adjust = JOY_BUTTONS;

  t = strtok((char*)args, s);
  if (t) id = atoi(t);
  if (id >= JOY_MAX) {
    debug_printf("command_map_mouse: Bad arguments, id (%d) is too big\n", id);
    return &err_badnoparms;
  }

  t = strtok(NULL, s);
  if (t) x = atoi(t);
  if (x >= joy_data[id].num_axes) {
    debug_printf("command_map_mouse: Bad arguments, x (%d) is too big\n", x);
    return &err_badnoparms;
  }

  t = strtok(NULL, s);
  if (t) y = atoi(t);
  if (y >= joy_data[id].num_axes) {
    debug_printf("command_map_mouse: Bad arguments, y (%d) is too big\n", y);
    return &err_badnoparms;
  }

  t = strtok(NULL, s);
  if (t) select = atoi(t);
  if (select >= joy_data[id].num_buttons) {
    debug_printf("command_map_mouse: Bad arguments, select (%d) is too big\n", select);
    return &err_badnoparms;
  }

  t = strtok(NULL, s);
  if (t) menu = atoi(t);
  if (menu >= joy_data[id].num_buttons) {
    debug_printf("command_map_mouse: Bad arguments, menu (%d) is too big\n", menu);
    return &err_badnoparms;
  }

  t = strtok(NULL, s);
  if (t) adjust = atoi(t);
  if (adjust >= joy_data[id].num_buttons) {
    debug_printf("command_map_mouse: Bad arguments, adjust (%d) is too big\n", adjust);
    return &err_badnoparms;
  }

  if (!joy_data[id].in_use) {
    debug_printf("command_map_mouse: Joystick id (%d) isn't active\n", id);
    return &err_joynotactive;
  }

  if (!joy_data[id].open) {
    debug_printf("command_map_mouse: Joystick id (%d) isn't open\n", id);
    return &err_joynotopen;
  }

  // map
  joy_data[id].mapped_mouse_x = x;
  joy_data[id].mapped_mouse_y = y;
  joy_data[id].mapped_mouse_select = select;
  joy_data[id].mapped_mouse_menu = menu;
  joy_data[id].mapped_mouse_adjust = adjust;

  // todo: take in additional parameters (sensitivity + factor for x and y axes)

  IGNORE(argc);
  return NULL;
}





_kernel_oserror *command_map_buttons(const char *args, int argc)
{
  if (argc < 2 || argc > 17) {
    debug_printf("command_map_buttons: Bad arguments, argc is less than 2 or greater than 17 (it is %d)\n", argc);
    return &err_badnoparms;
  }

  const char s[2] = " ";
  char *t = NULL;

  unsigned int id = JOY_MAX;      // the usb joystick id
  unsigned int b[ACORN_BUTTONS];

  t = strtok((char*)args, s);
  if (t) id = atoi(t);
  if (id >= JOY_MAX) {
    debug_printf("command_map_buttons: Bad arguments, id (%d) is out of range\n", id);
    return &err_badnoparms;
  }

  // read button numbers from remaining arguments
  unsigned int cmd_buttons = argc - 1;

  for (unsigned int i=0; i<cmd_buttons; i++) {
    // i is the mapped button number
    t = strtok(NULL, s);
    if (t) {
      unsigned int button = atoi(t);
      if (button >= JOY_BUTTONS) {
        debug_printf("command_map_buttons: Bad arguments, button (%d) is too big\n", button);
        return &err_badnoparms;
      }
      b[i] = button;
    }
  }

  if (!joy_data[id].in_use) {
    debug_printf("command_map_buttons: Joystick id (%d) isn't active\n", id);
    return &err_joynotactive;
  }

  if (!joy_data[id].open) {
    debug_printf("command_map_buttons: Joystick id (%d) isn't open\n", id);
    return &err_joynotopen;
  }

  // clear out ALL mappings for this number
  for (unsigned int i=0; i<ACORN_BUTTONS; i++)
    joy_data[id].mapped_buttons[i] = NOT_MAPPED;

  // map the supplied button list up
  for (unsigned int i=0; i<cmd_buttons; i++)
    joy_data[id].mapped_buttons[i] = b[i];

  return NULL;
}







_kernel_oserror *command_map_axes(const char *args, int argc, osbool sixteenbit)
{
  if (argc != 3) {
    debug_printf("command_map_axes: Bad arguments, argc is not 3 (it is %d)\n", argc);
    return &err_badnoparms;
  }

  const char s[2] = " ";
  char *t = NULL;

  unsigned int id = JOY_MAX;      // the usb joystick id
  unsigned int x = JOY_AXES;
  unsigned int y = JOY_AXES;

  t = strtok((char*)args, s);
  if (t) id = atoi(t);
  if (id >= JOY_MAX) {
    debug_printf("command_map_axes: Bad arguments, id (%d) is too big\n", id);
    return &err_badnoparms;
  }

  // todo: limit x axis index to max axis for the particular joystick?
  t = strtok(NULL, s);
  if (t) x = atoi(t);
  if (x >= JOY_AXES) {
    debug_printf("command_map_axes: Bad arguments, x (%d) is too big\n", x);
    return &err_badnoparms;
  }

  // todo: limit y axis index to max axis for the particular joystick?
  t = strtok(NULL, s);
  if (t) y = atoi(t);
  if (y >= JOY_AXES) {
    debug_printf("command_map_axes: Bad arguments, y (%d) is too big\n", y);
    return &err_badnoparms;
  }

  if (!joy_data[id].in_use) {
    debug_printf("command_map_axes: Joystick id (%d) isn't active\n", id);
    return &err_joynotactive;
  }

  if (!joy_data[id].open) {
    debug_printf("command_map_axes: Joystick id (%d) isn't open\n", id);
    return &err_joynotopen;
  }

  // map these axes
  if (!sixteenbit) {
    joy_data[id].mapped_x_8 = x;
    joy_data[id].mapped_y_8 = y;
  }
  else {
    joy_data[id].mapped_x_16 = x;
    joy_data[id].mapped_y_16 = y;
  }

  return NULL;
}






_kernel_oserror *command_map_adc_buttons(const char *args, int argc)
{
  // MapADCButtons <stick> <fire0> [<fire1>]

  const char s[2] = " ";
  char *t = NULL;

  unsigned int id = JOY_MAX;      // the usb joystick id
  unsigned int b[ADC_BUTTONS];

  t = strtok((char*)args, s);
  if (t) id = atoi(t);
  if (id >= JOY_MAX) {
    debug_printf("command_map_adc_buttons: Bad arguments, id (%d) is out of range\n", id);
    return &err_badnoparms;
  }

  // read button numbers from remaining arguments
  unsigned int cmd_buttons = argc - 1;

  for (unsigned int i=0; i<cmd_buttons; i++) {
    // i is the mapped button number
    t = strtok(NULL, s);
    if (t) {
      unsigned int button = atoi(t);
      if (button >= joy_data[id].num_buttons) {
        debug_printf("command_map_adc_buttons: Bad arguments, button %u (%u) is too big\n", i, button);
        return &err_badnoparms;
      }
      b[i] = button;
    }
  }

  if (!joy_data[id].in_use) {
    debug_printf("command_map_adc_buttons: Joystick id (%d) isn't active\n", id);
    return &err_joynotactive;
  }

  if (!joy_data[id].open) {
    debug_printf("command_map_adc_buttons: Joystick id (%d) isn't open\n", id);
    return &err_joynotopen;
  }

  // map the supplied button list up
  for (unsigned int i=0; i<cmd_buttons; i++) {
    adc_map.buttons[i].button = b[i];
    adc_map.buttons[i].joy_id = id;
  }

  return NULL;
}




_kernel_oserror *command_map_adc(const char *args, int argc)
{
  // *USBJoystick_MapADC <channel> <stick id> <axis> [<start>] [<end>]

  const char s[2] = " ";
  char *t = NULL;

  unsigned int channel = 0;
  unsigned int id = JOY_MAX;          // the usb joystick id
  unsigned int axis = 0;
  int start = 0;
  int end = 0;

  t = strtok((char*)args, s);
  if (t) channel = atoi(t);
  if (channel >= ADC_CHANNELS) {
    debug_printf("command_map_adc: Bad arguments, channel (%u) is too big\n", channel);
    return &err_badnoparms;
  }

  id = atoi(strtok(NULL, s));
  if (id >= JOY_MAX) {
    debug_printf("command_map_adc: Bad arguments, stick id (%d) is too big\n", id);
    return &err_badnoparms;
  }

  t = strtok(NULL, s);
  if (t) axis = atoi(t);
  if (axis >= joy_data[id].num_axes) {
    debug_printf("command_map_adc: Bad arguments, axis (%u) is too big\n", axis);
    return &err_badnoparms;
  }

  if (argc >= 4) {
    t = strtok(NULL, s);
    if (t) start = atoi(t);
    if (start < joy_data[id].axes[axis].min || start > joy_data[id].axes[axis].max) {
      debug_printf("command_map_adc: Bad arguments, start (%d) is out of range\n", start);
      return &err_badnoparms;
    }
  }
  else {
    start = joy_data[id].axes[axis].min;
  }

  if (argc >= 5) {
    t = strtok(NULL, s);
    if (t) end = atoi(t);
    if (end < joy_data[id].axes[axis].min || start > joy_data[id].axes[axis].max) {
      debug_printf("command_map_adc: Bad arguments, end (%d) is out of range\n", end);
      return &err_badnoparms;
    }
  }
  else {
    end = joy_data[id].axes[axis].max;
  }

  if (!joy_data[id].in_use) {
    debug_printf("command_map_adc: Joystick id (%d) isn't active\n", id);
    return &err_joynotactive;
  }

  if (!joy_data[id].open) {
    debug_printf("command_map_adc: Joystick id (%d) isn't open\n", id);
    return &err_joynotopen;
  }

  // all parameters are sane, so update map
  adc_map.channels[channel].joy_id = id;
  adc_map.channels[channel].axis = axis;
  if (end > start) {
    adc_map.channels[channel].start = start;
    adc_map.channels[channel].end = end;
    adc_map.channels[channel].flip = FALSE;
  }
  else {
    adc_map.channels[channel].end = start;
    adc_map.channels[channel].start = end;
    adc_map.channels[channel].flip = TRUE;
  }

  int range = adc_map.channels[channel].end - adc_map.channels[channel].start;
  adc_map.channels[channel].slope_div = (range > ADC_AXIS_VALUES);

  if (adc_map.channels[channel].slope_div)
    adc_map.channels[channel].slope = round_up_down((float)range / (float)ADC_AXIS_VALUES);
  else
    adc_map.channels[channel].slope = round_up_down((float)ADC_AXIS_VALUES / (float)range);

  return NULL;
}




int round_up_down(float n)
{
  int i = (int)(n < 0 ? (n - 0.5) : (n + 0.5));
  return i;
}




_kernel_oserror *command_flip(const char *args, int argc)
{
  if (argc != 3) {
    debug_printf("command_flip: Bad arguments, argc is not 3 (it is %d)\n", argc);
    return &err_badnoparms;
  }

  const char s[2] = " ";
  char *t = NULL;

  unsigned int id = JOY_MAX;      // the usb joystick id
  unsigned int axis = JOY_AXES;

  t = strtok((char*)args, s);
  if (t) id = atoi(t);
  if (id >= JOY_MAX) {
    debug_printf("command_flip: Bad arguments, id (%d) is too big\n", id);
    return &err_badnoparms;
  }

  t = strtok(NULL, s);
  if (t) axis = atoi(t);
  if (axis >= JOY_AXES) {
    debug_printf("command_flip: Bad arguments, axis (%d) is too big\n", axis);
    return &err_badnoparms;
  }

  // must have an on/off argument, so check if it's on or off
  t = strtok(NULL, s);
  osbool arg_on = (strncmp(t, "on", 2) == 0);
  osbool arg_off = (strncmp(t, "off", 3) == 0);

  if (!arg_on && !arg_off) {
    debug_printf("command_flip: Bad arguments, not on or off (it is %t)\n", argc);
    return &err_badnoparms;
  }

  debug_printf("command_flip: Setting flip on id %d, axis %d to %d\n", id, axis, arg_on);
  joy_data[id].axes[axis].flip = arg_on;

  return NULL;
}





_kernel_oserror *command_map_stick(const char *args, int argc)
{
  if (argc != 2) {
    debug_printf("command_map_stick: Bad arguments, argc is not 2 (it is %d)\n", argc);
    return &err_badnoparms;
  }

  const char s[2] = " ";
  char *t = NULL;

  unsigned int id = JOY_MAX;      // the usb joystick id (0-based)
  int number = JOY_MAX;           // the legacy api number (0-based)

  t = strtok((char*)args, s);
  if (t) id = atoi(t);
  if (id >= JOY_MAX) {
    debug_printf("command_map_stick: Bad arguments, id (%d) is too big\n", id);
    return &err_badnoparms;
  }

  t = strtok(NULL, s);
  if (t) number = atoi(t);
  if (number >= JOY_MAX) {
    debug_printf("command_map_stick: Bad arguments, number (%d) is out of range\n", number);
    return &err_badnoparms;
  }

  if (!joy_data[id].in_use) {
    debug_printf("command_map_stick: Joystick id (%d) isn't active\n", id);
    return &err_joynotactive;
  }

  if (!joy_data[id].open) {
    debug_printf("command_map_stick: Joystick id (%d) isn't open\n", id);
    return &err_joynotopen;
  }

  // clear out any other mappings for this number
  for (unsigned int i=0; i<JOY_MAX; i++) {
    if (joy_data[i].mapped_number == number) {
      joy_data[i].mapped_number = NOT_MAPPED;
    }
  }

  // map this one up
  joy_data[id].mapped_number = number;

  return NULL;
}










_kernel_oserror *command_emulate_acorn(const char *args, int argc)
{
  if (argc == 0) {
    printf("EmulateAcorn is ");
    if (emulate_acorn_on)
      printf("on\n");
    else
      printf("off\n");
    return NULL;
  }

  // must have one argument, so check if it's on or off
  int arg_on = (strncmp(args, "on", 2) == 0);
  int arg_off = (strncmp(args, "off", 3) == 0);

  if (!arg_on && !arg_off) {
    debug_printf("EmulateAcorn: Bad argument - must be on or off\n");
    return &err_badnoparms;
  }

  if (arg_on && emulate_acorn_on) {
    debug_printf("EmulateAcorn: Bad argument - already on\n");
    return &err_badnoparms;
  }

  if (arg_off && !emulate_acorn_on) {
    debug_printf("EmulateAcorn: Bad argument - already off\n");
    return &err_badnoparms;
  }

  emulate_acorn_on = arg_on;
  return NULL;
}




_kernel_oserror *command_emulate_serial_port(const char *args, int argc)
{
  if (argc == 0) {
    printf("EmulateSerialPort is ");
    if (emulate_serial_port_on)
      printf("on\n");
    else
      printf("off\n");
    return NULL;
  }

  // must have one argument, so check if it's on or off
  int arg_on = (strncmp(args, "on", 2) == 0);
  int arg_off = (strncmp(args, "off", 3) == 0);

  if (!arg_on && !arg_off) {
    debug_printf("EmulateSerialPort: Bad argument - must be on or off\n");
    return &err_badnoparms;
  }

  if (arg_on && emulate_serial_port_on) {
    debug_printf("EmulateSerialPort: Bad argument - already on\n");
    return &err_badnoparms;
  }

  if (arg_off && !emulate_serial_port_on) {
    debug_printf("EmulateSerialPort: Bad argument - already off\n");
    return &err_badnoparms;
  }

  emulate_serial_port_on = arg_on;
  return NULL;
}






_kernel_oserror *command_emulate_joy(const char *args, int argc)
{
  if (argc == 0) {
    printf("EmulateJoy is ");
    if (emulate_joy_on)
      printf("on\n");
    else
      printf("off\n");
    return NULL;
  }

  // must have one argument, so check if it's on or off
  int arg_on = (strncmp(args, "on", 2) == 0);
  int arg_off = (strncmp(args, "off", 3) == 0);

  if (!arg_on && !arg_off) {
    debug_printf("EmulateJoy: Bad argument - must be on or off\n");
    return &err_badnoparms;
  }

  if (arg_on && emulate_joy_on) {
    debug_printf("EmulateJoy: Bad argument - already on\n");
    return &err_badnoparms;
  }

  if (arg_off && !emulate_joy_on) {
    debug_printf("EmulateJoy: Bad argument - already off\n");
    return &err_badnoparms;
  }

  emulate_joy_on = arg_on;
  return NULL;
}




_kernel_oserror *command_emulate_adc(const char *args, int argc)
{
  if (argc == 0) {
    printf("EmulateADC is ");
    if (emulate_adc_on)
      printf("on\n");
    else
      printf("off\n");
    return NULL;
  }

  // must have one argument, so check if it's on or off
  int arg_on = (strncmp(args, "on", 2) == 0);
  int arg_off = (strncmp(args, "off", 3) == 0);

  if (!arg_on && !arg_off) {
    debug_printf("EmulateADC: Bad argument - must be on or off\n");
    return &err_badnoparms;
  }

  if (arg_on && emulate_adc_on) {
    debug_printf("EmulateADC: Bad argument - already on\n");
    return &err_badnoparms;
  }

  if (arg_off && !emulate_adc_on) {
    debug_printf("EmulateADC: Bad argument - already off\n");
    return &err_badnoparms;
  }

  emulate_adc_on = arg_on;
  return NULL;
}





_kernel_oserror *command_debug(const char *args, int argc)
{
  if (argc >= 1) {
    int desired_id = -1;
    unsigned int ret = sscanf(args, "%d", &desired_id);
    if (ret != 1) {
      debug_printf("Debug: Bad arguments, didn't read 1 args (read %d)\n", ret);
      return &err_badnoparms;
    }
  
    if (desired_id < 0 || desired_id >= JOY_MAX) {
      debug_printf("Debug: Bad arguments, id (%d) is too big\n", desired_id);
      return &err_badnoparms;
    }

    debug_printf("Debug: Printing out single stick id %d\n", desired_id);
    debug_joystick(desired_id);
  }
  else {
    osbool any = FALSE;
  
    for (unsigned int i=0; i<JOY_MAX; i++) {
      if (joy_data[i].in_use == TRUE) {
        debug_printf("Debug: Printing out all active sticks, currently on stick id %d\n", i);
        debug_joystick(i);
        any = TRUE;
      }
    }
  
    if (any == FALSE) {
      printf("No active joysticks.\n");
    }
  }

  return NULL;
}





_kernel_oserror *command_list(const char *args, int argc)
{
  if (argc >= 1) {
    int desired_id = -1;
    unsigned int ret = sscanf(args, "%d", &desired_id);
    if (ret != 1) {
      debug_printf("command_list: Bad arguments, didn't read 1 args (read %d)\n", ret);
      return &err_badnoparms;
    }
  
    if (desired_id < 0 || desired_id >= JOY_MAX) {
      debug_printf("command_list: Bad arguments, id (%d) is too big\n", desired_id);
      return &err_badnoparms;
    }

    debug_printf("command_list: Printing out single stick id %d\n", desired_id);
    print_joystick(desired_id);
  }
  else {
    osbool any = FALSE;
  
    for (unsigned int i=0; i<JOY_MAX; i++) {
      if (joy_data[i].in_use == TRUE) {
        debug_printf("command_list: Printing out all active sticks, currently on stick id %d\n", i);
        print_joystick(i);
        any = TRUE;
      }
    }
  
    if (any == FALSE) {
      printf("No active joysticks.\n");
    }
  }

  return NULL;
}




_kernel_oserror *command_mapped(const char *args, int argc)
{
  if (argc >= 1) {
    int i = -1;
    unsigned int ret = sscanf(args, "%d", &i);
    if (ret != 1) {
      debug_printf("command_mapped: Bad arguments, didn't read 1 args (read %d)\n", ret);
      return &err_badnoparms;
    }
  
    if (i < 0 || i >= JOY_MAX) {
      debug_printf("command_mapped: Bad arguments, id (%d) is too big\n", i);
      return &err_badnoparms;
    }

    debug_printf("command_mapped: Printing out single stick id %d\n", i);
    printf("Joystick id %u (%s - %s)\n", i, joy_data[i].manufacturer, joy_data[i].product);
    print_joystick_map(i);
  }
  else {
    osbool any = FALSE;
  
    for (unsigned int i=0; i<JOY_MAX; i++) {
      if (joy_data[i].in_use == TRUE && joy_data[i].mapped_number != NOT_MAPPED) {
        printf("Joystick id %u (%s - %s)\n", i, joy_data[i].manufacturer, joy_data[i].product);
        print_joystick_map(i);
        any = TRUE;
      }
    }
  
    if (any == FALSE) {
      printf("No mapped joysticks.\n");
    }
  }
  return NULL;
}





void print_joystick(unsigned int i)
{
  printf("Joystick id %u (%s - %s)\n", i, joy_data[i].manufacturer, joy_data[i].product);

  printf("  Local USB device name            : %s\n", joy_data[i].device_name);
  printf("  Interface                        : %d\n", joy_data[i].interface);
  printf("  Endpoint                         : %d\n", joy_data[i].endpoint);
  printf("  Device manufacturer              : %s\n", joy_data[i].manufacturer);
  printf("  Device product name              : %s\n", joy_data[i].product);
  printf("  Device serial number             : %s\n", joy_data[i].serial);
  printf("  Driver                           : %s\n", joy_data[i].device->name);

  printf("  Number of axes                   : %d\n", joy_data[i].num_axes);
  printf("  Number of buttons                : %d\n", joy_data[i].num_buttons);
                                       
  printf("  HAT switch                       : ");
  if (joy_data[i].hat_item)            
    printf("Yes\n");                   
  else                                 
    printf("No\n");                    
                                       
  printf("  DPAD                             : ");
  if (joy_data[i].has_dpad)            
    printf("Yes\n");
  else
    printf("No\n");

  printf("  Mouse control enabled            : ");
  if (mouse_joy == i)
    printf("Yes\n");
  else
    printf("No\n");

  printf("  Keyboard controls                : ");
  if (joy_data[i].key_up != NOT_MAPPED)
    printf("Up=%u ", joy_data[i].key_up);
  if (joy_data[i].key_down != NOT_MAPPED)
    printf("Down=%u ", joy_data[i].key_down);
  if (joy_data[i].key_left != NOT_MAPPED)
    printf("Left=%u ", joy_data[i].key_left);
  if (joy_data[i].key_right != NOT_MAPPED)
    printf("Right=%u ", joy_data[i].key_right);

  for (int j=0; j<ACORN_BUTTONS; j++) {
    if (joy_data[i].key_buttons[j] != NOT_MAPPED)
      printf("B%u=%u ", j, joy_data[i].key_buttons[j]);
  }
    printf("\n");

  //print_joystick_map(i);
}




void print_joystick_map(unsigned int i)
{
  printf("  Mapped to Joystick number        : ");
  if (joy_data[i].mapped_number == NOT_MAPPED)
    printf("N/A\n");
  else
    printf("%02d\n", joy_data[i].mapped_number);

  printf("  Mapped to Joystick  8-bit x-axis : ");
  if (joy_data[i].mapped_x_8 == NOT_MAPPED)
    printf("N/A\n");
  else
    printf("%02d (%s)\n", joy_data[i].mapped_x_8, joy_data[i].axes[joy_data[i].mapped_x_8].name);

  printf("  Mapped to Joystick  8-bit y-axis : ");
  if (joy_data[i].mapped_y_8 == NOT_MAPPED)
    printf("N/A\n");
  else
    printf("%02d (%s)\n", joy_data[i].mapped_y_8, joy_data[i].axes[joy_data[i].mapped_y_8].name);

  printf("  Mapped to Joystick 16-bit x-axis : ");
  if (joy_data[i].mapped_x_16 == NOT_MAPPED)
    printf("N/A\n");
  else
    printf("%02d (%s)\n", joy_data[i].mapped_x_16, joy_data[i].axes[joy_data[i].mapped_x_16].name);

  printf("  Mapped to Joystick 16-bit y-axis : ");
  if (joy_data[i].mapped_y_16 == NOT_MAPPED)
    printf("N/A\n");
  else
    printf("%02d (%s)\n", joy_data[i].mapped_y_16, joy_data[i].axes[joy_data[i].mapped_y_16].name);

  for (unsigned int j=0; j<ACORN_BUTTONS; j++) {
    if (joy_data[i].mapped_buttons[j] != NOT_MAPPED)
      printf("  Mapped to Joystick button %02d     : %02d\n", j, joy_data[i].mapped_buttons[j]);
  }

  for (unsigned int c=0; c<ADC_CHANNELS; c++) {
    if (adc_map.channels[c].joy_id == i) {
      printf("  Mapped to ADC channel %d          : ", c);
      if (adc_map.channels[c].axis == NOT_MAPPED)
        printf("N/A\n");
      else
        printf("%02d (%s) %d to %d\n", adc_map.channels[c].axis, joy_data[i].axes[adc_map.channels[c].axis].name, adc_map.channels[c].start, adc_map.channels[c].end);
    }
  }
  for (unsigned int b=0; b<ADC_BUTTONS; b++) {
    if (adc_map.buttons[b].joy_id == i) {
      printf("  Mapped to ADC fire %d             : ", b);
      if (adc_map.buttons[b].button == NOT_MAPPED)
        printf("N/A\n");
      else
        printf("%02u\n", adc_map.buttons[b].button);
    }
  }

  printf("  Mapped to mouse x-axis           : ");
  if (joy_data[i].mapped_mouse_x == NOT_MAPPED)
    printf("N/A\n");
  else
    printf("%02d (%s), sensitivity %u, factor %u\n", joy_data[i].mapped_mouse_x, joy_data[i].axes[joy_data[i].mapped_mouse_x].name, joy_data[i].axes[joy_data[i].mapped_mouse_x].mouse_sensitivity, joy_data[i].axes[joy_data[i].mapped_mouse_x].mouse_factor);

  printf("  Mapped to mouse y-axis           : ");
  if (joy_data[i].mapped_mouse_y == NOT_MAPPED)
    printf("N/A\n");
  else
    printf("%02d (%s), sensitivity %u, factor %u\n", joy_data[i].mapped_mouse_y, joy_data[i].axes[joy_data[i].mapped_mouse_y].name, joy_data[i].axes[joy_data[i].mapped_mouse_y].mouse_sensitivity, joy_data[i].axes[joy_data[i].mapped_mouse_y].mouse_factor);

  printf("  Mapped to mouse buttons          : SELECT=");
  if (joy_data[i].mapped_mouse_select == NOT_MAPPED)
    printf("N/A  ");
  else
    printf("%02d  ", joy_data[i].mapped_mouse_select);

  printf("MENU=");
  if (joy_data[i].mapped_mouse_menu == NOT_MAPPED)
    printf("N/A  ");
  else
    printf("%02d  ", joy_data[i].mapped_mouse_menu);

  printf("ADJUST=");
  if (joy_data[i].mapped_mouse_adjust == NOT_MAPPED)
    printf("N/A\n");
  else
    printf("%02d\n", joy_data[i].mapped_mouse_adjust);

}





void debug_joystick(unsigned int i)
{
  printf("Joystick id %u (%s - %s)\n", i, joy_data[i].manufacturer, joy_data[i].product);

  printf("  Path                    : %s\n", joy_data[i].usb_path);
  printf("  File handle             : %u\n", joy_data[i].fp);

  printf("  Data stream open        : ");
  if (joy_data[i].open)
    printf("Yes\n");
  else
    printf("No (is something else using it?)\n");

  printf("  Local USB device name   : %s\n", joy_data[i].device_name);
  printf("  Interface               : %d\n", joy_data[i].interface);
  printf("  Endpoint                : %d\n", joy_data[i].endpoint);
  printf("  Device manufacturer     : %s\n", joy_data[i].manufacturer);
  printf("  Device product name     : %s\n", joy_data[i].product);
  printf("  Device serial number    : %s\n", joy_data[i].serial);
                                       
  printf("  USB vendor id           : 0x%04x\n", joy_data[i].vendor_id);
  printf("  USB product id          : 0x%04x\n", joy_data[i].product_id);
  printf("  USB device id           : 0x%04x\n", joy_data[i].device_id);

  printf("  Buffer handle           : %u\n", (unsigned int)(joy_data[i].buffer_handle));
  printf("  Buffer internal id      : %u\n", (unsigned int)(joy_data[i].buffer_internal_id));
  printf("  Buffer service routine  : 0x%08x\n", (unsigned int)(joy_data[i].buffer_service_routine));
  printf("  Buffer workspace        : 0x%08x\n", (unsigned int)(joy_data[i].buffer_workspace));

  printf("  DeviceFS handle         : 0x%08x\n", joy_data[i].devicefs_handle);
  printf("  Report data length      : %u\n", joy_data[i].report_length);

  printf("  Upcalls                 : %u (delta: %u)\n", joy_data[i].upcalls, joy_data[i].upcalls_delta);
  joy_data[i].upcalls_delta = 0;

  printf("  Good reads              : %u (delta: %u)\n", joy_data[i].good_reads, joy_data[i].good_reads_delta);
  joy_data[i].good_reads_delta = 0;

  printf("  Bad reads               : %u (delta: %u)\n", joy_data[i].bad_reads, joy_data[i].bad_reads_delta);
  joy_data[i].bad_reads_delta = 0;

  printf("  Raw report data         : ");
  for (int b=0; b<joy_data[i].report_length; b++) {
    printf("%02x ", joy_data[i].data_buf[b]);
  }
  printf("\n");

  printf("  Number of axes          : %d\n", joy_data[i].num_axes);
  printf("  Number of buttons       : %d\n", joy_data[i].num_buttons);
                                       
  printf("  HAT switch              : ");
  if (joy_data[i].hat_item)            
    printf("Yes\n");                   
  else                                 
    printf("No\n");                    
                                       
  printf("  DPAD                    : ");
  if (joy_data[i].has_dpad)            
    printf("Yes\n");
  else
    printf("No\n");

  printf("  Axes:\n");

  for (int a=0; a<joy_data[i].num_axes; a++) {
    printf("  Axis %02d %5s : ", a, joy_data[i].axes[a].name);
    printf("Slope 8 = %4u ", joy_data[i].axes[a].acorn_slope_8);
    if (joy_data[i].axes[a].acorn_slope_div_8)
      printf("(div), ");
    else
      printf("(mul), ");

    printf("Slope 16 = %4u ", joy_data[i].axes[a].acorn_slope_16);
    if (joy_data[i].axes[a].acorn_slope_div_16)
      printf("(div), ");
    else
      printf("(mul), ");

    printf("Mouse factor = %4u, Mouse sensitivity = %4u ", joy_data[i].axes[a].mouse_factor, joy_data[i].axes[a].mouse_sensitivity);

    printf("SP = %6d:%6d, ", joy_data[i].axes[a].serial_port_lower_bound, joy_data[i].axes[a].serial_port_upper_bound);

    printf("Flip = ");
    if (joy_data[i].axes[a].flip)
      printf(" on, ");
    else
      printf("off, ");

    struct hid_item *item = get_axis_item_by_type(i, joy_data[i].axes[a].type);
    if (item)
      printf("ReportId = %d", item->report_ID);
    else
      printf("(no item?)");

    printf("\n");
  }

  printf("  Button ReportIds : ");
  for (int a=0; a<JOY_BUTTONS; a++) {
    if (joy_data[i].button_items[a])
      printf("%02d=%d ", a, joy_data[i].button_items[a]->report_ID);
  }
  printf("\n");
}





struct hid_item *get_axis_item_by_type(unsigned int i, unsigned int type) {
  switch (type)
  {
    case AXIS_HATX:
    case AXIS_HATY:
      return joy_data[i].hat_item;

    case AXIS_DPADX:
      return joy_data[i].dpad_left_item;

    case AXIS_DPADY:
      return joy_data[i].dpad_up_item;

    default:
    {
      for (unsigned int a=0; a<JOY_USAGE_AXES; a++) {
        if (joy_data[i].axes_items[a]) {
          unsigned int a_usage = HID_USAGE(joy_data[i].axes_items[a]->usage);
          unsigned int a_type = supported_axes_usage[a_usage];
          if (a_type == type)
            return joy_data[i].axes_items[a];
        }
      }
      return NULL;
    }
  }
}





_kernel_oserror *command_handler(const char *args, int argc, int cmdno, void *pw)
{
  IGNORE(pw);

  switch (cmdno) {
    case CMD_USBJoystick_Read:
      return command_read(args, argc);

    case CMD_USBJoystick_EmulateAcorn:
      return command_emulate_acorn(args, argc);

    case CMD_USBJoystick_EmulateSerialPort:
      return command_emulate_serial_port(args, argc);

    case CMD_USBJoystick_EmulateJoy:
      return command_emulate_joy(args, argc);

    case CMD_USBJoystick_EmulateADC:
      return command_emulate_adc(args, argc);

    case CMD_USBJoystick_List:
      return command_list(args, argc);

    case CMD_USBJoystick_MapStick:
      return command_map_stick(args, argc);

    case CMD_USBJoystick_MapAxes8:
      return command_map_axes(args, argc, FALSE);

    case CMD_USBJoystick_MapAxes16:
      return command_map_axes(args, argc, TRUE);

    case CMD_USBJoystick_MapADC:
      return command_map_adc(args, argc);

    case CMD_USBJoystick_MapButtons:
      return command_map_buttons(args, argc);

    case CMD_USBJoystick_MapADCButtons:
      return command_map_adc_buttons(args, argc);

    case CMD_USBJoystick_Flip:
      return command_flip(args, argc);

    case CMD_USBJoystick_Mapped:
      return command_mapped(args, argc);

    case CMD_USBJoystick_MapMouse:
      return command_map_mouse(args, argc);

    case CMD_USBJoystick_MouseControl:
      return command_mouse_control(args, argc);

    case CMD_USBJoystick_Debug:
      return command_debug(args, argc);

    case CMD_USBJoystick_KeyControls:
      return command_key_controls(args, argc);

    default:
      printf("Unimplemented command!\n");
  }

  return NULL;
}




void servicecall_handler(int service_number, _kernel_swi_regs *r, void *pw)
{
  IGNORE(pw);
  switch (service_number)
  {
    case Service_USBDriver:
      switch (r->r[0])
      {
        case Service_USBDriver_Attach:
          joy_check_device((const usb_service_t*)(r->r[2]), NULL);
          break;
      }
      break;

    case Service_DeviceDead:
      if (r->r[3] != 0)
        joy_remove_device((const char*)(r->r[3]));
      break;
  }
}




_kernel_oserror* swi_joystick_read(_kernel_swi_regs *r)
{
  char stick = r->r[0] & 0xFF;
  char reason = (r->r[0] & 0xFF00) >> 8;

  for (unsigned int i=0; i<JOY_MAX; i++) {
    if (joy_data[i].mapped_number == stick) {
      // found the stick, so return data in Acorn format
      if (reason == 1)
        joystick_read_acorn_16(r, i);
      else
        joystick_read_acorn_8(r, i);

      return NULL;
    }
  }

  //debug_printf("swi_joystick_read: stick %d reason %d, no map, returning default response\n", stick, reason);

  if (reason == 1) {
    // 16-bit: buttons on r1, axes both 32768
    r->r[0] = ACORN_AXES_REST_16;
    r->r[1] = ACORN_BUTTONS_REST_16;
  }
  else {
    // 8-bit: buttons and axes on r0 (all zero)
    r->r[0] = ACORN_REST_8;
  }

  //debug_printf("swi_joystick_read: default (reason %d):  r0 %u (0x%x)   r1 %u (0x%x)\n", reason, r->r[0], r->r[0], r->r[1], r->r[1]);

  return NULL;
}




int scaled_value_8(struct axisdata_struct *a)
{
  int value = 0;

  if (a->acorn_slope_div_8)
    value = ACORN_AXIS_MIN_VALUE_8 + ((a->val - a->min) / a->acorn_slope_8);
  else
    value = ACORN_AXIS_MIN_VALUE_8 + (a->acorn_slope_8 * (a->val - a->min));

  if (value < ACORN_AXIS_MIN_VALUE_8)
    return ACORN_AXIS_MIN_VALUE_8;
  else if (value > ACORN_AXIS_MAX_VALUE_8)
    return ACORN_AXIS_MAX_VALUE_8;
  else
    return value;
}




int scaled_value_16(struct axisdata_struct *a)
{
  int value = 0;

  if (a->acorn_slope_div_16)
    value = ACORN_AXIS_MIN_VALUE_16 + ((a->val - a->min) / a->acorn_slope_16);
  else
    value = ACORN_AXIS_MIN_VALUE_16 + (a->acorn_slope_16 * (a->val - a->min));

  if (value < ACORN_AXIS_MIN_VALUE_16)
    return ACORN_AXIS_MIN_VALUE_16;
  else if (value > ACORN_AXIS_MAX_VALUE_16)
    return ACORN_AXIS_MAX_VALUE_16;
  else
    return value;
}




int scaled_value_pc(struct axisdata_struct *a)
{
  int value = 0;

  if (a->pc_slope_div)
    value = (a->val - a->min) / a->pc_slope;
  else
    value = a->pc_slope * (a->val - a->min);

  if (value < 0)
    return 0;
  else if (value > 100)
    return 100;
  else
    return value;
}





void joystick_read_acorn_8(_kernel_swi_regs *r, unsigned int i)
{
  // 8-bit Acorn API
  //   -127 is down/left and +127 is up/right (centered is 0)
  //   Vertical Twist recommend digital inputs (HAT) to be switched at +64 and -64

  if (joy_data[i].mapped_x_8 == NOT_MAPPED || joy_data[i].mapped_y_8 == NOT_MAPPED) {
    r->r[0] = 0;
    return;
  }

  struct axisdata_struct *ax = &(joy_data[i].axes[joy_data[i].mapped_x_8]);
  struct axisdata_struct *ay = &(joy_data[i].axes[joy_data[i].mapped_y_8]);

  int x = 0;

  if (ax->is_digital) {
    // HAT value of -1, 0 or +1 easily translates to -64, 0 or +64
    x = ax->val * ACORN_AXIS_HAT_SCALE_8;
  }
  else {
    x = scaled_value_8(ax);
  }

  if (ax->flip)
    x = x * -1;

  // For Y-axis, we *-1 so the lower value equates to down/bottom
  int y = 0;

  if (ay->is_digital) {
    // HAT value of -1, 0 or +1 easily translates to -64, 0 or +64
    y = ay->val * ACORN_AXIS_HAT_SCALE_8;
  }
  else {
    y = scaled_value_8(ay);
  }

  if (!(ay->flip))
    y = y * -1;

  unsigned int acorn = (y & 0xff) | ((x & 0xff) << 8);

  for (int b=0; b<ACORN_BUTTONS; b++) {
    if (joy_data[i].mapped_buttons[b] != NOT_MAPPED) {
        unsigned int bd = joy_data[i].buttons[joy_data[i].mapped_buttons[b]];
        acorn = acorn | (bd << (16+b));
      }
  }

  //if (acorn != 0)
    //debug_printf("joystick_read_acorn_8: i %d   rawx %d x %d   rawy %d y %d --> result is %u\n", i, ax->val, x, ay->val, y, acorn);

  r->r[0] = acorn;
}





void joystick_read_acorn_16(_kernel_swi_regs *r, unsigned int i)
{
  // 16-bit Acorn API
  //   0 is down/left and 65535 is up/right (centered is 32768)
  //   Acorn advise using switch points of 12288 (down/left) and 53247 (up/right) for HAT

  if (joy_data[i].mapped_x_16 == NOT_MAPPED || joy_data[i].mapped_y_16 == NOT_MAPPED) {
    r->r[0] = 0;
    r->r[1] = 0;
    return;
  }
  
  struct axisdata_struct *ax = &(joy_data[i].axes[joy_data[i].mapped_x_16]);
  struct axisdata_struct *ay = &(joy_data[i].axes[joy_data[i].mapped_y_16]);

  int x = 0;

  if (ax->is_digital) {
    if (ax->val == ax->min)
      x = ACORN_AXIS_HAT_MIN_16;
    else if (ax->val == ax->max)
      x = ACORN_AXIS_HAT_MAX_16;
    else
      x = ACORN_AXIS_HAT_MID_16;
  }
  else {
    x = scaled_value_16(ax);
  }

  if (ax->flip)
    x = ACORN_AXIS_MAX_VALUE_16 - x;

  // For Y-axis, we invert so the lower value equates to down/bottom
  int y = 0;

  if (ay->is_digital) {
    if (ay->val == ay->min)
      y = ACORN_AXIS_HAT_MIN_16;
    else if (ay->val == ay->max)
      y = ACORN_AXIS_HAT_MAX_16;
    else
      y = ACORN_AXIS_HAT_MID_16;
  }
  else {
    y = scaled_value_16(ay);
  }

  if (!(ay->flip))
    y = ACORN_AXIS_MAX_VALUE_16 - y;

  unsigned int position = (y & 0xffff) | ((x & 0xffff) << 16);
  unsigned int buttons = 0;

  for (int b=0; b<ACORN_BUTTONS; b++) {
    if (joy_data[i].mapped_buttons[b] != NOT_MAPPED) {
        unsigned int bd = joy_data[i].buttons[joy_data[i].mapped_buttons[b]];
        buttons = buttons | (bd << b);
      }
  }

  //if (position != 0 || buttons != 0)
    //debug_printf("joystick_read_acorn_16: i %u   rawx %d, x %d, xdig %d   rawy %d, y %d, ydig %d   -->   pos %u, buttons %u\n", i, ax->val, x, ax->is_digital, ay->val, y, ay->is_digital, position, buttons);

  r->r[0] = position;
  r->r[1] = buttons;
}





_kernel_oserror* swi_joystick_status(_kernel_swi_regs *r)
{
  unsigned int stick1 = 0;
  unsigned int stick2 = 0;

  for (unsigned int i=0; i<JOY_MAX; i++) {
    if (joy_data[i].mapped_number == 0) {
      stick1 = encode_stick_serial_port(i);
    }
    else if (joy_data[i].mapped_number == 1) {
      stick2 = encode_stick_serial_port(i);
    }
  }

  unsigned int sticks = (stick1 & 0xFF) | ((stick2 & 0xFF) << 8);
  //debug_printf("swi_joystick_status: stick1 %u, stick2 %u, both sticks %u\n", stick1, stick2, sticks);
  r->r[0] = sticks;

  return NULL;
}




unsigned int encode_stick_serial_port(int i)
{
  if (joy_data[i].mapped_x_8 == NOT_MAPPED || joy_data[i].mapped_y_8 == NOT_MAPPED) {
    return 0;
  }
  
  struct axisdata_struct *ax = &(joy_data[i].axes[joy_data[i].mapped_x_8]);
  struct axisdata_struct *ay = &(joy_data[i].axes[joy_data[i].mapped_y_8]);

  int left = (ax->val < ax->serial_port_lower_bound);
  int right = (ax->val > ax->serial_port_upper_bound);

  if (ax->flip) {
    int temp = left;
    left = right;
    right = temp;
  }

  int up = (ay->val < ay->serial_port_lower_bound);
  int down = (ay->val > ay->serial_port_upper_bound);

  if (ay->flip) {
    int temp = up;
    up = down;
    down = temp;
  }

  // note: checks for any button
  int buttons = 0;
  for (int b=0; b<ACORN_BUTTONS; b++) {
    if (joy_data[i].mapped_buttons[b] != NOT_MAPPED) {
        unsigned int bd = joy_data[i].buttons[joy_data[i].mapped_buttons[b]];
        buttons = buttons | (bd << b);
      }
  }

  unsigned int anybutton = (buttons > 0);
  unsigned int sp = (right << 0) | (left << 1) | (down << 2) | (up << 3) | (anybutton << 4);
  //debug_printf("encode_stick_serial_port: id %d, left %d, right %d, up %d, down %d, buttons %d, anybutton %d --> result %u\n", i, left, right, up, down, buttons, anybutton, sp);

  return sp;
}




_kernel_oserror* swi_joy_read(_kernel_swi_regs *r, int n)
{
  // n is 0 or 1 (stick)

  // return r0 = % FUDLR
  for (unsigned int i=0; i<JOY_MAX; i++) {
    if (joy_data[i].mapped_number == n) {
      unsigned int j = encode_stick_serial_port(i);
      //debug_printf("swi_joy_read: Stick %d (id %u), returning encoded value %u in r0\n", n, i, j);
      r->r[0] = j;
      return NULL;       // ok to leave early!
    }
  }

  //debug_printf("swi_joy_read: No stick mapped as %d, so returning 0 in r0\n", n);
  r->r[0] = 0;  // default response if no stick mapped
  return NULL;
}





_kernel_oserror* swi_joy_test(_kernel_swi_regs *r)
{
  // 255: nothing in Econet
  // 128: Econet present
  //   0: RTFM present
  //debug_printf("swi_joy_test: returning 0 in r0\n");
  r->r[0] = 0;

  return NULL;
}





_kernel_oserror* swi_joy_readpipe(_kernel_swi_regs *r)
{
  IGNORE(r);
  // don't know what to do here...
  //debug_printf("swi_joy_readpipe: doing nothing\n");
  return NULL;
}





_kernel_oserror* ukswiv_hook_handler(_kernel_swi_regs *r, void *pw)
{
  IGNORE(pw);

  // our pre-veneer has copied R11 (SWI number) to R9 so we can get at it
  // it also uses r9 as an output parameter to claim or pass-on the vector

  unsigned int swi_number = r->r[9];

  if (emulate_serial_port_on && swi_number == USBJoystickSerialPortSWI)
  {
    r->r[9] = 1; // claim (see pre-veneer)
    return swi_joystick_status(r);
  }
  else if (emulate_joy_on && swi_number == JoySWI_Read0)
  {
    r->r[9] = 1;
    return swi_joy_read(r, 0);
  }
  else if (emulate_joy_on && swi_number == JoySWI_Read1)
  {
    r->r[9] = 1;
    return swi_joy_read(r, 1);
  }
  else if (emulate_joy_on && swi_number == JoySWI_Test)
  {
    r->r[9] = 1;
    return swi_joy_test(r);
  }
  else if (emulate_joy_on && swi_number == JoySWI_ReadPipe)
  {
    r->r[9] = 1;
    return swi_joy_readpipe(r);
  }
  else
  {
    r->r[9] = 0; // pass-on (see pre-veneer)
    return NULL;
  }
}





int bytev_hook_handler(_kernel_swi_regs *r, void *pw)
{
  IGNORE(pw);

  // an application is trying to use osbyte to perform an I/O podule ADC conversion

  if (!emulate_adc_on)
    return VECTOR_PASSON;

  unsigned int value = 0;
  IGNORE(value);     // not used all the time!

  switch ((osbyte_op)(r->r[0]))
  {
    case osbyte_CHANNEL_COUNT:
    {
      // OSBYTE 16 - select how many channels are to be sampled
      //debug_printf("bytev_hook_handler: OSByte 16\n");
      adc_data.channels_to_be_sampled = r->r[1];
      return VECTOR_CLAIM;
    }

    case osbyte_CONVERT_CHANNEL:
    {
      // OSBYTE 17 - force ADC conversion (we just set current channel, no need to 'force')
      if (r->r[1] >= 1 && r->r[1] <= 4) {
        //debug_printf("bytev_hook_handler: OSByte 17 (valid channel)\n");
        adc_data.current_channel = r->r[1];
        return VECTOR_CLAIM;
      }
      //debug_printf("bytev_hook_handler: OSByte 17 (invalid channel)\n");
      break;
    }

    case osbyte_BUFFER_OP:
    {
      // OSBYTE 128 - read ADC channel value and fire button status
      // BASIC's ADVAL() function uses this
      // TODO: Add cases for 7/8 which is mouse x/y position
      int value = 0;
      //debug_printf("bytev_hook_handlerOSByte 128 Buffer Op, r1 = %u\n", r->r[1]);
      switch (r->r[1])
      {
        case osbyte_OP_JOYSTICK_STATE:
        {
          // OSByte 128 with r1=0
          // hack: setting r2 to != 0 causes output of ADVAL(0) to be screwed up, so fix to 0
          //debug_printf("bytev_hook_handler: OSByte 128 Read joystick state");
          r->r[1] = adc_read_fire();   // bits 0-1 should be button status
          r->r[2] = 0;                 // channel number last used for conversion (or 0=none)
          return VECTOR_CLAIM;
        }

        case osbyte_OP_CHANNEL_POSITION1:
        case osbyte_OP_CHANNEL_POSITION2:
        case osbyte_OP_CHANNEL_POSITION3:
        case osbyte_OP_CHANNEL_POSITION4:
        {
          // OSByte 128 with r1=(1/2/3/4)
          //debug_printf("bytev_hook_handler: OSByte 128 Read ADC channel");
          value = adc_read_channel(r->r[1]);
          r->r[1] = (value & 0xFF) ;  // bits 0-7 should be low 8-bits
          r->r[2] = (value >> 8);     // bits 0-23 should be high 24 bits
          return VECTOR_CLAIM;
        }

        case osbyte_OP_POINTER_XPOSITION:
        case osbyte_OP_POINTER_YPOSITION:
        {
          //debug_printf("bytev_hook_handler: OSByte 128 Read pointer position - not implemented, will pass-on");
          // todo: is this wise?  maybe we should read position via OS_Mouse and return it?
          return VECTOR_PASSON;
        }

        //debug_printf("bytev_hook_handler: OSByte 128, unimplemented reason code %u", r->r[1]);
        //break;
      }
      debug_printf("bytev_hook_handler: OSByte 128, not a buffer op we care about");
      break;
    }

    case osbyte_VAR_CURRENT_CHANNEL:
    {
      // OSBYTE 188 - read current ADC channel number
      debug_printf("bytev_hook_handler: OSByte 188\n");
      r->r[1] = adc_data.current_channel;
      return VECTOR_CLAIM;
    }

    case osbyte_VAR_CHANNEL_COUNT:
    {
      // OSBYTE 189 - read maximum ADC channel number (as set by osbyte 16)
      debug_printf("bytev_hook_handler: OSByte 189\n");
      r->r[1] = adc_data.channels_to_be_sampled;
      return VECTOR_CLAIM;
    }

    case osbyte_VAR_CONVERSION_RESOLUTION:
    {
      // OSBYTE 190 - read resolution of conversion: 12 (r0=0 or 12) or 8-bit (r0=8)
      debug_printf("bytev_hook_handler: OSByte 190\n");
      r->r[1] = 12;
      return VECTOR_CLAIM;
    }

  }

  // bytev reason code/subcode was not for us
  return VECTOR_PASSON;
}





unsigned int adc_read_channel(unsigned int channel)
{
  int value = 0;

  // channel passed in is 1/2/3/4  (0 shouldn't be passed in, should it?!)
  struct adc_channel_struct *c = &(adc_map.channels[channel-1]);

  if (c->joy_id == NOT_MAPPED || c->axis == NOT_MAPPED)
    return value;

  struct axisdata_struct *a = &(joy_data[c->joy_id].axes[c->axis]);

  if (a->is_digital) {
    // todo: does this make any sense when ranges are used?!
    if (a->val == a->min)
      value = ADC_AXIS_HAT_MIN;
    else if (a->val == a->max)
      value = ADC_AXIS_HAT_MAX;
    else
      value = ADC_AXIS_HAT_MID;
  }
  else {
    if (a->val >= c->start && a->val <= c->end) {
      // axis is presenting a value in the range we care about
      // scale our input from the user-secified range to the ADC range
      if (c->slope_div)
        value = ADC_AXIS_MIN_VALUE + ((a->val - c->start) / c->slope);
      else
        value = ADC_AXIS_MIN_VALUE + (c->slope * (a->val - c->start));
      // this is required as scaling isn't terribly accurate: it may under/over-shoot
      if (value > ADC_AXIS_MAX_VALUE)
        value = ADC_AXIS_MAX_VALUE;
      else if (value < ADC_AXIS_MIN_VALUE)
        value = ADC_AXIS_MIN_VALUE;
    }
    else if (a->val < c->start) {
      // axis is presenting a value before our range, so show min
      value = ADC_AXIS_MIN_VALUE;
    }
    else if (a->val > c->end) {
      // axis is presenting a value after our range, so show max
      value = ADC_AXIS_MAX_VALUE;
    }
  }

  // do we need to flip?
  if (c->flip) {
    value = ADC_AXIS_MAX_VALUE - value;
  }

  return value;
}





unsigned int adc_read_fire(void)
{
  unsigned int buttons = 0;

  for (unsigned int b=0; b<ADC_BUTTONS; b++) {
    int mapped_joy_id = adc_map.buttons[b].joy_id;
    int mapped_button = adc_map.buttons[b].button;
    if (mapped_joy_id != NOT_MAPPED && mapped_button != NOT_MAPPED) {
        buttons = buttons | (joy_data[mapped_joy_id].buttons[mapped_button] << b);
      }
  }

  return (buttons & 0x3);   // todo: make 0x3 a const alongside ADC_BUTTONS?
}




int upcallv_hook_handler(_kernel_swi_regs *r, void *pw)
{
  IGNORE(pw);

  // we only care about data received
  if (r->r[0] != UpCall_DeviceRxDataPresent) {
    return VECTOR_PASSON;
  }

  // temporary buffer for holding received data
  byte buffer[JOY_MAX_DATA_LENGTH];
  os_fw fp = (os_fw) (r->r[1]);

  // test our active sticks to see if this is one of ours
  for (unsigned int i=0; i<JOY_MAX; i++) {
    if (joy_data[i].fp == fp) {
      // this is the stick
      joy_data[i].upcalls++;
      joy_data[i].upcalls_delta++;

      // we need to read the bytes into a buffer
      unsigned int read = 0;
      unsigned int offset = joy_data[i].device->report_data_offset;
      unsigned int to_read = joy_data[i].report_length + offset;
      buffer_read(joy_data[i].buffer_internal_id, to_read, buffer, &read, joy_data[i].buffer_service_routine, joy_data[i].buffer_workspace);

      // if we have read the correct number of bytes, so copy to stick 'last known good data'
      if (read == to_read) {
        joy_data[i].good_reads++;
        joy_data[i].good_reads_delta++;
        memcpy(joy_data[i].data_buf, buffer, read);
        joystick_decode(i);
      }
      else {
        joy_data[i].bad_reads++;
        joy_data[i].bad_reads_delta++;
      }
      // can stop here, since this rx event cannot also be for another stick!
      // todo: is pass-on right?  should we return claim?
      return VECTOR_PASSON;
    }
  }

  // upcall was not for one of our sticks
  return VECTOR_PASSON;
}





_kernel_oserror* buffer_read(buffer_internal_id id, unsigned int to_read, byte *data, unsigned int *read, asm_routine code, void *ws)
{
  if (id == 0)
  {
    *read = 0;
    return NULL;
  }

  // find out the used space (buffer manager 6)
  unsigned int bufused = 0;
  __asm
  {
    MOV      R0,#buffer_SERVICE_USED_SPACE
    MOV      R1,id
    MOV      R12,ws
    BLX      code,{R0,R1,R12},{R2},{LR,PSR}
    MOV      bufused,R2
  }

  // max we can read is the size of the buffer
  if (to_read > bufused)
    to_read = bufused;

  // if the buffer is empty, bail
  if (to_read == 0)
  {
    *read = 0;
    return NULL;
  }

  // perform the read, then flush the buffer
  __asm
  {
    MOV      R0,#buffer_SERVICE_REMOVE_BLOCK
    MOV      R1,id
    MOV      R2,data
    MOV      R3,to_read
    MOV      R12,ws
    BLX      code,{R0-R3,R12},{},{R2,R3,LR,PSR}
    MOV      R0,#buffer_SERVICE_PURGE_BUFFER
    BLX      code,{R0,R1,R12},{},{LR,PSR}
  }

  // note how many bytes read from buffer
  *read = to_read;

  return NULL;
}
