From 2476b9d6b2cb0ec53bb24f990e83b97a9da0786d Mon Sep 17 00:00:00 2001 From: harbaum Date: Fri, 14 Mar 2014 20:28:55 +0000 Subject: [PATCH] Started USB joystick support --- Makefile | 9 +- debug.h | 8 + usb/hid.c | 83 +++++++++- usb/hid.h | 8 + usb/hidparser.c | 403 ++++++++++++++++++++++++++++++++++++++++++++++++ usb/hidparser.h | 28 ++++ user_io.c | 6 +- 7 files changed, 532 insertions(+), 13 deletions(-) create mode 100644 usb/hidparser.c create mode 100644 usb/hidparser.h diff --git a/Makefile b/Makefile index a16af8e..914dade 100644 --- a/Makefile +++ b/Makefile @@ -9,8 +9,9 @@ TODAY = `date +"%m/%d/%y"` PRJ = firmware SRC = Cstartup_SAM7.c fat.c fdd.c firmware.c fpga.c hardware.c hdd.c main.c menu.c mmc.c osd.c syscalls.c user_io.c boot_print.c boot_logo.c rafile.c config.c tos.c ikbd.c -SRC += usb/max3421e.c usb/usb.c usb/hub.c usb/hid.c usb/timer.c +SRC += usb/max3421e.c usb/usb.c usb/hub.c usb/hid.c usb/hidparser.c usb/timer.c SRC += cdc_enumerate.c cdc_control.c + OBJ = $(SRC:.c=.o) DEP = $(SRC:.c=.d) @@ -19,7 +20,7 @@ LIBDIR = # Commandline options for each tool. DFLAGS = -I. -Iusb -DMIST -CFLAGS = -I. -Iusb -c -fno-common -O3 -DMIST -fsigned-char -DVDATE=\"`date +"%y%m%d"`\" +CFLAGS = $(DFLAGS) -c -fno-common -O3 -fsigned-char -DVDATE=\"`date +"%y%m%d"`\" AFLAGS = -ahls -mapcs-32 LFLAGS = -nostartfiles -Wl,-Map,$(PRJ).map -T$(LINKMAP) $(LIBDIR) CPFLAGS = --output-target=ihex @@ -36,10 +37,8 @@ clean: rm -f *.d *.o *.hex *.elf *.map *.lst core *~ */*.d */*.o $(MKUPG) *.bin *.upg INTERFACE=olimex-arm-usb-tiny-h -ADAPTER_KHZ=10000 - #INTERFACE=busblaster -#ADAPTER_KHZ=100 +ADAPTER_KHZ=10000 reset: openocd -f interface/$(INTERFACE).cfg -f target/at91sam7sx.cfg --command "adapter_khz $(ADAPTER_KHZ); init; reset init; resume; shutdown" diff --git a/debug.h b/debug.h index 3e919df..20fcee7 100644 --- a/debug.h +++ b/debug.h @@ -2,6 +2,14 @@ #ifndef DEBUG_H #define DEBUG_H +// ------------ usb debugging ----------- + +#if 1 +#define hidp_debugf(...) iprintf(__VA_ARGS__) +#else +#define hidp_debugf(...) +#endif + // ------------ generic debugging ----------- #if 0 diff --git a/usb/hid.c b/usb/hid.c index dd36942..2886590 100644 --- a/usb/hid.c +++ b/usb/hid.c @@ -3,9 +3,17 @@ #include "usb.h" #include "max3421e.h" #include "timer.h" +#include "hidparser.h" #include "../user_io.h" +// joystick todo: +// - renumber on unplug +// - shift legacy joysticks up +// - emulate extra joysticks (at printerport, ...) +// - second fire button (no known system uses it, but OSD may have a use ...) + static unsigned char kbd_led_state = 0; // default: all leds off +static unsigned char joysticks = 0; // number of detected usb joysticks static void hexdump(void *data, int size) { int i,n = 0, b2c; @@ -42,12 +50,26 @@ static uint8_t hid_get_report_descr(usb_device_t *dev, uint8_t iface, uint16_t s iprintf("%s(%x, if=%d, size=%d)\n", __FUNCTION__, dev->bAddress, iface, size); uint8_t buf[size]; + usb_hid_info_t *info = &(dev->hid_info); uint8_t rcode = usb_ctrl_req( dev, HID_REQ_HIDREPORT, USB_REQUEST_GET_DESCRIPTOR, 0x00, HID_DESCRIPTOR_REPORT, iface, size, size, buf, NULL); - if(!rcode) + if(!rcode) { + iprintf("HID report descriptor:\n"); hexdump(buf, size); - + + // we got a report descriptor. Try to parse it + if(parse_report_descriptor(buf, size)) { + if(hid_conf[0].type == CONFIG_TYPE_JOYSTICK) { + iprintf("Detected USB joystick %d\n", joysticks); + + info->iface_info[iface].device_type = HID_DEVICE_JOYSTICK; + info->iface_info[iface].conf = hid_conf[0]; + info->iface_info[iface].jindex = joysticks++; + } + } + } + return rcode; } @@ -119,6 +141,7 @@ static uint8_t usb_hid_parse_conf(usb_device_t *dev, uint8_t conf, uint16_t len) info->iface_info[info->bNumIfaces].iface_idx = p->iface_desc.bInterfaceNumber; info->iface_info[info->bNumIfaces].has_boot_mode = false; info->iface_info[info->bNumIfaces].device_type = HID_DEVICE_UNKNOWN; + info->iface_info[info->bNumIfaces].conf.type = CONFIG_TYPE_NONE; if(p->iface_desc.bInterfaceSubClass == HID_BOOT_INTF_SUBCLASS) { iprintf("Iface %d is Boot sub class\n", info->bNumIfaces); @@ -265,10 +288,13 @@ static uint8_t usb_hid_init(usb_device_t *dev) { // process all supported interfaces for(i=0; ibNumIfaces; i++) { - rcode = hid_get_report_descr(dev, - info->iface_info[i].iface_idx, info->iface_info[i].report_size); - if (rcode) - return rcode; + // no boot mode, try to parse HID report descriptor + if(!info->iface_info[info->bNumIfaces].has_boot_mode) { + rcode = hid_get_report_descr(dev, + info->iface_info[i].iface_idx, info->iface_info[i].report_size); + if (rcode) + return rcode; + } rcode = hid_set_idle(dev, info->iface_info[i].iface_idx, 0, 0); if (rcode && rcode != hrSTALL) @@ -291,7 +317,19 @@ static uint8_t usb_hid_init(usb_device_t *dev) { } static uint8_t usb_hid_release(usb_device_t *dev) { + usb_hid_info_t *info = &(dev->hid_info); + puts(__FUNCTION__); + + int8_t i; + // check if a joystick is released + for(i=0;ibNumIfaces;i++) { + if(info->iface_info[i].device_type == HID_DEVICE_JOYSTICK) { + iprintf("releasing joystick #%d, renumbering\n", info->iface_info[i].jindex); + + } + } + return 0; } @@ -337,6 +375,39 @@ static uint8_t usb_hid_poll(usb_device_t *dev) { } } } + + if(info->iface_info[i].device_type == HID_DEVICE_JOYSTICK) { + hid_config_t *conf = &info->iface_info[i].conf; + uint8_t jmap = 0; + uint8_t ax; + + iprintf("Joystick data:\n"); + hexdump(buf, read); + + // currently only byte sized axes are allowed + ax = buf[conf->joystick.axis_byte_offset[0]]; + if(ax < 64) jmap |= JOY_LEFT; + if(ax > 192) jmap |= JOY_RIGHT; + ax = buf[conf->joystick.axis_byte_offset[1]]; + if(ax < 64) jmap |= JOY_UP; + if(ax > 192) jmap |= JOY_DOWN; + + // ... and one button + if(buf[conf->joystick.button_byte_offset] & conf->joystick.button0_bitmask) + jmap |= JOY_BTN1; + + // swap joystick 0 and 1 since 1 is the one used primarily on most systems + ax = info->iface_info[i].jindex; + if(ax == 0) ax = 1; + else if(ax == 1) ax = 0; + + // check if joystick state has changed + if(jmap != info->iface_info[i].jmap) { + // and feed into joystick input system + user_io_joystick(ax, jmap); + info->iface_info[i].jmap = jmap; + } + } } } diff --git a/usb/hid.h b/usb/hid.h index d9149b1..bac4307 100644 --- a/usb/hid.h +++ b/usb/hid.h @@ -3,6 +3,7 @@ #include #include +#include "hidparser.h" #define HID_LED_NUM_LOCK 0x01 #define HID_LED_CAPS_LOCK 0x02 @@ -40,6 +41,7 @@ #define HID_DEVICE_UNKNOWN 0 #define HID_DEVICE_MOUSE 1 #define HID_DEVICE_KEYBOARD 2 +#define HID_DEVICE_JOYSTICK 3 typedef struct { uint8_t iface_idx; @@ -48,6 +50,12 @@ typedef struct { uint8_t device_type; bool has_boot_mode; // device supports boot mode + // additional info extracted from the report descriptor + // (currently only used for joysticks) + uint8_t jmap; // last reported joystick state + uint8_t jindex; // joystick index + hid_config_t conf; + } usb_hid_iface_info_t; typedef struct { diff --git a/usb/hidparser.c b/usb/hidparser.c new file mode 100644 index 0000000..68018a2 --- /dev/null +++ b/usb/hidparser.c @@ -0,0 +1,403 @@ +// http://www.frank-zhao.com/cache/hid_tutorial_1.php + +#include +#include +#include + +#include "hidparser.h" +#include "debug.h" + +#if 0 +#define hidp_extreme_debugf(...) iprintf(__VA_ARGS__) +#else +#define hidp_extreme_debugf(...) +#endif + +typedef struct { + uint8_t bSize: 2; + uint8_t bType: 2; + uint8_t bTag: 4; +} __attribute__((packed)) item_t; + +// flags for joystick components required +#define JOYSTICK_REQ_AXIS_X 0x01 +#define JOYSTICK_REQ_AXIS_Y 0x02 +#define JOYSTICK_REQ_BTN_0 0x04 +#define JOYSTICK_COMPLETE (JOYSTICK_REQ_AXIS_X | JOYSTICK_REQ_AXIS_Y | JOYSTICK_REQ_BTN_0) + +hid_config_t hid_conf[MAX_CONF]; + +#define USAGE_PAGE_GENERIC_DESKTOP 1 +#define USAGE_PAGE_SIMULATION 2 +#define USAGE_PAGE_VR 3 +#define USAGE_PAGE_SPORT 4 +#define USAGE_PAGE_GAMING 5 +#define USAGE_PAGE_GENERIC_DEVICE 6 +#define USAGE_PAGE_KEYBOARD 7 +#define USAGE_PAGE_LEDS 8 +#define USAGE_PAGE_BUTTON 9 +#define USAGE_PAGE_ORDINAL 10 +#define USAGE_PAGE_TELEPHONY 11 +#define USAGE_PAGE_CONSUMER 12 + + +#define USAGE_POINTER 1 +#define USAGE_MOUSE 2 +#define USAGE_JOYSTICK 4 +#define USAGE_GAMEPAD 5 +#define USAGE_KEYBOARD 6 +#define USAGE_KEYPAD 7 +#define USAGE_MULTIAXIS 8 + +#define USAGE_X 48 +#define USAGE_Y 49 +#define USAGE_Z 50 +#define USAGE_WHEEL 56 + +bool parse_report_descriptor(uint8_t *rep, uint16_t rep_size) { + int8_t app_collection = 0; + int8_t phys_log_collection = 0; + uint8_t skip_collection = 0; + int8_t generic_desktop = -1; // depth at which first gen_desk was found + uint8_t collection_depth = 0; + + uint8_t i; + + // + uint8_t report_size, report_count, config_idx = 0; + uint16_t bit_count = 0, usage_count = 0; + + // mask used to check of all required components have been found, so + // that e.g. both axes and the button of a joystick are ready to be used + uint8_t setup_complete = 0; + + // joystick/mouse components + int8_t axis[2] = { -1, -1}; + uint8_t btns = 0; + + for(i=0;ibTag; + uint8_t type = ((item_t*)rep)->bType; + uint8_t size = ((item_t*)rep)->bSize; + + rep++; + rep_size--; // one byte consumed + + uint32_t value = 0; + if(size) { // size 1/2/3 + value = *rep++; + rep_size--; + } + + if(size > 1) { // size 2/3 + value = (value & 0xff) + ((uint32_t)(*rep++)<<8); + rep_size--; + } + + if(size > 2) { // size 3 + value &= 0xffff; + value |= ((uint32_t)(*rep++)<<16); + value |= ((uint32_t)(*rep++)<<24); + rep_size-=2; + } + + // hidp_extreme_debugf("Value = %d (%u)\n", value, value); + + // we are currently skipping an unknown/unsupported collection) + if(skip_collection) { + if(!type) { // main item + // any new collection increases the depth of collections to skip + if(tag == 10) { + skip_collection++; + collection_depth++; + } + + // any end collection decreases it + if(tag == 12) { + skip_collection--; + collection_depth--; + + // leaving the depth the generic desktop was valid for + if(generic_desktop > collection_depth) + generic_desktop = -1; + } + } + + + } else { + // hidp_extreme_debugf("-> Item tag=%d type=%d size=%d\n", tag, type, size); + + switch(type) { + case 0: + // main item + + switch(tag) { + case 8: + // + if(btns) { + hidp_debugf("BUTTON0 @ %d (byte %d, mask %d)\n", + bit_count, bit_count/8, 1 << (bit_count%8)); + if(hid_conf[config_idx].type == CONFIG_TYPE_JOYSTICK) { + hid_conf[config_idx].joystick.button_byte_offset = bit_count/8; + hid_conf[config_idx].joystick.button0_bitmask = 1 << (bit_count%8); + setup_complete |= JOYSTICK_REQ_BTN_0; + } + } + + // + char c; + for(c=0;c<2;c++) { + if(axis[c] >= 0) { + uint16_t cnt = bit_count + report_size * axis[c]; + hidp_debugf("%c-AXIS @ %d (byte %d)\n", 'X'+c, + cnt, cnt/8); + + // only 8 bit axes at byte boundaries are supported for + // joysticks + if((hid_conf[config_idx].type == CONFIG_TYPE_JOYSTICK) && + (report_size == 8) && ((cnt&7) == 0)) { + // save in joystick config + hid_conf[config_idx].joystick.axis_byte_offset[c] = cnt/8; + if(c==0) setup_complete |= JOYSTICK_REQ_AXIS_X; + if(c==1) setup_complete |= JOYSTICK_REQ_AXIS_Y; + } + + if(report_size != 8) + hidp_debugf("Unsupported report size %d\n", report_size); + + if((cnt&7) != 0) + hidp_debugf("Unsupported bit offset %d\n", cnt&7); + } + } + + hidp_extreme_debugf("INPUT(%d)\n", value); + + // reset for next inputs + bit_count += report_count * report_size; + usage_count = 0; + btns = 0; + axis[0] = axis[1] = -1; + break; + + case 9: + hidp_extreme_debugf("OUTPUT(%d)\n", value); + break; + + case 11: + hidp_extreme_debugf("FEATURE(%d)\n", value); + break; + + case 10: + hidp_extreme_debugf("COLLECTION(%d)\n", value); + collection_depth++; + + if(value == 1) { // app collection + hidp_extreme_debugf(" -> application\n"); + app_collection++; + } else if(value == 0) { // physical collection + hidp_extreme_debugf(" -> physical\n"); + phys_log_collection++; + } else if(value == 2) { // logical collection + hidp_extreme_debugf(" -> logical\n"); + phys_log_collection++; + } else { + hidp_extreme_debugf("skipping unsupported collection\n"); + skip_collection++; + } + break; + + case 12: + hidp_extreme_debugf("END_COLLECTION(%d)\n", value); + collection_depth--; + + // leaving the depth the generic desktop was valid for + if(generic_desktop > collection_depth) + generic_desktop = -1; + + if(phys_log_collection) { + hidp_extreme_debugf(" -> phys/log end\n"); + phys_log_collection--; + } else if(app_collection) { + hidp_extreme_debugf(" -> app end\n"); + app_collection--; + } else { + hidp_debugf(" -> unexpected\n"); + return false; + } + break; + + default: + hidp_debugf("unexpected main item %d\n", tag); + return false; + break; + } + break; + + case 1: + // global item + switch(tag) { + case 0: + hidp_extreme_debugf("USAGE_PAGE(%d/0x%x)\n", value, value); + + if(value == USAGE_PAGE_KEYBOARD) { + hidp_extreme_debugf(" -> Keyboard\n"); + } else if(value == USAGE_PAGE_GAMING) { + hidp_extreme_debugf(" -> Game device\n"); + } else if(value == USAGE_PAGE_LEDS) { + hidp_extreme_debugf(" -> LEDs\n"); + } else if(value == USAGE_PAGE_CONSUMER) { + hidp_extreme_debugf(" -> Consumer\n"); + } else if(value == USAGE_PAGE_BUTTON) { + hidp_extreme_debugf(" -> Buttons\n"); + btns = 1; + } else if(value == USAGE_PAGE_GENERIC_DESKTOP) { + hidp_extreme_debugf(" -> Generic Desktop\n"); + + if(generic_desktop < 0) + generic_desktop = collection_depth; + } else + hidp_extreme_debugf(" -> UNSUPPORTED USAGE_PAGE\n"); + + break; + + case 1: + hidp_extreme_debugf("LOGICAL_MINIMUM(%d/%d)\n", value, (int8_t)value); + break; + + case 2: + hidp_extreme_debugf("LOGICAL_MAXIMUM(%d)\n", value); + break; + + case 3: + hidp_extreme_debugf("PHYSICAL_MINIMUM(%d/%d)\n", value, (int8_t)value); + break; + + case 4: + hidp_extreme_debugf("PHYSICAL_MAXIMUM(%d)\n", value); + break; + + case 5: + hidp_extreme_debugf("UNIT_EXPONENT(%d)\n", value); + break; + + case 6: + hidp_extreme_debugf("UNIT(%d)\n", value); + break; + + case 7: + hidp_extreme_debugf("REPORT_SIZE(%d)\n", value); + report_size = value; + break; + + case 8: + hidp_extreme_debugf("REPORT_ID(%d)\n", value); + hid_conf[config_idx].report_id = value; + break; + + case 9: + hidp_extreme_debugf("REPORT_COUNT(%d)\n", value); + report_count = value; + break; + + default: + hidp_debugf("unexpected global item %d\n", tag); + return false; + break; + } + break; + + case 2: + // local item + switch(tag) { + case 0: + // we only support mice, keyboards and joysticks + hidp_extreme_debugf("USAGE(%d/0x%x)\n", value, value); + + if( !collection_depth && (value == USAGE_KEYBOARD)) { + // usage(keyboard) is always allowed + hidp_debugf(" -> Keyboard\n"); + hid_conf[config_idx].type = CONFIG_TYPE_KEYBOARD; + } else if(!collection_depth && (value == USAGE_MOUSE)) { + // usage(mouse) is always allowed + hidp_debugf(" -> Mouse\n"); + hid_conf[config_idx].type = CONFIG_TYPE_MOUSE; + } else if(!collection_depth && + ((value == USAGE_GAMEPAD) || (value == USAGE_JOYSTICK))) { + hidp_extreme_debugf(" -> Gamepad/Joystick\n"); + hidp_debugf("Gamepad/Joystick usage found\n"); + hid_conf[config_idx].type = CONFIG_TYPE_JOYSTICK; + } else if(value == USAGE_POINTER && app_collection) { + // usage(pointer) is allowed within the application collection + + hidp_debugf(" -> Pointer\n"); + + } else if((value == USAGE_X || value == USAGE_Y || + value == USAGE_Z || value == USAGE_WHEEL) && + app_collection) { + // usage(x) and usage(y) are allowed within the app collection + hidp_extreme_debugf(" -> axis usage\n"); + + // we support x and y axis on joysticks + if(hid_conf[config_idx].type == CONFIG_TYPE_JOYSTICK) { + if(value == USAGE_X) { + hidp_extreme_debugf("JOYSTICK: found x axis @ %d\n", usage_count); + axis[0] = usage_count; + } + if(value == USAGE_Y) { + hidp_extreme_debugf("JOYSTICK: found y axis @ %d\n", usage_count); + axis[1] = usage_count; + } + } + usage_count++; + } else { + hidp_extreme_debugf(" -> UNSUPPORTED USAGE\n"); + // return false; + } + break; + + case 1: + hidp_extreme_debugf("USAGE_MINIMUM(%d)\n", value); + usage_count -= (value-1); + break; + + case 2: + hidp_extreme_debugf("USAGE_MAXIMUM(%d)\n", value); + usage_count += value; + break; + + default: + hidp_extreme_debugf("unexpected local item %d\n", tag); + // return false; + break; + } + break; + + default: + // reserved + hidp_extreme_debugf("unexpected resreved item %d\n", tag); + // return false; + break; + } + } + } + + hidp_debugf("total bit count: %d (%d bytes, %d bits)\n", + bit_count, bit_count/8, bit_count%8); + + // check if something useful was detected + if(hid_conf[config_idx].type == CONFIG_TYPE_JOYSTICK) { + if(setup_complete == JOYSTICK_COMPLETE) { + hidp_debugf("Joystick ok\n"); + return true; + } + + hidp_debugf("Ignoring incomplete joystick %x\n", setup_complete); + } else + hidp_debugf("No joystick %d\n", config_idx); + + return false; +} diff --git a/usb/hidparser.h b/usb/hidparser.h new file mode 100644 index 0000000..ba37335 --- /dev/null +++ b/usb/hidparser.h @@ -0,0 +1,28 @@ +#ifndef HIDPARSER_H +#define HIDPARSER_H + +#define CONFIG_TYPE_NONE 0 +#define CONFIG_TYPE_MOUSE 1 +#define CONFIG_TYPE_KEYBOARD 2 +#define CONFIG_TYPE_JOYSTICK 3 + +// currently only joysticks are supported +typedef struct { + uint8_t type: 2; // CONFIG_TYPE_... + uint8_t report_id; + + union { + struct { + uint8_t axis_byte_offset[2]; // x and y axis + uint8_t button_byte_offset; + uint8_t button0_bitmask; + } joystick; + }; +} hid_config_t; + +#define MAX_CONF 2 +extern hid_config_t hid_conf[MAX_CONF]; + +bool parse_report_descriptor(uint8_t *rep, uint16_t rep_size); + +#endif // HIDPARSER_H diff --git a/user_io.c b/user_io.c index 9d44709..6631f3d 100644 --- a/user_io.c +++ b/user_io.c @@ -137,6 +137,8 @@ void user_io_detect_core_type() { } void user_io_joystick(unsigned char joystick, unsigned char map) { + iprintf("j%d: %x\n", joystick, map); + // most cores process joystick events themselves if((core_type == CORE_TYPE_MINIMIG) || (core_type == CORE_TYPE_PACE) || @@ -314,7 +316,7 @@ void user_io_poll() { if(status != bit8_status) { char buffer[512]; - bit8_debugf("st %08x", status); + // bit8_debugf("st %08x", status); bit8_status = status; // sector read testing @@ -322,7 +324,7 @@ void user_io_poll() { if((status & 0xff) == 0xa5) { unsigned long sector = (status>>8)&0xffffff; - bit8_debugf("sec rd %u", sector); + // bit8_debugf("sec rd %u", sector); if(MMC_Read(sector, buffer)) { short i;