[PATCH] wifi: mt76: mt7996: use spin_lock_irqsave for reg_lock

Valera Kozlov <[email protected]>
Newsgroups org.kernel.vger.linux-wireless
Message-ID <[email protected]>
mt7996_rr()/wr()/rmw() and mt7996_memcpy_fromio() take dev->reg_lock
via spin_lock_bh() when accessing remapped registers. This is not
safe against hard-IRQ context, which can lead to race conditions and
kernel panics on some platforms.

MediaTek's own vendor tree independently found and fixed this same
issue, reporting it as causing AP panics via Asynchronous SError
Interrupt during normal WiFi operation:
https://github.com/mediatek/mtk-openwrt-feeds/commit/3edfc0cbf7dc666a0b051b54f51634d6ff2a3be6

Switch to spin_lock_irqsave()/spin_unlock_irqrestore() to make the
locking safe against hard-IRQ context.

Signed-off-by: Valera Kozlov <[email protected]>
---
 mt7996/mmio.c | 20 ++++++++++++--------
 1 file changed, 12 insertions(+), 8 deletions(-)

diff --git a/mt7996/mmio.c b/mt7996/mmio.c
index 1f7355cb..67ed157b 100644
--- a/mt7996/mmio.c
+++ b/mt7996/mmio.c
@@ -365,29 +365,31 @@ void mt7996_memcpy_fromio(struct mt7996_dev *dev, void *buf, u32 offset,
 			  size_t len)
 {
 	u32 addr = __mt7996_reg_addr(dev, offset);
+	unsigned long flags;
 
 	if (addr) {
 		memcpy_fromio(buf, dev->mt76.mmio.regs + addr, len);
 		return;
 	}
 
-	spin_lock_bh(&dev->reg_lock);
+	spin_lock_irqsave(&dev->reg_lock, flags);
 	memcpy_fromio(buf, dev->mt76.mmio.regs +
 			   __mt7996_reg_remap_addr(dev, offset), len);
-	spin_unlock_bh(&dev->reg_lock);
+	spin_unlock_irqrestore(&dev->reg_lock, flags);
 }
 
 static u32 mt7996_rr(struct mt76_dev *mdev, u32 offset)
 {
 	struct mt7996_dev *dev = container_of(mdev, struct mt7996_dev, mt76);
 	u32 addr = __mt7996_reg_addr(dev, offset), val;
+	unsigned long flags;
 
 	if (addr)
 		return dev->bus_ops->rr(mdev, addr);
 
-	spin_lock_bh(&dev->reg_lock);
+	spin_lock_irqsave(&dev->reg_lock, flags);
 	val = dev->bus_ops->rr(mdev, __mt7996_reg_remap_addr(dev, offset));
-	spin_unlock_bh(&dev->reg_lock);
+	spin_unlock_irqrestore(&dev->reg_lock, flags);
 
 	return val;
 }
@@ -396,28 +398,30 @@ static void mt7996_wr(struct mt76_dev *mdev, u32 offset, u32 val)
 {
 	struct mt7996_dev *dev = container_of(mdev, struct mt7996_dev, mt76);
 	u32 addr = __mt7996_reg_addr(dev, offset);
+	unsigned long flags;
 
 	if (addr) {
 		dev->bus_ops->wr(mdev, addr, val);
 		return;
 	}
 
-	spin_lock_bh(&dev->reg_lock);
+	spin_lock_irqsave(&dev->reg_lock, flags);
 	dev->bus_ops->wr(mdev, __mt7996_reg_remap_addr(dev, offset), val);
-	spin_unlock_bh(&dev->reg_lock);
+	spin_unlock_irqrestore(&dev->reg_lock, flags);
 }
 
 static u32 mt7996_rmw(struct mt76_dev *mdev, u32 offset, u32 mask, u32 val)
 {
 	struct mt7996_dev *dev = container_of(mdev, struct mt7996_dev, mt76);
 	u32 addr = __mt7996_reg_addr(dev, offset);
+	unsigned long flags;
 
 	if (addr)
 		return dev->bus_ops->rmw(mdev, addr, mask, val);
 
-	spin_lock_bh(&dev->reg_lock);
+	spin_lock_irqsave(&dev->reg_lock, flags);
 	val = dev->bus_ops->rmw(mdev, __mt7996_reg_remap_addr(dev, offset), mask, val);
-	spin_unlock_bh(&dev->reg_lock);
+	spin_unlock_irqrestore(&dev->reg_lock, flags);
 
 	return val;
 }
-- 
2.53.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.