From acb260838f627dafd172ff412253326acd24c175 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Tue, 10 May 2016 16:30:05 +0100 Subject: sky2: Check power state before trying to read stats When the device is shutdown, the state change can trigger a notifier which tries to gather network statistics by calling sky2_get_stats. Unfortunately, this attempts to read registers in hardware which may have been powered down leading to a bus abort (see below). Partially workaround this by checking device power state in sky2_get_stats. Unhandled fault: synchronous external abort (0x96000210) at 0xffffff800017a918 Internal error: : 96000210 [#1] PREEMPT SMP CPU: 0 PID: 229 Comm: kworker/0:2 Not tainted 3.18.31-00004-g60c1593-dirty #2 Hardware name: ARM Juno development board (r2) (DT) Workqueue: events linkwatch_event task: ffffffc97665c0c0 ti: ffffffc9766e0000 task.ti: ffffffc9766e0000 PC is at sky2_get_stats+0x74/0x40c LR is at sky2_get_stats+0x408/0x40c Call trace: [] sky2_get_stats+0x74/0x40c [] dev_get_stats+0x68/0xd0 [] rtnl_fill_ifinfo+0x388/0x8e0 [] rtmsg_ifinfo+0x78/0x10c [] netdev_state_change+0x48/0x54 [] linkwatch_do_dev+0x50/0x88 [] __linkwatch_run_queue+0x164/0x198 [] linkwatch_event+0x30/0x3c [] process_one_work+0x150/0x458 [] worker_thread+0x14c/0x47c [] kthread+0xe0/0xf4 Signed-off-by: Jon Medhurst --- drivers/net/ethernet/marvell/sky2.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/net/ethernet/marvell/sky2.c b/drivers/net/ethernet/marvell/sky2.c index 697d9b374f5e..77012649cb4e 100644 --- a/drivers/net/ethernet/marvell/sky2.c +++ b/drivers/net/ethernet/marvell/sky2.c @@ -3909,6 +3909,18 @@ static void sky2_get_stats(struct net_device *dev, unsigned int start; u64 _bytes, _packets; + /* Try and check if device if off. If it is, abort gathering stats as + * any attempt to read hardware registers will generate a bus fault. + * This test is hacky and racy as there's nothing stopping the device + * being powered off immediately after the test. + */ + if (hw->pdev->pm_cap) { + u16 pmcsr; + int ret = pci_read_config_word(hw->pdev, hw->pdev->pm_cap + PCI_PM_CTRL, &pmcsr); + if (ret || (pmcsr & PCI_PM_CTRL_STATE_MASK) > PCI_D2) + return; /* Can't read power state or it's state D3 (off) */ + } + do { start = u64_stats_fetch_begin_irq(&sky2->rx_stats.syncp); _bytes = sky2->rx_stats.bytes; -- cgit v1.2.3