⛏️ index : haiku.git

/*
 * Copyright 2003-2006, Haiku Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Michael Lotz <mmlr@mlotz.ch>
 *		Niels S. Reedijk
 */


#include "usb_private.h"

#include <stdio.h>

#include <algorithm>


Hub::Hub(Object *parent, int8 hubAddress, uint8 hubPort,
	usb_device_descriptor &desc, int8 deviceAddress, usb_speed speed,
	bool isRootHub, void *controllerCookie)
	:	Device(parent, hubAddress, hubPort, desc, deviceAddress, speed,
			isRootHub, controllerCookie),
		fInterruptPipe(NULL)
{
	TRACE("creating hub\n");

	memset(&fHubDescriptor, 0, sizeof(fHubDescriptor));
	for (int32 i = 0; i < USB_MAX_PORT_COUNT; i++)
		fChildren[i] = NULL;

	if (!fInitOK) {
		TRACE_ERROR("device failed to initialize\n");
		return;
	}

	// Set to false again for the hub init.
	fInitOK = false;

	if (fDeviceDescriptor.device_class != 9) {
		TRACE_ERROR("wrong class! bailing out\n");
		return;
	}

	TRACE("getting hub descriptor...\n");
	size_t actualLength;
	status_t status = GetDescriptor(USB_DESCRIPTOR_HUB, 0, 0,
		(void *)&fHubDescriptor, sizeof(usb_hub_descriptor), &actualLength);

	// we need at least 8 bytes
	if (status < B_OK || actualLength < 8) {
		TRACE_ERROR("error getting hub descriptor\n");
		return;
	}

	TRACE("hub descriptor (%ld bytes):\n", actualLength);
	TRACE("\tlength:..............%d\n", fHubDescriptor.length);
	TRACE("\tdescriptor_type:.....0x%02x\n", fHubDescriptor.descriptor_type);
	TRACE("\tnum_ports:...........%d\n", fHubDescriptor.num_ports);
	TRACE("\tcharacteristics:.....0x%04x\n", fHubDescriptor.characteristics);
	TRACE("\tpower_on_to_power_g:.%d\n", fHubDescriptor.power_on_to_power_good);
	TRACE("\tdevice_removeable:...0x%02x\n", fHubDescriptor.device_removeable);
	TRACE("\tpower_control_mask:..0x%02x\n", fHubDescriptor.power_control_mask);

	if (fHubDescriptor.num_ports > USB_MAX_PORT_COUNT) {
		TRACE_ALWAYS("hub supports more ports than we do (%d vs. %d)\n",
			fHubDescriptor.num_ports, USB_MAX_PORT_COUNT);
		fHubDescriptor.num_ports = USB_MAX_PORT_COUNT;
	}

	usb_interface_list *list = Configuration()->interface;
	Object *object = GetStack()->GetObject(list->active->endpoint[0].handle);
	if (object && (object->Type() & USB_OBJECT_INTERRUPT_PIPE) != 0) {
		fInterruptPipe = (InterruptPipe *)object;
		fInterruptPipe->QueueInterrupt(fInterruptStatus,
			sizeof(fInterruptStatus), InterruptCallback, this);
	} else {
		TRACE_ALWAYS("no interrupt pipe found\n");
	}
	object->SetBusy(false);

	// Wait some time before powering up the ports
	if (!isRootHub)
		snooze(USB_DELAY_HUB_POWER_UP);

	// Enable port power on all ports
	for (int32 i = 0; i < fHubDescriptor.num_ports; i++) {
		status = DefaultPipe()->SendRequest(USB_REQTYPE_CLASS | USB_REQTYPE_OTHER_OUT,
			USB_REQUEST_SET_FEATURE, PORT_POWER, i + 1, 0, NULL, 0, NULL);

		if (status < B_OK)
			TRACE_ERROR("power up failed on port %" B_PRId32 "\n", i);
	}

	// Wait for power to stabilize
	snooze(fHubDescriptor.power_on_to_power_good * 2000);

	fInitOK = true;
	TRACE("initialised ok\n");
}


Hub::~Hub()
{
	delete fInterruptPipe;
}


status_t
Hub::Changed(change_item **changeList, bool added)
{
	status_t result = Device::Changed(changeList, added);
	if (added || result < B_OK)
		return result;

	for (int32 i = 0; i < fHubDescriptor.num_ports; i++) {
		if (fChildren[i] == NULL)
			continue;

		fChildren[i]->Changed(changeList, false);
		fChildren[i] = NULL;
	}

	return B_OK;
}


