]> mj.ucw.cz Git - pciutils.git/blob - lmr/margin_hw.c
pcilmr: Fix margining for ports with Lane reversal
[pciutils.git] / lmr / margin_hw.c
1 /*
2  *      The PCI Utilities -- Verify and prepare devices before margining
3  *
4  *      Copyright (c) 2023-2024 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 <memory.h>
12
13 #include "lmr.h"
14
15 static u16 special_hw[][4] =
16   // Vendor ID, Device ID, Revision ID, margin_hw
17   { { 0x8086, 0x347A, 0x4, MARGIN_ICE_LAKE_RC }, 
18     { 0xFFFF, 0, 0, MARGIN_HW_DEFAULT } 
19   };
20
21 static enum margin_hw
22 detect_unique_hw(struct pci_dev *dev)
23 {
24   u16 vendor = pci_read_word(dev, PCI_VENDOR_ID);
25   u16 device = pci_read_word(dev, PCI_DEVICE_ID);
26   u8 revision = pci_read_byte(dev, PCI_REVISION_ID);
27
28   for (int i = 0; special_hw[i][0] != 0xFFFF; i++)
29     {
30       if (vendor == special_hw[i][0] && device == special_hw[i][1] && revision == special_hw[i][2])
31         return special_hw[i][3];
32     }
33   return MARGIN_HW_DEFAULT;
34 }
35
36 bool
37 margin_port_is_down(struct pci_dev *dev)
38 {
39   struct pci_cap *cap = pci_find_cap(dev, PCI_CAP_ID_EXP, PCI_CAP_NORMAL);
40   if (!cap)
41     return false;
42   u8 type = pci_read_byte(dev, PCI_HEADER_TYPE) & 0x7F;
43   u8 dir = GET_REG_MASK(pci_read_word(dev, cap->addr + PCI_EXP_FLAGS), PCI_EXP_FLAGS_TYPE);
44
45   if (type == PCI_HEADER_TYPE_BRIDGE
46       && (dir == PCI_EXP_TYPE_ROOT_PORT || dir == PCI_EXP_TYPE_DOWNSTREAM))
47     return true;
48   else
49     return false;
50 }
51
52 bool
53 margin_find_pair(struct pci_access *pacc, struct pci_dev *dev, struct pci_dev **down_port,
54                  struct pci_dev **up_port)
55 {
56   struct pci_cap *cap = pci_find_cap(dev, PCI_CAP_ID_EXP, PCI_CAP_NORMAL);
57   if (!cap)
58     return false;
59   bool given_down = margin_port_is_down(dev);
60
61   for (struct pci_dev *p = pacc->devices; p; p = p->next)
62     {
63       if (given_down && pci_read_byte(dev, PCI_SECONDARY_BUS) == p->bus && dev->domain == p->domain
64           && p->func == 0)
65         {
66           *down_port = dev;
67           *up_port = p;
68           return true;
69         }
70       else if (!given_down && pci_read_byte(p, PCI_SECONDARY_BUS) == dev->bus
71                && dev->domain == p->domain)
72         {
73           *down_port = p;
74           *up_port = dev;
75           return true;
76         }
77     }
78   return false;
79 }
80
81 bool
82 margin_verify_link(struct pci_dev *down_port, struct pci_dev *up_port)
83 {
84   struct pci_cap *cap = pci_find_cap(down_port, PCI_CAP_ID_EXP, PCI_CAP_NORMAL);
85   if (!cap)
86     return false;
87   if ((pci_read_word(down_port, cap->addr + PCI_EXP_LNKSTA) & PCI_EXP_LNKSTA_SPEED) < 4)
88     return false;
89   if ((pci_read_word(down_port, cap->addr + PCI_EXP_LNKSTA) & PCI_EXP_LNKSTA_SPEED) > 5)
90     return false;
91
92   u8 down_sec = pci_read_byte(down_port, PCI_SECONDARY_BUS);
93
94   // Verify that devices are linked, down_port is Root Port or Downstream Port of Switch,
95   // up_port is Function 0 of a Device
96   if (!(down_sec == up_port->bus && margin_port_is_down(down_port) && up_port->func == 0))
97     return false;
98
99   struct pci_cap *pm = pci_find_cap(up_port, PCI_CAP_ID_PM, PCI_CAP_NORMAL);
100   return pm && !(pci_read_word(up_port, pm->addr + PCI_PM_CTRL) & PCI_PM_CTRL_STATE_MASK); // D0
101 }
102
103 bool
104 margin_check_ready_bit(struct pci_dev *dev)
105 {
106   struct pci_cap *lmr = pci_find_cap(dev, PCI_EXT_CAP_ID_LMR, PCI_CAP_EXTENDED);
107   return lmr && (pci_read_word(dev, lmr->addr + PCI_LMR_PORT_STS) & PCI_LMR_PORT_STS_READY);
108 }
109
110 /* Awaits device at 16 GT/s or higher */
111 static struct margin_dev
112 fill_dev_wrapper(struct pci_dev *dev)
113 {
114   struct pci_cap *cap = pci_find_cap(dev, PCI_CAP_ID_EXP, PCI_CAP_NORMAL);
115   struct margin_dev res = {
116     .dev = dev,
117     .lmr_cap_addr = pci_find_cap(dev, PCI_EXT_CAP_ID_LMR, PCI_CAP_EXTENDED)->addr,
118     .neg_width = GET_REG_MASK(pci_read_word(dev, cap->addr + PCI_EXP_LNKSTA), PCI_EXP_LNKSTA_WIDTH),
119     .max_width = GET_REG_MASK(pci_read_long(dev, cap->addr + PCI_EXP_LNKCAP), PCI_EXP_LNKCAP_WIDTH),
120     .retimers_n
121     = (!!(pci_read_word(dev, cap->addr + PCI_EXP_LNKSTA2) & PCI_EXP_LINKSTA2_RETIMER))
122       + (!!(pci_read_word(dev, cap->addr + PCI_EXP_LNKSTA2) & PCI_EXP_LINKSTA2_2RETIMERS)),
123     .link_speed = (pci_read_word(dev, cap->addr + PCI_EXP_LNKSTA) & PCI_EXP_LNKSTA_SPEED),
124     .hw = detect_unique_hw(dev)
125   };
126   return res;
127 }
128
129 bool
130 margin_fill_link(struct pci_dev *down_port, struct pci_dev *up_port, struct margin_link *wrappers)
131 {
132   memset(wrappers, 0, sizeof(*wrappers));
133   if (!margin_verify_link(down_port, up_port))
134     return false;
135   wrappers->down_port = fill_dev_wrapper(down_port);
136   wrappers->up_port = fill_dev_wrapper(up_port);
137   return true;
138 }
139
140 /* Disable ASPM, set Hardware Autonomous Speed/Width Disable bits */
141 static bool
142 margin_prep_dev(struct margin_dev *dev)
143 {
144   struct pci_cap *pcie = pci_find_cap(dev->dev, PCI_CAP_ID_EXP, PCI_CAP_NORMAL);
145   if (!pcie)
146     return false;
147
148   u16 lnk_ctl = pci_read_word(dev->dev, pcie->addr + PCI_EXP_LNKCTL);
149   dev->aspm = lnk_ctl & PCI_EXP_LNKCTL_ASPM;
150   dev->hawd = !!(lnk_ctl & PCI_EXP_LNKCTL_HWAUTWD);
151   lnk_ctl &= ~PCI_EXP_LNKCTL_ASPM;
152   pci_write_word(dev->dev, pcie->addr + PCI_EXP_LNKCTL, lnk_ctl);
153   if (pci_read_word(dev->dev, pcie->addr + PCI_EXP_LNKCTL) & PCI_EXP_LNKCTL_ASPM)
154     return false;
155
156   lnk_ctl |= PCI_EXP_LNKCTL_HWAUTWD;
157   pci_write_word(dev->dev, pcie->addr + PCI_EXP_LNKCTL, lnk_ctl);
158
159   u16 lnk_ctl2 = pci_read_word(dev->dev, pcie->addr + PCI_EXP_LNKCTL2);
160   dev->hasd = !!(lnk_ctl2 & PCI_EXP_LNKCTL2_SPEED_DIS);
161   lnk_ctl2 |= PCI_EXP_LNKCTL2_SPEED_DIS;
162   pci_write_word(dev->dev, pcie->addr + PCI_EXP_LNKCTL2, lnk_ctl2);
163
164   return true;
165 }
166
167 /* Restore Device ASPM, Hardware Autonomous Speed/Width settings */
168 static void
169 margin_restore_dev(struct margin_dev *dev)
170 {
171   struct pci_cap *pcie = pci_find_cap(dev->dev, PCI_CAP_ID_EXP, PCI_CAP_NORMAL);
172   if (!pcie)
173     return;
174
175   u16 lnk_ctl = pci_read_word(dev->dev, pcie->addr + PCI_EXP_LNKCTL);
176   lnk_ctl = SET_REG_MASK(lnk_ctl, PCI_EXP_LNKCAP_ASPM, dev->aspm);
177   lnk_ctl = SET_REG_MASK(lnk_ctl, PCI_EXP_LNKCTL_HWAUTWD, dev->hawd);
178   pci_write_word(dev->dev, pcie->addr + PCI_EXP_LNKCTL, lnk_ctl);
179
180   u16 lnk_ctl2 = pci_read_word(dev->dev, pcie->addr + PCI_EXP_LNKCTL2);
181   lnk_ctl2 = SET_REG_MASK(lnk_ctl2, PCI_EXP_LNKCTL2_SPEED_DIS, dev->hasd);
182   pci_write_word(dev->dev, pcie->addr + PCI_EXP_LNKCTL2, lnk_ctl2);
183 }
184
185 bool
186 margin_prep_link(struct margin_link *link)
187 {
188   if (!link)
189     return false;
190   if (!margin_prep_dev(&link->down_port))
191     return false;
192   if (!margin_prep_dev(&link->up_port))
193     {
194       margin_restore_dev(&link->down_port);
195       return false;
196     }
197   return true;
198 }
199
200 void
201 margin_restore_link(struct margin_link *link)
202 {
203   margin_restore_dev(&link->down_port);
204   margin_restore_dev(&link->up_port);
205 }