pinctrl-bcm2835.c: fix race condition when setting gpio dir

[ Upstream commit b7badd752d ]

In the past setting the pin direction called pinctrl_gpio_direction()
which uses a mutex to serialize this. That was changed to set the
direction directly in the pin controller driver, but that lost the
serialization mechanism. Since the direction of multiple pins are in
the same register you can have a race condition, something that was
in fact observed with the cec-gpio driver.

Add a new spinlock to serialize writing to the FSEL registers.

Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Fixes: 1a4541b68e ("pinctrl-bcm2835: don't call pinctrl_gpio_direction()")
Link: https://lore.kernel.org/r/4302b66b-ca20-0f19-d2aa-ee8661118863@xs4all.nl
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
This commit is contained in:
Hans Verkuil 2023-04-20 14:47:05 +02:00 committed by Greg Kroah-Hartman
parent 810003cea6
commit 71be862f80
1 changed files with 15 additions and 4 deletions

View File

@ -90,6 +90,8 @@ struct bcm2835_pinctrl {
struct pinctrl_gpio_range gpio_range;
raw_spinlock_t irq_lock[BCM2835_NUM_BANKS];
/* Protect FSEL registers */
spinlock_t fsel_lock;
};
/* pins are just named GPIO0..GPIO53 */
@ -284,14 +286,19 @@ static inline void bcm2835_pinctrl_fsel_set(
struct bcm2835_pinctrl *pc, unsigned pin,
enum bcm2835_fsel fsel)
{
u32 val = bcm2835_gpio_rd(pc, FSEL_REG(pin));
enum bcm2835_fsel cur = (val >> FSEL_SHIFT(pin)) & BCM2835_FSEL_MASK;
u32 val;
enum bcm2835_fsel cur;
unsigned long flags;
spin_lock_irqsave(&pc->fsel_lock, flags);
val = bcm2835_gpio_rd(pc, FSEL_REG(pin));
cur = (val >> FSEL_SHIFT(pin)) & BCM2835_FSEL_MASK;
dev_dbg(pc->dev, "read %08x (%u => %s)\n", val, pin,
bcm2835_functions[cur]);
bcm2835_functions[cur]);
if (cur == fsel)
return;
goto unlock;
if (cur != BCM2835_FSEL_GPIO_IN && fsel != BCM2835_FSEL_GPIO_IN) {
/* always transition through GPIO_IN */
@ -309,6 +316,9 @@ static inline void bcm2835_pinctrl_fsel_set(
dev_dbg(pc->dev, "write %08x (%u <= %s)\n", val, pin,
bcm2835_functions[fsel]);
bcm2835_gpio_wr(pc, FSEL_REG(pin), val);
unlock:
spin_unlock_irqrestore(&pc->fsel_lock, flags);
}
static int bcm2835_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
@ -1248,6 +1258,7 @@ static int bcm2835_pinctrl_probe(struct platform_device *pdev)
pc->gpio_chip = *pdata->gpio_chip;
pc->gpio_chip.parent = dev;
spin_lock_init(&pc->fsel_lock);
for (i = 0; i < BCM2835_NUM_BANKS; i++) {
unsigned long events;
unsigned offset;