Fix deadlocks when device is unplugged

== DETAILS
TIL that it's bad to call synchronization code from callbacks.

To avoid that, I made the following changes:

- Implemented an atomic swap (see previous commit) to avoid explicit
  locking when working with the event list
- ensure locks are only acquired in either the main thread or the
  I/O polling thread
- use an explicit polling loop; we still use async reads, but the
  read doesn't immediately re-invoke itself.
- remove the sleep in the polling thread.
- remove unnecessary locking in the thread cleanup call--verified that
  the list can't be modified while it is being executed.

== TESTING
I tested locally, and was able to disconnect/reconnect USB devices several times without the worker thread getting deadlocked.
This commit is contained in:
gblues 2018-04-14 13:30:34 -07:00
parent dca36ebaf8
commit 97e09d179f
5 changed files with 216 additions and 82 deletions

View File

@ -153,14 +153,16 @@ static void *ds3_init(void *handle)
if(!instance)
goto error;
memset(instance, 0, sizeof(ds3_instance_t));
instance->handle = handle;
/* maybe not necessary? */
/*
RARCH_LOG("[ds3]: setting protocol\n");
/*
if(set_protocol(instance, 1))
errors++;
*/
set_protocol(instance, 1);
RARCH_LOG("[ds3]: sending control packet\n");
if(send_control_packet(instance) < 0)
errors++;

View File

@ -16,27 +16,63 @@
#include "hid_device_driver.h"
struct ds4_instance {
hid_driver_t *driver;
void *handle;
};
extern pad_connection_interface_t ds4_pad_connection;
typedef struct ds4_instance {
void *handle;
joypad_connection_t *pad;
int slot;
uint32_t buttons;
uint16_t motors[2];
uint8_t data[64];
} ds4_instance_t;
/**
* I'm leaving this code in here for posterity, and because maybe it can
* be used on other platforms. But using the DS4 on the Wii U directly is
* impossible because it doesn't generate a HID event. Which makes me think
* it's not a HID device at all--at least, not over USB.
*
* I imagine it might be useful in Bluetooth mode, though.
*/
static void *ds4_init(void *handle)
{
return NULL;
ds4_instance_t *instance;
instance = (ds4_instance_t *)calloc(1, sizeof(ds4_instance_t));
if(!instance)
goto error;
memset(instance, 0, sizeof(ds4_instance_t));
instance->handle = handle;
instance->pad = hid_pad_register(instance, &ds4_pad_connection);
if(!instance->pad)
goto error;
RARCH_LOG("[ds4]: init complete.\n");
return instance;
error:
RARCH_ERR("[ds4]: init failed.\n");
if(instance)
free(instance);
return NULL;
}
static void ds4_free(void *data)
{
struct ds4_instance *instance = (struct ds4_instance *)data;
if(!instance)
return;
ds4_instance_t *instance = (ds4_instance_t *)data;
free(instance);
if(instance)
free(instance);
}
static void ds4_handle_packet(void *data, uint8_t *buffer, size_t size)
{
ds4_instance_t *instance = (ds4_instance_t *)data;
if(instance && instance->pad)
instance->pad->iface->packet_handler(instance->pad->data, buffer, size);
}
static bool ds4_detect(uint16_t vendor_id, uint16_t product_id)
@ -51,3 +87,66 @@ hid_device_t ds4_hid_device = {
ds4_detect,
"Sony DualShock 4"
};
static void *ds4_pad_init(void *data, uint32_t slot, hid_driver_t *driver)
{
ds4_instance_t *instance = (ds4_instance_t *)data;
if(!instance)
return NULL;
instance->slot = slot;
return instance;
}
static void ds4_pad_deinit(void *data)
{
}
static void ds4_get_buttons(void *data, retro_bits_t *state)
{
ds4_instance_t *instance = (ds4_instance_t *)data;
if(!instance)
return;
/* TODO: get buttons */
}
static void ds4_packet_handler(void *data, uint8_t *packet, uint16_t size)
{
ds4_instance_t *instance = (ds4_instance_t *)data;
if(!instance)
return;
RARCH_LOG_BUFFER(packet, size);
}
static void ds4_set_rumble(void *data, enum retro_rumble_effect effect, uint16_t strength)
{
}
static int16_t ds4_get_axis(void *data, unsigned axis)
{
return 0;
}
static const char *ds4_get_name(void *data)
{
return "Sony DualShock 4";
}
static bool ds4_button(void *data, uint16_t joykey)
{
return false;
}
pad_connection_interface_t ds4_pad_connection = {
ds4_pad_init,
ds4_pad_deinit,
ds4_packet_handler,
ds4_set_rumble,
ds4_get_buttons,
ds4_get_axis,
ds4_get_name,
ds4_button
};

