freebsd-dev/sys/dev/usb/template/usb_template.c
Edward Tomasz Napierala 63722e5212 Add cdceem(4) driver, for virtual ethernet devices compliant
with Communication Device Class Ethernet Emulation Model (CDC EEM).
The driver supports both the device, and host side operation; there
is a new USB template (#11) for the former.

This enables communication with virtual USB NIC provided by iLO 5,
as found in new HPE Proliant servers.

Reviewed by:	hselasky
MFC after:	2 weeks
Relnotes:	yes
Sponsored by:	Hewlett Packard Enterprise
2019-08-07 18:14:45 +00:00

1488 lines
38 KiB
C

/* $FreeBSD$ */
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2007 Hans Petter Selasky. 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 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 AUTHOR 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.
*/
/*
* This file contains sub-routines to build up USB descriptors from
* USB templates.
*/
#ifdef USB_GLOBAL_INCLUDE_FILE
#include USB_GLOBAL_INCLUDE_FILE
#else
#include <sys/stdint.h>
#include <sys/stddef.h>
#include <sys/param.h>
#include <sys/queue.h>
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/bus.h>
#include <sys/module.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/condvar.h>
#include <sys/sysctl.h>
#include <sys/sx.h>
#include <sys/unistd.h>
#include <sys/callout.h>
#include <sys/malloc.h>
#include <sys/priv.h>
#include <dev/usb/usb.h>
#include <dev/usb/usb_ioctl.h>
#include <dev/usb/usbdi.h>
#include <dev/usb/usbdi_util.h>
#include "usbdevs.h"
#include <dev/usb/usb_cdc.h>
#include <dev/usb/usb_core.h>
#include <dev/usb/usb_dynamic.h>
#include <dev/usb/usb_busdma.h>
#include <dev/usb/usb_process.h>
#include <dev/usb/usb_device.h>
#include <dev/usb/usb_util.h>
#define USB_DEBUG_VAR usb_debug
#include <dev/usb/usb_debug.h>
#include <dev/usb/usb_controller.h>
#include <dev/usb/usb_bus.h>
#include <dev/usb/usb_request.h>
#include <dev/usb/template/usb_template.h>
#endif /* USB_GLOBAL_INCLUDE_FILE */
MODULE_DEPEND(usb_template, usb, 1, 1, 1);
MODULE_VERSION(usb_template, 1);
/* function prototypes */
static int sysctl_hw_usb_template_power(SYSCTL_HANDLER_ARGS);
static void usb_make_raw_desc(struct usb_temp_setup *, const uint8_t *);
static void usb_make_endpoint_desc(struct usb_temp_setup *,
const struct usb_temp_endpoint_desc *);
static void usb_make_interface_desc(struct usb_temp_setup *,
const struct usb_temp_interface_desc *);
static void usb_make_config_desc(struct usb_temp_setup *,
const struct usb_temp_config_desc *);
static void usb_make_device_desc(struct usb_temp_setup *,
const struct usb_temp_device_desc *);
static uint8_t usb_hw_ep_match(const struct usb_hw_ep_profile *, uint8_t,
uint8_t);
static uint8_t usb_hw_ep_find_match(struct usb_hw_ep_scratch *,
struct usb_hw_ep_scratch_sub *, uint8_t);
static uint8_t usb_hw_ep_get_needs(struct usb_hw_ep_scratch *, uint8_t,
uint8_t);
static usb_error_t usb_hw_ep_resolve(struct usb_device *,
struct usb_descriptor *);
static const struct usb_temp_device_desc *usb_temp_get_tdd(struct usb_device *);
static void *usb_temp_get_device_desc(struct usb_device *);
static void *usb_temp_get_qualifier_desc(struct usb_device *);
static void *usb_temp_get_config_desc(struct usb_device *, uint16_t *,
uint8_t);
static const void *usb_temp_get_string_desc(struct usb_device *, uint16_t,
uint8_t);
static const void *usb_temp_get_vendor_desc(struct usb_device *,
const struct usb_device_request *, uint16_t *plen);
static const void *usb_temp_get_hub_desc(struct usb_device *);
static usb_error_t usb_temp_get_desc(struct usb_device *,
struct usb_device_request *, const void **, uint16_t *);
static usb_error_t usb_temp_setup_by_index(struct usb_device *,
uint16_t index);
static void usb_temp_init(void *);
SYSCTL_NODE(_hw_usb, OID_AUTO, templates, CTLFLAG_RW, 0,
"USB device side templates");
SYSCTL_PROC(_hw_usb, OID_AUTO, template_power,
CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
NULL, 0, sysctl_hw_usb_template_power,
"I", "USB bus power consumption in mA at 5V");
static int usb_template_power = 500; /* 500mA */
static int
sysctl_hw_usb_template_power(SYSCTL_HANDLER_ARGS)
{
int error, val;
val = usb_template_power;
error = sysctl_handle_int(oidp, &val, 0, req);
if (error != 0 || req->newptr == NULL)
return (error);
if (val < 0 || val > 500)
return (EINVAL);
usb_template_power = val;
return (0);
}
/*------------------------------------------------------------------------*
* usb_decode_str_desc
*
* Helper function to decode string descriptors into a C string.
*------------------------------------------------------------------------*/
void
usb_decode_str_desc(struct usb_string_descriptor *sd, char *buf, size_t buflen)
{
size_t i;
if (sd->bLength < 2) {
buf[0] = '\0';
return;
}
for (i = 0; i < buflen - 1 && i < (sd->bLength / 2) - 1; i++)
buf[i] = UGETW(sd->bString[i]);
buf[i] = '\0';
}
/*------------------------------------------------------------------------*
* usb_temp_sysctl
*
* Callback for SYSCTL_PROC(9), to set and retrieve template string
* descriptors.
*------------------------------------------------------------------------*/
int
usb_temp_sysctl(SYSCTL_HANDLER_ARGS)
{
char buf[128];
struct usb_string_descriptor *sd = arg1;
size_t len, sdlen = arg2;
int error;
usb_decode_str_desc(sd, buf, sizeof(buf));
error = sysctl_handle_string(oidp, buf, sizeof(buf), req);
if (error != 0 || req->newptr == NULL)
return (error);
len = usb_make_str_desc(sd, sdlen, buf);
if (len == 0)
return (EINVAL);
return (0);
}
/*------------------------------------------------------------------------*
* usb_make_raw_desc
*
* This function will insert a raw USB descriptor into the generated
* USB configuration.
*------------------------------------------------------------------------*/
static void
usb_make_raw_desc(struct usb_temp_setup *temp,
const uint8_t *raw)
{
void *dst;
uint8_t len;
/*
* The first byte of any USB descriptor gives the length.
*/
if (raw) {
len = raw[0];
if (temp->buf) {
dst = USB_ADD_BYTES(temp->buf, temp->size);
memcpy(dst, raw, len);
/* check if we have got a CDC union descriptor */
if ((raw[0] == sizeof(struct usb_cdc_union_descriptor)) &&
(raw[1] == UDESC_CS_INTERFACE) &&
(raw[2] == UDESCSUB_CDC_UNION)) {
struct usb_cdc_union_descriptor *ud = (void *)dst;
/* update the interface numbers */
ud->bMasterInterface +=
temp->bInterfaceNumber;
ud->bSlaveInterface[0] +=
temp->bInterfaceNumber;
}
/* check if we have got an interface association descriptor */
if ((raw[0] == sizeof(struct usb_interface_assoc_descriptor)) &&
(raw[1] == UDESC_IFACE_ASSOC)) {
struct usb_interface_assoc_descriptor *iad = (void *)dst;
/* update the interface number */
iad->bFirstInterface +=
temp->bInterfaceNumber;
}
/* check if we have got a call management descriptor */
if ((raw[0] == sizeof(struct usb_cdc_cm_descriptor)) &&
(raw[1] == UDESC_CS_INTERFACE) &&
(raw[2] == UDESCSUB_CDC_CM)) {
struct usb_cdc_cm_descriptor *ccd = (void *)dst;
/* update the interface number */
ccd->bDataInterface +=
temp->bInterfaceNumber;
}
}
temp->size += len;
}
}
/*------------------------------------------------------------------------*
* usb_make_endpoint_desc
*
* This function will generate an USB endpoint descriptor from the
* given USB template endpoint descriptor, which will be inserted into
* the USB configuration.
*------------------------------------------------------------------------*/
static void
usb_make_endpoint_desc(struct usb_temp_setup *temp,
const struct usb_temp_endpoint_desc *ted)
{
struct usb_endpoint_descriptor *ed;
const void **rd;
uint16_t old_size;
uint16_t mps;
uint8_t ea; /* Endpoint Address */
uint8_t et; /* Endpiont Type */
/* Reserve memory */
old_size = temp->size;
ea = (ted->bEndpointAddress & (UE_ADDR | UE_DIR_IN | UE_DIR_OUT));
et = (ted->bmAttributes & UE_XFERTYPE);
if (et == UE_ISOCHRONOUS) {
/* account for extra byte fields */
temp->size += sizeof(*ed) + 2;
} else {
temp->size += sizeof(*ed);
}
/* Scan all Raw Descriptors first */
rd = ted->ppRawDesc;
if (rd) {
while (*rd) {
usb_make_raw_desc(temp, *rd);
rd++;
}
}
if (ted->pPacketSize == NULL) {
/* not initialized */
temp->err = USB_ERR_INVAL;
return;
}
mps = ted->pPacketSize->mps[temp->usb_speed];
if (mps == 0) {
/* not initialized */
temp->err = USB_ERR_INVAL;
return;
} else if (mps == UE_ZERO_MPS) {
/* escape for Zero Max Packet Size */
mps = 0;
}
/*
* Fill out the real USB endpoint descriptor
* in case there is a buffer present:
*/
if (temp->buf) {
ed = USB_ADD_BYTES(temp->buf, old_size);
if (et == UE_ISOCHRONOUS)
ed->bLength = sizeof(*ed) + 2;
else
ed->bLength = sizeof(*ed);
ed->bDescriptorType = UDESC_ENDPOINT;
ed->bEndpointAddress = ea;
ed->bmAttributes = ted->bmAttributes;
USETW(ed->wMaxPacketSize, mps);
/* setup bInterval parameter */
if (ted->pIntervals &&
ted->pIntervals->bInterval[temp->usb_speed]) {
ed->bInterval =
ted->pIntervals->bInterval[temp->usb_speed];
} else {
switch (et) {
case UE_BULK:
case UE_CONTROL:
ed->bInterval = 0; /* not used */
break;
case UE_INTERRUPT:
switch (temp->usb_speed) {
case USB_SPEED_LOW:
case USB_SPEED_FULL:
ed->bInterval = 1; /* 1 ms */
break;
default:
ed->bInterval = 4; /* 1 ms */
break;
}
break;
default: /* UE_ISOCHRONOUS */
switch (temp->usb_speed) {
case USB_SPEED_LOW:
case USB_SPEED_FULL:
ed->bInterval = 1; /* 1 ms */
break;
default:
ed->bInterval = 1; /* 125 us */
break;
}
break;
}
}
}
temp->bNumEndpoints++;
}
/*------------------------------------------------------------------------*
* usb_make_interface_desc
*
* This function will generate an USB interface descriptor from the
* given USB template interface descriptor, which will be inserted
* into the USB configuration.
*------------------------------------------------------------------------*/
static void
usb_make_interface_desc(struct usb_temp_setup *temp,
const struct usb_temp_interface_desc *tid)
{
struct usb_interface_descriptor *id;
const struct usb_temp_endpoint_desc **ted;
const void **rd;
uint16_t old_size;
/* Reserve memory */
old_size = temp->size;
temp->size += sizeof(*id);
/* Update interface and alternate interface numbers */
if (tid->isAltInterface == 0) {
temp->bAlternateSetting = 0;
temp->bInterfaceNumber++;
} else {
temp->bAlternateSetting++;
}
/* Scan all Raw Descriptors first */
rd = tid->ppRawDesc;
if (rd) {
while (*rd) {
usb_make_raw_desc(temp, *rd);
rd++;
}
}
/* Reset some counters */
temp->bNumEndpoints = 0;
/* Scan all Endpoint Descriptors second */
ted = tid->ppEndpoints;
if (ted) {
while (*ted) {
usb_make_endpoint_desc(temp, *ted);
ted++;
}
}
/*
* Fill out the real USB interface descriptor
* in case there is a buffer present:
*/
if (temp->buf) {
id = USB_ADD_BYTES(temp->buf, old_size);
id->bLength = sizeof(*id);
id->bDescriptorType = UDESC_INTERFACE;
id->bInterfaceNumber = temp->bInterfaceNumber;
id->bAlternateSetting = temp->bAlternateSetting;
id->bNumEndpoints = temp->bNumEndpoints;
id->bInterfaceClass = tid->bInterfaceClass;
id->bInterfaceSubClass = tid->bInterfaceSubClass;
id->bInterfaceProtocol = tid->bInterfaceProtocol;
id->iInterface = tid->iInterface;
}
}
/*------------------------------------------------------------------------*
* usb_make_config_desc
*
* This function will generate an USB config descriptor from the given
* USB template config descriptor, which will be inserted into the USB
* configuration.
*------------------------------------------------------------------------*/
static void
usb_make_config_desc(struct usb_temp_setup *temp,
const struct usb_temp_config_desc *tcd)
{
struct usb_config_descriptor *cd;
const struct usb_temp_interface_desc **tid;
uint16_t old_size;
int power;
/* Reserve memory */
old_size = temp->size;
temp->size += sizeof(*cd);
/* Reset some counters */
temp->bInterfaceNumber = 0xFF;
temp->bAlternateSetting = 0;
/* Scan all the USB interfaces */
tid = tcd->ppIfaceDesc;
if (tid) {
while (*tid) {
usb_make_interface_desc(temp, *tid);
tid++;
}
}
/*
* Fill out the real USB config descriptor
* in case there is a buffer present:
*/
if (temp->buf) {
cd = USB_ADD_BYTES(temp->buf, old_size);
/* compute total size */
old_size = temp->size - old_size;
cd->bLength = sizeof(*cd);
cd->bDescriptorType = UDESC_CONFIG;
USETW(cd->wTotalLength, old_size);
cd->bNumInterface = temp->bInterfaceNumber + 1;
cd->bConfigurationValue = temp->bConfigurationValue;
cd->iConfiguration = tcd->iConfiguration;
cd->bmAttributes = tcd->bmAttributes;
power = usb_template_power;
cd->bMaxPower = power / 2; /* 2 mA units */
cd->bmAttributes |= UC_REMOTE_WAKEUP;
if (power > 0) {
cd->bmAttributes |= UC_BUS_POWERED;
cd->bmAttributes &= ~UC_SELF_POWERED;
} else {
cd->bmAttributes &= ~UC_BUS_POWERED;
cd->bmAttributes |= UC_SELF_POWERED;
}
}
}
/*------------------------------------------------------------------------*
* usb_make_device_desc
*
* This function will generate an USB device descriptor from the
* given USB template device descriptor.
*------------------------------------------------------------------------*/
static void
usb_make_device_desc(struct usb_temp_setup *temp,
const struct usb_temp_device_desc *tdd)
{
struct usb_temp_data *utd;
const struct usb_temp_config_desc **tcd;
uint16_t old_size;
/* Reserve memory */
old_size = temp->size;
temp->size += sizeof(*utd);
/* Scan all the USB configs */
temp->bConfigurationValue = 1;
tcd = tdd->ppConfigDesc;
if (tcd) {
while (*tcd) {
usb_make_config_desc(temp, *tcd);
temp->bConfigurationValue++;
tcd++;
}
}
/*
* Fill out the real USB device descriptor
* in case there is a buffer present:
*/
if (temp->buf) {
utd = USB_ADD_BYTES(temp->buf, old_size);
/* Store a pointer to our template device descriptor */
utd->tdd = tdd;
/* Fill out USB device descriptor */
utd->udd.bLength = sizeof(utd->udd);
utd->udd.bDescriptorType = UDESC_DEVICE;
utd->udd.bDeviceClass = tdd->bDeviceClass;
utd->udd.bDeviceSubClass = tdd->bDeviceSubClass;
utd->udd.bDeviceProtocol = tdd->bDeviceProtocol;
USETW(utd->udd.idVendor, tdd->idVendor);
USETW(utd->udd.idProduct, tdd->idProduct);
USETW(utd->udd.bcdDevice, tdd->bcdDevice);
utd->udd.iManufacturer = tdd->iManufacturer;
utd->udd.iProduct = tdd->iProduct;
utd->udd.iSerialNumber = tdd->iSerialNumber;
utd->udd.bNumConfigurations = temp->bConfigurationValue - 1;
/*
* Fill out the USB device qualifier. Pretend that we
* don't support any other speeds by setting
* "bNumConfigurations" equal to zero. That saves us
* generating an extra set of configuration
* descriptors.
*/
utd->udq.bLength = sizeof(utd->udq);
utd->udq.bDescriptorType = UDESC_DEVICE_QUALIFIER;
utd->udq.bDeviceClass = tdd->bDeviceClass;
utd->udq.bDeviceSubClass = tdd->bDeviceSubClass;
utd->udq.bDeviceProtocol = tdd->bDeviceProtocol;
utd->udq.bNumConfigurations = 0;
USETW(utd->udq.bcdUSB, 0x0200);
utd->udq.bMaxPacketSize0 = 0;
switch (temp->usb_speed) {
case USB_SPEED_LOW:
USETW(utd->udd.bcdUSB, 0x0110);
utd->udd.bMaxPacketSize = 8;
break;
case USB_SPEED_FULL:
USETW(utd->udd.bcdUSB, 0x0110);
utd->udd.bMaxPacketSize = 32;
break;
case USB_SPEED_HIGH:
USETW(utd->udd.bcdUSB, 0x0200);
utd->udd.bMaxPacketSize = 64;
break;
case USB_SPEED_VARIABLE:
USETW(utd->udd.bcdUSB, 0x0250);
utd->udd.bMaxPacketSize = 255; /* 512 bytes */
break;
case USB_SPEED_SUPER:
USETW(utd->udd.bcdUSB, 0x0300);
utd->udd.bMaxPacketSize = 9; /* 2**9 = 512 bytes */
break;
default:
temp->err = USB_ERR_INVAL;
break;
}
}
}
/*------------------------------------------------------------------------*
* usb_hw_ep_match
*
* Return values:
* 0: The endpoint profile does not match the criteria
* Else: The endpoint profile matches the criteria
*------------------------------------------------------------------------*/
static uint8_t
usb_hw_ep_match(const struct usb_hw_ep_profile *pf,
uint8_t ep_type, uint8_t ep_dir_in)
{
if (ep_type == UE_CONTROL) {
/* special */
return (pf->support_control);
}
if ((pf->support_in && ep_dir_in) ||
(pf->support_out && !ep_dir_in)) {
if ((pf->support_interrupt && (ep_type == UE_INTERRUPT)) ||
(pf->support_isochronous && (ep_type == UE_ISOCHRONOUS)) ||
(pf->support_bulk && (ep_type == UE_BULK))) {
return (1);
}
}
return (0);
}
/*------------------------------------------------------------------------*
* usb_hw_ep_find_match
*
* This function is used to find the best matching endpoint profile
* for and endpoint belonging to an USB descriptor.
*
* Return values:
* 0: Success. Got a match.
* Else: Failure. No match.
*------------------------------------------------------------------------*/
static uint8_t
usb_hw_ep_find_match(struct usb_hw_ep_scratch *ues,
struct usb_hw_ep_scratch_sub *ep, uint8_t is_simplex)
{
const struct usb_hw_ep_profile *pf;
uint16_t distance;
uint16_t temp;
uint16_t max_frame_size;
uint8_t n;
uint8_t best_n;
uint8_t dir_in;
uint8_t dir_out;
distance = 0xFFFF;
best_n = 0;
if ((!ep->needs_in) && (!ep->needs_out)) {
return (0); /* we are done */
}
if (ep->needs_ep_type == UE_CONTROL) {
dir_in = 1;
dir_out = 1;
} else {
if (ep->needs_in) {
dir_in = 1;
dir_out = 0;
} else {
dir_in = 0;
dir_out = 1;
}
}
for (n = 1; n != (USB_EP_MAX / 2); n++) {
/* get HW endpoint profile */
(ues->methods->get_hw_ep_profile) (ues->udev, &pf, n);
if (pf == NULL) {
/* end of profiles */
break;
}
/* check if IN-endpoint is reserved */
if (dir_in || pf->is_simplex) {
if (ues->bmInAlloc[n / 8] & (1 << (n % 8))) {
/* mismatch */
continue;
}
}
/* check if OUT-endpoint is reserved */
if (dir_out || pf->is_simplex) {
if (ues->bmOutAlloc[n / 8] & (1 << (n % 8))) {
/* mismatch */
continue;
}
}
/* check simplex */
if (pf->is_simplex == is_simplex) {
/* mismatch */
continue;
}
/* check if HW endpoint matches */
if (!usb_hw_ep_match(pf, ep->needs_ep_type, dir_in)) {
/* mismatch */
continue;
}
/* get maximum frame size */
if (dir_in)
max_frame_size = pf->max_in_frame_size;
else
max_frame_size = pf->max_out_frame_size;
/* check if we have a matching profile */
if (max_frame_size >= ep->max_frame_size) {
temp = (max_frame_size - ep->max_frame_size);
if (distance > temp) {
distance = temp;
best_n = n;
ep->pf = pf;
}
}
}
/* see if we got a match */
if (best_n != 0) {
/* get the correct profile */
pf = ep->pf;
/* reserve IN-endpoint */
if (dir_in) {
ues->bmInAlloc[best_n / 8] |=
(1 << (best_n % 8));
ep->hw_endpoint_in = best_n | UE_DIR_IN;
ep->needs_in = 0;
}
/* reserve OUT-endpoint */
if (dir_out) {
ues->bmOutAlloc[best_n / 8] |=
(1 << (best_n % 8));
ep->hw_endpoint_out = best_n | UE_DIR_OUT;
ep->needs_out = 0;
}
return (0); /* got a match */
}
return (1); /* failure */
}
/*------------------------------------------------------------------------*
* usb_hw_ep_get_needs
*
* This function will figure out the type and number of endpoints
* which are needed for an USB configuration.
*
* Return values:
* 0: Success.
* Else: Failure.
*------------------------------------------------------------------------*/
static uint8_t
usb_hw_ep_get_needs(struct usb_hw_ep_scratch *ues,
uint8_t ep_type, uint8_t is_complete)
{
const struct usb_hw_ep_profile *pf;
struct usb_hw_ep_scratch_sub *ep_iface;
struct usb_hw_ep_scratch_sub *ep_curr;
struct usb_hw_ep_scratch_sub *ep_max;
struct usb_hw_ep_scratch_sub *ep_end;
struct usb_descriptor *desc;
struct usb_interface_descriptor *id;
struct usb_endpoint_descriptor *ed;
enum usb_dev_speed speed;
uint16_t wMaxPacketSize;
uint16_t temp;
uint8_t ep_no;
ep_iface = ues->ep_max;
ep_curr = ues->ep_max;
ep_end = ues->ep + USB_EP_MAX;
ep_max = ues->ep_max;
desc = NULL;
speed = usbd_get_speed(ues->udev);
repeat:
while ((desc = usb_desc_foreach(ues->cd, desc))) {
if ((desc->bDescriptorType == UDESC_INTERFACE) &&
(desc->bLength >= sizeof(*id))) {
id = (void *)desc;
if (id->bAlternateSetting == 0) {
/* going forward */
ep_iface = ep_max;
} else {
/* reset */
ep_curr = ep_iface;
}
}
if ((desc->bDescriptorType == UDESC_ENDPOINT) &&
(desc->bLength >= sizeof(*ed))) {
ed = (void *)desc;
goto handle_endpoint_desc;
}
}
ues->ep_max = ep_max;
return (0);
handle_endpoint_desc:
temp = (ed->bmAttributes & UE_XFERTYPE);
if (temp == ep_type) {
if (ep_curr == ep_end) {
/* too many endpoints */
return (1); /* failure */
}
wMaxPacketSize = UGETW(ed->wMaxPacketSize);
if ((wMaxPacketSize & 0xF800) &&
(speed == USB_SPEED_HIGH)) {
/* handle packet multiplier */
temp = (wMaxPacketSize >> 11) & 3;
wMaxPacketSize &= 0x7FF;
if (temp == 1) {
wMaxPacketSize *= 2;
} else {
wMaxPacketSize *= 3;
}
}
/*
* Check if we have a fixed endpoint number, else the
* endpoint number is allocated dynamically:
*/
ep_no = (ed->bEndpointAddress & UE_ADDR);
if (ep_no != 0) {
/* get HW endpoint profile */
(ues->methods->get_hw_ep_profile)
(ues->udev, &pf, ep_no);
if (pf == NULL) {
/* HW profile does not exist - failure */
DPRINTFN(0, "Endpoint profile %u "
"does not exist\n", ep_no);
return (1);
}
/* reserve fixed endpoint number */
if (ep_type == UE_CONTROL) {
ues->bmInAlloc[ep_no / 8] |=
(1 << (ep_no % 8));
ues->bmOutAlloc[ep_no / 8] |=
(1 << (ep_no % 8));
if ((pf->max_in_frame_size < wMaxPacketSize) ||
(pf->max_out_frame_size < wMaxPacketSize)) {
DPRINTFN(0, "Endpoint profile %u "
"has too small buffer\n", ep_no);
return (1);
}
} else if (ed->bEndpointAddress & UE_DIR_IN) {
ues->bmInAlloc[ep_no / 8] |=
(1 << (ep_no % 8));
if (pf->max_in_frame_size < wMaxPacketSize) {
DPRINTFN(0, "Endpoint profile %u "
"has too small buffer\n", ep_no);
return (1);
}
} else {
ues->bmOutAlloc[ep_no / 8] |=
(1 << (ep_no % 8));
if (pf->max_out_frame_size < wMaxPacketSize) {
DPRINTFN(0, "Endpoint profile %u "
"has too small buffer\n", ep_no);
return (1);
}
}
} else if (is_complete) {
/* check if we have enough buffer space */
if (wMaxPacketSize >
ep_curr->max_frame_size) {
return (1); /* failure */
}
if (ed->bEndpointAddress & UE_DIR_IN) {
ed->bEndpointAddress =
ep_curr->hw_endpoint_in;
} else {
ed->bEndpointAddress =
ep_curr->hw_endpoint_out;
}
} else {
/* compute the maximum frame size */
if (ep_curr->max_frame_size < wMaxPacketSize) {
ep_curr->max_frame_size = wMaxPacketSize;
}
if (temp == UE_CONTROL) {
ep_curr->needs_in = 1;
ep_curr->needs_out = 1;
} else {
if (ed->bEndpointAddress & UE_DIR_IN) {
ep_curr->needs_in = 1;
} else {
ep_curr->needs_out = 1;
}
}
ep_curr->needs_ep_type = ep_type;
}
ep_curr++;
if (ep_max < ep_curr) {
ep_max = ep_curr;
}
}
goto repeat;
}
/*------------------------------------------------------------------------*
* usb_hw_ep_resolve
*
* This function will try to resolve endpoint requirements by the
* given endpoint profiles that the USB hardware reports.
*
* Return values:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
static usb_error_t
usb_hw_ep_resolve(struct usb_device *udev,
struct usb_descriptor *desc)
{
struct usb_hw_ep_scratch *ues;
struct usb_hw_ep_scratch_sub *ep;
const struct usb_hw_ep_profile *pf;
const struct usb_bus_methods *methods;
struct usb_device_descriptor *dd;
uint16_t mps;
if (desc == NULL)
return (USB_ERR_INVAL);
/* get bus methods */
methods = udev->bus->methods;
if (methods->get_hw_ep_profile == NULL)
return (USB_ERR_INVAL);
if (desc->bDescriptorType == UDESC_DEVICE) {
if (desc->bLength < sizeof(*dd))
return (USB_ERR_INVAL);
dd = (void *)desc;
/* get HW control endpoint 0 profile */
(methods->get_hw_ep_profile) (udev, &pf, 0);
if (pf == NULL) {
return (USB_ERR_INVAL);
}
if (!usb_hw_ep_match(pf, UE_CONTROL, 0)) {
DPRINTFN(0, "Endpoint 0 does not "
"support control\n");
return (USB_ERR_INVAL);
}
mps = dd->bMaxPacketSize;
if (udev->speed == USB_SPEED_FULL) {
/*
* We can optionally choose another packet size !
*/
while (1) {
/* check if "mps" is ok */
if (pf->max_in_frame_size >= mps) {
break;
}
/* reduce maximum packet size */
mps /= 2;
/* check if "mps" is too small */
if (mps < 8) {
return (USB_ERR_INVAL);
}
}
dd->bMaxPacketSize = mps;
} else {
/* We only have one choice */
if (mps == 255) {
mps = 512;
}
/* Check if we support the specified wMaxPacketSize */
if (pf->max_in_frame_size < mps) {
return (USB_ERR_INVAL);
}
}
return (0); /* success */
}
if (desc->bDescriptorType != UDESC_CONFIG)
return (USB_ERR_INVAL);
if (desc->bLength < sizeof(*(ues->cd)))
return (USB_ERR_INVAL);
ues = udev->scratch.hw_ep_scratch;
memset(ues, 0, sizeof(*ues));
ues->ep_max = ues->ep;
ues->cd = (void *)desc;
ues->methods = methods;
ues->udev = udev;
/* Get all the endpoints we need */
if (usb_hw_ep_get_needs(ues, UE_ISOCHRONOUS, 0) ||
usb_hw_ep_get_needs(ues, UE_INTERRUPT, 0) ||
usb_hw_ep_get_needs(ues, UE_CONTROL, 0) ||
usb_hw_ep_get_needs(ues, UE_BULK, 0)) {
DPRINTFN(0, "Could not get needs\n");
return (USB_ERR_INVAL);
}
for (ep = ues->ep; ep != ues->ep_max; ep++) {
while (ep->needs_in || ep->needs_out) {
/*
* First try to use a simplex endpoint.
* Then try to use a duplex endpoint.
*/
if (usb_hw_ep_find_match(ues, ep, 1) &&
usb_hw_ep_find_match(ues, ep, 0)) {
DPRINTFN(0, "Could not find match\n");
return (USB_ERR_INVAL);
}
}
}
ues->ep_max = ues->ep;
/* Update all endpoint addresses */
if (usb_hw_ep_get_needs(ues, UE_ISOCHRONOUS, 1) ||
usb_hw_ep_get_needs(ues, UE_INTERRUPT, 1) ||
usb_hw_ep_get_needs(ues, UE_CONTROL, 1) ||
usb_hw_ep_get_needs(ues, UE_BULK, 1)) {
DPRINTFN(0, "Could not update endpoint address\n");
return (USB_ERR_INVAL);
}
return (0); /* success */
}
/*------------------------------------------------------------------------*
* usb_temp_get_tdd
*
* Returns:
* NULL: No USB template device descriptor found.
* Else: Pointer to the USB template device descriptor.
*------------------------------------------------------------------------*/
static const struct usb_temp_device_desc *
usb_temp_get_tdd(struct usb_device *udev)
{
if (udev->usb_template_ptr == NULL) {
return (NULL);
}
return (udev->usb_template_ptr->tdd);
}
/*------------------------------------------------------------------------*
* usb_temp_get_device_desc
*
* Returns:
* NULL: No USB device descriptor found.
* Else: Pointer to USB device descriptor.
*------------------------------------------------------------------------*/
static void *
usb_temp_get_device_desc(struct usb_device *udev)
{
struct usb_device_descriptor *dd;
if (udev->usb_template_ptr == NULL) {
return (NULL);
}
dd = &udev->usb_template_ptr->udd;
if (dd->bDescriptorType != UDESC_DEVICE) {
/* sanity check failed */
return (NULL);
}
return (dd);
}
/*------------------------------------------------------------------------*
* usb_temp_get_qualifier_desc
*
* Returns:
* NULL: No USB device_qualifier descriptor found.
* Else: Pointer to USB device_qualifier descriptor.
*------------------------------------------------------------------------*/
static void *
usb_temp_get_qualifier_desc(struct usb_device *udev)
{
struct usb_device_qualifier *dq;
if (udev->usb_template_ptr == NULL) {
return (NULL);
}
dq = &udev->usb_template_ptr->udq;
if (dq->bDescriptorType != UDESC_DEVICE_QUALIFIER) {
/* sanity check failed */
return (NULL);
}
return (dq);
}
/*------------------------------------------------------------------------*
* usb_temp_get_config_desc
*
* Returns:
* NULL: No USB config descriptor found.
* Else: Pointer to USB config descriptor having index "index".
*------------------------------------------------------------------------*/
static void *
usb_temp_get_config_desc(struct usb_device *udev,
uint16_t *pLength, uint8_t index)
{
struct usb_device_descriptor *dd;
struct usb_config_descriptor *cd;
uint16_t temp;
if (udev->usb_template_ptr == NULL) {
return (NULL);
}
dd = &udev->usb_template_ptr->udd;
cd = (void *)(udev->usb_template_ptr + 1);
if (index >= dd->bNumConfigurations) {
/* out of range */
return (NULL);
}
while (index--) {
if (cd->bDescriptorType != UDESC_CONFIG) {
/* sanity check failed */
return (NULL);
}
temp = UGETW(cd->wTotalLength);
cd = USB_ADD_BYTES(cd, temp);
}
if (pLength) {
*pLength = UGETW(cd->wTotalLength);
}
return (cd);
}
/*------------------------------------------------------------------------*
* usb_temp_get_vendor_desc
*
* Returns:
* NULL: No vendor descriptor found.
* Else: Pointer to a vendor descriptor.
*------------------------------------------------------------------------*/
static const void *
usb_temp_get_vendor_desc(struct usb_device *udev,
const struct usb_device_request *req, uint16_t *plen)
{
const struct usb_temp_device_desc *tdd;
tdd = usb_temp_get_tdd(udev);
if (tdd == NULL) {
return (NULL);
}
if (tdd->getVendorDesc == NULL) {
return (NULL);
}
return ((tdd->getVendorDesc) (req, plen));
}
/*------------------------------------------------------------------------*
* usb_temp_get_string_desc
*
* Returns:
* NULL: No string descriptor found.
* Else: Pointer to a string descriptor.
*------------------------------------------------------------------------*/
static const void *
usb_temp_get_string_desc(struct usb_device *udev,
uint16_t lang_id, uint8_t string_index)
{
const struct usb_temp_device_desc *tdd;
tdd = usb_temp_get_tdd(udev);
if (tdd == NULL) {
return (NULL);
}
if (tdd->getStringDesc == NULL) {
return (NULL);
}
return ((tdd->getStringDesc) (lang_id, string_index));
}
/*------------------------------------------------------------------------*
* usb_temp_get_hub_desc
*
* Returns:
* NULL: No USB HUB descriptor found.
* Else: Pointer to a USB HUB descriptor.
*------------------------------------------------------------------------*/
static const void *
usb_temp_get_hub_desc(struct usb_device *udev)
{
return (NULL); /* needs to be implemented */
}
/*------------------------------------------------------------------------*
* usb_temp_get_desc
*
* This function is a demultiplexer for local USB device side control
* endpoint requests.
*------------------------------------------------------------------------*/
static usb_error_t
usb_temp_get_desc(struct usb_device *udev, struct usb_device_request *req,
const void **pPtr, uint16_t *pLength)
{
const uint8_t *buf;
uint16_t len;
buf = NULL;
len = 0;
switch (req->bmRequestType) {
case UT_READ_DEVICE:
switch (req->bRequest) {
case UR_GET_DESCRIPTOR:
goto tr_handle_get_descriptor;
default:
goto tr_stalled;
}
case UT_READ_CLASS_DEVICE:
switch (req->bRequest) {
case UR_GET_DESCRIPTOR:
goto tr_handle_get_class_descriptor;
default:
goto tr_stalled;
}
default:
goto tr_stalled;
}
tr_handle_get_descriptor:
switch (req->wValue[1]) {
case UDESC_DEVICE:
if (req->wValue[0]) {
goto tr_stalled;
}
buf = usb_temp_get_device_desc(udev);
goto tr_valid;
case UDESC_DEVICE_QUALIFIER:
if (udev->speed != USB_SPEED_HIGH) {
goto tr_stalled;
}
if (req->wValue[0]) {
goto tr_stalled;
}
buf = usb_temp_get_qualifier_desc(udev);
goto tr_valid;
case UDESC_OTHER_SPEED_CONFIGURATION:
if (udev->speed != USB_SPEED_HIGH) {
goto tr_stalled;
}
case UDESC_CONFIG:
buf = usb_temp_get_config_desc(udev,
&len, req->wValue[0]);
goto tr_valid;
case UDESC_STRING:
buf = usb_temp_get_string_desc(udev,
UGETW(req->wIndex), req->wValue[0]);
goto tr_valid;
default:
goto tr_stalled;
}
tr_handle_get_class_descriptor:
if (req->wValue[0]) {
goto tr_stalled;
}
buf = usb_temp_get_hub_desc(udev);
goto tr_valid;
tr_valid:
if (buf == NULL)
goto tr_stalled;
if (len == 0)
len = buf[0];
*pPtr = buf;
*pLength = len;
return (0); /* success */
tr_stalled:
/* try to get a vendor specific descriptor */
len = 0;
buf = usb_temp_get_vendor_desc(udev, req, &len);
if (buf != NULL)
goto tr_valid;
*pPtr = NULL;
*pLength = 0;
return (0); /* we ignore failures */
}
/*------------------------------------------------------------------------*
* usb_temp_setup
*
* This function generates USB descriptors according to the given USB
* template device descriptor. It will also try to figure out the best
* matching endpoint addresses using the hardware endpoint profiles.
*
* Returns:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
usb_error_t
usb_temp_setup(struct usb_device *udev,
const struct usb_temp_device_desc *tdd)
{
struct usb_temp_setup *uts;
void *buf;
usb_error_t error;
uint8_t n;
uint8_t do_unlock;
/* be NULL safe */
if (tdd == NULL)
return (0);
/* Protect scratch area */
do_unlock = usbd_ctrl_lock(udev);
uts = udev->scratch.temp_setup;
memset(uts, 0, sizeof(*uts));
uts->usb_speed = udev->speed;
uts->self_powered = udev->flags.self_powered;
/* first pass */
usb_make_device_desc(uts, tdd);
if (uts->err) {
/* some error happened */
goto done;
}
/* sanity check */
if (uts->size == 0) {
uts->err = USB_ERR_INVAL;
goto done;
}
/* allocate zeroed memory */
uts->buf = usbd_alloc_config_desc(udev, uts->size);
/*
* Allow malloc() to return NULL regardless of M_WAITOK flag.
* This helps when porting the software to non-FreeBSD
* systems.
*/
if (uts->buf == NULL) {
/* could not allocate memory */
uts->err = USB_ERR_NOMEM;
goto done;
}
/* second pass */
uts->size = 0;
usb_make_device_desc(uts, tdd);
/*
* Store a pointer to our descriptors:
*/
udev->usb_template_ptr = uts->buf;
if (uts->err) {
/* some error happened during second pass */
goto done;
}
/*
* Resolve all endpoint addresses !
*/
buf = usb_temp_get_device_desc(udev);
uts->err = usb_hw_ep_resolve(udev, buf);
if (uts->err) {
DPRINTFN(0, "Could not resolve endpoints for "
"Device Descriptor, error = %s\n",
usbd_errstr(uts->err));
goto done;
}
for (n = 0;; n++) {
buf = usb_temp_get_config_desc(udev, NULL, n);
if (buf == NULL) {
break;
}
uts->err = usb_hw_ep_resolve(udev, buf);
if (uts->err) {
DPRINTFN(0, "Could not resolve endpoints for "
"Config Descriptor %u, error = %s\n", n,
usbd_errstr(uts->err));
goto done;
}
}
done:
error = uts->err;
if (error)
usb_temp_unsetup(udev);
if (do_unlock)
usbd_ctrl_unlock(udev);
return (error);
}
/*------------------------------------------------------------------------*
* usb_temp_unsetup
*
* This function frees any memory associated with the currently
* setup template, if any.
*------------------------------------------------------------------------*/
void
usb_temp_unsetup(struct usb_device *udev)
{
usbd_free_config_desc(udev, udev->usb_template_ptr);
udev->usb_template_ptr = NULL;
}
static usb_error_t
usb_temp_setup_by_index(struct usb_device *udev, uint16_t index)
{
usb_error_t err;
switch (index) {
case USB_TEMP_MSC:
err = usb_temp_setup(udev, &usb_template_msc);
break;
case USB_TEMP_CDCE:
err = usb_temp_setup(udev, &usb_template_cdce);
break;
case USB_TEMP_MTP:
err = usb_temp_setup(udev, &usb_template_mtp);
break;
case USB_TEMP_MODEM:
err = usb_temp_setup(udev, &usb_template_modem);
break;
case USB_TEMP_AUDIO:
err = usb_temp_setup(udev, &usb_template_audio);
break;
case USB_TEMP_KBD:
err = usb_temp_setup(udev, &usb_template_kbd);
break;
case USB_TEMP_MOUSE:
err = usb_temp_setup(udev, &usb_template_mouse);
break;
case USB_TEMP_PHONE:
err = usb_temp_setup(udev, &usb_template_phone);
break;
case USB_TEMP_SERIALNET:
err = usb_temp_setup(udev, &usb_template_serialnet);
break;
case USB_TEMP_MIDI:
err = usb_temp_setup(udev, &usb_template_midi);
break;
case USB_TEMP_MULTI:
err = usb_temp_setup(udev, &usb_template_multi);
break;
case USB_TEMP_CDCEEM:
err = usb_temp_setup(udev, &usb_template_cdceem);
break;
default:
return (USB_ERR_INVAL);
}
return (err);
}
static void
usb_temp_init(void *arg)
{
/* register our functions */
usb_temp_get_desc_p = &usb_temp_get_desc;
usb_temp_setup_by_index_p = &usb_temp_setup_by_index;
usb_temp_unsetup_p = &usb_temp_unsetup;
}
SYSINIT(usb_temp_init, SI_SUB_LOCK, SI_ORDER_FIRST, usb_temp_init, NULL);
SYSUNINIT(usb_temp_unload, SI_SUB_LOCK, SI_ORDER_ANY, usb_temp_unload, NULL);