]> www.infradead.org Git - users/jedix/linux-maple.git/commitdiff
ixgbe: Add protection from VF invalid target DMA
authorGreg Rose <gregory.v.rose@intel.com>
Wed, 7 Sep 2011 05:59:35 +0000 (05:59 +0000)
committerJoe Jin <joe.jin@oracle.com>
Thu, 17 May 2012 14:19:04 +0000 (22:19 +0800)
It is possible for a VF to set an invalid target DMA address in its
Tx/Rx descriptor buffer pointers.  The workarounds in this patch
will guard against such an event and issue a VFLR to the VF in response.
The VFLR will shut down the VF until an administrator can take action
to investigate the event and correct the problem.

(cherry picked from commit 83c61fa97a7d4ef16506a760f9e52b3144978346)
Signed-off-by: Greg Rose <gregory.v.rose@intel.com>
Signed-off-by: Jeff Kirsher <jeffrey.t.kirsher@intel.com>
Signed-off-by: Joe Jin <joe.jin@oracle.com>
drivers/net/ixgbe/ixgbe.h
drivers/net/ixgbe/ixgbe_main.c

index 38940d72991ddeb3259595d65d2a85df8eaf39a6..c1f76aaf87744ea444a2eb4554f0cf1b431f537b 100644 (file)
 #define MAX_EMULATION_MAC_ADDRS         16
 #define IXGBE_MAX_PF_MACVLANS           15
 #define VMDQ_P(p)   ((p) + adapter->num_vfs)
+#define IXGBE_82599_VF_DEVICE_ID        0x10ED
+#define IXGBE_X540_VF_DEVICE_ID         0x1515
 
 struct vf_data_storage {
        unsigned char vf_mac_addresses[ETH_ALEN];
@@ -512,6 +514,8 @@ struct ixgbe_adapter {
        struct hlist_head fdir_filter_list;
        union ixgbe_atr_input fdir_mask;
        int fdir_filter_count;
+       u32 timer_event_accumulator;
+       u32 vferr_refcount;
 };
 
 struct ixgbe_fdir_filter {
index 9da560b482d700f40aea8b52fc69e25953b0ff3a..21c1aeb7f42301513bfaf39f6692c4a63f2e609f 100644 (file)
@@ -6111,6 +6111,51 @@ static void ixgbe_sfp_link_config_subtask(struct ixgbe_adapter *adapter)
        clear_bit(__IXGBE_IN_SFP_INIT, &adapter->state);
 }
 
+#ifdef CONFIG_PCI_IOV
+static void ixgbe_check_for_bad_vf(struct ixgbe_adapter *adapter)
+{
+       int vf;
+       struct ixgbe_hw *hw = &adapter->hw;
+       struct net_device *netdev = adapter->netdev;
+       u32 gpc;
+       u32 ciaa, ciad;
+
+       gpc = IXGBE_READ_REG(hw, IXGBE_TXDGPC);
+       if (gpc) /* If incrementing then no need for the check below */
+               return;
+       /*
+        * Check to see if a bad DMA write target from an errant or
+        * malicious VF has caused a PCIe error.  If so then we can
+        * issue a VFLR to the offending VF(s) and then resume without
+        * requesting a full slot reset.
+        */
+
+       for (vf = 0; vf < adapter->num_vfs; vf++) {
+               ciaa = (vf << 16) | 0x80000000;
+               /* 32 bit read so align, we really want status at offset 6 */
+               ciaa |= PCI_COMMAND;
+               IXGBE_WRITE_REG(hw, IXGBE_CIAA_82599, ciaa);
+               ciad = IXGBE_READ_REG(hw, IXGBE_CIAD_82599);
+               ciaa &= 0x7FFFFFFF;
+               /* disable debug mode asap after reading data */
+               IXGBE_WRITE_REG(hw, IXGBE_CIAA_82599, ciaa);
+               /* Get the upper 16 bits which will be the PCI status reg */
+               ciad >>= 16;
+               if (ciad & PCI_STATUS_REC_MASTER_ABORT) {
+                       netdev_err(netdev, "VF %d Hung DMA\n", vf);
+                       /* Issue VFLR */
+                       ciaa = (vf << 16) | 0x80000000;
+                       ciaa |= 0xA8;
+                       IXGBE_WRITE_REG(hw, IXGBE_CIAA_82599, ciaa);
+                       ciad = 0x00008000;  /* VFLR */
+                       IXGBE_WRITE_REG(hw, IXGBE_CIAD_82599, ciad);
+                       ciaa &= 0x7FFFFFFF;
+                       IXGBE_WRITE_REG(hw, IXGBE_CIAA_82599, ciaa);
+               }
+       }
+}
+
+#endif
 /**
  * ixgbe_service_timer - Timer Call-back
  * @data: pointer to adapter cast into an unsigned long
@@ -6119,17 +6164,49 @@ static void ixgbe_service_timer(unsigned long data)
 {
        struct ixgbe_adapter *adapter = (struct ixgbe_adapter *)data;
        unsigned long next_event_offset;
+       bool ready = true;
 
+#ifdef CONFIG_PCI_IOV
+       ready = false;
+
+       /*
+        * don't bother with SR-IOV VF DMA hang check if there are
+        * no VFs or the link is down
+        */
+       if (!adapter->num_vfs ||
+           (adapter->flags & IXGBE_FLAG_NEED_LINK_UPDATE)) {
+               ready = true;
+               goto normal_timer_service;
+       }
+
+       /* If we have VFs allocated then we must check for DMA hangs */
+       ixgbe_check_for_bad_vf(adapter);
+       next_event_offset = HZ / 50;
+       adapter->timer_event_accumulator++;
+
+       if (adapter->timer_event_accumulator >= 100) {
+               ready = true;
+               adapter->timer_event_accumulator = 0;
+       }
+
+       goto schedule_event;
+
+normal_timer_service:
+#endif
        /* poll faster when waiting for link */
        if (adapter->flags & IXGBE_FLAG_NEED_LINK_UPDATE)
                next_event_offset = HZ / 10;
        else
                next_event_offset = HZ * 2;
 
+#ifdef CONFIG_PCI_IOV
+schedule_event:
+#endif
        /* Reset the timer */
        mod_timer(&adapter->service_timer, next_event_offset + jiffies);
 
-       ixgbe_service_event_schedule(adapter);
+       if (ready)
+               ixgbe_service_event_schedule(adapter);
 }
 
 static void ixgbe_reset_subtask(struct ixgbe_adapter *adapter)
