Received: by 2002:a05:6a10:6ea6:0:0:0:0 with SMTP id ie38csp637823pxb; Wed, 30 Jun 2021 13:15:55 -0700 (PDT) X-Google-Smtp-Source: ABdhPJz6U4MkUU8S504vU2Z0BfUNU0XrPXz0ojRAfqJ+ZPinIfM3cA3GCOZ/HLHM5cYGcJ3GMsrG X-Received: by 2002:a05:6402:290c:: with SMTP id ee12mr3420925edb.161.1625084155055; Wed, 30 Jun 2021 13:15:55 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1625084155; cv=none; d=google.com; s=arc-20160816; b=hWiiGtoGZWUz7IuPZs82HjdHr0K88SWCmdfPTSmc6Fjs5W9FAlYhqOH6XEy9IIlyVq IIOZX1ecmrYkzUAnzgiAzD35CHITytRt7QvK9TdFlOYnXgmfvGkl7FLXILVNaz22lHzK PJivNSEMcQVDfwGkC80mf2v3H47Bhtp4JXgZV95plS1ddfD3EB7ufhLHZ2FkjVIErIh4 8V2g0TOWtlBOWDY3W+JBGv8rgnFer3TXIWmRwMNDE1VkL9QAtCEepyjjgL585ZzoiSAo 5KPnkQJXRuL5bNjhLWYcbTK6DCJG3BOVQ/bIH+CaARKGsv01RaJs2CCEnOnYLQ31iky5 2O8w== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :message-id:date:subject:cc:to:from; bh=zF99SpKzNIOylAHFl8p6aq5NejlfrRZbuBZkd+LNyiE=; b=c2eJ3bXi8+LIhtJn2GBZLzFUHjM5GVC5RDCZWmyNkCKOQY2o6wOMEm39NXs9GbPCGa I5J2//oEx4Dq6L5h/clS8/QZ6tkFVCwQw/V5QTLJnvBlOTWuSsUW683i/Gl0vtXLcL4n NWJsMOzXUQmFLm6bImo6I6JFukgJeHlZI0ZByJke4eOmYAD+xUwIWovZrQyiq4QqmFjh jykWVF5f8U6Gv+Jpdpt7LZ7JKyUtptVmfZDXMO5w9VTKim/c3UMMdF4bow9zEiTAWmlK Tb++ByLDb3KtvBB3flR+eGmzAFSrctPjb19tz0aZFBSSGOG0Xi+zX98nEiNw1B3JbH78 brZQ== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [23.128.96.18]) by mx.google.com with ESMTP id el21si21431661ejc.619.2021.06.30.13.15.30; Wed, 30 Jun 2021 13:15:55 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) client-ip=23.128.96.18; Authentication-Results: mx.google.com; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S234040AbhF3UQy (ORCPT + 99 others); Wed, 30 Jun 2021 16:16:54 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:47396 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S233847AbhF3UQx (ORCPT ); Wed, 30 Jun 2021 16:16:53 -0400 Received: from sipsolutions.net (s3.sipsolutions.net [IPv6:2a01:4f8:191:4433::2]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 60A4DC061756; Wed, 30 Jun 2021 13:14:24 -0700 (PDT) Received: by sipsolutions.net with esmtpsa (TLS1.3:ECDHE_X25519__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim 4.94.2) (envelope-from ) id 1lygb9-00DuUv-Ok; Wed, 30 Jun 2021 22:14:15 +0200 From: Johannes Berg To: linux-leds@vger.kernel.org Cc: Pavel Machek , Boqun Feng , Peter Zijlstra , Andrea Righi , linux-kernel@vger.kernel.org, Johannes Berg Subject: [RFC PATCH] leds: trigger: use RCU to protect the led_cdevs list Date: Wed, 30 Jun 2021 22:14:11 +0200 Message-Id: <20210630221411.30af93c6dce6.I1a28b342d2d52cdeeeb81ecd6020c25cbf1dbfc0@changeid> X-Mailer: git-send-email 2.31.1 MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org From: Johannes Berg Even with the previous commit 27af8e2c90fb ("leds: trigger: fix potential deadlock with libata") to this file, we still get lockdep unhappy, and Boqun explained the report here: https://lore.kernel.org/r/YNA+d1X4UkoQ7g8a@boqun-archlinux Effectively, this means that the read_lock_irqsave() isn't enough here because another CPU might be trying to do a write lock, and thus block the readers. This is all pretty messy, but it doesn't seem right that the LEDs framework imposes some locking requirements on users, in particular we'd have to make the spinlock in the iwlwifi driver always disable IRQs, even if we don't need that for any other reason, just to avoid this deadlock. Since writes to the led_cdevs list are rare (and are done by userspace), just switch the list to RCU. This costs a synchronize_rcu() at removal time so we can ensure things are correct, but that seems like a small price to pay for getting lock-free iterations and no deadlocks (nor any locking requirements imposed on users.) Signed-off-by: Johannes Berg --- RFC for now because I haven't actually tested it in the situation that caused the referenced lockdep report, but it seems fairly obvious that it'll prevent it since only the read-side was involved there. --- drivers/leds/led-triggers.c | 41 +++++++++++++++++++------------------ include/linux/leds.h | 2 +- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c index 4e7b78a84149..072491d3e17b 100644 --- a/drivers/leds/led-triggers.c +++ b/drivers/leds/led-triggers.c @@ -157,7 +157,6 @@ EXPORT_SYMBOL_GPL(led_trigger_read); /* Caller must ensure led_cdev->trigger_lock held */ int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig) { - unsigned long flags; char *event = NULL; char *envp[2]; const char *name; @@ -171,10 +170,13 @@ int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig) /* Remove any existing trigger */ if (led_cdev->trigger) { - write_lock_irqsave(&led_cdev->trigger->leddev_list_lock, flags); - list_del(&led_cdev->trig_list); - write_unlock_irqrestore(&led_cdev->trigger->leddev_list_lock, - flags); + spin_lock(&led_cdev->trigger->leddev_list_lock); + list_del_rcu(&led_cdev->trig_list); + spin_unlock(&led_cdev->trigger->leddev_list_lock); + + /* ensure it's no longer visible on the led_cdevs list */ + synchronize_rcu(); + cancel_work_sync(&led_cdev->set_brightness_work); led_stop_software_blink(led_cdev); if (led_cdev->trigger->deactivate) @@ -186,9 +188,9 @@ int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig) led_set_brightness(led_cdev, LED_OFF); } if (trig) { - write_lock_irqsave(&trig->leddev_list_lock, flags); - list_add_tail(&led_cdev->trig_list, &trig->led_cdevs); - write_unlock_irqrestore(&trig->leddev_list_lock, flags); + spin_lock(&trig->leddev_list_lock); + list_add_tail_rcu(&led_cdev->trig_list, &trig->led_cdevs); + spin_unlock(&trig->leddev_list_lock); led_cdev->trigger = trig; if (trig->activate) @@ -223,9 +225,10 @@ int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig) trig->deactivate(led_cdev); err_activate: - write_lock_irqsave(&led_cdev->trigger->leddev_list_lock, flags); - list_del(&led_cdev->trig_list); - write_unlock_irqrestore(&led_cdev->trigger->leddev_list_lock, flags); + spin_lock(&led_cdev->trigger->leddev_list_lock); + list_del_rcu(&led_cdev->trig_list); + spin_unlock(&led_cdev->trigger->leddev_list_lock); + synchronize_rcu(); led_cdev->trigger = NULL; led_cdev->trigger_data = NULL; led_set_brightness(led_cdev, LED_OFF); @@ -285,7 +288,7 @@ int led_trigger_register(struct led_trigger *trig) struct led_classdev *led_cdev; struct led_trigger *_trig; - rwlock_init(&trig->leddev_list_lock); + spin_lock_init(&trig->leddev_list_lock); INIT_LIST_HEAD(&trig->led_cdevs); down_write(&triggers_list_lock); @@ -378,15 +381,14 @@ void led_trigger_event(struct led_trigger *trig, enum led_brightness brightness) { struct led_classdev *led_cdev; - unsigned long flags; if (!trig) return; - read_lock_irqsave(&trig->leddev_list_lock, flags); - list_for_each_entry(led_cdev, &trig->led_cdevs, trig_list) + rcu_read_lock(); + list_for_each_entry_rcu(led_cdev, &trig->led_cdevs, trig_list) led_set_brightness(led_cdev, brightness); - read_unlock_irqrestore(&trig->leddev_list_lock, flags); + rcu_read_unlock(); } EXPORT_SYMBOL_GPL(led_trigger_event); @@ -397,20 +399,19 @@ static void led_trigger_blink_setup(struct led_trigger *trig, int invert) { struct led_classdev *led_cdev; - unsigned long flags; if (!trig) return; - read_lock_irqsave(&trig->leddev_list_lock, flags); - list_for_each_entry(led_cdev, &trig->led_cdevs, trig_list) { + rcu_read_lock(); + list_for_each_entry_rcu(led_cdev, &trig->led_cdevs, trig_list) { if (oneshot) led_blink_set_oneshot(led_cdev, delay_on, delay_off, invert); else led_blink_set(led_cdev, delay_on, delay_off); } - read_unlock_irqrestore(&trig->leddev_list_lock, flags); + rcu_read_unlock(); } void led_trigger_blink(struct led_trigger *trig, diff --git a/include/linux/leds.h b/include/linux/leds.h index 329fd914cf24..fa59326b0ad9 100644 --- a/include/linux/leds.h +++ b/include/linux/leds.h @@ -354,7 +354,7 @@ struct led_trigger { struct led_hw_trigger_type *trigger_type; /* LEDs under control by this trigger (for simple triggers) */ - rwlock_t leddev_list_lock; + spinlock_t leddev_list_lock; struct list_head led_cdevs; /* Link to next registered trigger */ -- 2.31.1