linux/drivers/net/ethernet/stmicro/stmmac/stmmac_platform.c
Srinivas Kandagatla 1f9defa0c2 net:stmmac: fix broken stmmac_pltfr_remove.
This patch fixes stmmac_pltfr_remove function, which is broken because,
it is accessing plat variable via freed memory priv pointer which gets
freed by free_netdev called from stmmac_dvr_remove.

In short this patch caches the plat pointer in local variable before
calling stmmac_dvr_remove to prevent code accessing freed memory.

Without this patch any attempt to remove the stmmac device will fail as
below:

Unregistering eth 0 ...
Unable to handle kernel paging request at virtual address 6b6b6bab
pgd = de5dc000
[6b6b6bab] *pgd=00000000
Internal error: Oops: 5 [#1] PREEMPT SMP
Modules linked in: cdev(O+)
CPU: 0    Tainted: G           O  (3.3.1_stm24_0210-b2000+ #25)
PC is at stmmac_pltfr_remove+0x2c/0xa0
LR is at stmmac_pltfr_remove+0x28/0xa0
pc : [<c01b8908>]    lr : [<c01b8904>]    psr: 60000013
sp : def6be78  ip : de6c5a00  fp : 00000000
r10: 00000028  r9 : c082d81d  r8 : 00000001
r7 : de65a600  r6 : df81b240  r5 : c0413fd8  r4 : 00000000
r3 : 6b6b6b6b  r2 : def6be6c  r1 : c0355e2b  r0 : 00000020
Flags: nZCv  IRQs on  FIQs on  Mode SVC_32  ISA ARM  Segment user
Control: 10c53c7d  Table: 5e5dc04a  DAC: 00000015
Process insmod (pid: 738, stack limit = 0xdef6a2f0)
Stack: (0xdef6be78 to 0xdef6c000)
be60:                                                       c0413fe0
c0403658
be80: c0400bb0 c019270c c01926f8 c0191478 00000000 c0414014 c0413fe0
c01914d8
bea0: 00000000 c0413fe0 df8045d0 c019109c c0413fe0 c0400bf0 c0413fd8
c018f04c
bec0: 00000000 bf000000 c0413fd8 c01929a0 c0413fd8 bf000000 00000000
c0192bfc
bee0: bf00009c bf000014 def6a000 c000859c 00000000 00000001 bf00009c
bf00009c
bf00: 00000001 bf00009c 00000001 bf0000e4 de65a600 00000001 c082d81d
c0058cd0
bf20: bf0000a8 c004fbd8 c0056414 c082d815 c02aea20 bf0001f0 00b0b008
e0846208
bf40: c03ec8a0 e0846000 0000db0d e0850604 e08504de e0853a24 00000204
000002d4
bf60: 00000000 00000000 0000001c 0000001d 00000009 00000000 00000006
00000000
bf80: 00000003 f63d4e2e 0000db0d bef02ed8 00000080 c000d2e8 def6a000
00000000
bfa0: 00000000 c000d140 f63d4e2e 0000db0d 00b0b018 0000db0d 00b0b008
b6f4f298
bfc0: f63d4e2e 0000db0d bef02ed8 00000080 00000003 00000000 00010000
00000000
bfe0: 00b0b008 bef02c64 00008d20 b6ef3784 60000010 00b0b018 5a5a5a5a
5a5a5a5a
[<c01b8908>] (stmmac_pltfr_remove+0x2c/0xa0) from [<c019270c>]
(platform_drv_remove+0x14/0x18)
[<c019270c>] (platform_drv_remove+0x14/0x18) from [<c0191478>]
(__device_release_driver+0x64/0xa4)
[<c0191478>] (__device_release_driver+0x64/0xa4) from [<c01914d8>]
(device_release_driver+0x20/0x2c)
[<c01914d8>] (device_release_driver+0x20/0x2c) from [<c019109c>]
(bus_remove_device+0xcc/0xdc)
[<c019109c>] (bus_remove_device+0xcc/0xdc) from [<c018f04c>]
(device_del+0x104/0x160)
[<c018f04c>] (device_del+0x104/0x160) from [<c01929a0>]
(platform_device_del+0x18/0x58)
[<c01929a0>] (platform_device_del+0x18/0x58) from [<c0192bfc>]
(platform_device_unregister+0xc/0x18)
[<c0192bfc>] (platform_device_unregister+0xc/0x18) from [<bf000014>]
(r_init+0x14/0x2c [cdev])
[<bf000014>] (r_init+0x14/0x2c [cdev]) from [<c000859c>]
(do_one_initcall+0x90/0x160)
[<c000859c>] (do_one_initcall+0x90/0x160) from [<c0058cd0>]
(sys_init_module+0x15c4/0x1794)
[<c0058cd0>] (sys_init_module+0x15c4/0x1794) from [<c000d140>]
(ret_fast_syscall+0x0/0x30)
Code: e1a04000 e59f0070 eb039b65 e59636e4 (e5933040)

Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@st.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2012-08-31 16:11:16 -04:00

279 lines
7.4 KiB
C

/*******************************************************************************
This contains the functions to handle the platform driver.
Copyright (C) 2007-2011 STMicroelectronics Ltd
This program is free software; you can redistribute it and/or modify it
under the terms and conditions of the GNU General Public License,
version 2, as published by the Free Software Foundation.
This program is distributed in the hope it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
The full GNU General Public License is included in this distribution in
the file called "COPYING".
Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
*******************************************************************************/
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_net.h>
#include "stmmac.h"
#ifdef CONFIG_OF
static int __devinit stmmac_probe_config_dt(struct platform_device *pdev,
struct plat_stmmacenet_data *plat,
const char **mac)
{
struct device_node *np = pdev->dev.of_node;
if (!np)
return -ENODEV;
*mac = of_get_mac_address(np);
plat->interface = of_get_phy_mode(np);
plat->mdio_bus_data = devm_kzalloc(&pdev->dev,
sizeof(struct stmmac_mdio_bus_data),
GFP_KERNEL);
/*
* Currently only the properties needed on SPEAr600
* are provided. All other properties should be added
* once needed on other platforms.
*/
if (of_device_is_compatible(np, "st,spear600-gmac") ||
of_device_is_compatible(np, "snps,dwmac-3.70a") ||
of_device_is_compatible(np, "snps,dwmac")) {
plat->has_gmac = 1;
plat->pmt = 1;
}
return 0;
}
#else
static int __devinit stmmac_probe_config_dt(struct platform_device *pdev,
struct plat_stmmacenet_data *plat,
const char **mac)
{
return -ENOSYS;
}
#endif /* CONFIG_OF */
/**
* stmmac_pltfr_probe
* @pdev: platform device pointer
* Description: platform_device probe function. It allocates
* the necessary resources and invokes the main to init
* the net device, register the mdio bus etc.
*/
static int __devinit stmmac_pltfr_probe(struct platform_device *pdev)
{
int ret = 0;
struct resource *res;
void __iomem *addr = NULL;
struct stmmac_priv *priv = NULL;
struct plat_stmmacenet_data *plat_dat = NULL;
const char *mac = NULL;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -ENODEV;
if (!request_mem_region(res->start, resource_size(res), pdev->name)) {
pr_err("%s: ERROR: memory allocation failed"
"cannot get the I/O addr 0x%x\n",
__func__, (unsigned int)res->start);
return -EBUSY;
}
addr = ioremap(res->start, resource_size(res));
if (!addr) {
pr_err("%s: ERROR: memory mapping failed", __func__);
ret = -ENOMEM;
goto out_release_region;
}
if (pdev->dev.of_node) {
plat_dat = devm_kzalloc(&pdev->dev,
sizeof(struct plat_stmmacenet_data),
GFP_KERNEL);
if (!plat_dat) {
pr_err("%s: ERROR: no memory", __func__);
ret = -ENOMEM;
goto out_unmap;
}
ret = stmmac_probe_config_dt(pdev, plat_dat, &mac);
if (ret) {
pr_err("%s: main dt probe failed", __func__);
goto out_unmap;
}
} else {
plat_dat = pdev->dev.platform_data;
}
/* Custom initialisation (if needed)*/
if (plat_dat->init) {
ret = plat_dat->init(pdev);
if (unlikely(ret))
goto out_unmap;
}
priv = stmmac_dvr_probe(&(pdev->dev), plat_dat, addr);
if (!priv) {
pr_err("%s: main driver probe failed", __func__);
goto out_unmap;
}
/* Get MAC address if available (DT) */
if (mac)
memcpy(priv->dev->dev_addr, mac, ETH_ALEN);
/* Get the MAC information */
priv->dev->irq = platform_get_irq_byname(pdev, "macirq");
if (priv->dev->irq == -ENXIO) {
pr_err("%s: ERROR: MAC IRQ configuration "
"information not found\n", __func__);
ret = -ENXIO;
goto out_unmap;
}
/*
* On some platforms e.g. SPEAr the wake up irq differs from the mac irq
* The external wake up irq can be passed through the platform code
* named as "eth_wake_irq"
*
* In case the wake up interrupt is not passed from the platform
* so the driver will continue to use the mac irq (ndev->irq)
*/
priv->wol_irq = platform_get_irq_byname(pdev, "eth_wake_irq");
if (priv->wol_irq == -ENXIO)
priv->wol_irq = priv->dev->irq;
priv->lpi_irq = platform_get_irq_byname(pdev, "eth_lpi");
platform_set_drvdata(pdev, priv->dev);
pr_debug("STMMAC platform driver registration completed");
return 0;
out_unmap:
iounmap(addr);
platform_set_drvdata(pdev, NULL);
out_release_region:
release_mem_region(res->start, resource_size(res));
return ret;
}
/**
* stmmac_pltfr_remove
* @pdev: platform device pointer
* Description: this function calls the main to free the net resources
* and calls the platforms hook and release the resources (e.g. mem).
*/
static int stmmac_pltfr_remove(struct platform_device *pdev)
{
struct net_device *ndev = platform_get_drvdata(pdev);
struct stmmac_priv *priv = netdev_priv(ndev);
void __iomem *addr = priv->ioaddr;
struct resource *res;
int ret = stmmac_dvr_remove(ndev);
if (priv->plat->exit)
priv->plat->exit(pdev);
platform_set_drvdata(pdev, NULL);
iounmap(addr);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
release_mem_region(res->start, resource_size(res));
return ret;
}
#ifdef CONFIG_PM
static int stmmac_pltfr_suspend(struct device *dev)
{
struct net_device *ndev = dev_get_drvdata(dev);
return stmmac_suspend(ndev);
}
static int stmmac_pltfr_resume(struct device *dev)
{
struct net_device *ndev = dev_get_drvdata(dev);
return stmmac_resume(ndev);
}
int stmmac_pltfr_freeze(struct device *dev)
{
int ret;
struct plat_stmmacenet_data *plat_dat = dev_get_platdata(dev);
struct net_device *ndev = dev_get_drvdata(dev);
struct platform_device *pdev = to_platform_device(dev);
ret = stmmac_freeze(ndev);
if (plat_dat->exit)
plat_dat->exit(pdev);
return ret;
}
int stmmac_pltfr_restore(struct device *dev)
{
struct plat_stmmacenet_data *plat_dat = dev_get_platdata(dev);
struct net_device *ndev = dev_get_drvdata(dev);
struct platform_device *pdev = to_platform_device(dev);
if (plat_dat->init)
plat_dat->init(pdev);
return stmmac_restore(ndev);
}
static const struct dev_pm_ops stmmac_pltfr_pm_ops = {
.suspend = stmmac_pltfr_suspend,
.resume = stmmac_pltfr_resume,
.freeze = stmmac_pltfr_freeze,
.thaw = stmmac_pltfr_restore,
.restore = stmmac_pltfr_restore,
};
#else
static const struct dev_pm_ops stmmac_pltfr_pm_ops;
#endif /* CONFIG_PM */
static const struct of_device_id stmmac_dt_ids[] = {
{ .compatible = "st,spear600-gmac"},
{ .compatible = "snps,dwmac-3.70a"},
{ .compatible = "snps,dwmac"},
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, stmmac_dt_ids);
struct platform_driver stmmac_pltfr_driver = {
.probe = stmmac_pltfr_probe,
.remove = stmmac_pltfr_remove,
.driver = {
.name = STMMAC_RESOURCE_NAME,
.owner = THIS_MODULE,
.pm = &stmmac_pltfr_pm_ops,
.of_match_table = of_match_ptr(stmmac_dt_ids),
},
};
MODULE_DESCRIPTION("STMMAC 10/100/1000 Ethernet PLATFORM driver");
MODULE_AUTHOR("Giuseppe Cavallaro <peppe.cavallaro@st.com>");
MODULE_LICENSE("GPL");