Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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

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: 1a4541b68e25 ("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>

authored by

Hans Verkuil and committed by
Linus Walleij
b7badd75 b19a1d8f

+15 -4
+15 -4
drivers/pinctrl/bcm/pinctrl-bcm2835.c
··· 90 90 struct pinctrl_gpio_range gpio_range; 91 91 92 92 raw_spinlock_t irq_lock[BCM2835_NUM_BANKS]; 93 + /* Protect FSEL registers */ 94 + spinlock_t fsel_lock; 93 95 }; 94 96 95 97 /* pins are just named GPIO0..GPIO53 */ ··· 286 284 struct bcm2835_pinctrl *pc, unsigned pin, 287 285 enum bcm2835_fsel fsel) 288 286 { 289 - u32 val = bcm2835_gpio_rd(pc, FSEL_REG(pin)); 290 - enum bcm2835_fsel cur = (val >> FSEL_SHIFT(pin)) & BCM2835_FSEL_MASK; 287 + u32 val; 288 + enum bcm2835_fsel cur; 289 + unsigned long flags; 290 + 291 + spin_lock_irqsave(&pc->fsel_lock, flags); 292 + val = bcm2835_gpio_rd(pc, FSEL_REG(pin)); 293 + cur = (val >> FSEL_SHIFT(pin)) & BCM2835_FSEL_MASK; 291 294 292 295 dev_dbg(pc->dev, "read %08x (%u => %s)\n", val, pin, 293 - bcm2835_functions[cur]); 296 + bcm2835_functions[cur]); 294 297 295 298 if (cur == fsel) 296 - return; 299 + goto unlock; 297 300 298 301 if (cur != BCM2835_FSEL_GPIO_IN && fsel != BCM2835_FSEL_GPIO_IN) { 299 302 /* always transition through GPIO_IN */ ··· 316 309 dev_dbg(pc->dev, "write %08x (%u <= %s)\n", val, pin, 317 310 bcm2835_functions[fsel]); 318 311 bcm2835_gpio_wr(pc, FSEL_REG(pin), val); 312 + 313 + unlock: 314 + spin_unlock_irqrestore(&pc->fsel_lock, flags); 319 315 } 320 316 321 317 static int bcm2835_gpio_direction_input(struct gpio_chip *chip, unsigned offset) ··· 1258 1248 pc->gpio_chip = *pdata->gpio_chip; 1259 1249 pc->gpio_chip.parent = dev; 1260 1250 1251 + spin_lock_init(&pc->fsel_lock); 1261 1252 for (i = 0; i < BCM2835_NUM_BANKS; i++) { 1262 1253 unsigned long events; 1263 1254 unsigned offset;