Received: by 2002:a25:6193:0:0:0:0:0 with SMTP id v141csp836691ybb; Wed, 1 Apr 2020 10:30:22 -0700 (PDT) X-Google-Smtp-Source: ADFU+vtc3wP0lEfXR/Adk9KLEN4UnyPppvFsDbBTObQ2geM/0EpiZLK15w5iCwvjXaZ+A3r55w+7 X-Received: by 2002:a4a:da48:: with SMTP id f8mr17757901oou.44.1585762221835; Wed, 01 Apr 2020 10:30:21 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1585762221; cv=none; d=google.com; s=arc-20160816; b=o0/7tZik5B9EnYzqZLhXa1g8qNdlOOnw3X+sYtlpygEMdzyjsUclaJGO1PUkxl4Zn7 9ElhLiQ1kbGTmZMKclvcbYbEmrYgctb14ADkbhnuVXvqZQCrORVW5QlAAS5kao2HgI1G /QCytQthffvBKqwG/SUdISVDGuQpG3P5YMNz3caA3YeJZvvZh/tRTmH246bPrM7RAq/V e7Joo45d1xvuNEwvGWsiGYLLb8MWkh+wiERDcYAC/GeLYgvhKiDUoNZ29LLHQC/ni9lT TVQw8gRfR0EZFJTz1yEYGBCWrdPWjaaPmHcd/1j27lSubzmIAZoRnMEGD86WYwPESYRD pX+Q== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:content-transfer-encoding:mime-version :message-id:date:subject:cc:to:from:dkim-signature; bh=NfVkgcJwROCOa0Gvoa39DfTa9hL2ePWFTZdlOJfOves=; b=Pre32lk+dttPj0Jx6YZ63xgs1NziGnH8wMmVcnU4x2dd4YBUZC6Pl5kvgS86vdZSIX aejpdMU0PwJrlA+mbBi14pWK3P7oR17Evv18mdkUrESk4Ia4Hc88QBrbkPFMQenpinau D8rXG6Y5nBthivKFR2HnxVP5jgJfNRFVvutT922SUBEkMXR8KV33D8t12hnt/EH/Zjab wESQbEuKA23o8xuw3yWc1nFrOSMR1RXxOd2kyZRZjElFexmYAmlyGDLgq5iVqgSZM88H ULUPIxb7R+E+zpEEvVh+5CagMB4eFY54JV1F2hjLHWZubZ5/eO4fyYnatpn+/SJ8rZ0j 7ZOg== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@pqgruber.com header.s=mail header.b=0b7o4o8o; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=pqgruber.com Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id k194si1058549oib.251.2020.04.01.10.29.55; Wed, 01 Apr 2020 10:30:21 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=pass header.i=@pqgruber.com header.s=mail header.b=0b7o4o8o; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=pqgruber.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1732535AbgDARBg (ORCPT + 99 others); Wed, 1 Apr 2020 13:01:36 -0400 Received: from mail.pqgruber.com ([52.59.78.55]:57696 "EHLO mail.pqgruber.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1732304AbgDARBg (ORCPT ); Wed, 1 Apr 2020 13:01:36 -0400 Received: from workstation.tuxnet (213-47-165-233.cable.dynamic.surfer.at [213.47.165.233]) by mail.pqgruber.com (Postfix) with ESMTPSA id 708BCC45B56; Wed, 1 Apr 2020 19:01:33 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pqgruber.com; s=mail; t=1585760493; bh=NfVkgcJwROCOa0Gvoa39DfTa9hL2ePWFTZdlOJfOves=; h=From:To:Cc:Subject:Date:From; b=0b7o4o8opumpRq8uti0QY+/V4D9f9CZxr/+OVdWXHS6y6lCGnYY4WrG1x255L6bgW Qs77vBu629FCX61mDhm4DFrnYDaXL0p0vhjTqJLWocA4+eT7uogqDKA9y56w5EbMfW RYcHqBpxnepFTV/QkvqTrMMUVUGpTiJm1HZf5524= From: Clemens Gruber To: linux-pwm@vger.kernel.org Cc: Thierry Reding , Andy Shevchenko , Matthias Schiffer , =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= , linux-kernel@vger.kernel.org, Sven Van Asbroeck , YueHaibing , Mika Westerberg , Clemens Gruber Subject: [PATCH v2 REBASED] pwm: pca9685: fix pwm/gpio inter-operation Date: Wed, 1 Apr 2020 19:01:06 +0200 Message-Id: <20200401170106.134037-1-clemens.gruber@pqgruber.com> X-Mailer: git-send-email 2.26.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org From: Sven Van Asbroeck This driver allows pwms to be requested as gpios via gpiolib. Obviously, it should not be allowed to request a gpio when its corresponding pwm is already requested (and vice versa). So it requires some exclusion code. Given that the pwm and gpio cores are not synchronized with respect to each other, this exclusion code will also require proper synchronization. Such a mechanism was in place, but was inadvertently removed by Uwe's clean-up patch. Upon revisiting the synchronization mechanism, we found that theoretically, it could allow two threads to successfully request conflicting pwms / gpios. Replace with a bitmap which tracks pwm in-use, plus a mutex. As long as pwm and gpio's respective request/free functions modify the in-use bitmap while holding the mutex, proper synchronization will be guaranteed. Reported-by: YueHaibing Fixes: e926b12c611c ("pwm: Clear chip_data in pwm_put()") Cc: Mika Westerberg Cc: Uwe Kleine-König Cc: YueHaibing Link: https://lkml.org/lkml/2019/5/31/963 Signed-off-by: Sven Van Asbroeck Reviewed-by: Mika Westerberg Tested-by: Clemens Gruber [cg: Tested on an i.MX6Q board with two NXP PCA9685 chips] --- drivers/pwm/pwm-pca9685.c | 85 ++++++++++++++++++++++----------------- 1 file changed, 48 insertions(+), 37 deletions(-) diff --git a/drivers/pwm/pwm-pca9685.c b/drivers/pwm/pwm-pca9685.c index 20bdc59a0cbb..76cd22bd6614 100644 --- a/drivers/pwm/pwm-pca9685.c +++ b/drivers/pwm/pwm-pca9685.c @@ -20,6 +20,7 @@ #include #include #include +#include /* * Because the PCA9685 has only one prescaler per chip, changing the period of @@ -73,6 +74,7 @@ struct pca9685 { #if IS_ENABLED(CONFIG_GPIOLIB) struct mutex lock; struct gpio_chip gpio; + DECLARE_BITMAP(pwms_inuse, PCA9685_MAXCHAN + 1); #endif }; @@ -82,51 +84,51 @@ static inline struct pca9685 *to_pca(struct pwm_chip *chip) } #if IS_ENABLED(CONFIG_GPIOLIB) -static int pca9685_pwm_gpio_request(struct gpio_chip *gpio, unsigned int offset) +static bool pca9685_pwm_test_and_set_inuse(struct pca9685 *pca, int pwm_idx) { - struct pca9685 *pca = gpiochip_get_data(gpio); - struct pwm_device *pwm; + bool is_inuse; mutex_lock(&pca->lock); - - pwm = &pca->chip.pwms[offset]; - - if (pwm->flags & (PWMF_REQUESTED | PWMF_EXPORTED)) { - mutex_unlock(&pca->lock); - return -EBUSY; + if (pwm_idx >= PCA9685_MAXCHAN) { + /* + * "all LEDs" channel: + * pretend already in use if any of the PWMs are requested + */ + if (!bitmap_empty(pca->pwms_inuse, PCA9685_MAXCHAN)) { + is_inuse = true; + goto out; + } + } else { + /* + * regular channel: + * pretend already in use if the "all LEDs" channel is requested + */ + if (test_bit(PCA9685_MAXCHAN, pca->pwms_inuse)) { + is_inuse = true; + goto out; + } } - - pwm_set_chip_data(pwm, (void *)1); - + is_inuse = test_and_set_bit(pwm_idx, pca->pwms_inuse); +out: mutex_unlock(&pca->lock); - pm_runtime_get_sync(pca->chip.dev); - return 0; + return is_inuse; } -static bool pca9685_pwm_is_gpio(struct pca9685 *pca, struct pwm_device *pwm) +static void pca9685_pwm_clear_inuse(struct pca9685 *pca, int pwm_idx) { - bool is_gpio = false; - mutex_lock(&pca->lock); + clear_bit(pwm_idx, pca->pwms_inuse); + mutex_unlock(&pca->lock); +} - if (pwm->hwpwm >= PCA9685_MAXCHAN) { - unsigned int i; - - /* - * Check if any of the GPIOs are requested and in that case - * prevent using the "all LEDs" channel. - */ - for (i = 0; i < pca->gpio.ngpio; i++) - if (gpiochip_is_requested(&pca->gpio, i)) { - is_gpio = true; - break; - } - } else if (pwm_get_chip_data(pwm)) { - is_gpio = true; - } +static int pca9685_pwm_gpio_request(struct gpio_chip *gpio, unsigned int offset) +{ + struct pca9685 *pca = gpiochip_get_data(gpio); - mutex_unlock(&pca->lock); - return is_gpio; + if (pca9685_pwm_test_and_set_inuse(pca, offset)) + return -EBUSY; + pm_runtime_get_sync(pca->chip.dev); + return 0; } static int pca9685_pwm_gpio_get(struct gpio_chip *gpio, unsigned int offset) @@ -161,6 +163,7 @@ static void pca9685_pwm_gpio_free(struct gpio_chip *gpio, unsigned int offset) pca9685_pwm_gpio_set(gpio, offset, 0); pm_runtime_put(pca->chip.dev); + pca9685_pwm_clear_inuse(pca, offset); } static int pca9685_pwm_gpio_get_direction(struct gpio_chip *chip, @@ -212,12 +215,17 @@ static int pca9685_pwm_gpio_probe(struct pca9685 *pca) return devm_gpiochip_add_data(dev, &pca->gpio, pca); } #else -static inline bool pca9685_pwm_is_gpio(struct pca9685 *pca, - struct pwm_device *pwm) +static inline bool pca9685_pwm_test_and_set_inuse(struct pca9685 *pca, + int pwm_idx) { return false; } +static inline void +pca9685_pwm_clear_inuse(struct pca9685 *pca, int pwm_idx) +{ +} + static inline int pca9685_pwm_gpio_probe(struct pca9685 *pca) { return 0; @@ -399,7 +407,7 @@ static int pca9685_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) { struct pca9685 *pca = to_pca(chip); - if (pca9685_pwm_is_gpio(pca, pwm)) + if (pca9685_pwm_test_and_set_inuse(pca, pwm->hwpwm)) return -EBUSY; pm_runtime_get_sync(chip->dev); @@ -408,8 +416,11 @@ static int pca9685_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) static void pca9685_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) { + struct pca9685 *pca = to_pca(chip); + pca9685_pwm_disable(chip, pwm); pm_runtime_put(chip->dev); + pca9685_pwm_clear_inuse(pca, pwm->hwpwm); } static const struct pwm_ops pca9685_pwm_ops = { -- 2.26.0