@@ -7717,6 +7794,91 @@ static pci_ers_result_t ixgbe_io_error_detected(struct pci_dev *pdev,
        struct ixgbe_adapter *adapter = pci_get_drvdata(pdev);
        struct net_device *netdev = adapter->netdev;
 
+#ifdef CONFIG_PCI_IOV
+       struct pci_dev *bdev, *vfdev;
+       u32 dw0, dw1, dw2, dw3;
+       int vf, pos;
+       u16 req_id, pf_func;
+
+       if (adapter->hw.mac.type == ixgbe_mac_82598EB ||
+           adapter->num_vfs == 0)
+               goto skip_bad_vf_detection;
+
+       bdev = pdev->bus->self;
+       while (bdev && (bdev->pcie_type != PCI_EXP_TYPE_ROOT_PORT))
+               bdev = bdev->bus->self;
+
+       if (!bdev)
+               goto skip_bad_vf_detection;
+
+       pos = pci_find_ext_capability(bdev, PCI_EXT_CAP_ID_ERR);
+       if (!pos)
+               goto skip_bad_vf_detection;
+
+       pci_read_config_dword(bdev, pos + PCI_ERR_HEADER_LOG, &dw0);
+       pci_read_config_dword(bdev, pos + PCI_ERR_HEADER_LOG + 4, &dw1);
+       pci_read_config_dword(bdev, pos + PCI_ERR_HEADER_LOG + 8, &dw2);
+       pci_read_config_dword(bdev, pos + PCI_ERR_HEADER_LOG + 12, &dw3);
+
+       req_id = dw1 >> 16;
+       /* On the 82599 if bit 7 of the requestor ID is set then it's a VF */
+       if (!(req_id & 0x0080))
+               goto skip_bad_vf_detection;
+
+       pf_func = req_id & 0x01;
+       if ((pf_func & 1) == (pdev->devfn & 1)) {
+               unsigned int device_id;
+
+               vf = (req_id & 0x7F) >> 1;
+               e_dev_err("VF %d has caused a PCIe error\n", vf);
+               e_dev_err("TLP: dw0: %8.8x\tdw1: %8.8x\tdw2: "
+                               "%8.8x\tdw3: %8.8x\n",
+               dw0, dw1, dw2, dw3);
+               switch (adapter->hw.mac.type) {
+               case ixgbe_mac_82599EB:
+                       device_id = IXGBE_82599_VF_DEVICE_ID;
+                       break;
+               case ixgbe_mac_X540:
+                       device_id = IXGBE_X540_VF_DEVICE_ID;
+                       break;
+               default:
+                       device_id = 0;
+                       break;
+               }
+
+               /* Find the pci device of the offending VF */
+               vfdev = pci_get_device(IXGBE_INTEL_VENDOR_ID, device_id, NULL);
+               while (vfdev) {
+                       if (vfdev->devfn == (req_id & 0xFF))
+                               break;
+                       vfdev = pci_get_device(IXGBE_INTEL_VENDOR_ID,
+                                              device_id, vfdev);
+               }
+               /*
+                * There's a slim chance the VF could have been hot plugged,
+                * so if it is no longer present we don't need to issue the
+                * VFLR.  Just clean up the AER in that case.
+                */
+               if (vfdev) {
+                       e_dev_err("Issuing VFLR to VF %d\n", vf);
+                       pci_write_config_dword(vfdev, 0xA8, 0x00008000);
+               }
+
+               pci_cleanup_aer_uncorrect_error_status(pdev);
+       }
+
+       /*
+        * Even though the error may have occurred on the other port
+        * we still need to increment the vf error reference count for
+        * both ports because the I/O resume function will be called
+        * for both of them.
+        */
+       adapter->vferr_refcount++;
+
+       return PCI_ERS_RESULT_RECOVERED;
+
+skip_bad_vf_detection:
+#endif /* CONFIG_PCI_IOV */
        netif_device_detach(netdev);
 
        if (state == pci_channel_io_perm_failure)
@@ -7779,6 +7941,14 @@ static void ixgbe_io_resume(struct pci_dev *pdev)
        struct ixgbe_adapter *adapter = pci_get_drvdata(pdev);
        struct net_device *netdev = adapter->netdev;
 
+#ifdef CONFIG_PCI_IOV
+       if (adapter->vferr_refcount) {
+               e_info(drv, "Resuming after VF err\n");
+               adapter->vferr_refcount--;
+               return;
+       }
+
+#endif
        if (netif_running(netdev))
                ixgbe_up(adapter);