d/wmr: Recover from bad camera transfers

Fix USB overflow problems when camera readout is
delayed by rounding up the transfer size to always
be a multiple of the endpoint transfer size, and
by discarding invalid frames that happen when
the headset ends up with too much data queued.

Part-of: <https://gitlab.freedesktop.org/monado/monado/-/merge_requests/2394>
This commit is contained in:
Jan Schmidt 2025-01-05 19:34:10 +11:00 committed by Marge Bot
parent 4a905bbb24
commit d93e4b92a6

View file

@ -99,6 +99,7 @@ struct wmr_camera
int slam_cam_count; //!< Number of tracking cameras used for SLAM
size_t xfer_size;
size_t frame_xfer_size;
uint32_t frame_width, frame_height;
uint8_t last_seq;
uint64_t last_frame_ts;
@ -172,6 +173,13 @@ compute_frame_size(struct wmr_camera *cam)
F = 26;
libusb_device *dev = libusb_get_device(cam->dev);
int ep_pkt_size = libusb_get_max_packet_size(dev, CAM_ENDPOINT);
if (ep_pkt_size < 0) {
WMR_CAM_ERROR(cam, "Failed to retrieve endpoint descriptor: result %d", ep_pkt_size);
return false;
}
for (i = 0; i < cam->tcam_count; i++) {
const struct wmr_camera_config *config = &cam->tcam_confs[i];
@ -204,7 +212,14 @@ compute_frame_size(struct wmr_camera *cam)
n_packets = F / (0x6000 - 32);
leftover = F - n_packets * (0x6000 - 32);
cam->xfer_size = n_packets * 0x6000 + 32 + leftover;
cam->frame_xfer_size = n_packets * 0x6000 + 32 + leftover;
// Round up to a multiple of the max packet size to avoid overflows
// in case of stalls reading things out
size_t round_up = ep_pkt_size - (cam->frame_xfer_size % ep_pkt_size);
cam->xfer_size = cam->frame_xfer_size + round_up;
WMR_CAM_DEBUG(cam, "Rounding up xfer size by %zu from frame size %zu to %zu", round_up, cam->frame_xfer_size,
cam->xfer_size);
cam->frame_width = width;
cam->frame_height = height;
@ -288,8 +303,9 @@ img_xfer_cb(struct libusb_transfer *xfer)
goto out;
}
if (xfer->actual_length < xfer->length) {
WMR_CAM_DEBUG(cam, "Camera transfer only delivered %d bytes", xfer->actual_length);
if ((size_t)xfer->actual_length < cam->frame_xfer_size) {
WMR_CAM_DEBUG(cam, "Camera transfer only delivered %d bytes of %zu per frame", xfer->actual_length,
cam->frame_xfer_size);
goto out;
}
@ -308,7 +324,7 @@ img_xfer_cb(struct libusb_transfer *xfer)
const size_t chunk_size = 0x6000 - 32;
DRV_TRACE_BEGIN(copy_to_frame);
while (dst_remain > 0) {
while (dst_remain >= 0x20) {
const size_t to_copy = dst_remain > chunk_size ? chunk_size : dst_remain;
/* 32 byte header seems to contain:
@ -319,6 +335,11 @@ img_xfer_cb(struct libusb_transfer *xfer)
* but repeat every 8 slices. They're different each boot
* of the headset. Might just be uninitialised memory?
*/
uint32_t magic = __le32_to_cpu(*(__le32 *)(src));
if (magic != WMR_MAGIC) {
WMR_CAM_WARN(cam, "Invalid frame magic (got %x, expected %x). Dropping", magic, WMR_MAGIC);
goto drop_frame;
}
src += 0x20;
memcpy(dst, src, to_copy);
@ -328,8 +349,11 @@ img_xfer_cb(struct libusb_transfer *xfer)
}
DRV_TRACE_END(copy_to_frame);
/* There should be exactly a 26 byte footer left over */
assert(xfer->buffer + xfer->length - src == 26);
/* There should be exactly a 26 byte footer left over if we completely consumed the right amount of data */
if (xfer->buffer + cam->frame_xfer_size - src != 26) {
WMR_CAM_WARN(cam, "Invalid frame. Dropping");
goto drop_frame;
}
/* Footer contains:
* __le64 start_ts; - 100ns unit timestamp, from same clock as video_timestamps on the IMU feed
@ -401,6 +425,7 @@ img_xfer_cb(struct libusb_transfer *xfer)
}
}
drop_frame:
xrt_frame_reference(&xf, NULL);
out: