* Copyright 2011 Michael Lotz <mmlr@mlotz.ch>
* Distributed under the terms of the MIT license.
*/
#include "Driver.h"
#include "JoystickProtocolHandler.h"
#include "HIDCollection.h"
#include "HIDDevice.h"
#include "HIDReport.h"
#include "HIDReportItem.h"
#include <new>
#include <string.h>
#include <usb/USB_hid.h>
#include <kernel.h>
#include <util/AutoLock.h>
JoystickProtocolHandler::JoystickProtocolHandler(HIDReport &report)
:
ProtocolHandler(report.Device(), "joystick/" DEVICE_PATH_SUFFIX "/", 0),
fReport(report),
fAxisCount(0),
fAxis(NULL),
fHatCount(0),
fHats(NULL),
fButtonCount(0),
fMaxButton(0),
fButtons(NULL),
fOpenCount(0),
fUpdateThread(-1)
{
mutex_init(&fUpdateLock, "joystick update lock");
memset(&fJoystickModuleInfo, 0, sizeof(joystick_module_info));
memset(&fCurrentValues, 0, sizeof(variable_joystick));
for (uint32 i = 0; i < report.CountItems(); i++) {
HIDReportItem *item = report.ItemAt(i);
if (!item->HasData())
continue;
switch (item->UsagePage()) {
case B_HID_USAGE_PAGE_BUTTON:
{
if (item->UsageID() > INT16_MAX)
break;
HIDReportItem **newButtons = (HIDReportItem **)realloc(fButtons,
++fButtonCount * sizeof(HIDReportItem *));
if (newButtons == NULL) {
fButtonCount--;
break;
}
fButtons = newButtons;
fButtons[fButtonCount - 1] = item;
if (fMaxButton < item->UsageID())
fMaxButton = item->UsageID();
break;
}
case B_HID_USAGE_PAGE_GENERIC_DESKTOP:
{
if (item->UsageID() == B_HID_UID_GD_HAT_SWITCH) {
HIDReportItem **newHats = (HIDReportItem **)realloc(fHats,
++fHatCount * sizeof(HIDReportItem *));
if (newHats == NULL) {
fHatCount--;
break;
}
fHats = newHats;
fHats[fHatCount - 1] = item;
break;
}
if (item->UsageID() >= B_HID_UID_GD_X
&& item->UsageID() <= B_HID_UID_GD_WHEEL) {
} else if (item->UsageID() >= B_HID_UID_GD_VX
&& item->UsageID() <= B_HID_UID_GD_VNO) {
} else
break;
HIDReportItem **newAxis = (HIDReportItem **)realloc(fAxis,
++fAxisCount * sizeof(HIDReportItem *));
if (newAxis == NULL) {
fAxisCount--;
break;
}
fAxis = newAxis;
fAxis[fAxisCount - 1] = item;
break;
}
}
}
fCurrentValues.initialize(fAxisCount, fHatCount, fMaxButton);
TRACE("joystick device with %" B_PRIu32 " buttons, %" B_PRIu32
" axes and %" B_PRIu32 " hats\n",
fButtonCount, fAxisCount, fHatCount);
TRACE("report id: %u\n", report.ID());
}
JoystickProtocolHandler::~JoystickProtocolHandler()
{
free(fCurrentValues.data);
free(fAxis);
free(fHats);
free(fButtons);
}
void
JoystickProtocolHandler::AddHandlers(HIDDevice &device,
HIDCollection &collection, ProtocolHandler *&handlerList)
{
if (collection.UsagePage() != B_HID_USAGE_PAGE_GENERIC_DESKTOP
|| (collection.UsageID() != B_HID_UID_GD_JOYSTICK
&& collection.UsageID() != B_HID_UID_GD_GAMEPAD
&& collection.UsageID() != B_HID_UID_GD_MULTIAXIS)) {
TRACE("collection not a joystick or gamepad\n");
return;
}
HIDParser &parser = device.Parser();
uint32 maxReportCount = parser.CountReports(HID_REPORT_TYPE_INPUT);
if (maxReportCount == 0)
return;
uint32 inputReportCount = 0;
HIDReport *inputReports[maxReportCount];
collection.BuildReportList(HID_REPORT_TYPE_INPUT, inputReports,
inputReportCount);
for (uint32 i = 0; i < inputReportCount; i++) {
HIDReport *inputReport = inputReports[i];
bool foundAxis = false;
for (uint32 j = 0; j < inputReport->CountItems(); j++) {
HIDReportItem *item = inputReport->ItemAt(j);
if (item == NULL || !item->HasData())
continue;
if (item->UsagePage() != B_HID_USAGE_PAGE_GENERIC_DESKTOP)
continue;
if (item->UsageID() >= B_HID_UID_GD_X
&& item->UsageID() <= B_HID_UID_GD_RZ) {
foundAxis = true;
break;
}
}
if (!foundAxis)
continue;
ProtocolHandler *newHandler
= new(std::nothrow) JoystickProtocolHandler(*inputReport);
if (newHandler == NULL) {
TRACE("failed to allocated joystick protocol handler\n");
continue;
}
newHandler->SetNextHandler(handlerList);
handlerList = newHandler;
}
}
status_t
JoystickProtocolHandler::Open(uint32 flags, uint32 *cookie)
{
if (fCurrentValues.data == NULL)
return B_NO_INIT;
status_t result = mutex_lock(&fUpdateLock);
if (result != B_OK)
return result;
if (fUpdateThread < 0) {
fUpdateThread = spawn_kernel_thread(_UpdateThread, "joystick update",
B_NORMAL_PRIORITY, (void *)this);
if (fUpdateThread < 0)
result = fUpdateThread;
else
resume_thread(fUpdateThread);
}
if (result == B_OK)
fOpenCount++;
mutex_unlock(&fUpdateLock);
if (result != B_OK)
return result;
return ProtocolHandler::Open(flags, cookie);
}
status_t
JoystickProtocolHandler::Close(uint32 *cookie)
{
status_t result = mutex_lock(&fUpdateLock);
if (result == B_OK) {
if (--fOpenCount == 0)
fUpdateThread = -1;
mutex_unlock(&fUpdateLock);
}
return ProtocolHandler::Close(cookie);
}
status_t
JoystickProtocolHandler::Read(uint32 *cookie, off_t position, void *buffer,
size_t *numBytes)
{
if (*numBytes < fCurrentValues.data_size)
return B_BUFFER_OVERFLOW;
MutexLocker locker(fUpdateLock);
if (!locker.IsLocked()) {
*numBytes = 0;
return B_ERROR;
}
if (!IS_USER_ADDRESS(buffer) || user_memcpy(buffer, fCurrentValues.data,
fCurrentValues.data_size) != B_OK)
return B_BAD_ADDRESS;
*numBytes = fCurrentValues.data_size;
return B_OK;
}
status_t
JoystickProtocolHandler::Write(uint32 *cookie, off_t position,
const void *buffer, size_t *numBytes)
{
*numBytes = 0;
return B_NOT_SUPPORTED;
}
status_t
JoystickProtocolHandler::Control(uint32 *cookie, uint32 op, void *buffer,
size_t length)
{
switch (op) {
case B_GET_DEVICE_NAME:
{
const char name[] = DEVICE_NAME" Joystick";
return IOGetDeviceName(name, buffer, length);
}
case B_JOYSTICK_SET_DEVICE_MODULE:
{
if (length < sizeof(joystick_module_info))
return B_BAD_VALUE;
status_t result = mutex_lock(&fUpdateLock);
if (result != B_OK)
return result;
if (!IS_USER_ADDRESS(buffer)
|| user_memcpy(&fJoystickModuleInfo, buffer,
sizeof(joystick_module_info)) != B_OK) {
return B_BAD_ADDRESS;
}
bool supportsVariable = (fJoystickModuleInfo.flags
& js_flag_variable_size_reads) != 0;
if (!supportsVariable) {
free(fCurrentValues.data);
fCurrentValues.initialize_to_extended_joystick();
if (fAxisCount > MAX_AXES)
fAxisCount = MAX_AXES;
if (fHatCount > MAX_HATS)
fHatCount = MAX_HATS;
if (fMaxButton > MAX_BUTTONS)
fMaxButton = MAX_BUTTONS;
TRACE_ALWAYS("using joystick in extended_joystick mode\n");
} else {
TRACE_ALWAYS("using joystick in variable mode\n");
}
fJoystickModuleInfo.num_axes = fAxisCount;
fJoystickModuleInfo.num_buttons = fMaxButton;
fJoystickModuleInfo.num_hats = fHatCount;
fJoystickModuleInfo.num_sticks = 1;
fJoystickModuleInfo.config_size = 0;
mutex_unlock(&fUpdateLock);
return B_OK;
}
case B_JOYSTICK_GET_DEVICE_MODULE:
if (length < sizeof(joystick_module_info))
return B_BAD_VALUE;
if (!IS_USER_ADDRESS(buffer)
|| user_memcpy(buffer, &fJoystickModuleInfo,
sizeof(joystick_module_info)) != B_OK) {
return B_BAD_ADDRESS;
}
return B_OK;
}
return B_ERROR;
}
int32
JoystickProtocolHandler::_UpdateThread(void *data)
{
JoystickProtocolHandler *handler = (JoystickProtocolHandler *)data;
while (handler->fUpdateThread == find_thread(NULL)) {
status_t result = handler->_Update();
if (result != B_OK)
return result;
}
return B_OK;
}
status_t
JoystickProtocolHandler::_Update()
{
status_t result = fReport.WaitForReport(B_INFINITE_TIMEOUT);
if (result != B_OK) {
if (fReport.Device()->IsRemoved()) {
TRACE("device has been removed\n");
return B_DEV_NOT_READY;
}
if (result == B_INTERRUPTED)
return result;
if (result != B_BUSY) {
TRACE_ALWAYS("error waiting for report: %s\n", strerror(result));
}
return B_OK;
}
result = mutex_lock(&fUpdateLock);
if (result != B_OK) {
fReport.DoneProcessing();
return result;
}
memset(fCurrentValues.data, 0, fCurrentValues.data_size);
for (uint32 i = 0; i < fAxisCount; i++) {
if (fAxis[i] == NULL)
continue;
if (fAxis[i]->Extract() == B_OK && fAxis[i]->Valid())
fCurrentValues.axes[i] = (int16)fAxis[i]->ScaledData(16, true);
}
for (uint32 i = 0; i < fHatCount; i++) {
HIDReportItem *hat = fHats[i];
if (hat == NULL)
continue;
if (hat->Extract() != B_OK || !hat->Valid())
continue;
fCurrentValues.hats[i] = hat->ScaledRangeData(1, 8);
}
for (uint32 i = 0; i < fButtonCount; i++) {
HIDReportItem *button = fButtons[i];
if (button == NULL)
break;
uint16 index = button->UsageID() - 1;
if (index >= fMaxButton)
continue;
if (button->Extract() == B_OK && button->Valid()) {
fCurrentValues.buttons[index / 32]
|= (button->Data() & 1) << (index % 32);
}
}
fReport.DoneProcessing();
TRACE("got joystick report\n");
*fCurrentValues.timestamp = system_time();
mutex_unlock(&fUpdateLock);
return B_OK;
}