diff -ruN a/sys/bus/u4b/uvc/uvc_drv.c b/sys/bus/u4b/uvc/uvc_drv.c --- a/sys/bus/u4b/uvc/uvc_drv.c 2026-10-14 18:40:54.648294861 +0000 +++ b/sys/bus/u4b/uvc/uvc_drv.c 2026-10-14 18:41:11.748074091 +0000 @@ -499,11 +499,15 @@ } else { const uint32_t min = frame->interval[0].val; const uint32_t max = frame->interval[1].val; - const uint32_t step = frame->interval[2].val; + uint32_t step = frame->interval[2].val; + if (step == 0) + step = 1; interval = min + (interval - min + step/2) / step * step; if (interval > max) interval = max; + if (interval < min) + interval = min; } return interval; @@ -1460,6 +1464,10 @@ break; } } + if (fmt == NULL) { + kprintf("No format desc found.\n"); + return EINVAL; + } if (fmt->nfrm == 0) { kprintf("No frame desc found for default format.\n"); return EINVAL; @@ -1532,6 +1540,10 @@ } } + if (fmt == NULL) { + DPRINTF("No format desc found.\n"); + goto done; + } if (fmt->nfrm == 0) { DPRINTF("No frame desc found for default format.\n"); goto done; @@ -1919,8 +1931,28 @@ itv->val = interval ? interval : 1; itv++; } - if (frm->interval_type == 0) + if (frm->interval_type == 0) { + /* + * Continuous frame: dFrameInterval[] carries + * min/max/step; read them instead of leaving + * the (M_ZERO) slots at 0 -- step==0 later + * divides by zero in + * uvc_drv_try_frame_interval(). + */ + if (frmd->bLength >= 26 + 12) { + interval = UGETDW(frmd->dFrameInterval[0]); + itv->val = interval ? interval : 1; + interval = UGETDW(frmd->dFrameInterval[1]); + (itv + 1)->val = interval ? interval : 1; + interval = UGETDW(frmd->dFrameInterval[2]); + (itv + 2)->val = interval ? interval : 1; + } else { + itv->val = 1; + (itv + 1)->val = 1; + (itv + 2)->val = 1; + } itv += 3; + } fmt->nfrm++; frm++; break; @@ -1950,8 +1982,22 @@ itv->val = interval ? interval : 1; itv++; } - if (frm->interval_type == 0) + if (frm->interval_type == 0) { + /* same as above: read min/max/step */ + if (fbrmd->bLength >= 26 + 12) { + interval = UGETDW(fbrmd->dFrameInterval[0]); + itv->val = interval ? interval : 1; + interval = UGETDW(fbrmd->dFrameInterval[1]); + (itv + 1)->val = interval ? interval : 1; + interval = UGETDW(fbrmd->dFrameInterval[2]); + (itv + 2)->val = interval ? interval : 1; + } else { + itv->val = 1; + (itv + 1)->val = 1; + (itv + 2)->val = 1; + } itv += 3; + } fmt->nfrm++; frm++; break; @@ -2494,11 +2540,32 @@ uvc_drv_detach(device_t self) { struct uvc_softc *sc = device_get_softc(self); + int i; DPRINTF("%s-%d-%p\n", __func__, __LINE__, self); //delete sysfs //sysfs_video_del_device(sc->udev, sc->iface); + /* + * Destroy the /dev/videoX cdev FIRST and let outstanding users + * drain before freeing any memory. destroy_dev() is + * asynchronous (devfs_destroy_dev -> devfs_msg_send_dev) and + * open file descriptors keep dispatching ioctls into the driver + * until the devfs core processes the destroy; freeing + * sc->data/sc->video before that turns concurrent V4L2 ioctls + * (VIDIOC_ENUM_FMT etc., which read video->data->fmt without + * holding the mutex) into use-after-free reads. + */ + if (sc->video) { + /* entry close */ + uvc_v4l2_unreg(sc->video); + for (i = 0; i < 1000; i++) { + if (atomic_load_64(&sc->video->users) == 0) + break; + tsleep(sc->video, 0, "uvcdt", 1); + } + } + /* destroy video hard code only one */ uvc_drv_destroy_video(sc->video); @@ -2507,12 +2574,13 @@ uvc_drv_destroy_ctrl(sc->ctrl); if (sc->video) { - /* entry close */ - uvc_v4l2_unreg(sc->video); /* data free */ kfree(sc->video, M_UVC); sc->video = NULL; } + sc->data = NULL; + sc->ctrl = NULL; + DPRINTF("%s-%d-%p\n", __func__, __LINE__, self); return 0; } diff -ruN a/sys/bus/u4b/uvc/uvc_drv.h b/sys/bus/u4b/uvc/uvc_drv.h --- a/sys/bus/u4b/uvc/uvc_drv.h 2026-10-14 18:40:54.648294861 +0000 +++ b/sys/bus/u4b/uvc/uvc_drv.h 2026-10-14 18:41:11.752074039 +0000 @@ -659,10 +659,9 @@ struct usb_xfer *xfer[UVC_N_BULKTRANSFER]; /* data format */ - uint8_t nfmt; - uint8_t nfrm; - uint8_t nitv; - uint8_t unuse[3]; + uint16_t nfmt; + uint16_t nfrm; + uint16_t nitv; struct uvc_data_format *fmt; };