status_t
Hub::UpdatePortStatus(uint8 index)
{
	// get the current port status
	size_t actualLength = 0;
	status_t result = DefaultPipe()->SendRequest(USB_REQTYPE_CLASS | USB_REQTYPE_OTHER_IN,
		USB_REQUEST_GET_STATUS, 0, index + 1, sizeof(usb_port_status),
		(void *)&fPortStatus[index], sizeof(usb_port_status), &actualLength);

	if (result < B_OK || actualLength < sizeof(usb_port_status)) {
		TRACE_ERROR("error updating port status\n");
		return B_ERROR;
	}

	return B_OK;
}


status_t
Hub::ResetPort(uint8 index)
{
	status_t result = DefaultPipe()->SendRequest(USB_REQTYPE_CLASS | USB_REQTYPE_OTHER_OUT,
		USB_REQUEST_SET_FEATURE, PORT_RESET, index + 1, 0, NULL, 0, NULL);

	if (result < B_OK)
		return result;

	for (int32 i = 0; i < 10; i++) {
		snooze(USB_DELAY_PORT_RESET);

		result = UpdatePortStatus(index);
		if (result < B_OK)
			return result;

		if ((fPortStatus[index].change & PORT_STATUS_RESET) != 0
			|| (fPortStatus[index].status & PORT_STATUS_RESET) == 0) {
			// reset is done
			break;
		}
	}

	if ((fPortStatus[index].change & PORT_STATUS_RESET) == 0
			&& (fPortStatus[index].status & PORT_STATUS_RESET) != 0) {
		TRACE_ERROR("port %d won't reset (%#x, %#x)\n", index,
			fPortStatus[index].change, fPortStatus[index].status);
		return B_ERROR;
	}

	// clear the reset change
	result = DefaultPipe()->SendRequest(USB_REQTYPE_CLASS | USB_REQTYPE_OTHER_OUT,
		USB_REQUEST_CLEAR_FEATURE, C_PORT_RESET, index + 1, 0, NULL, 0, NULL);
	if (result < B_OK)
		return result;

	// wait for reset recovery
	snooze(USB_DELAY_PORT_RESET_RECOVERY);
	TRACE("port %d was reset successfully\n", index);
	return B_OK;
}


status_t
Hub::DisablePort(uint8 index)
{
	return DefaultPipe()->SendRequest(USB_REQTYPE_CLASS
		| USB_REQTYPE_OTHER_OUT, USB_REQUEST_CLEAR_FEATURE, PORT_ENABLE,
		index + 1, 0, NULL, 0, NULL);
}



