diff options
author | Mitsuhiro Kimura <mitsuhiro.kimura.kc@renesas.com> | 2014-11-27 20:04:15 -0500 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2014-11-30 00:16:54 -0500 |
commit | 7fa2955ff70ce4532f144d26b8a087095f9c9ffc (patch) | |
tree | 53e7d463d553014241de7aeb0d1965e6e71b35e0 /drivers/net/ethernet | |
parent | 28603d13997e2ef47f18589cc9a44553aad49c86 (diff) |
sh_eth: Fix sleeping function called from invalid context
This resolves the following bug which can be reproduced by building the
kernel with CONFIG_DEBUG_ATOMIC_SLEEP=y and reading network statistics
while the network interface is down.
e.g.:
ifconfig eth0 down
cat /sys/class/net/eth0/statistics/tx_errors
----
[ 1238.161349] BUG: sleeping function called from invalid context at drivers/base/power/runtime.c:952
[ 1238.188279] in_atomic(): 1, irqs_disabled(): 0, pid: 1388, name: cat
[ 1238.207425] CPU: 0 PID: 1388 Comm: cat Not tainted 3.10.31-ltsi-00046-gefa0b46 #1087
[ 1238.230737] Backtrace:
[ 1238.238123] [<c0012e64>] (dump_backtrace+0x0/0x10c) from [<c0013000>] (show_stack+0x18/0x1c)
[ 1238.263499] r6:000003b8 r5:c06160c0 r4:c0669e00 r3:00404000
[ 1238.280583] [<c0012fe8>] (show_stack+0x0/0x1c) from [<c04515a4>] (dump_stack+0x20/0x28)
[ 1238.304631] [<c0451584>] (dump_stack+0x0/0x28) from [<c004970c>] (__might_sleep+0xf8/0x118)
[ 1238.329734] [<c0049614>] (__might_sleep+0x0/0x118) from [<c02465ac>] (__pm_runtime_resume+0x38/0x90)
[ 1238.357170] r7:d616f000 r6:c049c458 r5:00000004 r4:d6a17210
[ 1238.374251] [<c0246574>] (__pm_runtime_resume+0x0/0x90) from [<c029b1c4>] (sh_eth_get_stats+0x44/0x280)
[ 1238.402468] r7:d616f000 r6:c049c458 r5:d5c21000 r4:d5c21000
[ 1238.419552] [<c029b180>] (sh_eth_get_stats+0x0/0x280) from [<c03ae39c>] (dev_get_stats+0x54/0x88)
[ 1238.446204] r5:d5c21000 r4:d5ed7e08
[ 1238.456980] [<c03ae348>] (dev_get_stats+0x0/0x88) from [<c03c677c>] (netstat_show.isra.15+0x54/0x9c)
[ 1238.484413] r6:d5c21000 r5:d5c21238 r4:00000028 r3:00000001
[ 1238.501495] [<c03c6728>] (netstat_show.isra.15+0x0/0x9c) from [<c03c69b8>] (show_tx_errors+0x18/0x1c)
[ 1238.529196] r7:d5f945d8 r6:d5f945c0 r5:c049716c r4:c0650e7c
[ 1238.546279] [<c03c69a0>] (show_tx_errors+0x0/0x1c) from [<c023963c>] (dev_attr_show+0x24/0x50)
[ 1238.572157] [<c0239618>] (dev_attr_show+0x0/0x50) from [<c010c148>] (sysfs_read_file+0xb0/0x140)
[ 1238.598554] r5:c049716c r4:d5c21240
[ 1238.609326] [<c010c098>] (sysfs_read_file+0x0/0x140) from [<c00b9ee4>] (vfs_read+0xb0/0x13c)
[ 1238.634679] [<c00b9e34>] (vfs_read+0x0/0x13c) from [<c00ba0ac>] (SyS_read+0x44/0x74)
[ 1238.657944] r8:bef45bf0 r7:00000000 r6:d6ac0600 r5:00000000 r4:00000000
[ 1238.678172] [<c00ba068>] (SyS_read+0x0/0x74) from [<c000eec0>] (ret_fast_syscall+0x0/0x30)
----
Signed-off-by: Mitsuhiro Kimura <mitsuhiro.kimura.kc@renesas.com>
Signed-off-by: Yoshihiro Kaneko <ykaneko0929@gmail.com>
Signed-off-by: Simon Horman <horms+renesas@verge.net.au>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/net/ethernet')
-rw-r--r-- | drivers/net/ethernet/renesas/sh_eth.c | 64 | ||||
-rw-r--r-- | drivers/net/ethernet/renesas/sh_eth.h | 1 |
2 files changed, 36 insertions, 29 deletions
diff --git a/drivers/net/ethernet/renesas/sh_eth.c b/drivers/net/ethernet/renesas/sh_eth.c index f9e30b87ecd5..b5db6b3f939f 100644 --- a/drivers/net/ethernet/renesas/sh_eth.c +++ b/drivers/net/ethernet/renesas/sh_eth.c | |||
@@ -2036,6 +2036,8 @@ static int sh_eth_open(struct net_device *ndev) | |||
2036 | if (ret) | 2036 | if (ret) |
2037 | goto out_free_irq; | 2037 | goto out_free_irq; |
2038 | 2038 | ||
2039 | mdp->is_opened = 1; | ||
2040 | |||
2039 | return ret; | 2041 | return ret; |
2040 | 2042 | ||
2041 | out_free_irq: | 2043 | out_free_irq: |
@@ -2125,6 +2127,36 @@ static int sh_eth_start_xmit(struct sk_buff *skb, struct net_device *ndev) | |||
2125 | return NETDEV_TX_OK; | 2127 | return NETDEV_TX_OK; |
2126 | } | 2128 | } |
2127 | 2129 | ||
2130 | static struct net_device_stats *sh_eth_get_stats(struct net_device *ndev) | ||
2131 | { | ||
2132 | struct sh_eth_private *mdp = netdev_priv(ndev); | ||
2133 | |||
2134 | if (sh_eth_is_rz_fast_ether(mdp)) | ||
2135 | return &ndev->stats; | ||
2136 | |||
2137 | if (!mdp->is_opened) | ||
2138 | return &ndev->stats; | ||
2139 | |||
2140 | ndev->stats.tx_dropped += sh_eth_read(ndev, TROCR); | ||
2141 | sh_eth_write(ndev, 0, TROCR); /* (write clear) */ | ||
2142 | ndev->stats.collisions += sh_eth_read(ndev, CDCR); | ||
2143 | sh_eth_write(ndev, 0, CDCR); /* (write clear) */ | ||
2144 | ndev->stats.tx_carrier_errors += sh_eth_read(ndev, LCCR); | ||
2145 | sh_eth_write(ndev, 0, LCCR); /* (write clear) */ | ||
2146 | |||
2147 | if (sh_eth_is_gether(mdp)) { | ||
2148 | ndev->stats.tx_carrier_errors += sh_eth_read(ndev, CERCR); | ||
2149 | sh_eth_write(ndev, 0, CERCR); /* (write clear) */ | ||
2150 | ndev->stats.tx_carrier_errors += sh_eth_read(ndev, CEECR); | ||
2151 | sh_eth_write(ndev, 0, CEECR); /* (write clear) */ | ||
2152 | } else { | ||
2153 | ndev->stats.tx_carrier_errors += sh_eth_read(ndev, CNDCR); | ||
2154 | sh_eth_write(ndev, 0, CNDCR); /* (write clear) */ | ||
2155 | } | ||
2156 | |||
2157 | return &ndev->stats; | ||
2158 | } | ||
2159 | |||
2128 | /* device close function */ | 2160 | /* device close function */ |
2129 | static int sh_eth_close(struct net_device *ndev) | 2161 | static int sh_eth_close(struct net_device *ndev) |
2130 | { | 2162 | { |
@@ -2139,6 +2171,7 @@ static int sh_eth_close(struct net_device *ndev) | |||
2139 | sh_eth_write(ndev, 0, EDTRR); | 2171 | sh_eth_write(ndev, 0, EDTRR); |
2140 | sh_eth_write(ndev, 0, EDRRR); | 2172 | sh_eth_write(ndev, 0, EDRRR); |
2141 | 2173 | ||
2174 | sh_eth_get_stats(ndev); | ||
2142 | /* PHY Disconnect */ | 2175 | /* PHY Disconnect */ |
2143 | if (mdp->phydev) { | 2176 | if (mdp->phydev) { |
2144 | phy_stop(mdp->phydev); | 2177 | phy_stop(mdp->phydev); |
@@ -2157,36 +2190,9 @@ static int sh_eth_close(struct net_device *ndev) | |||
2157 | 2190 | ||
2158 | pm_runtime_put_sync(&mdp->pdev->dev); | 2191 | pm_runtime_put_sync(&mdp->pdev->dev); |
2159 | 2192 | ||
2160 | return 0; | 2193 | mdp->is_opened = 0; |
2161 | } | ||
2162 | |||
2163 | static struct net_device_stats *sh_eth_get_stats(struct net_device *ndev) | ||
2164 | { | ||
2165 | struct sh_eth_private *mdp = netdev_priv(ndev); | ||
2166 | |||
2167 | if (sh_eth_is_rz_fast_ether(mdp)) | ||
2168 | return &ndev->stats; | ||
2169 | |||
2170 | pm_runtime_get_sync(&mdp->pdev->dev); | ||
2171 | |||
2172 | ndev->stats.tx_dropped += sh_eth_read(ndev, TROCR); | ||
2173 | sh_eth_write(ndev, 0, TROCR); /* (write clear) */ | ||
2174 | ndev->stats.collisions += sh_eth_read(ndev, CDCR); | ||
2175 | sh_eth_write(ndev, 0, CDCR); /* (write clear) */ | ||
2176 | ndev->stats.tx_carrier_errors += sh_eth_read(ndev, LCCR); | ||
2177 | sh_eth_write(ndev, 0, LCCR); /* (write clear) */ | ||
2178 | if (sh_eth_is_gether(mdp)) { | ||
2179 | ndev->stats.tx_carrier_errors += sh_eth_read(ndev, CERCR); | ||
2180 | sh_eth_write(ndev, 0, CERCR); /* (write clear) */ | ||
2181 | ndev->stats.tx_carrier_errors += sh_eth_read(ndev, CEECR); | ||
2182 | sh_eth_write(ndev, 0, CEECR); /* (write clear) */ | ||
2183 | } else { | ||
2184 | ndev->stats.tx_carrier_errors += sh_eth_read(ndev, CNDCR); | ||
2185 | sh_eth_write(ndev, 0, CNDCR); /* (write clear) */ | ||
2186 | } | ||
2187 | pm_runtime_put_sync(&mdp->pdev->dev); | ||
2188 | 2194 | ||
2189 | return &ndev->stats; | 2195 | return 0; |
2190 | } | 2196 | } |
2191 | 2197 | ||
2192 | /* ioctl to device function */ | 2198 | /* ioctl to device function */ |
diff --git a/drivers/net/ethernet/renesas/sh_eth.h b/drivers/net/ethernet/renesas/sh_eth.h index 9fa93321d512..22301bf9c21d 100644 --- a/drivers/net/ethernet/renesas/sh_eth.h +++ b/drivers/net/ethernet/renesas/sh_eth.h | |||
@@ -522,6 +522,7 @@ struct sh_eth_private { | |||
522 | 522 | ||
523 | unsigned no_ether_link:1; | 523 | unsigned no_ether_link:1; |
524 | unsigned ether_link_active_low:1; | 524 | unsigned ether_link_active_low:1; |
525 | unsigned is_opened:1; | ||
525 | }; | 526 | }; |
526 | 527 | ||
527 | static inline void sh_eth_soft_swap(char *src, int len) | 528 | static inline void sh_eth_soft_swap(char *src, int len) |