⛏️ index : haiku.git

/*
 * Copyright 2011 Michael Lotz <mmlr@mlotz.ch>
 * Distributed under the terms of the MIT license.
 */


//!	Driver for USB Human Interface Devices.


#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;
				}

// TODO: "axis" is set but not used!
//				uint16 axis = 0;
				if (item->UsageID() >= B_HID_UID_GD_X
					&& item->UsageID() <= B_HID_UID_GD_WHEEL) {
//					axis = item->UsageID() - B_HID_UID_GD_X;
				} else if (item->UsageID() >= B_HID_UID_GD_VX
					&& item->UsageID() <= B_HID_UID_GD_VNO) {
//					axis = item->UsageID() - B_HID_UID_GD_VX;
				} 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];

		// try to find at least one axis
		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;

	// this is a polling interface, we just return the current value
	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) {
				// We revert to a structure that we can support using only
				// the data available in an extended_joystick structure.
				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) {
			// "busy" happens when other reports come in on the same input as ours
			TRACE_ALWAYS("error waiting for report: %s\n", strerror(result));
		}

		// signal that we simply want to try again
		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;
}