void
Hub::Explore(change_item **changeList)
{
	for (int32 i = 0; i < fHubDescriptor.num_ports; i++) {
		status_t result = UpdatePortStatus(i);
		if (result < B_OK)
			continue;

#ifdef TRACE_USB
		if (fPortStatus[i].change) {
			TRACE("port %" B_PRId32 ": status: 0x%04x; change: 0x%04x\n", i,
				fPortStatus[i].status, fPortStatus[i].change);
			TRACE("device at port %" B_PRId32 ": %p (%" B_PRId32 ")\n", i,
				fChildren[i], fChildren[i] != NULL
					? fChildren[i]->USBID() : 0);
		}
#endif

		if ((fPortStatus[i].change & PORT_STATUS_CONNECTION)
				|| ((fPortStatus[i].status & PORT_STATUS_CONNECTION)
					&& fChildren[i] == NULL)) {
			// clear status change
			DefaultPipe()->SendRequest(USB_REQTYPE_CLASS | USB_REQTYPE_OTHER_OUT,
				USB_REQUEST_CLEAR_FEATURE, C_PORT_CONNECTION, i + 1,
				0, NULL, 0, NULL);

			if (fPortStatus[i].status & PORT_STATUS_CONNECTION) {
				// new device attached!
				TRACE_ALWAYS("port %" B_PRId32 ": new device connected\n", i);

				int32 retry = 2;
				while (retry--) {
					// wait for stable device power
					result = _DebouncePort(i);
					if (result != B_OK) {
						TRACE_ERROR("debouncing port %" B_PRId32
							" failed: %s\n", i, strerror(result));
						break;
					}

					// reset the port, this will also enable it
					result = ResetPort(i);
					if (result < B_OK) {
						TRACE_ERROR("resetting port %" B_PRId32 " failed\n",
							i);
						break;
					}

					result = UpdatePortStatus(i);
					if (result < B_OK)
						break;

					if ((fPortStatus[i].status & PORT_STATUS_CONNECTION) == 0) {
						// device has vanished after reset, ignore
						TRACE("device disappeared on reset\n");
						break;
					}

					if (fChildren[i] != NULL) {
						TRACE_ERROR("new device on a port that is already in "
							"use\n");
						fChildren[i]->Changed(changeList, false);
						fChildren[i] = NULL;
					}

					// Determine the device speed.
					usb_speed speed;

					// PORT_STATUS_LOW_SPEED and PORT_STATUS_SS_POWER are the
					// same, but PORT_STATUS_POWER will not be set for SS
					// devices, hence this somewhat convoluted logic.
					if ((fPortStatus[i].status & PORT_STATUS_POWER) != 0) {
						if ((fPortStatus[i].status & PORT_STATUS_HIGH_SPEED) != 0)
							speed = USB_SPEED_HIGHSPEED;
						else if ((fPortStatus[i].status & PORT_STATUS_LOW_SPEED) != 0)
							speed = USB_SPEED_LOWSPEED;
						else
							speed = USB_SPEED_FULLSPEED;
					} else {
						// This must be a SuperSpeed device, which will
						// simply inherit our speed.
						speed = Speed();
					}
					if (speed > Speed())
						speed = Speed();

					// either let the device inherit our addresses (if we are
					// already potentially using a transaction translator) or
					// set ourselves as the hub when we might become the
					// transaction translator for the device.
					int8 hubAddress = HubAddress();
					uint8 hubPort = HubPort();
					if (Speed() == USB_SPEED_HIGHSPEED || Speed() == USB_SPEED_SUPERSPEED) {
						hubAddress = DeviceAddress();
						hubPort = i + 1;
					}

					Device *newDevice = GetBusManager()->AllocateDevice(this,
						hubAddress, hubPort, speed);

					if (newDevice) {
						newDevice->Changed(changeList, true);
						fChildren[i] = newDevice;
						break;
					} else {
						// the device failed to setup correctly, disable the
						// port so that the device doesn't get in the way of
						// future addressing.
						DisablePort(i);
					}
				}
			} else {
				// Device removed...
				TRACE_ALWAYS("port %" B_PRId32 ": device removed\n", i);
				if (fChildren[i] != NULL) {
					TRACE("removing device %p\n", fChildren[i]);
					fChildren[i]->Changed(changeList, false);
					fChildren[i] = NULL;
				}
			}
		}

		// other port changes we do not really handle, report and clear them
		if (fPortStatus[i].change & PORT_STATUS_ENABLE) {
			TRACE_ALWAYS("port %" B_PRId32 " %sabled\n", i,
				(fPortStatus[i].status & PORT_STATUS_ENABLE) ? "en" : "dis");
			DefaultPipe()->SendRequest(USB_REQTYPE_CLASS | USB_REQTYPE_OTHER_OUT,
				USB_REQUEST_CLEAR_FEATURE, C_PORT_ENABLE, i + 1,
				0, NULL, 0, NULL);
		}

		if (fPortStatus[i].change & PORT_STATUS_SUSPEND) {
			TRACE_ALWAYS("port %" B_PRId32 " is %ssuspended\n", i,
				(fPortStatus[i].status & PORT_STATUS_SUSPEND) ? "" : "not ");
			DefaultPipe()->SendRequest(USB_REQTYPE_CLASS | USB_REQTYPE_OTHER_OUT,
				USB_REQUEST_CLEAR_FEATURE, C_PORT_SUSPEND, i + 1,
				0, NULL, 0, NULL);
		}

		if (fPortStatus[i].change & PORT_STATUS_OVER_CURRENT) {
			TRACE_ALWAYS("port %" B_PRId32 " is %sin an over current state\n",
				i, (fPortStatus[i].status & PORT_STATUS_OVER_CURRENT) ? "" : "not ");
			DefaultPipe()->SendRequest(USB_REQTYPE_CLASS | USB_REQTYPE_OTHER_OUT,
				USB_REQUEST_CLEAR_FEATURE, C_PORT_OVER_CURRENT, i + 1,
				0, NULL, 0, NULL);
		}

		if (fPortStatus[i].change & PORT_STATUS_RESET) {
			TRACE_ALWAYS("port %" B_PRId32 " was reset\n", i);
			DefaultPipe()->SendRequest(USB_REQTYPE_CLASS | USB_REQTYPE_OTHER_OUT,
				USB_REQUEST_CLEAR_FEATURE, C_PORT_RESET, i + 1,
				0, NULL, 0, NULL);
		}

		if (fPortStatus[i].change & PORT_CHANGE_LINK_STATE) {
			TRACE_ALWAYS("port %" B_PRId32 " link state changed\n", i);
			DefaultPipe()->SendRequest(USB_REQTYPE_CLASS | USB_REQTYPE_OTHER_OUT,
				USB_REQUEST_CLEAR_FEATURE, C_PORT_LINK_STATE, i + 1,
				0, NULL, 0, NULL);
		}

		if (fPortStatus[i].change & PORT_CHANGE_BH_PORT_RESET) {
			TRACE_ALWAYS("port %" B_PRId32 " was warm reset\n", i);
			DefaultPipe()->SendRequest(USB_REQTYPE_CLASS | USB_REQTYPE_OTHER_OUT,
				USB_REQUEST_CLEAR_FEATURE, C_PORT_BH_PORT_RESET, i + 1,
				0, NULL, 0, NULL);
		}

	}

	// explore down the tree if we have hubs connected
	for (int32 i = 0; i < fHubDescriptor.num_ports; i++) {
		if (!fChildren[i] || (fChildren[i]->Type() & USB_OBJECT_HUB) == 0)
			continue;

		((Hub *)fChildren[i])->Explore(changeList);
	}
}