View File

@ -21,7 +21,7 @@ hid_driver_instance_t hid_instance = {0};
hid_device_t *hid_device_list[] = {
&wiiu_gca_hid_device,
&ds3_hid_device,
&ds4_hid_device,
/* &ds4_hid_device, */
NULL /* must be last entry in list */
};

View File

@ -15,6 +15,7 @@
*/
#include "wiiu_hid.h"
#include <wiiu/os/atomic.h>
static wiiu_event_list events;
static wiiu_adapter_list adapters;
@ -377,21 +378,17 @@ static void synchronized_process_adapters(wiiu_hid_t *hid)
static void synchronized_add_event(wiiu_attach_event *event)
{
OSFastMutex_Lock(&(events.lock));
event->next = events.list;
events.list = event;
OSFastMutex_Unlock(&(events.lock));
wiiu_attach_event *head = (wiiu_attach_event *)SwapAtomic32((uint32_t *)&events.list, 0);
event->next = head;
head = event;
SwapAtomic32((uint32_t *)&events.list, (uint32_t)head);
}
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;
return (wiiu_attach_event *)SwapAtomic32((uint32_t *)&events.list, 0);
}
static wiiu_adapter_t *synchronized_lookup_adapter(uint32_t handle)
@ -422,24 +419,24 @@ static int32_t wiiu_attach_callback(HIDClient *client,
{
wiiu_attach_event *event = NULL;
switch(attach)
{
case HID_DEVICE_ATTACH:
log_device(device);
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;
if(attach) {
RARCH_LOG("[hid]: Device attach event generated.\n");
log_device(device);
} else {
RARCH_LOG("[hid]: Device detach event generated.\n");
}
if (device)
event = new_attach_event(device);
if(!event)
goto error;
event->type = attach;
synchronized_add_event(event);
return DEVICE_USED;
error:
delete_attach_event(event);
return DEVICE_UNUSED;
@ -453,7 +450,7 @@ static void wiiu_hid_detach(wiiu_hid_t *hid, wiiu_attach_event *event)
* the read loop method will update this to ADAPTER_STATE_GC
* so the adapter poll method can clean it up. */
if(adapter)
adapter->state = ADAPTER_STATE_DONE;
adapter->connected = false;
}
@ -479,15 +476,6 @@ error:
delete_adapter(adapter);
}
void wiiu_start_read_loop(wiiu_adapter_t *adapter)
{
HIDRead(adapter->handle,
adapter->rx_buffer,
adapter->rx_size,
wiiu_hid_read_loop_callback,
adapter);
}
static void wiiu_hid_read_loop_callback(uint32_t handle, int32_t error,
uint8_t *buffer, uint32_t buffer_size, void *userdata)
{
@ -498,17 +486,49 @@ static void wiiu_hid_read_loop_callback(uint32_t handle, int32_t error,
return;
}
if(adapter->hid->polling_thread_quit)
if(error < 0)
{
RARCH_LOG("Shutting down read loop for device: %s\n",
adapter->driver->name);
adapter->state = ADAPTER_STATE_DONE;
int16_t hid_error_code = error & 0xffff;
switch(hid_error_code)
{
case -100:
RARCH_ERR("[hid]: Invalid RM command (%s)\n", adapter->driver->name);
break;
case -102:
RARCH_ERR("[hid]: Invalid IOCTL command (%s)\n", adapter->driver->name);
break;
case -103:
RARCH_ERR("[hid]: bad vector count (%s)\n", adapter->driver->name);
break;
case -104:
RARCH_ERR("[hid]: invalid memory bank (%s)\n", adapter->driver->name);
break;
case -105:
RARCH_ERR("[hid]: invalid memory alignment (%s)\n", adapter->driver->name);
break;
case -106:
RARCH_ERR("[hid]: invalid data size (%s)\n", adapter->driver->name);
break;
case -107:
RARCH_ERR("[hid]: request cancelled (%s)\n", adapter->driver->name);
break;
case -108:
RARCH_ERR("[hid]: request timed out (%s)\n", adapter->driver->name);
break;
case -109:
RARCH_ERR("[hid]: request aborted (%s)\n", adapter->driver->name);
break;
case -110:
RARCH_ERR("[hid]: client priority error (%s)\n", adapter->driver->name);
break;
case -111:
RARCH_ERR("[hid]: invalid device handle (%s)\n", adapter->driver->name);
break;
}
}
if(adapter->state == ADAPTER_STATE_READY ||
adapter->state == ADAPTER_STATE_READING) {
adapter->state = ADAPTER_STATE_READING;
if(adapter->state == ADAPTER_STATE_READING) {
adapter->state = ADAPTER_STATE_READY;
/* "error" usually is something benign like "device not ready", at
* least from my own experiments. Just ignore the error and retry
* the read. */
@ -517,16 +537,6 @@ static void wiiu_hid_read_loop_callback(uint32_t handle, int32_t error,
buffer, buffer_size);
}
}
/* this can also get set if something goes wrong in initialization */
if(adapter->state == ADAPTER_STATE_DONE)
{
adapter->state = ADAPTER_STATE_GC;
return;
}
HIDRead(adapter->handle, adapter->rx_buffer, adapter->rx_size,
wiiu_hid_read_loop_callback, adapter);
}
/**
@ -540,17 +550,18 @@ static void wiiu_hid_polling_thread_cleanup(OSThread *thread, void *stack)
RARCH_LOG("Waiting for in-flight reads to finish.\n");
/* We don't need to protect the adapter list here because nothing else
will access it during this method (the HID system is shut down, and
the only other access is the polling thread that just stopped */
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)
{
RARCH_LOG("All in-flight reads complete.\n");
@ -562,7 +573,6 @@ static void wiiu_hid_polling_thread_cleanup(OSThread *thread, void *stack)
delete_adapter(adapter);
}
}
OSFastMutex_Unlock(&(adapters.lock));
if(incomplete)
usleep(5000);
@ -572,8 +582,7 @@ static void wiiu_hid_polling_thread_cleanup(OSThread *thread, void *stack)
RARCH_WARN("[hid]: timed out waiting for in-flight read to finish.\n");
incomplete = 0;
}
}while(incomplete);
} while(incomplete);
}
static void wiiu_handle_attach_events(wiiu_hid_t *hid, wiiu_attach_event *list)
@ -594,7 +603,7 @@ static void wiiu_handle_attach_events(wiiu_hid_t *hid, wiiu_attach_event *list)
}
}
static void wiiu_handle_ready_adapters(wiiu_hid_t *hid)
static void wiiu_poll_adapters(wiiu_hid_t *hid)
{
wiiu_adapter_t *it;
OSFastMutex_Lock(&(adapters.lock));
@ -602,11 +611,25 @@ static void wiiu_handle_ready_adapters(wiiu_hid_t *hid)
for(it = adapters.list; it != NULL; it = it->next)
{
if(it->state == ADAPTER_STATE_READY)
wiiu_start_read_loop(it);
{
if(it->connected)
{
it->state = ADAPTER_STATE_READING;
HIDRead(it->handle,
it->rx_buffer,
it->rx_size,
wiiu_hid_read_loop_callback,
it);
} else {
it->state = ADAPTER_STATE_DONE;
}
}
if(it->state == ADAPTER_STATE_DONE)
it->state = ADAPTER_STATE_GC;
}
OSFastMutex_Unlock(&(adapters.lock));
}
static int wiiu_hid_polling_thread(int argc, const char **argv)
@ -618,8 +641,7 @@ static int wiiu_hid_polling_thread(int argc, const char **argv)
while(!hid->polling_thread_quit)
{
wiiu_handle_attach_events(hid, synchronized_get_events_list());
wiiu_handle_ready_adapters(hid);
usleep(10000);
wiiu_poll_adapters(hid);
}
RARCH_LOG("[hid]: polling thread is stopping\n");
@ -674,6 +696,17 @@ static void delete_hidclient(HIDClient *client)
free(client);
}
static void init_cachealigned_buffer(int32_t min_size, uint8_t **out_buf_ptr, int32_t *actual_size)
{
int cacheblocks = (min_size < 32) ? 1 : min_size / 32;
if(min_size > 32 && min_size % 32 != 0)
cacheblocks++;
*actual_size = 32 * cacheblocks;
*out_buf_ptr = alloc_zeroed(32, *actual_size);
}
static wiiu_adapter_t *new_adapter(wiiu_attach_event *event)
{
wiiu_adapter_t *adapter = alloc_zeroed(32, sizeof(wiiu_adapter_t));
@ -683,10 +716,9 @@ static wiiu_adapter_t *new_adapter(wiiu_attach_event *event)
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);
init_cachealigned_buffer(event->max_packet_size_rx, &adapter->rx_buffer, &adapter->rx_size);
init_cachealigned_buffer(event->max_packet_size_tx, &adapter->tx_buffer, &adapter->tx_size);
adapter->connected = true;
return adapter;
}

View File

@ -62,6 +62,7 @@ struct wiiu_adapter {
int32_t tx_size;
uint32_t handle;
uint8_t interface_index;
bool connected;
};
/**