summaryrefslogtreecommitdiffstats
path: root/src/soc/intel/common/block/pcie/pcie_rp.c
blob: 145159f6eb6691cfbd7d2c7a4b52845c1ec0fe70 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/* SPDX-License-Identifier: GPL-2.0-only */

#include <stdint.h>

#include <commonlib/helpers.h>
#include <console/console.h>
#include <device/device.h>
#include <device/pci_def.h>
#include <device/pci_ops.h>
#include <device/pci_type.h>
#include <intelblocks/pcie_rp.h>

static int pcie_rp_original_idx(
		const struct pcie_rp_group *const group,
		const unsigned int offset,
		const pci_devfn_t dev)
{
	const uint16_t clist = pci_s_find_capability(dev, PCI_CAP_ID_PCIE);
	if (clist == 0) {
		printk(BIOS_WARNING,
		       "%s: Can't find PCIe capapilities for PCI: 00:%02x.%x, ignoring.\n",
		       __func__, group->slot, PCI_FUNC(PCI_DEV2DEVFN(dev)));
		return -1;
	}

	const uint16_t xcap = pci_s_read_config16(dev, clist + PCI_EXP_FLAGS);
	if ((xcap & PCI_EXP_FLAGS_TYPE) >> 4 != PCI_EXP_TYPE_ROOT_PORT) {
		printk(BIOS_WARNING, "%s: Non root-port found at PCI: 00:%02x.%x, ignoring.\n",
		       __func__, group->slot, PCI_FUNC(PCI_DEV2DEVFN(dev)));
		return -1;
	}

	const uint32_t lcap = pci_s_read_config32(dev, clist + PCI_EXP_LNKCAP);

	/* Read n-based absolute port number from LCAP register.
	   This reflects the numbering scheme that Intel uses in their
	   documentation and what we use as index (0-based, though) in
	   our mapping. */
	const unsigned int port_num = (lcap & PCI_EXP_LNKCAP_PORT) >> 24;

	/* Subtract lcap_port_base from port_num to get 0-based index */
	const unsigned int port_idx = port_num - group->lcap_port_base;

	/* Check if port_idx (0-based) is out of bounds */
	if (port_idx < offset || port_idx >= offset + group->count) {
		printk(BIOS_WARNING, "%s: Unexpected root-port number '%u'"
				     " at PCI: 00:%02x.%x, ignoring.\n",
		       __func__, port_num, group->slot, PCI_FUNC(PCI_DEV2DEVFN(dev)));
		return -1;
	}

	return port_idx;
}

/* Scan actual PCI config space to reconstruct current mapping */
static void pcie_rp_scan_groups(int mapping[], const struct pcie_rp_group *const groups)
{
	unsigned int offset = 0;
	const struct pcie_rp_group *group;
	for (group = groups; group->count; ++group) {
		unsigned int fn;
		for (fn = rp_start_fn(group); fn <= rp_end_fn(group); ++fn) {
			const pci_devfn_t dev = PCI_DEV(0, group->slot, fn);
			const uint16_t did = pci_s_read_config16(dev, PCI_DEVICE_ID);
			if (did == 0xffff) {
				if (fn == 0)
					break;
				continue;
			}

			const int rp_idx = pcie_rp_original_idx(group, offset, dev);
			if (rp_idx < 0)
				continue;
			if (mapping[rp_idx] != -1) {
				printk(BIOS_WARNING, "%s: Root Port #%u reported by PCI: "
				       "00:%02x.%x already reported by PCI: 00:%02x.%x!\n",
				       __func__, rp_idx + 1, group->slot, fn,
				       group->slot, mapping[rp_idx]);
				continue;
			}

			printk(BIOS_INFO, "Found PCIe Root Port #%u at PCI: 00:%02x.%x.\n",
			       rp_idx + 1, group->slot, fn);
			mapping[rp_idx] = fn;
		}
		offset += group->count;
	}
}

/* Returns `true` if the device should be unlinked. */
static bool pcie_rp_update_dev(
		struct device *const dev,
		const struct pcie_rp_group *const groups,
		const int mapping[])
{
	if (dev->path.type != DEVICE_PATH_PCI)
		return false;

	/* Find matching group and offset. */
	unsigned int offset = 0;
	const struct pcie_rp_group *group;
	for (group = groups; group->count; ++group) {
		if (PCI_SLOT(dev->path.pci.devfn) == group->slot &&
		    PCI_FUNC(dev->path.pci.devfn) >= rp_start_fn(group) &&
		    PCI_FUNC(dev->path.pci.devfn) <= rp_end_fn(group))
			break;
		offset += group->count;
	}
	if (!group->count)
		return false;

	/* Now update based on what we know. */
	const int rp_idx = offset + PCI_FUNC(dev->path.pci.devfn);
	const int new_fn = mapping[rp_idx];
	if (new_fn < 0) {
		if (dev->enabled) {
			printk(BIOS_NOTICE, "%s: Couldn't find PCIe Root Port #%u "
			       "(originally %s) which was enabled in devicetree, removing.\n",
			       __func__, rp_idx + 1, dev_path(dev));
		}
		return true;
	} else if (PCI_FUNC(dev->path.pci.devfn) != new_fn) {
		printk(BIOS_INFO,
		       "Remapping PCIe Root Port #%u from %s to new function number %u.\n",
		       rp_idx + 1, dev_path(dev), new_fn);
		dev->path.pci.devfn = PCI_DEVFN(PCI_SLOT(dev->path.pci.devfn), new_fn);
	}
	return false;
}

void pcie_rp_update_devicetree(const struct pcie_rp_group *const groups)
{
	/* Maps absolute root-port numbers to function numbers.
	   Negative if disabled, new function number otherwise. */
	int mapping[CONFIG_MAX_ROOT_PORTS];
	unsigned int offset, i;

	if (!groups || !groups->count)
		return;

	struct bus *const root = pci_root_bus();
	if (!root)
		return;

	offset = 0;
	const struct pcie_rp_group *group;
	for (group = groups; group->count; ++group)
		offset += group->count;

	if (offset > ARRAY_SIZE(mapping)) {
		printk(BIOS_ERR, "%s: Error: Group exceeds CONFIG_MAX_ROOT_PORTS.\n", __func__);
		return;
	}

	/* Assume everything we don't encounter later is disabled */
	for (i = 0; i < ARRAY_SIZE(mapping); ++i)
		mapping[i] = -1;

	pcie_rp_scan_groups(mapping, groups);

	struct device *dev;
	struct device **link = &root->children;
	for (dev = *link; dev; dev = *link) {
		if (pcie_rp_update_dev(dev, groups, mapping)) {
			/* Unlink vanished device. */
			*link = dev->sibling;
			dev->sibling = NULL;
			continue;
		}

		link = &dev->sibling;
	}
}