net/fec: fix the .remove code
The .remove code is broken in several ways. - mdiobus_unregister() is called twice for the same object in case of dual FEC - phy_disconnect() is being called when the PHY is already disconnected - the requested IRQ(s) are not freed - fec_stop() is being called with the inteface already stopped All of those lead to kernel crashes if the remove function is actually used. Signed-off-by: Lothar Waßmann <LW@KARO-electronics.de> Tested-by: Shawn Guo <shawn.guo@linaro.org> Acked-by: Shawn Guo <shawn.guo@linaro.org> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
		
					parent
					
						
							
								42431dc24d
							
						
					
				
			
			
				commit
				
					
						e163cc97f9
					
				
			
		
					 1 changed files with 22 additions and 9 deletions
				
			
		| 
						 | 
					@ -259,6 +259,8 @@ struct fec_enet_private {
 | 
				
			||||||
/* Transmitter timeout */
 | 
					/* Transmitter timeout */
 | 
				
			||||||
#define TX_TIMEOUT (2 * HZ)
 | 
					#define TX_TIMEOUT (2 * HZ)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int mii_cnt;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void *swap_buffer(void *bufaddr, int len)
 | 
					static void *swap_buffer(void *bufaddr, int len)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	int i;
 | 
						int i;
 | 
				
			||||||
| 
						 | 
					@ -1040,8 +1042,12 @@ static int fec_enet_mii_init(struct platform_device *pdev)
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	if ((id_entry->driver_data & FEC_QUIRK_ENET_MAC) && pdev->id > 0) {
 | 
						if ((id_entry->driver_data & FEC_QUIRK_ENET_MAC) && pdev->id > 0) {
 | 
				
			||||||
		/* fec1 uses fec0 mii_bus */
 | 
							/* fec1 uses fec0 mii_bus */
 | 
				
			||||||
		fep->mii_bus = fec0_mii_bus;
 | 
							if (mii_cnt && fec0_mii_bus) {
 | 
				
			||||||
		return 0;
 | 
								fep->mii_bus = fec0_mii_bus;
 | 
				
			||||||
 | 
								mii_cnt++;
 | 
				
			||||||
 | 
								return 0;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return -ENOENT;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	fep->mii_timeout = 0;
 | 
						fep->mii_timeout = 0;
 | 
				
			||||||
| 
						 | 
					@ -1086,6 +1092,8 @@ static int fec_enet_mii_init(struct platform_device *pdev)
 | 
				
			||||||
	if (mdiobus_register(fep->mii_bus))
 | 
						if (mdiobus_register(fep->mii_bus))
 | 
				
			||||||
		goto err_out_free_mdio_irq;
 | 
							goto err_out_free_mdio_irq;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mii_cnt++;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/* save fec0 mii_bus */
 | 
						/* save fec0 mii_bus */
 | 
				
			||||||
	if (id_entry->driver_data & FEC_QUIRK_ENET_MAC)
 | 
						if (id_entry->driver_data & FEC_QUIRK_ENET_MAC)
 | 
				
			||||||
		fec0_mii_bus = fep->mii_bus;
 | 
							fec0_mii_bus = fep->mii_bus;
 | 
				
			||||||
| 
						 | 
					@ -1102,11 +1110,11 @@ err_out:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void fec_enet_mii_remove(struct fec_enet_private *fep)
 | 
					static void fec_enet_mii_remove(struct fec_enet_private *fep)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	if (fep->phy_dev)
 | 
						if (--mii_cnt == 0) {
 | 
				
			||||||
		phy_disconnect(fep->phy_dev);
 | 
							mdiobus_unregister(fep->mii_bus);
 | 
				
			||||||
	mdiobus_unregister(fep->mii_bus);
 | 
							kfree(fep->mii_bus->irq);
 | 
				
			||||||
	kfree(fep->mii_bus->irq);
 | 
							mdiobus_free(fep->mii_bus);
 | 
				
			||||||
	mdiobus_free(fep->mii_bus);
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int fec_enet_get_settings(struct net_device *ndev,
 | 
					static int fec_enet_get_settings(struct net_device *ndev,
 | 
				
			||||||
| 
						 | 
					@ -1646,13 +1654,18 @@ fec_drv_remove(struct platform_device *pdev)
 | 
				
			||||||
	struct net_device *ndev = platform_get_drvdata(pdev);
 | 
						struct net_device *ndev = platform_get_drvdata(pdev);
 | 
				
			||||||
	struct fec_enet_private *fep = netdev_priv(ndev);
 | 
						struct fec_enet_private *fep = netdev_priv(ndev);
 | 
				
			||||||
	struct resource *r;
 | 
						struct resource *r;
 | 
				
			||||||
 | 
						int i;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	fec_stop(ndev);
 | 
						unregister_netdev(ndev);
 | 
				
			||||||
	fec_enet_mii_remove(fep);
 | 
						fec_enet_mii_remove(fep);
 | 
				
			||||||
 | 
						for (i = 0; i < FEC_IRQ_NUM; i++) {
 | 
				
			||||||
 | 
							int irq = platform_get_irq(pdev, i);
 | 
				
			||||||
 | 
							if (irq > 0)
 | 
				
			||||||
 | 
								free_irq(irq, ndev);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	clk_disable(fep->clk);
 | 
						clk_disable(fep->clk);
 | 
				
			||||||
	clk_put(fep->clk);
 | 
						clk_put(fep->clk);
 | 
				
			||||||
	iounmap(fep->hwp);
 | 
						iounmap(fep->hwp);
 | 
				
			||||||
	unregister_netdev(ndev);
 | 
					 | 
				
			||||||
	free_netdev(ndev);
 | 
						free_netdev(ndev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 | 
						r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue