/* RetroArch - A frontend for libretro. * Copyright (C) 2013-2014 - Jason Fetters * Copyright (C) 2011-2017 - Daniel De Matteis * * RetroArch is free software: you can redistribute it and/or modify it under the terms * of the GNU General Public License as published by the Free Software Found- * ation, either version 3 of the License, or (at your option) any later version. * * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with RetroArch. * If not, see . */ #include "wiiu_hid.h" static wiiu_event_list events; static wiiu_adapter_list adapters; static bool wiiu_hid_joypad_query(void *data, unsigned slot) { wiiu_hid_t *hid = (wiiu_hid_t *)data; if (!hid) return false; return slot < hid->connections_size; } static const char *wiiu_hid_joypad_name(void *data, unsigned slot) { if (!wiiu_hid_joypad_query(data, slot)) return NULL; wiiu_hid_t *hid = (wiiu_hid_t *)data; return hid->connections[slot].iface->get_name(data); } static void wiiu_hid_joypad_get_buttons(void *data, unsigned port, retro_bits_t *state) { (void)data; (void)port; BIT256_CLEAR_ALL_PTR(state); } static bool wiiu_hid_joypad_button(void *data, unsigned port, uint16_t joykey) { (void)data; (void)port; (void)joykey; return false; } static bool wiiu_hid_joypad_rumble(void *data, unsigned pad, enum retro_rumble_effect effect, uint16_t strength) { (void)data; (void)pad; (void)effect; (void)strength; return false; } static int16_t wiiu_hid_joypad_axis(void *data, unsigned port, uint32_t joyaxis) { (void)data; (void)port; (void)joyaxis; return 0; } static void *wiiu_hid_init(void) { RARCH_LOG("[hid]: initializing HID subsystem\n"); wiiu_hid_t *hid = new_hid(); HIDClient *client = new_hidclient(); if (!hid || !client) goto error; wiiu_hid_init_lists(); start_polling_thread(hid); if (!hid->polling_thread) goto error; RARCH_LOG("[hid]: Registering HIDClient\n"); HIDAddClient(client, wiiu_attach_callback); hid->client = client; RARCH_LOG("[hid]: init success\n"); return hid; error: RARCH_LOG("[hid]: initialization failed. cleaning up.\n"); stop_polling_thread(hid); delete_hid(hid); delete_hidclient(client); return NULL; } static void wiiu_hid_free(const void *data) { wiiu_hid_t *hid = (wiiu_hid_t*)data; if (!hid) return; stop_polling_thread(hid); delete_hidclient(hid->client); delete_hid(hid); if (events.list) { wiiu_attach_event *event = NULL; while( (event = events.list) != NULL) { events.list = event->next; delete_attach_event(event); } memset(&events, 0, sizeof(events)); } } static void wiiu_hid_poll(void *data) { (void)data; } static void wiiu_hid_send_control(void *data, uint8_t *buf, size_t size) { wiiu_adapter_t *adapter = (wiiu_adapter_t *)data; int32_t result; if (!adapter) { RARCH_ERR("[hid]: send_control: bad adapter.\n"); return; } memset(adapter->tx_buffer, 0, adapter->tx_size); memcpy(adapter->tx_buffer, buf, size); /* From testing, HIDWrite returns an error that looks like it's two * int16_t's bitmasked together. For example, one error I saw when trying * to write a single byte was 0xffe2ff97, which works out to -30 and -105. * I have no idea what these mean. */ result = HIDWrite(adapter->handle, adapter->tx_buffer, adapter->tx_size, NULL, NULL); if(result < 0) { int16_t r1 = (result & 0x0000FFFF); int16_t r2 = ((result & 0xFFFF0000) >> 16); RARCH_LOG("[hid]: write failed: %08x (%d:%d)\n", result, r2, r1); } } static int32_t wiiu_hid_set_report(void *data, uint8_t report_type, uint8_t report_id, void *report_data, uint32_t report_length) { wiiu_adapter_t *adapter = (wiiu_adapter_t *)data; if (!adapter) return -1; return HIDSetReport(adapter->handle, report_type, report_id, report_data, report_length, NULL, NULL); } static int32_t wiiu_hid_set_idle(void *data, uint8_t duration) { wiiu_adapter_t *adapter = (wiiu_adapter_t *)data; if (!adapter) return -1; return HIDSetIdle(adapter->handle, adapter->interface_index, duration, NULL, NULL); } static int32_t wiiu_hid_set_protocol(void *data, uint8_t protocol) { wiiu_adapter_t *adapter = (wiiu_adapter_t *)data; if (!adapter) return -1; return HIDSetProtocol(adapter->handle, adapter->interface_index, protocol, NULL, NULL); } static void start_polling_thread(wiiu_hid_t *hid) { OSThreadAttributes attributes = OS_THREAD_ATTRIB_AFFINITY_CPU2; BOOL result = false; int32_t stack_size = 0x8000; int32_t priority = 10; OSThread *thread = new_thread(); void *stack = alloc_zeroed(16, stack_size); RARCH_LOG("[hid]: starting polling thread.\n"); if (!thread || !stack) { RARCH_LOG("[hid]: allocation failed, aborting thread start.\n"); goto error; } if (!OSCreateThread(thread, wiiu_hid_polling_thread, 1, (char *)hid, stack+stack_size, stack_size, priority, attributes)) { RARCH_LOG("[hid]: OSCreateThread failed.\n"); goto error; } OSSetThreadCleanupCallback(thread, wiiu_hid_polling_thread_cleanup); hid->polling_thread = thread; hid->polling_thread_stack = stack; OSResumeThread(thread); return; error: if (thread) free(thread); if (stack) free(stack); return; } static void stop_polling_thread(wiiu_hid_t *hid) { int thread_result = 0; RARCH_LOG("[hid]: stopping polling thread.\n"); if (!hid || !hid->polling_thread) return; /* Unregister our HID client so we don't get any new events. */ if(hid->client) { HIDDelClient(hid->client); hid->client = NULL; } /* tell the thread it's time to stop. */ hid->polling_thread_quit = true; /* This returns once the thread runs and the cleanup method completes. */ OSJoinThread(hid->polling_thread, &thread_result); free(hid->polling_thread); free(hid->polling_thread_stack); hid->polling_thread = NULL; hid->polling_thread_stack = NULL; } static void log_device(HIDDevice *device) { if (!device) { RARCH_LOG("NULL device.\n"); } RARCH_LOG(" handle: %d\n", device->handle); RARCH_LOG(" physical_device_inst: %d\n", device->physical_device_inst); RARCH_LOG(" vid: 0x%x\n", device->vid); RARCH_LOG(" pid: 0x%x\n", device->pid); RARCH_LOG(" interface_index: %d\n", device->interface_index); RARCH_LOG(" sub_class: %d\n", device->sub_class); RARCH_LOG(" protocol: %d\n", device->protocol); RARCH_LOG(" max_packet_size_rx: %d\n", device->max_packet_size_rx); RARCH_LOG(" max_packet_size_tx: %d\n", device->max_packet_size_tx); } static void synchronized_add_event(wiiu_attach_event *event) { OSFastMutex_Lock(&(events.lock)); event->next = events.list; events.list = event; OSFastMutex_Unlock(&(events.lock)); } static wiiu_attach_event *synchronized_get_events_list(void) { wiiu_attach_event *list; OSFastMutex_Lock(&(events.lock)); list = events.list; events.list = NULL; OSFastMutex_Unlock(&(events.lock)); return list; } static void synchronized_add_to_adapters_list(wiiu_adapter_t *adapter) { OSFastMutex_Lock(&(adapters.lock)); adapter->next = adapters.list; adapters.list = adapter; OSFastMutex_Unlock(&(adapters.lock)); } static int32_t wiiu_attach_callback(HIDClient *client, HIDDevice *device, uint32_t attach) { wiiu_attach_event *event = NULL; log_device(device); switch(attach) { case HID_DEVICE_ATTACH: case HID_DEVICE_DETACH: if (device) event = new_attach_event(device); if(!event) goto error; event->type = attach; synchronized_add_event(event); return DEVICE_USED; default: break; } error: delete_attach_event(event); return DEVICE_UNUSED; } static void wiiu_hid_detach(wiiu_hid_t *hid, wiiu_attach_event *event) { } static void wiiu_hid_attach(wiiu_hid_t *hid, wiiu_attach_event *event) { wiiu_adapter_t *adapter = new_adapter(event); if(!adapter) { RARCH_ERR("[hid]: Failed to allocate adapter.\n"); goto error; } adapter->hid = hid; adapter->slot = pad_connection_pad_init(hid->connections, "hid", event->vendor_id, event->product_id, adapter, &wiiu_hid); if(adapter->slot < 0) { RARCH_ERR("[hid]: No available slots.\n"); goto error; } RARCH_LOG("[hid]: got slot %d\n", adapter->slot); if(!pad_connection_has_interface(hid->connections, adapter->slot)) { RARCH_ERR("[hid]: Interface not found for HID device with vid=0x%04x pid=0x%04x\n", event->vendor_id, event->product_id); goto error; } RARCH_LOG("[hid]: adding to adapter list\n"); synchronized_add_to_adapters_list(adapter); RARCH_LOG("[hid]: starting read loop\n"); wiiu_start_read_loop(adapter); return; error: delete_adapter(adapter); } void wiiu_start_read_loop(wiiu_adapter_t *adapter) { adapter->state = ADAPTER_STATE_READING; #if 0 RARCH_LOG("HIDRead(0x%08x, 0x%08x, %d, 0x%08x, 0x%08x)\n", adapter->handle, adapter->rx_buffer, adapter->rx_size, wiiu_hid_read_loop_callback, adapter); #endif HIDRead(adapter->handle, adapter->rx_buffer, adapter->rx_size, wiiu_hid_read_loop_callback, adapter); } /** * Takes a buffer and formats it for the log file, 16 bytes per line. * * When the end of the buffer is reached, it is padded out with 0xff. So e.g. * a 5-byte buffer might look like: * * 5 bytes read fro HIDRead: * 0102030405ffffff ffffffffffffffff * ================================== */ static void log_buffer(uint8_t *data, uint32_t len) { int i, o; int padding = len % 16; uint8_t buf[16]; (uint8_t *)data; (uint32_t)len; RARCH_LOG("%d bytes read from HIDRead:\n", len); for(i = 0, o = 0; i < len; i++) { buf[o] = data[i]; o++; if(o == 16) { o = 0; RARCH_LOG("%02x%02x%02x%02x%02x%02x%02x%02x %02x%02x%02x%02x%02x%02x%02x%02x\n", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15]); } } if(padding) { for(i = padding; i < 16; i++) buf[i] = 0xff; RARCH_LOG("%02x%02x%02x%02x%02x%02x%02x%02x %02x%02x%02x%02x%02x%02x%02x%02x\n", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], buf[8], buf[9], buf[10], buf[11], buf[12], buf[13], buf[14], buf[15]); } RARCH_LOG("==================================\n"); } static void wiiu_hid_do_read(wiiu_adapter_t *adapter, uint8_t *data, uint32_t length) { #if 0 log_buffer(data, length); #endif /* TODO: get this data to the connect_xxx driver somehow. */ } static void wiiu_hid_read_loop_callback(uint32_t handle, int32_t error, uint8_t *buffer, uint32_t buffer_size, void *userdata) { wiiu_adapter_t *adapter = (wiiu_adapter_t *)userdata; if(!adapter) { RARCH_ERR("read_loop_callback: bad userdata\n"); return; } if(adapter->hid->polling_thread_quit) { RARCH_LOG("Shutting down read loop for slot %d\n", adapter->slot); adapter->state = ADAPTER_STATE_DONE; return; } wiiu_hid_do_read(adapter, buffer, buffer_size); adapter->state = ADAPTER_STATE_READING; HIDRead(adapter->handle, adapter->rx_buffer, adapter->rx_size, wiiu_hid_read_loop_callback, adapter); } /** * Block until all the HIDRead() calls have returned. */ static void wiiu_hid_polling_thread_cleanup(OSThread *thread, void *stack) { int incomplete = 0; int retries = 0; wiiu_adapter_t *adapter = NULL; RARCH_LOG("Waiting for in-flight reads to finish.\n"); do { OSFastMutex_Lock(&(adapters.lock)); incomplete = 0; for(adapter = adapters.list; adapter != NULL; adapter = adapter->next) { if(adapter->state == ADAPTER_STATE_READING) incomplete++; } /* We are clear for shutdown. Clean up the list * while we are holding the lock. */ if(incomplete == 0) { while(adapters.list != NULL) { adapter = adapters.list; adapters.list = adapter->next; delete_adapter(adapter); } } OSFastMutex_Unlock(&(adapters.lock)); if(incomplete) usleep(5000); if(++retries >= 1000) { RARCH_WARN("[hid]: timed out waiting for in-flight read to finish.\n"); incomplete = 0; } }while(incomplete); RARCH_LOG("All in-flight reads complete.\n"); } static void wiiu_handle_attach_events(wiiu_hid_t *hid, wiiu_attach_event *list) { wiiu_attach_event *event, *event_next = NULL; if(!hid || !list) return; for(event = list; event != NULL; event = event_next) { event_next = event->next; if(event->type == HID_DEVICE_ATTACH) wiiu_hid_attach(hid, event); else wiiu_hid_detach(hid, event); delete_attach_event(event); } } static int wiiu_hid_polling_thread(int argc, const char **argv) { wiiu_hid_t *hid = (wiiu_hid_t *)argv; int i = 0; RARCH_LOG("[hid]: polling thread is starting\n"); while(!hid->polling_thread_quit) { wiiu_handle_attach_events(hid, synchronized_get_events_list()); usleep(10000); i += 10000; if(i >= (1000 * 1000 * 3)) i = 0; } RARCH_LOG("[hid]: polling thread is stopping\n"); return 0; } static OSThread *new_thread(void) { OSThread *t = alloc_zeroed(8, sizeof(OSThread)); if (!t) return NULL; t->tag = OS_THREAD_TAG; return t; } static void wiiu_hid_init_lists(void) { RARCH_LOG("[hid]: Initializing events list\n"); memset(&events, 0, sizeof(events)); OSFastMutex_Init(&(events.lock), "attach_events"); RARCH_LOG("[hid]: Initializing adapters list\n"); memset(&adapters, 0, sizeof(adapters)); OSFastMutex_Init(&(adapters.lock), "adapters"); } static wiiu_hid_t *new_hid(void) { RARCH_LOG("[hid]: new_hid()\n"); return alloc_zeroed(4, sizeof(wiiu_hid_t)); } static void delete_hid(wiiu_hid_t *hid) { RARCH_LOG("[hid]: delete_hid()\n"); if(hid) free(hid); } static HIDClient *new_hidclient(void) { RARCH_LOG("[hid]: new_hidclient()\n"); return alloc_zeroed(32, sizeof(HIDClient)); } static void delete_hidclient(HIDClient *client) { RARCH_LOG("[hid]: delete_hidclient()\n"); if(client) free(client); } static wiiu_adapter_t *new_adapter(wiiu_attach_event *event) { wiiu_adapter_t *adapter = alloc_zeroed(32, sizeof(wiiu_adapter_t)); if (!adapter) return NULL; adapter->handle = event->handle; adapter->interface_index = event->interface_index; adapter->rx_size = event->max_packet_size_rx; adapter->rx_buffer = alloc_zeroed(32, adapter->rx_size); adapter->tx_size = event->max_packet_size_tx; adapter->tx_buffer = alloc_zeroed(32, adapter->tx_size); return adapter; } static void delete_adapter(wiiu_adapter_t *adapter) { if (!adapter) return; if(adapter->rx_buffer) { free(adapter->rx_buffer); adapter->rx_buffer = NULL; } if(adapter->tx_buffer) { free(adapter->tx_buffer); adapter->tx_buffer = NULL; } free(adapter); } static wiiu_attach_event *new_attach_event(HIDDevice *device) { wiiu_attach_event *event = alloc_zeroed(4, sizeof(wiiu_attach_event)); if(!event) return NULL; event->handle = device->handle; event->vendor_id = device->vid; event->product_id = device->pid; event->interface_index = device->interface_index; event->is_keyboard = (device->sub_class == 1 && device->protocol == 1); event->is_mouse = (device->sub_class == 1 && device->protocol == 2); event->max_packet_size_rx = device->max_packet_size_rx; event->max_packet_size_tx = device->max_packet_size_tx; return event; } static void delete_attach_event(wiiu_attach_event *event) { if(event) free(event); } void *alloc_zeroed(size_t alignment, size_t size) { void *result = memalign(alignment, size); if(result) memset(result, 0, size); return result; } hid_driver_t wiiu_hid = { wiiu_hid_init, wiiu_hid_joypad_query, wiiu_hid_free, wiiu_hid_joypad_button, wiiu_hid_joypad_get_buttons, wiiu_hid_joypad_axis, wiiu_hid_poll, wiiu_hid_joypad_rumble, wiiu_hid_joypad_name, "wiiu", wiiu_hid_send_control, wiiu_hid_set_report, wiiu_hid_set_idle, wiiu_hid_set_protocol };