Message ID | 20221212-uvc-race-v5-0-3db3933d1608@chromium.org |
---|---|
State | New |
Headers |
Return-Path: <linux-kernel-owner@vger.kernel.org> Delivered-To: ouuuleilei@gmail.com Received: by 2002:a5d:4e01:0:0:0:0:0 with SMTP id p1csp4152540wrt; Mon, 2 Jan 2023 05:12:19 -0800 (PST) X-Google-Smtp-Source: AMrXdXu7uWYEAlqw/xmuUZqkDrsB0fhA5XwhXR6OFB0gYYvSosQGlVHv7hI6f1gQrxuJPSW5Up8y X-Received: by 2002:a05:6a21:3288:b0:af:a896:850c with SMTP id yt8-20020a056a21328800b000afa896850cmr14371974pzb.5.1672665138885; Mon, 02 Jan 2023 05:12:18 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1672665138; cv=none; d=google.com; s=arc-20160816; b=h5qabw/8jdqXuRvTgWFzyeqo/vySoLujFePuBseEH5vP5yTgStXcNyuFF72SpKyUiW gTUUTOQVx2tjp3lC92t5DsGU/rb5QNidvm+rqQjIaBynlpXmq4KHyn39QrLqIjM5DWIa mNt6lC+0JJbL+sSv/2U3byKORJzYMNT51x9bGCSTHewOLdUBFb4kcw8jtz0FDrSOGujB GjY2ssW5mWJYfRb0VKr+xS16eEJm1MOYIHpFomQYmw+S1ncuSxIScesXRttNhedzSIwF dfqQxaHNrhj0+AY0qx7t/DSzLBfqugaXYEVliNkRn0KYYVhoIdyHDV/zfQ2eILPO3Gwz FnHg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:cc:to:message-id:content-transfer-encoding :mime-version:subject:date:from:dkim-signature; bh=4mSpRdBbR8hRsjiFVakEqY6OCTQghFDsF57WE7uaZ24=; b=MEVrf4irfTjD0Jkqt7m5ThchyZ8BrvSGJHjMVW+tdBb8Ie9jwUmn2F9pCXNYYRX/xP 38Ei+xSgKWlPO538Ldk9a+HjTJ5z9xPvZAjR2a+vtTSeGS08+lB9Wg9q/GxTN5Xnw4LI 5QdUS6UspiouzGebPhnc7roF10JIMKxC3ufReNeIPg+xu9sv0Rdn33LVoCINbXJqsqId h9tpcF9Qh4JP8YVOMvg7wmqTIikyG0C94Y/Co4hFf+7DZzj7/S0tyQbeGQ+MR7EpGAjp 4ciPF8LvEyV9mHVJYEGlpoCh1+3ZD/ylsLTZ2ZBG56ApTgxzSZJqJ1ZJ377mP6bINaec zDVA== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@chromium.org header.s=google header.b="ftNK/aNT"; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=chromium.org Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id z18-20020a170903019200b0018981c83ffcsi1529950plg.4.2023.01.02.05.12.06; Mon, 02 Jan 2023 05:12:18 -0800 (PST) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; dkim=pass header.i=@chromium.org header.s=google header.b="ftNK/aNT"; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=chromium.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S235964AbjABMyU (ORCPT <rfc822;yugsuo@gmail.com> + 99 others); Mon, 2 Jan 2023 07:54:20 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:56070 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S235976AbjABMyK (ORCPT <rfc822;linux-kernel@vger.kernel.org>); Mon, 2 Jan 2023 07:54:10 -0500 Received: from mail-ej1-x62e.google.com (mail-ej1-x62e.google.com [IPv6:2a00:1450:4864:20::62e]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id C5492392 for <linux-kernel@vger.kernel.org>; Mon, 2 Jan 2023 04:54:08 -0800 (PST) Received: by mail-ej1-x62e.google.com with SMTP id t17so66369712eju.1 for <linux-kernel@vger.kernel.org>; Mon, 02 Jan 2023 04:54:08 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; h=cc:to:message-id:content-transfer-encoding:mime-version:subject :date:from:from:to:cc:subject:date:message-id:reply-to; bh=4mSpRdBbR8hRsjiFVakEqY6OCTQghFDsF57WE7uaZ24=; b=ftNK/aNTJUYoLnNPnZtpYA0c2auYvuuZKBly8Ot7SG7yW80AnySckkTePJ4xHSKbVs qGWNQ+TBR+xT8+ytjem9zJrRHsBevryPUeqG17zBdheg1DsgTDoYTELa/DS0kb6V6qhR 71JZNOJMfUTcRck6vbmmnqo392zr6FgeXCnOg= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=cc:to:message-id:content-transfer-encoding:mime-version:subject :date:from:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=4mSpRdBbR8hRsjiFVakEqY6OCTQghFDsF57WE7uaZ24=; b=8IToRoqD0wteOy3t04sLzx8qvuLek07U7cHbnJLftmdroXb6/gmb2RrYT4YJi0Rvet vPqsUOSs+zONZbi0iyjHOHJVgVbmlZ+9zuneEs9VcZo7YCY90TNKZhskku9EmiC8/J4y 2vukLVnpznnHSsuDUmzX4OcmIVIb3m9R3/SRFEY8jnDn0+0MNq/Mxd7b5mVgeeHpY7EF M7V51e/md+Ihi7PZbc9aqvsY09P/xw8MXPt56hixBCWA80fzXF39Fy5togW+NGOqMljs Glu4epNuqmDcaIA3Z3cCuzbeDVfRLXkK1b3DGCm2rAUg4Hpa/Bi63g1sCxZ2uIHx7Gur kGAw== X-Gm-Message-State: AFqh2kpeex2P9aAPk6r/JZzGbQAO70bcUmXxIPn5dHgz1VnHAYUzMNh3 jWuF1v1wmqslQcA7p0uNCmnAzg== X-Received: by 2002:a17:906:9f07:b0:7c1:6f0a:a2cf with SMTP id fy7-20020a1709069f0700b007c16f0aa2cfmr35989436ejc.32.1672664047325; Mon, 02 Jan 2023 04:54:07 -0800 (PST) Received: from alco.roam.corp.google.com ([2620:0:1059:10:6ef6:ea10:76ec:977f]) by smtp.gmail.com with ESMTPSA id j15-20020a170906094f00b007add28659b0sm13205598ejd.140.2023.01.02.04.54.06 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 02 Jan 2023 04:54:06 -0800 (PST) From: Ricardo Ribalda <ribalda@chromium.org> Date: Mon, 02 Jan 2023 13:53:38 +0100 Subject: [PATCH v5] media: uvcvideo: Fix race condition with usb_kill_urb MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Message-Id: <20221212-uvc-race-v5-0-3db3933d1608@chromium.org> To: Sergey Senozhatsky <senozhatsky@chromium.org>, Mauro Carvalho Chehab <mchehab@kernel.org>, Laurent Pinchart <laurent.pinchart@ideasonboard.com>, Max Staudt <mstaudt@google.com> Cc: linux-media@vger.kernel.org, stable@vger.kernel.org, linux-kernel@vger.kernel.org, Yunke Cao <yunkec@chromium.org>, Ricardo Ribalda <ribalda@chromium.org> X-Mailer: b4 0.11.0-dev-696ae X-Developer-Signature: v=1; a=openpgp-sha256; l=5506; i=ribalda@chromium.org; h=from:subject:message-id; bh=+NHyWPvCyiTeSkvSubZaZ/Q7vNEi0zyCacfqmuUFlec=; b=owEBbQKS/ZANAwAKAdE30T7POsSIAcsmYgBjstPpb14iWK6RcBI4U/PFcjqha1VTQAciMfENYSVQ E6oV2xWJAjMEAAEKAB0WIQREDzjr+/4oCDLSsx7RN9E+zzrEiAUCY7LT6QAKCRDRN9E+zzrEiJciD/ 0QhKqGkYR0UyIX7FhHkAGoH2TSg5zA4GQlTlCH0inYvovIfE9a5nql/WSl+nfDPU0UOiAJyZQhx9EI Gwze+7gey1+WYXHo8oexhS1zHupNXWoaGwaEH1Om/HAU6/PGYOon81TxK2bXf+rM7JP2/J8tn++S07 0bTPCCHDx2cJPvIwgkUqXM1qzPZdwXnEshv9A/jgK+1A9MOY92TbF5D2mR7cakcOLM5XAonXViFxh2 Rxehg952173WD5jLzxamdCB4WOPXIp7Pa/P2PTb0gQjUg6VYbc+Oi0SX11PE4sGC1TRl212XzyeaxL uUlxbbdWnsZpa/qb87rDF0isbHmFuySVBTn51CmAxiJ2dNw/sqhqkiec1O0FOnP2NpIWkytOkttlSu Vm8OpvXvAJZvAm58PcddIrO3qURhQw9HLFl18OzTOxu+KsQicyi3WvVuQNzZLQYcRrEpAnszDliUL0 +KE09fP90LTCQ5XkYTSgTONgDX7VT6rLpnQVfOMxG4QOLq3ITax+hChTLBcUDXhHgfQXFV7giThyO9 HQo3G8IPLQ0jMlhmLioRuFDRsynLnYoluMJ90rhHZMkGhyX/7A49d/qCEp1dZk5qavxu5CPfdo7xTy pLt7dpATJQ9o2TY3vjqEqDbvzW8IVLjC0pG8HCjaI6MWGOlbsFJkjQPzzihg== X-Developer-Key: i=ribalda@chromium.org; a=openpgp; fpr=9EC3BB66E2FC129A6F90B39556A0D81F9F782DA9 X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIMWL_WL_HIGH, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,RCVD_IN_DNSWL_NONE, SPF_HELO_NONE,SPF_PASS autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: <linux-kernel.vger.kernel.org> X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= X-GMAIL-THRID: =?utf-8?q?1752208104024059594?= X-GMAIL-MSGID: =?utf-8?q?1753916520821459801?= |
Series |
[v5] media: uvcvideo: Fix race condition with usb_kill_urb
|
|
Commit Message
Ricardo Ribalda
Jan. 2, 2023, 12:53 p.m. UTC
usb_kill_urb warranties that all the handlers are finished when it returns, but does not protect against threads that might be handling asynchronously the urb. For UVC, the function uvc_ctrl_status_event_async() takes care of control changes asynchronously. If the code is executed in the following order: CPU 0 CPU 1 ===== ===== uvc_status_complete() uvc_status_stop() uvc_ctrl_status_event_work() uvc_status_start() -> FAIL Then uvc_status_start will keep failing and this error will be shown: <4>[ 5.540139] URB 0000000000000000 submitted while active drivers/usb/core/urb.c:378 usb_submit_urb+0x4c3/0x528 Let's improve the current situation, by not re-submiting the urb if we are stopping the status event. Also process the queued work (if any) during stop. CPU 0 CPU 1 ===== ===== uvc_status_complete() uvc_status_stop() uvc_status_start() uvc_ctrl_status_event_work() -> FAIL Hopefully, with the usb layer protection this should be enough to cover all the cases. Cc: stable@vger.kernel.org Fixes: e5225c820c05 ("media: uvcvideo: Send a control event when a Control Change interrupt arrives") Reviewed-by: Yunke Cao <yunkec@chromium.org> Signed-off-by: Ricardo Ribalda <ribalda@chromium.org> --- uvc: Fix race condition on uvc Make sure that all the async work is finished when we stop the status urb. To: Yunke Cao <yunkec@chromium.org> To: Sergey Senozhatsky <senozhatsky@chromium.org> To: Max Staudt <mstaudt@google.com> To: Laurent Pinchart <laurent.pinchart@ideasonboard.com> To: Mauro Carvalho Chehab <mchehab@kernel.org> Cc: linux-media@vger.kernel.org Cc: linux-kernel@vger.kernel.org --- Changes in v5: - atomic_t do not impose barriers, use smp_mb() instead. (Thanks Laurent) - Add an extra cancel_work_sync(). - Link to v4: https://lore.kernel.org/r/20221212-uvc-race-v4-0-38d7075b03f5@chromium.org Changes in v4: - Replace bool with atomic_t to avoid compiler reordering. - First complete the async work and then kill the urb to avoid race (Thanks Laurent!) - Link to v3: https://lore.kernel.org/r/20221212-uvc-race-v3-0-954efc752c9a@chromium.org Changes in v3: - Remove the patch for dev->status, makes more sense in another series, and makes the zero day less nervous. - Update reviewed-by (thanks Yunke!). - Link to v2: https://lore.kernel.org/r/20221212-uvc-race-v2-0-54496cc3b8ab@chromium.org Changes in v2: - Add a patch for not kalloc dev->status - Redo the logic mechanism, so it also works with suspend (Thanks Yunke!) - Link to v1: https://lore.kernel.org/r/20221212-uvc-race-v1-0-c52e1783c31d@chromium.org --- drivers/media/usb/uvc/uvc_ctrl.c | 3 +++ drivers/media/usb/uvc/uvc_status.c | 36 ++++++++++++++++++++++++++++++++++++ drivers/media/usb/uvc/uvcvideo.h | 1 + 3 files changed, 40 insertions(+) --- base-commit: 0ec5a38bf8499f403f81cb81a0e3a60887d1993c change-id: 20221212-uvc-race-09276ea68bf8 Best regards,
Comments
Hi Ricardo, Thank you for the patch. On Mon, Jan 02, 2023 at 01:53:38PM +0100, Ricardo Ribalda wrote: > usb_kill_urb warranties that all the handlers are finished when it > returns, but does not protect against threads that might be handling > asynchronously the urb. > > For UVC, the function uvc_ctrl_status_event_async() takes care of > control changes asynchronously. > > If the code is executed in the following order: > > CPU 0 CPU 1 > ===== ===== > uvc_status_complete() > uvc_status_stop() > uvc_ctrl_status_event_work() > uvc_status_start() -> FAIL > > Then uvc_status_start will keep failing and this error will be shown: > > <4>[ 5.540139] URB 0000000000000000 submitted while active > drivers/usb/core/urb.c:378 usb_submit_urb+0x4c3/0x528 > > Let's improve the current situation, by not re-submiting the urb if > we are stopping the status event. Also process the queued work > (if any) during stop. > > CPU 0 CPU 1 > ===== ===== > uvc_status_complete() > uvc_status_stop() > uvc_status_start() > uvc_ctrl_status_event_work() -> FAIL > > Hopefully, with the usb layer protection this should be enough to cover > all the cases. > > Cc: stable@vger.kernel.org > Fixes: e5225c820c05 ("media: uvcvideo: Send a control event when a Control Change interrupt arrives") > Reviewed-by: Yunke Cao <yunkec@chromium.org> > Signed-off-by: Ricardo Ribalda <ribalda@chromium.org> > --- > uvc: Fix race condition on uvc > > Make sure that all the async work is finished when we stop the status urb. > > To: Yunke Cao <yunkec@chromium.org> > To: Sergey Senozhatsky <senozhatsky@chromium.org> > To: Max Staudt <mstaudt@google.com> > To: Laurent Pinchart <laurent.pinchart@ideasonboard.com> > To: Mauro Carvalho Chehab <mchehab@kernel.org> > Cc: linux-media@vger.kernel.org > Cc: linux-kernel@vger.kernel.org > --- > Changes in v5: > - atomic_t do not impose barriers, use smp_mb() instead. (Thanks Laurent) > - Add an extra cancel_work_sync(). > - Link to v4: https://lore.kernel.org/r/20221212-uvc-race-v4-0-38d7075b03f5@chromium.org > > Changes in v4: > - Replace bool with atomic_t to avoid compiler reordering. > - First complete the async work and then kill the urb to avoid race (Thanks Laurent!) > - Link to v3: https://lore.kernel.org/r/20221212-uvc-race-v3-0-954efc752c9a@chromium.org > > Changes in v3: > - Remove the patch for dev->status, makes more sense in another series, and makes > the zero day less nervous. > - Update reviewed-by (thanks Yunke!). > - Link to v2: https://lore.kernel.org/r/20221212-uvc-race-v2-0-54496cc3b8ab@chromium.org > > Changes in v2: > - Add a patch for not kalloc dev->status > - Redo the logic mechanism, so it also works with suspend (Thanks Yunke!) > - Link to v1: https://lore.kernel.org/r/20221212-uvc-race-v1-0-c52e1783c31d@chromium.org > --- > drivers/media/usb/uvc/uvc_ctrl.c | 3 +++ > drivers/media/usb/uvc/uvc_status.c | 36 ++++++++++++++++++++++++++++++++++++ > drivers/media/usb/uvc/uvcvideo.h | 1 + > 3 files changed, 40 insertions(+) > > diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c > index c95a2229f4fa..5160facc8e20 100644 > --- a/drivers/media/usb/uvc/uvc_ctrl.c > +++ b/drivers/media/usb/uvc/uvc_ctrl.c > @@ -1442,6 +1442,9 @@ static void uvc_ctrl_status_event_work(struct work_struct *work) > > uvc_ctrl_status_event(w->chain, w->ctrl, w->data); > > + if (dev->flush_status) > + return; > + > /* Resubmit the URB. */ > w->urb->interval = dev->int_ep->desc.bInterval; > ret = usb_submit_urb(w->urb, GFP_KERNEL); > diff --git a/drivers/media/usb/uvc/uvc_status.c b/drivers/media/usb/uvc/uvc_status.c > index 7518ffce22ed..5911e63776e1 100644 > --- a/drivers/media/usb/uvc/uvc_status.c > +++ b/drivers/media/usb/uvc/uvc_status.c > @@ -6,6 +6,7 @@ > * Laurent Pinchart (laurent.pinchart@ideasonboard.com) > */ > > +#include <asm/barrier.h> > #include <linux/kernel.h> > #include <linux/input.h> > #include <linux/slab.h> > @@ -309,5 +310,40 @@ int uvc_status_start(struct uvc_device *dev, gfp_t flags) > > void uvc_status_stop(struct uvc_device *dev) > { > + struct uvc_ctrl_work *w = &dev->async_ctrl; > + > + /* From this point, the status urb is not re-queued */ > + dev->flush_status = 1; flush_status is now a bool, set it to true instead of 1. Same below for false instead of 0. > + /* > + * Make sure that the other CPUs are aware of the new value of > + * flush_status. > + */ > + smp_mb(); /* * Prevent to asynchronous control handler from requeing the URB. The * barrier is needed the flush_status change is visible to other CPUs * running the asynchronous handler before usb_kill_urb() is called * below. */ dev->flush_status = true; smp_mb(); > + > + /* If there is any status event on the queue, process it. */ > + if (cancel_work_sync(&w->work)) > + uvc_ctrl_status_event(w->chain, w->ctrl, w->data); > + > + /* Kill the urb. */ > usb_kill_urb(dev->int_urb); > + > + /* > + * If an status event was queued between cancel_work_sync() and > + * usb_kill_urb(), process it. > + */ /* * The URB completion handler may have queued asynchronous work. This * won't resubmit the URB as flush_status is set, but it needs to be * cancelled before returning or it could then race with a future * uvc_status_start() call. */ > + if (cancel_work_sync(&w->work)) > + uvc_ctrl_status_event(w->chain, w->ctrl, w->data); I think I'd still prefer checking flush_status in uvc_ctrl_status_event_async() and drop the event, as it would be simpler here. uvc_status_stop() is called when the last user indicates it's not interested in events anymore (by closing the device at the moment, possibly by unsubscribing from events in the future), so dropping the event isn't a problem as far as I can tell. What do you think ? > + > + /* > + * From this point, there are no events on the queue and the status urb s/urb/URB/ > + * is dead, this is, no events will be queued until uvc_status_start() > + * is called. > + */ > + dev->flush_status = 0; > + /* > + * Write to memory the value of flush_status before uvc_status_start() > + * is called again, s/,/./ > + */ > + smp_mb(); /* * From this point, the status URB is dead, and no asynchronous work is * queued. No event will be processed until uvc_status_start() is * called. Reset flush_status and make it visible to the asynchronous * handler before uvc_status_start() requeues the status URB. */ dev->flush_status = 0; smp_mb(); The barrier here is most likely overkill given that I'd be very surprised if a URB could be queued, followed by a work item being queued, without any memory barrier, but it's good to be explicit I suppose :-) > + Drop the blank line. > } > diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h > index df93db259312..6a9b72d6789e 100644 > --- a/drivers/media/usb/uvc/uvcvideo.h > +++ b/drivers/media/usb/uvc/uvcvideo.h > @@ -560,6 +560,7 @@ struct uvc_device { > struct usb_host_endpoint *int_ep; > struct urb *int_urb; > u8 *status; > + bool flush_status; > struct input_dev *input; > char input_phys[64]; > > > --- > base-commit: 0ec5a38bf8499f403f81cb81a0e3a60887d1993c > change-id: 20221212-uvc-race-09276ea68bf8
Hi Laurent On Mon, 2 Jan 2023 at 15:28, Laurent Pinchart <laurent.pinchart@ideasonboard.com> wrote: > > Hi Ricardo, > > Thank you for the patch. > > On Mon, Jan 02, 2023 at 01:53:38PM +0100, Ricardo Ribalda wrote: > > usb_kill_urb warranties that all the handlers are finished when it > > returns, but does not protect against threads that might be handling > > asynchronously the urb. > > > > For UVC, the function uvc_ctrl_status_event_async() takes care of > > control changes asynchronously. > > > > If the code is executed in the following order: > > > > CPU 0 CPU 1 > > ===== ===== > > uvc_status_complete() > > uvc_status_stop() > > uvc_ctrl_status_event_work() > > uvc_status_start() -> FAIL > > > > Then uvc_status_start will keep failing and this error will be shown: > > > > <4>[ 5.540139] URB 0000000000000000 submitted while active > > drivers/usb/core/urb.c:378 usb_submit_urb+0x4c3/0x528 > > > > Let's improve the current situation, by not re-submiting the urb if > > we are stopping the status event. Also process the queued work > > (if any) during stop. > > > > CPU 0 CPU 1 > > ===== ===== > > uvc_status_complete() > > uvc_status_stop() > > uvc_status_start() > > uvc_ctrl_status_event_work() -> FAIL > > > > Hopefully, with the usb layer protection this should be enough to cover > > all the cases. > > > > Cc: stable@vger.kernel.org > > Fixes: e5225c820c05 ("media: uvcvideo: Send a control event when a Control Change interrupt arrives") > > Reviewed-by: Yunke Cao <yunkec@chromium.org> > > Signed-off-by: Ricardo Ribalda <ribalda@chromium.org> > > --- > > uvc: Fix race condition on uvc > > > > Make sure that all the async work is finished when we stop the status urb. > > > > To: Yunke Cao <yunkec@chromium.org> > > To: Sergey Senozhatsky <senozhatsky@chromium.org> > > To: Max Staudt <mstaudt@google.com> > > To: Laurent Pinchart <laurent.pinchart@ideasonboard.com> > > To: Mauro Carvalho Chehab <mchehab@kernel.org> > > Cc: linux-media@vger.kernel.org > > Cc: linux-kernel@vger.kernel.org > > --- > > Changes in v5: > > - atomic_t do not impose barriers, use smp_mb() instead. (Thanks Laurent) > > - Add an extra cancel_work_sync(). > > - Link to v4: https://lore.kernel.org/r/20221212-uvc-race-v4-0-38d7075b03f5@chromium.org > > > > Changes in v4: > > - Replace bool with atomic_t to avoid compiler reordering. > > - First complete the async work and then kill the urb to avoid race (Thanks Laurent!) > > - Link to v3: https://lore.kernel.org/r/20221212-uvc-race-v3-0-954efc752c9a@chromium.org > > > > Changes in v3: > > - Remove the patch for dev->status, makes more sense in another series, and makes > > the zero day less nervous. > > - Update reviewed-by (thanks Yunke!). > > - Link to v2: https://lore.kernel.org/r/20221212-uvc-race-v2-0-54496cc3b8ab@chromium.org > > > > Changes in v2: > > - Add a patch for not kalloc dev->status > > - Redo the logic mechanism, so it also works with suspend (Thanks Yunke!) > > - Link to v1: https://lore.kernel.org/r/20221212-uvc-race-v1-0-c52e1783c31d@chromium.org > > --- > > drivers/media/usb/uvc/uvc_ctrl.c | 3 +++ > > drivers/media/usb/uvc/uvc_status.c | 36 ++++++++++++++++++++++++++++++++++++ > > drivers/media/usb/uvc/uvcvideo.h | 1 + > > 3 files changed, 40 insertions(+) > > > > diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c > > index c95a2229f4fa..5160facc8e20 100644 > > --- a/drivers/media/usb/uvc/uvc_ctrl.c > > +++ b/drivers/media/usb/uvc/uvc_ctrl.c > > @@ -1442,6 +1442,9 @@ static void uvc_ctrl_status_event_work(struct work_struct *work) > > > > uvc_ctrl_status_event(w->chain, w->ctrl, w->data); > > > > + if (dev->flush_status) > > + return; > > + > > /* Resubmit the URB. */ > > w->urb->interval = dev->int_ep->desc.bInterval; > > ret = usb_submit_urb(w->urb, GFP_KERNEL); > > diff --git a/drivers/media/usb/uvc/uvc_status.c b/drivers/media/usb/uvc/uvc_status.c > > index 7518ffce22ed..5911e63776e1 100644 > > --- a/drivers/media/usb/uvc/uvc_status.c > > +++ b/drivers/media/usb/uvc/uvc_status.c > > @@ -6,6 +6,7 @@ > > * Laurent Pinchart (laurent.pinchart@ideasonboard.com) > > */ > > > > +#include <asm/barrier.h> > > #include <linux/kernel.h> > > #include <linux/input.h> > > #include <linux/slab.h> > > @@ -309,5 +310,40 @@ int uvc_status_start(struct uvc_device *dev, gfp_t flags) > > > > void uvc_status_stop(struct uvc_device *dev) > > { > > + struct uvc_ctrl_work *w = &dev->async_ctrl; > > + > > + /* From this point, the status urb is not re-queued */ > > + dev->flush_status = 1; > > flush_status is now a bool, set it to true instead of 1. Same below for > false instead of 0. > > > + /* > > + * Make sure that the other CPUs are aware of the new value of > > + * flush_status. > > + */ > > + smp_mb(); > > /* > * Prevent to asynchronous control handler from requeing the URB. The > * barrier is needed the flush_status change is visible to other CPUs > * running the asynchronous handler before usb_kill_urb() is called > * below. > */ > dev->flush_status = true; > smp_mb(); > > > + > > + /* If there is any status event on the queue, process it. */ > > + if (cancel_work_sync(&w->work)) > > + uvc_ctrl_status_event(w->chain, w->ctrl, w->data); > > + > > + /* Kill the urb. */ > > usb_kill_urb(dev->int_urb); > > + > > + /* > > + * If an status event was queued between cancel_work_sync() and > > + * usb_kill_urb(), process it. > > + */ > > /* > * The URB completion handler may have queued asynchronous work. This > * won't resubmit the URB as flush_status is set, but it needs to be > * cancelled before returning or it could then race with a future > * uvc_status_start() call. > */ > > > + if (cancel_work_sync(&w->work)) > > + uvc_ctrl_status_event(w->chain, w->ctrl, w->data); > > I think I'd still prefer checking flush_status in > uvc_ctrl_status_event_async() and drop the event, as it would be simpler > here. uvc_status_stop() is called when the last user indicates it's not > interested in events anymore (by closing the device at the moment, > possibly by unsubscribing from events in the future), so dropping the > event isn't a problem as far as I can tell. What do you think ? Since the user do not have direct control to when uvc_status_stop is called, I prefer to have the second cancel_work_sync() here. That will give us more flexibility when we rework the power saving. Also, uvc_status_stop is done only once and not in IRQ context. the change you propose will make the irq context bigger.... Yes I know it is only one if, but I had to say something :P > > > + > > + /* > > + * From this point, there are no events on the queue and the status urb > > s/urb/URB/ > > > + * is dead, this is, no events will be queued until uvc_status_start() > > + * is called. > > + */ > > + dev->flush_status = 0; > > + /* > > + * Write to memory the value of flush_status before uvc_status_start() > > + * is called again, > > s/,/./ > > > + */ > > + smp_mb(); > > /* > * From this point, the status URB is dead, and no asynchronous work is > * queued. No event will be processed until uvc_status_start() is > * called. Reset flush_status and make it visible to the asynchronous > * handler before uvc_status_start() requeues the status URB. > */ > dev->flush_status = 0; > smp_mb(); > > The barrier here is most likely overkill given that I'd be very > surprised if a URB could be queued, followed by a work item being > queued, without any memory barrier, but it's good to be explicit I > suppose :-) > > > + > > Drop the blank line. > > > } > > diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h > > index df93db259312..6a9b72d6789e 100644 > > --- a/drivers/media/usb/uvc/uvcvideo.h > > +++ b/drivers/media/usb/uvc/uvcvideo.h > > @@ -560,6 +560,7 @@ struct uvc_device { > > struct usb_host_endpoint *int_ep; > > struct urb *int_urb; > > u8 *status; > > + bool flush_status; > > struct input_dev *input; > > char input_phys[64]; > > > > > > --- > > base-commit: 0ec5a38bf8499f403f81cb81a0e3a60887d1993c > > change-id: 20221212-uvc-race-09276ea68bf8 > > -- > Regards, > > Laurent Pinchart
diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c index c95a2229f4fa..5160facc8e20 100644 --- a/drivers/media/usb/uvc/uvc_ctrl.c +++ b/drivers/media/usb/uvc/uvc_ctrl.c @@ -1442,6 +1442,9 @@ static void uvc_ctrl_status_event_work(struct work_struct *work) uvc_ctrl_status_event(w->chain, w->ctrl, w->data); + if (dev->flush_status) + return; + /* Resubmit the URB. */ w->urb->interval = dev->int_ep->desc.bInterval; ret = usb_submit_urb(w->urb, GFP_KERNEL); diff --git a/drivers/media/usb/uvc/uvc_status.c b/drivers/media/usb/uvc/uvc_status.c index 7518ffce22ed..5911e63776e1 100644 --- a/drivers/media/usb/uvc/uvc_status.c +++ b/drivers/media/usb/uvc/uvc_status.c @@ -6,6 +6,7 @@ * Laurent Pinchart (laurent.pinchart@ideasonboard.com) */ +#include <asm/barrier.h> #include <linux/kernel.h> #include <linux/input.h> #include <linux/slab.h> @@ -309,5 +310,40 @@ int uvc_status_start(struct uvc_device *dev, gfp_t flags) void uvc_status_stop(struct uvc_device *dev) { + struct uvc_ctrl_work *w = &dev->async_ctrl; + + /* From this point, the status urb is not re-queued */ + dev->flush_status = 1; + /* + * Make sure that the other CPUs are aware of the new value of + * flush_status. + */ + smp_mb(); + + /* If there is any status event on the queue, process it. */ + if (cancel_work_sync(&w->work)) + uvc_ctrl_status_event(w->chain, w->ctrl, w->data); + + /* Kill the urb. */ usb_kill_urb(dev->int_urb); + + /* + * If an status event was queued between cancel_work_sync() and + * usb_kill_urb(), process it. + */ + if (cancel_work_sync(&w->work)) + uvc_ctrl_status_event(w->chain, w->ctrl, w->data); + + /* + * From this point, there are no events on the queue and the status urb + * is dead, this is, no events will be queued until uvc_status_start() + * is called. + */ + dev->flush_status = 0; + /* + * Write to memory the value of flush_status before uvc_status_start() + * is called again, + */ + smp_mb(); + } diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h index df93db259312..6a9b72d6789e 100644 --- a/drivers/media/usb/uvc/uvcvideo.h +++ b/drivers/media/usb/uvc/uvcvideo.h @@ -560,6 +560,7 @@ struct uvc_device { struct usb_host_endpoint *int_ep; struct urb *int_urb; u8 *status; + bool flush_status; struct input_dev *input; char input_phys[64];