netbsd-src/sys/dev/usb/usbroothub.c

623 lines
18 KiB
C

/* $NetBSD: usbroothub.c,v 1.16 2024/02/04 05:43:06 mrg Exp $ */
/*-
* Copyright (c) 1998, 2004, 2011, 2012 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Lennart Augustsson (lennart@augustsson.net) at
* Carlstedt Research & Technology, Jared D. McNeill (jmcneill@invisible.ca),
* Matthew R. Green (mrg@eterna23.net) and Nick Hudson.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Copyright (c) 2008
* Matthias Drochner. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: usbroothub.c,v 1.16 2024/02/04 05:43:06 mrg Exp $");
#include <sys/param.h>
#include <sys/systm.h> /* for ostype */
#include <dev/usb/usb.h>
#include <dev/usb/usbdi.h>
#include <dev/usb/usbdivar.h>
#include <dev/usb/usbroothub.h>
#include <dev/usb/usbhist.h>
/* helper functions for USB root hub emulation */
static usbd_status roothub_ctrl_transfer(struct usbd_xfer *);
static usbd_status roothub_ctrl_start(struct usbd_xfer *);
static void roothub_ctrl_abort(struct usbd_xfer *);
static void roothub_ctrl_close(struct usbd_pipe *);
static void roothub_ctrl_done(struct usbd_xfer *);
static void roothub_noop(struct usbd_pipe *pipe);
const struct usbd_pipe_methods roothub_ctrl_methods = {
.upm_transfer = roothub_ctrl_transfer,
.upm_start = roothub_ctrl_start,
.upm_abort = roothub_ctrl_abort,
.upm_close = roothub_ctrl_close,
.upm_cleartoggle = roothub_noop,
.upm_done = roothub_ctrl_done,
};
int
usb_makestrdesc(usb_string_descriptor_t *p, int l, const char *s)
{
int i;
if (l == 0)
return 0;
p->bLength = 2 * strlen(s) + 2;
if (l == 1)
return 1;
p->bDescriptorType = UDESC_STRING;
l -= 2;
/* poor man's utf-16le conversion */
for (i = 0; s[i] && l > 1; i++, l -= 2)
USETW2(p->bString[i], 0, s[i]);
return 2 * i + 2;
}
int
usb_makelangtbl(usb_string_descriptor_t *p, int l)
{
if (l == 0)
return 0;
p->bLength = 4;
if (l == 1)
return 1;
p->bDescriptorType = UDESC_STRING;
if (l < 4)
return 2;
USETW(p->bString[0], 0x0409); /* english/US */
return 4;
}
/*
* Data structures and routines to emulate the root hub.
*/
static const usb_device_descriptor_t usbroothub_devd1 = {
.bLength = sizeof(usb_device_descriptor_t),
.bDescriptorType = UDESC_DEVICE,
.bcdUSB = {0x00, 0x01},
.bDeviceClass = UDCLASS_HUB,
.bDeviceSubClass = UDSUBCLASS_HUB,
.bDeviceProtocol = UDPROTO_FSHUB,
.bMaxPacketSize = 64,
.idVendor = {0},
.idProduct = {0},
.bcdDevice = {0x00, 0x01},
.iManufacturer = 1,
.iProduct = 2,
.iSerialNumber = 0,
.bNumConfigurations = 1
};
static const struct usb_roothub_descriptors usbroothub_confd1 = {
.urh_confd = {
.bLength = USB_CONFIG_DESCRIPTOR_SIZE,
.bDescriptorType = UDESC_CONFIG,
.wTotalLength = USETWD(sizeof(usbroothub_confd1)),
.bNumInterface = 1,
.bConfigurationValue = 1,
.iConfiguration = 0,
.bmAttributes = UC_ATTR_MBO | UC_SELF_POWERED,
.bMaxPower = 0,
},
.urh_ifcd = {
.bLength = USB_INTERFACE_DESCRIPTOR_SIZE,
.bDescriptorType = UDESC_INTERFACE,
.bInterfaceNumber = 0,
.bAlternateSetting = 0,
.bNumEndpoints = 1,
.bInterfaceClass = UICLASS_HUB,
.bInterfaceSubClass = UISUBCLASS_HUB,
.bInterfaceProtocol = UIPROTO_FSHUB,
.iInterface = 0
},
.urh_endpd = {
.bLength = USB_ENDPOINT_DESCRIPTOR_SIZE,
.bDescriptorType = UDESC_ENDPOINT,
.bEndpointAddress = UE_DIR_IN | USBROOTHUB_INTR_ENDPT,
.bmAttributes = UE_INTERRUPT,
.wMaxPacketSize = USETWD(8), /* max packet */
.bInterval = 255,
},
};
/* USB 3.0 10.15.1 */
static const usb_device_descriptor_t usbroothub_devd3 = {
.bLength = sizeof(usb_device_descriptor_t),
.bDescriptorType = UDESC_DEVICE,
.bcdUSB = {0x00, 0x03},
.bDeviceClass = UDCLASS_HUB,
.bDeviceSubClass = UDSUBCLASS_HUB,
.bDeviceProtocol = UDPROTO_SSHUB,
.bMaxPacketSize = 9,
.idVendor = {0},
.idProduct = {0},
.bcdDevice = {0x00, 0x01},
.iManufacturer = 1,
.iProduct = 2,
.iSerialNumber = 0,
.bNumConfigurations = 1
};
static const usb_device_descriptor_t usbroothub_devd2 = {
.bLength = sizeof(usb_device_descriptor_t),
.bDescriptorType = UDESC_DEVICE,
.bcdUSB = {0x00, 0x02},
.bDeviceClass = UDCLASS_HUB,
.bDeviceSubClass = UDSUBCLASS_HUB,
.bDeviceProtocol = UDPROTO_HSHUBSTT,
.bMaxPacketSize = 64,
.idVendor = {0},
.idProduct = {0},
.bcdDevice = {0x00, 0x01},
.iManufacturer = 1,
.iProduct = 2,
.iSerialNumber = 0,
.bNumConfigurations = 1
};
static const usb_device_qualifier_t usbroothub_odevd2 = {
.bLength = USB_DEVICE_QUALIFIER_SIZE,
.bDescriptorType = UDESC_DEVICE_QUALIFIER,
.bcdUSB = {0x00, 0x02},
.bDeviceClass = UDCLASS_HUB,
.bDeviceSubClass = UDSUBCLASS_HUB,
.bDeviceProtocol = UDPROTO_FSHUB,
.bMaxPacketSize0 = 64,
.bNumConfigurations = 1,
};
static const struct usb_roothub_descriptors usbroothub_confd2 = {
.urh_confd = {
.bLength = USB_CONFIG_DESCRIPTOR_SIZE,
.bDescriptorType = UDESC_CONFIG,
.wTotalLength = USETWD(sizeof(usbroothub_confd2)),
.bNumInterface = 1,
.bConfigurationValue = 1,
.iConfiguration = 0,
.bmAttributes = UC_ATTR_MBO | UC_SELF_POWERED,
.bMaxPower = 0,
},
.urh_ifcd = {
.bLength = USB_INTERFACE_DESCRIPTOR_SIZE,
.bDescriptorType = UDESC_INTERFACE,
.bInterfaceNumber = 0,
.bAlternateSetting = 0,
.bNumEndpoints = 1,
.bInterfaceClass = UICLASS_HUB,
.bInterfaceSubClass = UISUBCLASS_HUB,
.bInterfaceProtocol = UIPROTO_HSHUBSTT,
.iInterface = 0
},
.urh_endpd = {
.bLength = USB_ENDPOINT_DESCRIPTOR_SIZE,
.bDescriptorType = UDESC_ENDPOINT,
.bEndpointAddress = UE_DIR_IN | USBROOTHUB_INTR_ENDPT,
.bmAttributes = UE_INTERRUPT,
.wMaxPacketSize = USETWD(8), /* max packet */
.bInterval = 12,
},
};
static const struct usb3_roothub_descriptors usbroothub_confd3 = {
.urh_confd = {
.bLength = USB_CONFIG_DESCRIPTOR_SIZE,
.bDescriptorType = UDESC_CONFIG,
.wTotalLength = USETWD(sizeof(usbroothub_confd3)),
.bNumInterface = 1,
.bConfigurationValue = 1,
.iConfiguration = 0,
.bmAttributes = UC_SELF_POWERED, /* 10.13.1 */
.bMaxPower = 0,
},
.urh_ifcd = {
.bLength = USB_INTERFACE_DESCRIPTOR_SIZE,
.bDescriptorType = UDESC_INTERFACE,
.bInterfaceNumber = 0,
.bAlternateSetting = 0,
.bNumEndpoints = 1,
.bInterfaceClass = UICLASS_HUB,
.bInterfaceSubClass = UISUBCLASS_HUB,
.bInterfaceProtocol = 0, /* UIPROTO_SSHUB ??? */
.iInterface = 0
},
.urh_endpd = {
.bLength = USB_ENDPOINT_DESCRIPTOR_SIZE,
.bDescriptorType = UDESC_ENDPOINT,
.bEndpointAddress = UE_DIR_IN | USBROOTHUB_INTR_ENDPT,
.bmAttributes = UE_INTERRUPT,
.wMaxPacketSize = USETWD(2), /* max packet */
.bInterval = 8,
},
.urh_endpssd = {
.bLength = USB_ENDPOINT_SS_COMP_DESCRIPTOR_SIZE,
.bDescriptorType = UDESC_ENDPOINT_SS_COMP,
.bMaxBurst = 0,
.bmAttributes = 0,
.wBytesPerInterval = USETWD(2)
},
};
static const struct usb3_roothub_bos_descriptors usbroothub_bosd3 = {
.urh_bosd = {
.bLength = USB_BOS_DESCRIPTOR_SIZE,
.bDescriptorType = UDESC_BOS,
.wTotalLength = USETWD(sizeof(usbroothub_bosd3)),
.bNumDeviceCaps = 3,
},
/* 9.6.2.1 USB 2.0 Extension */
.urh_usb2extd = {
.bLength = USB_DEVCAP_USB2EXT_DESCRIPTOR_SIZE,
.bDescriptorType = 1,
.bDevCapabilityType = 2,
.bmAttributes[0] = 2,
},
/* 9.6.2.2 Superspeed device capability */
.urh_ssd = {
.bLength = USB_DEVCAP_SS_DESCRIPTOR_SIZE,
.bDescriptorType = UDESC_DEVICE_CAPABILITY,
.bDevCapabilityType = USB_DEVCAP_SUPER_SPEED,
.bmAttributes = 0, /* USB_DEVCAP_SS_LTM */
.wSpeedsSupported = USETWD(
USB_DEVCAP_SS_SPEED_LS | USB_DEVCAP_SS_SPEED_FS |
USB_DEVCAP_SS_SPEED_HS | USB_DEVCAP_SS_SPEED_SS),
.bFunctionalitySupport = 8, /* SS is 3, i.e. 1 << 3? */
.bU1DevExitLat = 255, /* Dummy... 0? */
.wU2DevExitLat = USETWD(8), /* Also dummy... 0? */
},
/* 9.6.2.3 Container ID - see RFC 4122 */
.urh_containerd = {
.bLength = USB_DEVCAP_CONTAINER_ID_DESCRIPTOR_SIZE,
.bDescriptorType = 1,
.bDevCapabilityType = 4,
.bReserved = 0,
// ContainerID will be zero
},
};
static const usb_hub_descriptor_t usbroothub_hubd = {
.bDescLength = USB_HUB_DESCRIPTOR_SIZE,
.bDescriptorType = UDESC_HUB,
.bNbrPorts = 1,
.wHubCharacteristics = USETWD(UHD_PWR_NO_SWITCH | UHD_OC_INDIVIDUAL),
.bPwrOn2PwrGood = 50,
.bHubContrCurrent = 0,
.DeviceRemovable = {0}, /* port is removable */
};
/*
* Simulate a hardware hub by handling all the necessary requests.
*/
usbd_status
roothub_ctrl_transfer(struct usbd_xfer *xfer)
{
/* Pipe isn't running, start first */
return roothub_ctrl_start(SIMPLEQ_FIRST(&xfer->ux_pipe->up_queue));
}
static usbd_status
roothub_ctrl_start(struct usbd_xfer *xfer)
{
struct usbd_pipe *pipe = xfer->ux_pipe;
struct usbd_bus *bus = pipe->up_dev->ud_bus;
usb_device_request_t *req;
usbd_status err = USBD_IOERROR; /* XXX STALL? */
uint16_t len, value;
int buflen, actlen = -1;
void *buf;
USBHIST_FUNC();
/*
* XXX Should really assert pipe lock, in case ever have
* per-pipe locking instead of using the bus lock for all
* pipes.
*/
KASSERT(bus->ub_usepolling || mutex_owned(bus->ub_lock));
/* Roothub xfers are serialized through the pipe. */
KASSERTMSG(bus->ub_rhxfer == NULL, "rhxfer=%p", bus->ub_rhxfer);
KASSERT(xfer->ux_rqflags & URQ_REQUEST);
req = &xfer->ux_request;
len = UGETW(req->wLength);
value = UGETW(req->wValue);
USBHIST_CALLARGS(usbdebug, "type=%#jx request=%#jx len=%#jx value=%#jx",
req->bmRequestType, req->bRequest, len, value);
buf = len ? usbd_get_buffer(xfer) : NULL;
buflen = 0;
#define C(x,y) ((x) | ((y) << 8))
switch (C(req->bRequest, req->bmRequestType)) {
case C(UR_CLEAR_FEATURE, UT_WRITE_DEVICE):
case C(UR_CLEAR_FEATURE, UT_WRITE_INTERFACE):
case C(UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT):
/*
* DEVICE_REMOTE_WAKEUP and ENDPOINT_HALT are no-ops
* for the integrated root hub.
*/
break;
case C(UR_GET_CONFIG, UT_READ_DEVICE):
if (len > 0) {
uint8_t *out = buf;
*out = bus->ub_rhconf;
buflen = sizeof(*out);
}
break;
case C(UR_GET_DESCRIPTOR, UT_READ_DEVICE):
USBHIST_LOG(usbdebug, "wValue=%#4jx", value, 0, 0, 0);
if (len == 0)
break;
switch (value) {
case C(0, UDESC_DEVICE):
if (bus->ub_revision >= USBREV_3_0) {
buflen = uimin(len, sizeof(usbroothub_devd3));
memcpy(buf, &usbroothub_devd3, buflen);
} else if (bus->ub_revision == USBREV_2_0) {
buflen = uimin(len, sizeof(usbroothub_devd2));
memcpy(buf, &usbroothub_devd2, buflen);
} else {
buflen = uimin(len, sizeof(usbroothub_devd1));
memcpy(buf, &usbroothub_devd1, buflen);
}
break;
case C(0, UDESC_CONFIG):
if (bus->ub_revision >= USBREV_3_0) {
buflen = uimin(len, sizeof(usbroothub_confd3));
memcpy(buf, &usbroothub_confd3, buflen);
} else if (bus->ub_revision == USBREV_2_0) {
buflen = uimin(len, sizeof(usbroothub_confd2));
memcpy(buf, &usbroothub_confd2, buflen);
} else {
buflen = uimin(len, sizeof(usbroothub_confd1));
memcpy(buf, &usbroothub_confd1, buflen);
}
break;
case C(0, UDESC_DEVICE_QUALIFIER):
if (bus->ub_revision == USBREV_2_0) {
/*
* We can't really operate at another speed,
* but the spec says we need this descriptor.
*/
buflen = uimin(len, sizeof(usbroothub_odevd2));
memcpy(buf, &usbroothub_odevd2, buflen);
} else
goto fail;
break;
case C(0, UDESC_OTHER_SPEED_CONFIGURATION):
if (bus->ub_revision == USBREV_2_0) {
struct usb_roothub_descriptors confd;
/*
* We can't really operate at another speed,
* but the spec says we need this descriptor.
*/
buflen = uimin(len, sizeof(usbroothub_confd2));
memcpy(&confd, &usbroothub_confd2, buflen);
confd.urh_confd.bDescriptorType =
UDESC_OTHER_SPEED_CONFIGURATION;
memcpy(buf, &confd, buflen);
} else
goto fail;
break;
case C(0, UDESC_BOS):
if (bus->ub_revision >= USBREV_3_0) {
buflen = uimin(len, sizeof(usbroothub_bosd3));
memcpy(buf, &usbroothub_bosd3, buflen);
} else
goto fail;
break;
#define sd ((usb_string_descriptor_t *)buf)
case C(0, UDESC_STRING):
/* Language table */
buflen = usb_makelangtbl(sd, len);
break;
case C(1, UDESC_STRING):
/* Vendor */
buflen = usb_makestrdesc(sd, len, ostype);
break;
case C(2, UDESC_STRING):
/* Product */
buflen = usb_makestrdesc(sd, len, "Root hub");
break;
#undef sd
default:
/* Default to error */
buflen = -1;
}
break;
case C(UR_GET_DESCRIPTOR, UT_READ_CLASS_DEVICE):
buflen = uimin(len, sizeof(usbroothub_hubd));
memcpy(buf, &usbroothub_hubd, buflen);
break;
case C(UR_GET_INTERFACE, UT_READ_INTERFACE):
/* Get Interface, 9.4.4 */
if (len > 0) {
uint8_t *out = buf;
*out = 0;
buflen = sizeof(*out);
}
break;
case C(UR_GET_STATUS, UT_READ_DEVICE):
/* Get Status from device, 9.4.5 */
if (len > 1) {
usb_status_t *out = buf;
USETW(out->wStatus, UDS_SELF_POWERED);
buflen = sizeof(*out);
}
break;
case C(UR_GET_STATUS, UT_READ_INTERFACE):
case C(UR_GET_STATUS, UT_READ_ENDPOINT):
/* Get Status from interface, endpoint, 9.4.5 */
if (len > 1) {
usb_status_t *out = buf;
USETW(out->wStatus, 0);
buflen = sizeof(*out);
}
break;
case C(UR_SET_ADDRESS, UT_WRITE_DEVICE):
/* Set Address, 9.4.6 */
USBHIST_LOG(usbdebug, "UR_SET_ADDRESS, UT_WRITE_DEVICE: "
"addr %jd", value, 0, 0, 0);
if (value >= USB_MAX_DEVICES) {
goto fail;
}
bus->ub_rhaddr = value;
break;
case C(UR_SET_CONFIG, UT_WRITE_DEVICE):
/* Set Configuration, 9.4.7 */
if (value != 0 && value != 1) {
goto fail;
}
bus->ub_rhconf = value;
break;
case C(UR_SET_DESCRIPTOR, UT_WRITE_DEVICE):
/* Set Descriptor, 9.4.8, not supported */
break;
case C(UR_SET_FEATURE, UT_WRITE_DEVICE):
case C(UR_SET_FEATURE, UT_WRITE_INTERFACE):
case C(UR_SET_FEATURE, UT_WRITE_ENDPOINT):
/* Set Feature, 9.4.9, not supported */
goto fail;
case C(UR_SET_INTERFACE, UT_WRITE_INTERFACE):
/* Set Interface, 9.4.10, not supported */
break;
case C(UR_SYNCH_FRAME, UT_WRITE_ENDPOINT):
/* Synch Frame, 9.4.11, not supported */
break;
default:
/* Default to error */
buflen = -1;
break;
}
KASSERTMSG(bus->ub_rhxfer == NULL, "rhxfer=%p", bus->ub_rhxfer);
bus->ub_rhxfer = xfer;
if (!bus->ub_usepolling)
mutex_exit(bus->ub_lock);
actlen = bus->ub_methods->ubm_rhctrl(bus, req, buf, buflen);
if (!bus->ub_usepolling)
mutex_enter(bus->ub_lock);
KASSERTMSG(bus->ub_rhxfer == xfer, "rhxfer=%p", bus->ub_rhxfer);
bus->ub_rhxfer = NULL;
cv_signal(&bus->ub_rhxfercv);
if (actlen < 0)
goto fail;
xfer->ux_actlen = actlen;
err = USBD_NORMAL_COMPLETION;
fail:
USBHIST_LOG(usbdebug, "xfer %#jx buflen %jd actlen %jd err %jd",
(uintptr_t)xfer, buflen, actlen, err);
xfer->ux_status = err;
usb_transfer_complete(xfer);
return USBD_NORMAL_COMPLETION;
}
/* Abort a root control request. */
Static void
roothub_ctrl_abort(struct usbd_xfer *xfer)
{
struct usbd_bus *bus = xfer->ux_bus;
KASSERT(mutex_owned(bus->ub_lock));
KASSERTMSG(bus->ub_rhxfer == xfer, "rhxfer=%p", bus->ub_rhxfer);
/*
* No mechanism to abort the xfer (would have to coordinate
* with the bus's ubm_rhctrl to be useful, and usually at most
* there's some short bounded delays of a few tens of
* milliseconds), so just wait for it to complete.
*/
while (bus->ub_rhxfer == xfer)
cv_wait(&bus->ub_rhxfercv, bus->ub_lock);
}
/* Close the root pipe. */
Static void
roothub_ctrl_close(struct usbd_pipe *pipe)
{
/* Nothing to do. */
}
Static void
roothub_ctrl_done(struct usbd_xfer *xfer)
{
/* Nothing to do. */
}
static void
roothub_noop(struct usbd_pipe *pipe)
{
}