drivers/usbhost: Convert the xHCI endpoint interval from the descriptor.

The Interval field of an endpoint context is an exponent: the controller
services the endpoint every 2^Interval microframes.  An endpoint
descriptor states its period differently depending on device speed, so the
number cannot be copied across, which is what this did.  A low speed
keyboard asking to be polled every 10ms was programmed as 2^10
microframes, which the controller would not accept: Configure Endpoint
went unanswered and allocation failed with -EIO.

Low and full speed interrupt endpoints state a period in frames, so the
exponent is the highest bit of that period in microframes, clamped to the
range the specification allows.  Other periodic endpoints already state an
exponent, one greater than the one wanted here.  Control and bulk
endpoints are not periodic and the field means nothing to them.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Justin Hammond <justin@dynam.ac>
This commit is contained in:
Justin Hammond 2026-08-16 17:17:15 +08:00 • committed by Xiang Xiao
parent f8ea0f3d02
commit a11cecd100

View file

@ -3801,6 +3801,68 @@ static int xhci_ep0configure(FAR struct usbhost_driver_s *drvr,
return ret;
}
/****************************************************************************
* Name: xhci_interval
*
* Description:
* Work out the Interval an endpoint context wants.
*
* The field is an exponent: the controller services the endpoint every
* 2^Interval microframes. An endpoint descriptor does not say it that
* way, and what it does say depends on how fast the device is, so the
* number cannot simply be copied across.
*
* A low or full speed interrupt endpoint counts in frames, so its period
* is bInterval milliseconds, or bInterval * 8 microframes, and the
* exponent is the position of the highest bit of that. Everything else
* that is periodic already states an exponent, one greater than the one
* wanted here. Control and bulk endpoints are not periodic and the field
* means nothing to them.
*
****************************************************************************/
static uint8_t xhci_interval(uint8_t speed, uint8_t xfrtype,
uint8_t interval)
{
unsigned int exp;
if (xfrtype != USB_EP_ATTR_XFER_INT && xfrtype != USB_EP_ATTR_XFER_ISOC)
{
return 0;
}
if ((speed == USB_SPEED_LOW || speed == USB_SPEED_FULL) &&
xfrtype == USB_EP_ATTR_XFER_INT)
{
/* Frames. Round down to a power of two, and keep it inside what the
* specification allows for this kind of endpoint: 2^3 microframes is
* one frame, 2^10 is 128 of them.
*/
if (interval == 0)
{
interval = 1;
}
for (exp = 0; (1u << (exp + 1)) <= interval * 8u; exp++);
if (exp < 3)
{
exp = 3;
}
else if (exp > 10)
{
exp = 10;
}
return exp;
}
/* Already an exponent, counted from one */
return interval > 0 ? interval - 1 : 0;
}
/****************************************************************************
* Name: xhci_epalloc
*
@ -3879,7 +3941,8 @@ static int xhci_epalloc(FAR struct usbhost_driver_s *drvr,
epinfo->epno = epdesc->addr;
#ifndef CONFIG_USBHOST_INT_DISABLE
epinfo->interval = epdesc->interval;
epinfo->interval = xhci_interval(hport->speed, epdesc->xfrtype,
epdesc->interval);
#endif
epinfo->xfrtype = epdesc->xfrtype;
nxsem_init(&epinfo->iocsem, 0, 0);