void
Hub::InterruptCallback(void *cookie, status_t status, void *data,
	size_t actualLength)
{
	TRACE_STATIC((Hub *)cookie, "interrupt callback!\n");
}


status_t
Hub::GetDescriptor(uint8 descriptorType, uint8 index, uint16 languageID,
	void *data, size_t dataLength, size_t *actualLength)
{
	return DefaultPipe()->SendRequest(
		USB_REQTYPE_DEVICE_IN | USB_REQTYPE_CLASS,			// type
		USB_REQUEST_GET_DESCRIPTOR,							// request
		(descriptorType << 8) | index,						// value
		languageID,											// index
		dataLength,											// length
		data,												// buffer
		dataLength,											// buffer length
		actualLength);										// actual length
}


status_t
Hub::ReportDevice(usb_support_descriptor *supportDescriptors,
	uint32 supportDescriptorCount, const usb_notify_hooks *hooks,
	usb_driver_cookie **cookies, bool added, bool recursive)
{
	status_t result = B_UNSUPPORTED;

	if (added) {
		// Report hub before children when adding devices
		TRACE("reporting hub before children\n");
		result = Device::ReportDevice(supportDescriptors,
			supportDescriptorCount, hooks, cookies, added, recursive);
	}

	for (int32 i = 0; recursive && i < fHubDescriptor.num_ports; i++) {
		if (!fChildren[i])
			continue;

		if (fChildren[i]->ReportDevice(supportDescriptors,
				supportDescriptorCount, hooks, cookies, added, true) == B_OK)
			result = B_OK;
	}

	if (!added) {
		// Report hub after children when removing devices
		TRACE("reporting hub after children\n");
		if (Device::ReportDevice(supportDescriptors, supportDescriptorCount,
				hooks, cookies, added, recursive) == B_OK)
			result = B_OK;
	}

	return result;
}


status_t
Hub::BuildDeviceName(char *string, uint32 *index, size_t bufferSize,
	Device *device)
{
	status_t result = Device::BuildDeviceName(string, index, bufferSize, device);
	if (result < B_OK) {
		// recursion to parent failed, we're at the root(hub)
		if (*index < bufferSize) {
			int32 managerIndex = GetStack()->IndexOfBusManager(GetBusManager());
			size_t totalBytes = snprintf(string + *index, bufferSize - *index,
				"%" B_PRId32, managerIndex);
			*index += std::min(totalBytes, (size_t)(bufferSize - *index - 1));
		}
	}

	if (!device) {
		// no device was specified - report the hub
		if (*index < bufferSize) {
			size_t totalBytes = snprintf(string + *index, bufferSize - *index,
				"/hub");
			*index += std::min(totalBytes, (size_t)(bufferSize - *index - 1));
		}
	} else {
		// find out where the requested device sitts
		for (int32 i = 0; i < fHubDescriptor.num_ports; i++) {
			if (fChildren[i] == device) {
				if (*index < bufferSize) {
					size_t totalBytes = snprintf(string + *index,
						bufferSize - *index, "/%" B_PRId32, i);
					*index += std::min(totalBytes,
						(size_t)(bufferSize - *index - 1));
				}
				break;
			}
		}
	}

	return B_OK;
}


status_t
Hub::_DebouncePort(uint8 index)
{
	uint32 timeout = 0;
	uint32 stableTime = 0;
	while (timeout < USB_DEBOUNCE_TIMEOUT) {
		snooze(USB_DEBOUNCE_CHECK_INTERVAL);
		timeout += USB_DEBOUNCE_CHECK_INTERVAL;

		status_t result = UpdatePortStatus(index);
		if (result != B_OK)
			return result;

		if ((fPortStatus[index].change & PORT_STATUS_CONNECTION) == 0) {
			stableTime += USB_DEBOUNCE_CHECK_INTERVAL;
			if (stableTime >= USB_DEBOUNCE_STABLE_TIME)
				return B_OK;
			continue;
		}

		// clear the connection change and reset stable time
		result = DefaultPipe()->SendRequest(USB_REQTYPE_CLASS
			| USB_REQTYPE_OTHER_OUT, USB_REQUEST_CLEAR_FEATURE,
			C_PORT_CONNECTION, index + 1, 0, NULL, 0, NULL);
		if (result != B_OK)
			return result;

		TRACE("got connection change during debounce, resetting stable time\n");
		stableTime = 0;
	}

	return B_TIMED_OUT;
}