commit cbe6c3a8f8f4315b96e46e1a1c70393c06d95a4c upstream.
This will reset deeply on freeze and thaw instead of suspend and
resume and prevent null pointer dereferences of the uninitialized ring
0 buffer while thawing.
The impact is an indefinitely hanging kernel. You can't switch
consoles after this and the only possible user interaction is SysRq.
BUG: kernel NULL pointer dereference
RIP: 0010:aq_ring_rx_fill+0xcf/0x210 [atlantic]
aq_vec_init+0x85/0xe0 [atlantic]
aq_nic_init+0xf7/0x1d0 [atlantic]
atl_resume_common+0x4f/0x100 [atlantic]
pci_pm_thaw+0x42/0xa0
resolves in aq_ring.o to
```
0000000000000ae0 <aq_ring_rx_fill>:
{
/* ... */
baf: 48 8b 43 08 mov 0x8(%rbx),%rax
buff->flags = 0U; /* buff is NULL */
```
The bug has been present since the introduction of the new pm code in
8aaa112a57 ("net: atlantic: refactoring pm logic") and was hidden
until 8ce8427169 ("net: atlantic: changes for multi-TC support"),
which refactored the aq_vec_{free,alloc} functions into
aq_vec_{,ring}_{free,alloc}, but is technically not wrong. The
original functions just always reinitialized the buffers on S3/S4. If
the interface is down before freezing, the bug does not occur. It does
not matter, whether the initrd contains and loads the module before
thawing.
So the fix is to invert the boolean parameter deep in all pm function
calls, which was clearly intended to be set like that.
First report was on Github [1], which you have to guess from the
resume logs in the posted dmesg snippet. Recently I posted one on
Bugzilla [2], since I did not have an AQC device so far.
#regzbot introduced: 8ce8427169
#regzbot from: koo5 <kolman.jindrich@gmail.com>
#regzbot monitor: https://github.com/Aquantia/AQtion/issues/32
Fixes: 8aaa112a57 ("net: atlantic: refactoring pm logic")
Link: https://github.com/Aquantia/AQtion/issues/32 [1]
Link: https://bugzilla.kernel.org/show_bug.cgi?id=215798 [2]
Cc: stable@vger.kernel.org
Reported-by: koo5 <kolman.jindrich@gmail.com>
Signed-off-by: Manuel Ullmann <labre@posteo.de>
Signed-off-by: David S. Miller <davem@davemloft.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
495 lines
12 KiB
C
495 lines
12 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/* Atlantic Network Driver
|
|
*
|
|
* Copyright (C) 2014-2019 aQuantia Corporation
|
|
* Copyright (C) 2019-2020 Marvell International Ltd.
|
|
*/
|
|
|
|
/* File aq_pci_func.c: Definition of PCI functions. */
|
|
|
|
#include <linux/interrupt.h>
|
|
#include <linux/module.h>
|
|
|
|
#include "aq_main.h"
|
|
#include "aq_nic.h"
|
|
#include "aq_vec.h"
|
|
#include "aq_hw.h"
|
|
#include "aq_pci_func.h"
|
|
#include "hw_atl/hw_atl_a0.h"
|
|
#include "hw_atl/hw_atl_b0.h"
|
|
#include "hw_atl2/hw_atl2.h"
|
|
#include "aq_filters.h"
|
|
#include "aq_drvinfo.h"
|
|
#include "aq_macsec.h"
|
|
|
|
static const struct pci_device_id aq_pci_tbl[] = {
|
|
{ PCI_VDEVICE(AQUANTIA, AQ_DEVICE_ID_0001), },
|
|
{ PCI_VDEVICE(AQUANTIA, AQ_DEVICE_ID_D100), },
|
|
{ PCI_VDEVICE(AQUANTIA, AQ_DEVICE_ID_D107), },
|
|
{ PCI_VDEVICE(AQUANTIA, AQ_DEVICE_ID_D108), },
|
|
{ PCI_VDEVICE(AQUANTIA, AQ_DEVICE_ID_D109), },
|
|
|
|
{ PCI_VDEVICE(AQUANTIA, AQ_DEVICE_ID_AQC100), },
|
|
{ PCI_VDEVICE(AQUANTIA, AQ_DEVICE_ID_AQC107), },
|
|
{ PCI_VDEVICE(AQUANTIA, AQ_DEVICE_ID_AQC108), },
|
|
{ PCI_VDEVICE(AQUANTIA, AQ_DEVICE_ID_AQC109), },
|
|
{ PCI_VDEVICE(AQUANTIA, AQ_DEVICE_ID_AQC111), },
|
|
{ PCI_VDEVICE(AQUANTIA, AQ_DEVICE_ID_AQC112), },
|
|
|
|
{ PCI_VDEVICE(AQUANTIA, AQ_DEVICE_ID_AQC100S), },
|
|
{ PCI_VDEVICE(AQUANTIA, AQ_DEVICE_ID_AQC107S), },
|
|
{ PCI_VDEVICE(AQUANTIA, AQ_DEVICE_ID_AQC108S), },
|
|
{ PCI_VDEVICE(AQUANTIA, AQ_DEVICE_ID_AQC109S), },
|
|
{ PCI_VDEVICE(AQUANTIA, AQ_DEVICE_ID_AQC111S), },
|
|
{ PCI_VDEVICE(AQUANTIA, AQ_DEVICE_ID_AQC112S), },
|
|
|
|
{ PCI_VDEVICE(AQUANTIA, AQ_DEVICE_ID_AQC113DEV), },
|
|
{ PCI_VDEVICE(AQUANTIA, AQ_DEVICE_ID_AQC113CS), },
|
|
{ PCI_VDEVICE(AQUANTIA, AQ_DEVICE_ID_AQC114CS), },
|
|
{ PCI_VDEVICE(AQUANTIA, AQ_DEVICE_ID_AQC113), },
|
|
{ PCI_VDEVICE(AQUANTIA, AQ_DEVICE_ID_AQC113C), },
|
|
{ PCI_VDEVICE(AQUANTIA, AQ_DEVICE_ID_AQC115C), },
|
|
{ PCI_VDEVICE(AQUANTIA, AQ_DEVICE_ID_AQC113CA), },
|
|
{ PCI_VDEVICE(AQUANTIA, AQ_DEVICE_ID_AQC116C), },
|
|
|
|
{}
|
|
};
|
|
|
|
static const struct aq_board_revision_s hw_atl_boards[] = {
|
|
{ AQ_DEVICE_ID_0001, AQ_HWREV_1, &hw_atl_ops_a0, &hw_atl_a0_caps_aqc107, },
|
|
{ AQ_DEVICE_ID_D100, AQ_HWREV_1, &hw_atl_ops_a0, &hw_atl_a0_caps_aqc100, },
|
|
{ AQ_DEVICE_ID_D107, AQ_HWREV_1, &hw_atl_ops_a0, &hw_atl_a0_caps_aqc107, },
|
|
{ AQ_DEVICE_ID_D108, AQ_HWREV_1, &hw_atl_ops_a0, &hw_atl_a0_caps_aqc108, },
|
|
{ AQ_DEVICE_ID_D109, AQ_HWREV_1, &hw_atl_ops_a0, &hw_atl_a0_caps_aqc109, },
|
|
|
|
{ AQ_DEVICE_ID_0001, AQ_HWREV_2, &hw_atl_ops_b0, &hw_atl_b0_caps_aqc107, },
|
|
{ AQ_DEVICE_ID_D100, AQ_HWREV_2, &hw_atl_ops_b0, &hw_atl_b0_caps_aqc100, },
|
|
{ AQ_DEVICE_ID_D107, AQ_HWREV_2, &hw_atl_ops_b0, &hw_atl_b0_caps_aqc107, },
|
|
{ AQ_DEVICE_ID_D108, AQ_HWREV_2, &hw_atl_ops_b0, &hw_atl_b0_caps_aqc108, },
|
|
{ AQ_DEVICE_ID_D109, AQ_HWREV_2, &hw_atl_ops_b0, &hw_atl_b0_caps_aqc109, },
|
|
|
|
{ AQ_DEVICE_ID_AQC100, AQ_HWREV_ANY, &hw_atl_ops_b1, &hw_atl_b0_caps_aqc100, },
|
|
{ AQ_DEVICE_ID_AQC107, AQ_HWREV_ANY, &hw_atl_ops_b1, &hw_atl_b0_caps_aqc107, },
|
|
{ AQ_DEVICE_ID_AQC108, AQ_HWREV_ANY, &hw_atl_ops_b1, &hw_atl_b0_caps_aqc108, },
|
|
{ AQ_DEVICE_ID_AQC109, AQ_HWREV_ANY, &hw_atl_ops_b1, &hw_atl_b0_caps_aqc109, },
|
|
{ AQ_DEVICE_ID_AQC111, AQ_HWREV_ANY, &hw_atl_ops_b1, &hw_atl_b0_caps_aqc111, },
|
|
{ AQ_DEVICE_ID_AQC112, AQ_HWREV_ANY, &hw_atl_ops_b1, &hw_atl_b0_caps_aqc112, },
|
|
|
|
{ AQ_DEVICE_ID_AQC100S, AQ_HWREV_ANY, &hw_atl_ops_b1, &hw_atl_b0_caps_aqc100s, },
|
|
{ AQ_DEVICE_ID_AQC107S, AQ_HWREV_ANY, &hw_atl_ops_b1, &hw_atl_b0_caps_aqc107s, },
|
|
{ AQ_DEVICE_ID_AQC108S, AQ_HWREV_ANY, &hw_atl_ops_b1, &hw_atl_b0_caps_aqc108s, },
|
|
{ AQ_DEVICE_ID_AQC109S, AQ_HWREV_ANY, &hw_atl_ops_b1, &hw_atl_b0_caps_aqc109s, },
|
|
{ AQ_DEVICE_ID_AQC111S, AQ_HWREV_ANY, &hw_atl_ops_b1, &hw_atl_b0_caps_aqc111s, },
|
|
{ AQ_DEVICE_ID_AQC112S, AQ_HWREV_ANY, &hw_atl_ops_b1, &hw_atl_b0_caps_aqc112s, },
|
|
|
|
{ AQ_DEVICE_ID_AQC113DEV, AQ_HWREV_ANY, &hw_atl2_ops, &hw_atl2_caps_aqc113, },
|
|
{ AQ_DEVICE_ID_AQC113, AQ_HWREV_ANY, &hw_atl2_ops, &hw_atl2_caps_aqc113, },
|
|
{ AQ_DEVICE_ID_AQC113CS, AQ_HWREV_ANY, &hw_atl2_ops, &hw_atl2_caps_aqc113, },
|
|
{ AQ_DEVICE_ID_AQC114CS, AQ_HWREV_ANY, &hw_atl2_ops, &hw_atl2_caps_aqc113, },
|
|
{ AQ_DEVICE_ID_AQC113C, AQ_HWREV_ANY, &hw_atl2_ops, &hw_atl2_caps_aqc113, },
|
|
{ AQ_DEVICE_ID_AQC115C, AQ_HWREV_ANY, &hw_atl2_ops, &hw_atl2_caps_aqc115c, },
|
|
{ AQ_DEVICE_ID_AQC113CA, AQ_HWREV_ANY, &hw_atl2_ops, &hw_atl2_caps_aqc113, },
|
|
{ AQ_DEVICE_ID_AQC116C, AQ_HWREV_ANY, &hw_atl2_ops, &hw_atl2_caps_aqc116c, },
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(pci, aq_pci_tbl);
|
|
|
|
static int aq_pci_probe_get_hw_by_id(struct pci_dev *pdev,
|
|
const struct aq_hw_ops **ops,
|
|
const struct aq_hw_caps_s **caps)
|
|
{
|
|
int i;
|
|
|
|
if (pdev->vendor != PCI_VENDOR_ID_AQUANTIA)
|
|
return -EINVAL;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(hw_atl_boards); i++) {
|
|
if (hw_atl_boards[i].devid == pdev->device &&
|
|
(hw_atl_boards[i].revision == AQ_HWREV_ANY ||
|
|
hw_atl_boards[i].revision == pdev->revision)) {
|
|
*ops = hw_atl_boards[i].ops;
|
|
*caps = hw_atl_boards[i].caps;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i == ARRAY_SIZE(hw_atl_boards))
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int aq_pci_func_init(struct pci_dev *pdev)
|
|
{
|
|
int err;
|
|
|
|
err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
|
|
if (err)
|
|
err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
|
|
if (err) {
|
|
err = -ENOSR;
|
|
goto err_exit;
|
|
}
|
|
|
|
err = pci_request_regions(pdev, AQ_CFG_DRV_NAME "_mmio");
|
|
if (err < 0)
|
|
goto err_exit;
|
|
|
|
pci_set_master(pdev);
|
|
|
|
return 0;
|
|
|
|
err_exit:
|
|
return err;
|
|
}
|
|
|
|
int aq_pci_func_alloc_irq(struct aq_nic_s *self, unsigned int i,
|
|
char *name, irq_handler_t irq_handler,
|
|
void *irq_arg, cpumask_t *affinity_mask)
|
|
{
|
|
struct pci_dev *pdev = self->pdev;
|
|
int err;
|
|
|
|
if (pdev->msix_enabled || pdev->msi_enabled)
|
|
err = request_irq(pci_irq_vector(pdev, i), irq_handler, 0,
|
|
name, irq_arg);
|
|
else
|
|
err = request_irq(pci_irq_vector(pdev, i), aq_vec_isr_legacy,
|
|
IRQF_SHARED, name, irq_arg);
|
|
|
|
if (err >= 0) {
|
|
self->msix_entry_mask |= (1 << i);
|
|
|
|
if (pdev->msix_enabled && affinity_mask)
|
|
irq_set_affinity_hint(pci_irq_vector(pdev, i),
|
|
affinity_mask);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
void aq_pci_func_free_irqs(struct aq_nic_s *self)
|
|
{
|
|
struct pci_dev *pdev = self->pdev;
|
|
unsigned int i;
|
|
void *irq_data;
|
|
|
|
for (i = 32U; i--;) {
|
|
if (!((1U << i) & self->msix_entry_mask))
|
|
continue;
|
|
if (self->aq_nic_cfg.link_irq_vec &&
|
|
i == self->aq_nic_cfg.link_irq_vec)
|
|
irq_data = self;
|
|
else if (i < AQ_CFG_VECS_MAX)
|
|
irq_data = self->aq_vec[i];
|
|
else
|
|
continue;
|
|
|
|
if (pdev->msix_enabled)
|
|
irq_set_affinity_hint(pci_irq_vector(pdev, i), NULL);
|
|
free_irq(pci_irq_vector(pdev, i), irq_data);
|
|
self->msix_entry_mask &= ~(1U << i);
|
|
}
|
|
}
|
|
|
|
unsigned int aq_pci_func_get_irq_type(struct aq_nic_s *self)
|
|
{
|
|
if (self->pdev->msix_enabled)
|
|
return AQ_HW_IRQ_MSIX;
|
|
if (self->pdev->msi_enabled)
|
|
return AQ_HW_IRQ_MSI;
|
|
|
|
return AQ_HW_IRQ_LEGACY;
|
|
}
|
|
|
|
static void aq_pci_free_irq_vectors(struct aq_nic_s *self)
|
|
{
|
|
pci_free_irq_vectors(self->pdev);
|
|
}
|
|
|
|
static int aq_pci_probe(struct pci_dev *pdev,
|
|
const struct pci_device_id *pci_id)
|
|
{
|
|
struct net_device *ndev;
|
|
resource_size_t mmio_pa;
|
|
struct aq_nic_s *self;
|
|
u32 numvecs;
|
|
u32 bar;
|
|
int err;
|
|
|
|
err = pci_enable_device(pdev);
|
|
if (err)
|
|
return err;
|
|
|
|
err = aq_pci_func_init(pdev);
|
|
if (err)
|
|
goto err_pci_func;
|
|
|
|
ndev = aq_ndev_alloc();
|
|
if (!ndev) {
|
|
err = -ENOMEM;
|
|
goto err_ndev;
|
|
}
|
|
|
|
self = netdev_priv(ndev);
|
|
self->pdev = pdev;
|
|
SET_NETDEV_DEV(ndev, &pdev->dev);
|
|
pci_set_drvdata(pdev, self);
|
|
|
|
mutex_init(&self->fwreq_mutex);
|
|
|
|
err = aq_pci_probe_get_hw_by_id(pdev, &self->aq_hw_ops,
|
|
&aq_nic_get_cfg(self)->aq_hw_caps);
|
|
if (err)
|
|
goto err_ioremap;
|
|
|
|
self->aq_hw = kzalloc(sizeof(*self->aq_hw), GFP_KERNEL);
|
|
if (!self->aq_hw) {
|
|
err = -ENOMEM;
|
|
goto err_ioremap;
|
|
}
|
|
self->aq_hw->aq_nic_cfg = aq_nic_get_cfg(self);
|
|
if (self->aq_hw->aq_nic_cfg->aq_hw_caps->priv_data_len) {
|
|
int len = self->aq_hw->aq_nic_cfg->aq_hw_caps->priv_data_len;
|
|
|
|
self->aq_hw->priv = kzalloc(len, GFP_KERNEL);
|
|
if (!self->aq_hw->priv) {
|
|
err = -ENOMEM;
|
|
goto err_free_aq_hw;
|
|
}
|
|
}
|
|
|
|
for (bar = 0; bar < 4; ++bar) {
|
|
if (IORESOURCE_MEM & pci_resource_flags(pdev, bar)) {
|
|
resource_size_t reg_sz;
|
|
|
|
mmio_pa = pci_resource_start(pdev, bar);
|
|
if (mmio_pa == 0U) {
|
|
err = -EIO;
|
|
goto err_free_aq_hw_priv;
|
|
}
|
|
|
|
reg_sz = pci_resource_len(pdev, bar);
|
|
if ((reg_sz <= 24 /*ATL_REGS_SIZE*/)) {
|
|
err = -EIO;
|
|
goto err_free_aq_hw_priv;
|
|
}
|
|
|
|
self->aq_hw->mmio = ioremap(mmio_pa, reg_sz);
|
|
if (!self->aq_hw->mmio) {
|
|
err = -EIO;
|
|
goto err_free_aq_hw_priv;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bar == 4) {
|
|
err = -EIO;
|
|
goto err_free_aq_hw_priv;
|
|
}
|
|
|
|
numvecs = min((u8)AQ_CFG_VECS_DEF,
|
|
aq_nic_get_cfg(self)->aq_hw_caps->msix_irqs);
|
|
numvecs = min(numvecs, num_online_cpus());
|
|
/* Request IRQ vector for PTP */
|
|
numvecs += 1;
|
|
|
|
numvecs += AQ_HW_SERVICE_IRQS;
|
|
/*enable interrupts */
|
|
#if !AQ_CFG_FORCE_LEGACY_INT
|
|
err = pci_alloc_irq_vectors(self->pdev, 1, numvecs,
|
|
PCI_IRQ_MSIX | PCI_IRQ_MSI |
|
|
PCI_IRQ_LEGACY);
|
|
|
|
if (err < 0)
|
|
goto err_hwinit;
|
|
numvecs = err;
|
|
#endif
|
|
self->irqvecs = numvecs;
|
|
|
|
/* net device init */
|
|
aq_nic_cfg_start(self);
|
|
|
|
aq_nic_ndev_init(self);
|
|
|
|
err = aq_nic_ndev_register(self);
|
|
if (err < 0)
|
|
goto err_register;
|
|
|
|
aq_drvinfo_init(ndev);
|
|
|
|
return 0;
|
|
|
|
err_register:
|
|
aq_nic_free_vectors(self);
|
|
aq_pci_free_irq_vectors(self);
|
|
err_hwinit:
|
|
iounmap(self->aq_hw->mmio);
|
|
err_free_aq_hw_priv:
|
|
kfree(self->aq_hw->priv);
|
|
err_free_aq_hw:
|
|
kfree(self->aq_hw);
|
|
err_ioremap:
|
|
free_netdev(ndev);
|
|
err_ndev:
|
|
pci_release_regions(pdev);
|
|
err_pci_func:
|
|
pci_disable_device(pdev);
|
|
|
|
return err;
|
|
}
|
|
|
|
static void aq_pci_remove(struct pci_dev *pdev)
|
|
{
|
|
struct aq_nic_s *self = pci_get_drvdata(pdev);
|
|
|
|
if (self->ndev) {
|
|
aq_clear_rxnfc_all_rules(self);
|
|
if (self->ndev->reg_state == NETREG_REGISTERED)
|
|
unregister_netdev(self->ndev);
|
|
|
|
#if IS_ENABLED(CONFIG_MACSEC)
|
|
aq_macsec_free(self);
|
|
#endif
|
|
aq_nic_free_vectors(self);
|
|
aq_pci_free_irq_vectors(self);
|
|
iounmap(self->aq_hw->mmio);
|
|
kfree(self->aq_hw->priv);
|
|
kfree(self->aq_hw);
|
|
pci_release_regions(pdev);
|
|
free_netdev(self->ndev);
|
|
}
|
|
|
|
pci_disable_device(pdev);
|
|
}
|
|
|
|
static void aq_pci_shutdown(struct pci_dev *pdev)
|
|
{
|
|
struct aq_nic_s *self = pci_get_drvdata(pdev);
|
|
|
|
aq_nic_shutdown(self);
|
|
|
|
pci_disable_device(pdev);
|
|
|
|
if (system_state == SYSTEM_POWER_OFF) {
|
|
pci_wake_from_d3(pdev, false);
|
|
pci_set_power_state(pdev, PCI_D3hot);
|
|
}
|
|
}
|
|
|
|
static int aq_suspend_common(struct device *dev, bool deep)
|
|
{
|
|
struct aq_nic_s *nic = pci_get_drvdata(to_pci_dev(dev));
|
|
|
|
rtnl_lock();
|
|
|
|
nic->power_state = AQ_HW_POWER_STATE_D3;
|
|
netif_device_detach(nic->ndev);
|
|
netif_tx_stop_all_queues(nic->ndev);
|
|
|
|
if (netif_running(nic->ndev))
|
|
aq_nic_stop(nic);
|
|
|
|
if (deep) {
|
|
aq_nic_deinit(nic, !nic->aq_hw->aq_nic_cfg->wol);
|
|
aq_nic_set_power(nic);
|
|
}
|
|
|
|
rtnl_unlock();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int atl_resume_common(struct device *dev, bool deep)
|
|
{
|
|
struct pci_dev *pdev = to_pci_dev(dev);
|
|
struct aq_nic_s *nic;
|
|
int ret = 0;
|
|
|
|
nic = pci_get_drvdata(pdev);
|
|
|
|
rtnl_lock();
|
|
|
|
pci_set_power_state(pdev, PCI_D0);
|
|
pci_restore_state(pdev);
|
|
|
|
if (deep) {
|
|
/* Reinitialize Nic/Vecs objects */
|
|
aq_nic_deinit(nic, !nic->aq_hw->aq_nic_cfg->wol);
|
|
}
|
|
|
|
if (netif_running(nic->ndev)) {
|
|
ret = aq_nic_init(nic);
|
|
if (ret)
|
|
goto err_exit;
|
|
|
|
ret = aq_nic_start(nic);
|
|
if (ret)
|
|
goto err_exit;
|
|
}
|
|
|
|
netif_device_attach(nic->ndev);
|
|
netif_tx_start_all_queues(nic->ndev);
|
|
|
|
err_exit:
|
|
if (ret < 0)
|
|
aq_nic_deinit(nic, true);
|
|
|
|
rtnl_unlock();
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int aq_pm_freeze(struct device *dev)
|
|
{
|
|
return aq_suspend_common(dev, true);
|
|
}
|
|
|
|
static int aq_pm_suspend_poweroff(struct device *dev)
|
|
{
|
|
return aq_suspend_common(dev, false);
|
|
}
|
|
|
|
static int aq_pm_thaw(struct device *dev)
|
|
{
|
|
return atl_resume_common(dev, true);
|
|
}
|
|
|
|
static int aq_pm_resume_restore(struct device *dev)
|
|
{
|
|
return atl_resume_common(dev, false);
|
|
}
|
|
|
|
static const struct dev_pm_ops aq_pm_ops = {
|
|
.suspend = aq_pm_suspend_poweroff,
|
|
.poweroff = aq_pm_suspend_poweroff,
|
|
.freeze = aq_pm_freeze,
|
|
.resume = aq_pm_resume_restore,
|
|
.restore = aq_pm_resume_restore,
|
|
.thaw = aq_pm_thaw,
|
|
};
|
|
|
|
static struct pci_driver aq_pci_ops = {
|
|
.name = AQ_CFG_DRV_NAME,
|
|
.id_table = aq_pci_tbl,
|
|
.probe = aq_pci_probe,
|
|
.remove = aq_pci_remove,
|
|
.shutdown = aq_pci_shutdown,
|
|
#ifdef CONFIG_PM
|
|
.driver.pm = &aq_pm_ops,
|
|
#endif
|
|
};
|
|
|
|
int aq_pci_func_register_driver(void)
|
|
{
|
|
return pci_register_driver(&aq_pci_ops);
|
|
}
|
|
|
|
void aq_pci_func_unregister_driver(void)
|
|
{
|
|
pci_unregister_driver(&aq_pci_ops);
|
|
}
|
|
|