]> mj.ucw.cz Git - pciutils.git/blob - lmr/margin_hw.c
pcilmr: Add functions for device checking and preparations before main margining...
[pciutils.git] / lmr / margin_hw.c
1 /*
2  *      The PCI Utilities -- Verify and prepare devices before margining
3  *
4  *      Copyright (c) 2023 KNS Group LLC (YADRO)
5  *
6  *      Can be freely distributed and used under the terms of the GNU GPL v2+.
7  *
8  *      SPDX-License-Identifier: GPL-2.0-or-later
9  */
10
11 #include "lmr.h"
12
13 bool
14 margin_verify_link(struct pci_dev *down_port, struct pci_dev *up_port)
15 {
16   struct pci_cap *cap = pci_find_cap(down_port, PCI_CAP_ID_EXP, PCI_CAP_NORMAL);
17   if (!cap)
18     return false;
19   if ((pci_read_word(down_port, cap->addr + PCI_EXP_LNKSTA) & PCI_EXP_LNKSTA_SPEED) < 4)
20     return false;
21   if ((pci_read_word(down_port, cap->addr + PCI_EXP_LNKSTA) & PCI_EXP_LNKSTA_SPEED) > 5)
22     return false;
23
24   u8 down_type = pci_read_byte(down_port, PCI_HEADER_TYPE) & 0x7F;
25   u8 down_sec = pci_read_byte(down_port, PCI_SECONDARY_BUS);
26   u8 down_dir
27     = GET_REG_MASK(pci_read_word(down_port, cap->addr + PCI_EXP_FLAGS), PCI_EXP_FLAGS_TYPE);
28
29   // Verify that devices are linked, down_port is Root Port or Downstream Port of Switch,
30   // up_port is Function 0 of a Device
31   if (!(down_sec == up_port->bus && down_type == PCI_HEADER_TYPE_BRIDGE
32         && (down_dir == PCI_EXP_TYPE_ROOT_PORT || down_dir == PCI_EXP_TYPE_DOWNSTREAM)
33         && up_port->func == 0))
34     return false;
35
36   struct pci_cap *pm = pci_find_cap(up_port, PCI_CAP_ID_PM, PCI_CAP_NORMAL);
37   return pm && !(pci_read_word(up_port, pm->addr + PCI_PM_CTRL) & PCI_PM_CTRL_STATE_MASK); // D0
38 }
39
40 bool
41 margin_check_ready_bit(struct pci_dev *dev)
42 {
43   struct pci_cap *lmr = pci_find_cap(dev, PCI_EXT_CAP_ID_LMR, PCI_CAP_EXTENDED);
44   return lmr && (pci_read_word(dev, lmr->addr + PCI_LMR_PORT_STS) & PCI_LMR_PORT_STS_READY);
45 }
46
47 /* Awaits device at 16 GT/s or higher */
48 static struct margin_dev
49 fill_dev_wrapper(struct pci_dev *dev)
50 {
51   struct pci_cap *cap = pci_find_cap(dev, PCI_CAP_ID_EXP, PCI_CAP_NORMAL);
52   struct margin_dev res
53     = { .dev = dev,
54         .lmr_cap_addr = pci_find_cap(dev, PCI_EXT_CAP_ID_LMR, PCI_CAP_EXTENDED)->addr,
55         .width = GET_REG_MASK(pci_read_word(dev, cap->addr + PCI_EXP_LNKSTA), PCI_EXP_LNKSTA_WIDTH),
56         .retimers_n
57         = (!!(pci_read_word(dev, cap->addr + PCI_EXP_LNKSTA2) & PCI_EXP_LINKSTA2_RETIMER))
58           + (!!(pci_read_word(dev, cap->addr + PCI_EXP_LNKSTA2) & PCI_EXP_LINKSTA2_2RETIMERS)),
59         .link_speed = (pci_read_word(dev, cap->addr + PCI_EXP_LNKSTA) & PCI_EXP_LNKSTA_SPEED) };
60   return res;
61 }
62
63 bool
64 margin_fill_link(struct pci_dev *down_port, struct pci_dev *up_port, struct margin_link *wrappers)
65 {
66   if (!margin_verify_link(down_port, up_port))
67     return false;
68   wrappers->down_port = fill_dev_wrapper(down_port);
69   wrappers->up_port = fill_dev_wrapper(up_port);
70   return true;
71 }
72
73 /* Disable ASPM, set Hardware Autonomous Speed/Width Disable bits */
74 static bool
75 margin_prep_dev(struct margin_dev *dev)
76 {
77   struct pci_cap *pcie = pci_find_cap(dev->dev, PCI_CAP_ID_EXP, PCI_CAP_NORMAL);
78   if (!pcie)
79     return false;
80
81   u16 lnk_ctl = pci_read_word(dev->dev, pcie->addr + PCI_EXP_LNKCTL);
82   dev->aspm = lnk_ctl & PCI_EXP_LNKCTL_ASPM;
83   dev->hawd = !!(lnk_ctl & PCI_EXP_LNKCTL_HWAUTWD);
84   lnk_ctl &= ~PCI_EXP_LNKCTL_ASPM;
85   pci_write_word(dev->dev, pcie->addr + PCI_EXP_LNKCTL, lnk_ctl);
86   if (pci_read_word(dev->dev, pcie->addr + PCI_EXP_LNKCTL) & PCI_EXP_LNKCTL_ASPM)
87     return false;
88
89   lnk_ctl |= PCI_EXP_LNKCTL_HWAUTWD;
90   pci_write_word(dev->dev, pcie->addr + PCI_EXP_LNKCTL, lnk_ctl);
91
92   u16 lnk_ctl2 = pci_read_word(dev->dev, pcie->addr + PCI_EXP_LNKCTL2);
93   dev->hasd = !!(lnk_ctl2 & PCI_EXP_LNKCTL2_SPEED_DIS);
94   lnk_ctl2 |= PCI_EXP_LNKCTL2_SPEED_DIS;
95   pci_write_word(dev->dev, pcie->addr + PCI_EXP_LNKCTL2, lnk_ctl2);
96
97   return true;
98 }
99
100 /* Restore Device ASPM, Hardware Autonomous Speed/Width settings */
101 static void
102 margin_restore_dev(struct margin_dev *dev)
103 {
104   struct pci_cap *pcie = pci_find_cap(dev->dev, PCI_CAP_ID_EXP, PCI_CAP_NORMAL);
105   if (!pcie)
106     return;
107
108   u16 lnk_ctl = pci_read_word(dev->dev, pcie->addr + PCI_EXP_LNKCTL);
109   lnk_ctl = SET_REG_MASK(lnk_ctl, PCI_EXP_LNKCAP_ASPM, dev->aspm);
110   lnk_ctl = SET_REG_MASK(lnk_ctl, PCI_EXP_LNKCTL_HWAUTWD, dev->hawd);
111   pci_write_word(dev->dev, pcie->addr + PCI_EXP_LNKCTL, lnk_ctl);
112
113   u16 lnk_ctl2 = pci_read_word(dev->dev, pcie->addr + PCI_EXP_LNKCTL2);
114   lnk_ctl2 = SET_REG_MASK(lnk_ctl2, PCI_EXP_LNKCTL2_SPEED_DIS, dev->hasd);
115   pci_write_word(dev->dev, pcie->addr + PCI_EXP_LNKCTL2, lnk_ctl2);
116 }
117
118 bool
119 margin_prep_link(struct margin_link *link)
120 {
121   if (!link)
122     return false;
123   if (!margin_prep_dev(&link->down_port))
124     return false;
125   if (!margin_prep_dev(&link->up_port))
126     {
127       margin_restore_dev(&link->down_port);
128       return false;
129     }
130   return true;
131 }
132
133 void
134 margin_restore_link(struct margin_link *link)
135 {
136   margin_restore_dev(&link->down_port);
137   margin_restore_dev(&link->up_port);
138 }