mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	phy: improve safety of fixed-phy MII register reading
There is no prevention of a concurrent call to both fixed_mdio_read() and fixed_phy_update_state(), which can result in the state being modified while it's being inspected. Fix this by using a seqcount to detect modifications, and memcpy()ing the state. We remain slightly naughty here, calling link_update() and updating the link status within the read-side loop - which would need rework of the design to change. Reviewed-by: Florian Fainelli <f.fainelli@gmail.com> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
		
							parent
							
								
									37688e3f53
								
							
						
					
					
						commit
						bf7afb29d5
					
				
					 1 changed files with 21 additions and 7 deletions
				
			
		| 
						 | 
				
			
			@ -23,6 +23,7 @@
 | 
			
		|||
#include <linux/slab.h>
 | 
			
		||||
#include <linux/of.h>
 | 
			
		||||
#include <linux/gpio.h>
 | 
			
		||||
#include <linux/seqlock.h>
 | 
			
		||||
 | 
			
		||||
#include "swphy.h"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -34,6 +35,7 @@ struct fixed_mdio_bus {
 | 
			
		|||
struct fixed_phy {
 | 
			
		||||
	int addr;
 | 
			
		||||
	struct phy_device *phydev;
 | 
			
		||||
	seqcount_t seqcount;
 | 
			
		||||
	struct fixed_phy_status status;
 | 
			
		||||
	int (*link_update)(struct net_device *, struct fixed_phy_status *);
 | 
			
		||||
	struct list_head node;
 | 
			
		||||
| 
						 | 
				
			
			@ -58,13 +60,21 @@ static int fixed_mdio_read(struct mii_bus *bus, int phy_addr, int reg_num)
 | 
			
		|||
 | 
			
		||||
	list_for_each_entry(fp, &fmb->phys, node) {
 | 
			
		||||
		if (fp->addr == phy_addr) {
 | 
			
		||||
			/* Issue callback if user registered it. */
 | 
			
		||||
			if (fp->link_update) {
 | 
			
		||||
				fp->link_update(fp->phydev->attached_dev,
 | 
			
		||||
						&fp->status);
 | 
			
		||||
				fixed_phy_update(fp);
 | 
			
		||||
			}
 | 
			
		||||
			return swphy_read_reg(reg_num, &fp->status);
 | 
			
		||||
			struct fixed_phy_status state;
 | 
			
		||||
			int s;
 | 
			
		||||
 | 
			
		||||
			do {
 | 
			
		||||
				s = read_seqcount_begin(&fp->seqcount);
 | 
			
		||||
				/* Issue callback if user registered it. */
 | 
			
		||||
				if (fp->link_update) {
 | 
			
		||||
					fp->link_update(fp->phydev->attached_dev,
 | 
			
		||||
							&fp->status);
 | 
			
		||||
					fixed_phy_update(fp);
 | 
			
		||||
				}
 | 
			
		||||
				state = fp->status;
 | 
			
		||||
			} while (read_seqcount_retry(&fp->seqcount, s));
 | 
			
		||||
 | 
			
		||||
			return swphy_read_reg(reg_num, &state);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -116,6 +126,7 @@ int fixed_phy_update_state(struct phy_device *phydev,
 | 
			
		|||
 | 
			
		||||
	list_for_each_entry(fp, &fmb->phys, node) {
 | 
			
		||||
		if (fp->addr == phydev->mdio.addr) {
 | 
			
		||||
			write_seqcount_begin(&fp->seqcount);
 | 
			
		||||
#define _UPD(x) if (changed->x) \
 | 
			
		||||
	fp->status.x = status->x
 | 
			
		||||
			_UPD(link);
 | 
			
		||||
| 
						 | 
				
			
			@ -125,6 +136,7 @@ int fixed_phy_update_state(struct phy_device *phydev,
 | 
			
		|||
			_UPD(asym_pause);
 | 
			
		||||
#undef _UPD
 | 
			
		||||
			fixed_phy_update(fp);
 | 
			
		||||
			write_seqcount_end(&fp->seqcount);
 | 
			
		||||
			return 0;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -149,6 +161,8 @@ int fixed_phy_add(unsigned int irq, int phy_addr,
 | 
			
		|||
	if (!fp)
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
 | 
			
		||||
	seqcount_init(&fp->seqcount);
 | 
			
		||||
 | 
			
		||||
	if (irq != PHY_POLL)
 | 
			
		||||
		fmb->mii_bus->irq[phy_addr] = irq;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue