blob: be357eea552c2b4235732836c538b6dc11804680 [file] [log] [blame]
Dan Williams4812be92021-06-09 09:01:35 -07001// SPDX-License-Identifier: GPL-2.0-only
2/* Copyright(c) 2021 Intel Corporation. All rights reserved. */
3#include <linux/platform_device.h>
4#include <linux/module.h>
5#include <linux/device.h>
6#include <linux/kernel.h>
7#include <linux/acpi.h>
Dan Williams3b94ce72021-06-09 09:01:51 -07008#include <linux/pci.h>
Dan Williams4812be92021-06-09 09:01:35 -07009#include "cxl.h"
10
Dan Williams3b94ce72021-06-09 09:01:51 -070011struct cxl_walk_context {
12 struct device *dev;
13 struct pci_bus *root;
14 struct cxl_port *port;
15 int error;
16 int count;
17};
18
19static int match_add_root_ports(struct pci_dev *pdev, void *data)
20{
21 struct cxl_walk_context *ctx = data;
22 struct pci_bus *root_bus = ctx->root;
23 struct cxl_port *port = ctx->port;
24 int type = pci_pcie_type(pdev);
25 struct device *dev = ctx->dev;
26 u32 lnkcap, port_num;
27 int rc;
28
29 if (pdev->bus != root_bus)
30 return 0;
31 if (!pci_is_pcie(pdev))
32 return 0;
33 if (type != PCI_EXP_TYPE_ROOT_PORT)
34 return 0;
35 if (pci_read_config_dword(pdev, pci_pcie_cap(pdev) + PCI_EXP_LNKCAP,
36 &lnkcap) != PCIBIOS_SUCCESSFUL)
37 return 0;
38
39 /* TODO walk DVSEC to find component register base */
40 port_num = FIELD_GET(PCI_EXP_LNKCAP_PN, lnkcap);
41 rc = cxl_add_dport(port, &pdev->dev, port_num, CXL_RESOURCE_NONE);
42 if (rc) {
43 ctx->error = rc;
44 return rc;
45 }
46 ctx->count++;
47
48 dev_dbg(dev, "add dport%d: %s\n", port_num, dev_name(&pdev->dev));
49
50 return 0;
51}
52
Dan Williams7d4b5ca2021-06-09 09:01:46 -070053static struct acpi_device *to_cxl_host_bridge(struct device *dev)
54{
55 struct acpi_device *adev = to_acpi_device(dev);
56
57 if (strcmp(acpi_device_hid(adev), "ACPI0016") == 0)
58 return adev;
59 return NULL;
60}
61
Dan Williams3b94ce72021-06-09 09:01:51 -070062/*
63 * A host bridge is a dport to a CFMWS decode and it is a uport to the
64 * dport (PCIe Root Ports) in the host bridge.
65 */
66static int add_host_bridge_uport(struct device *match, void *arg)
67{
68 struct acpi_device *bridge = to_cxl_host_bridge(match);
69 struct cxl_port *root_port = arg;
70 struct device *host = root_port->dev.parent;
71 struct acpi_pci_root *pci_root;
72 struct cxl_walk_context ctx;
Dan Williams40ba17a2021-06-09 09:43:29 -070073 struct cxl_decoder *cxld;
Dan Williams3b94ce72021-06-09 09:01:51 -070074 struct cxl_port *port;
75
76 if (!bridge)
77 return 0;
78
79 pci_root = acpi_pci_find_root(bridge->handle);
80 if (!pci_root)
81 return -ENXIO;
82
83 /* TODO: fold in CEDT.CHBS retrieval */
84 port = devm_cxl_add_port(host, match, CXL_RESOURCE_NONE, root_port);
85 if (IS_ERR(port))
86 return PTR_ERR(port);
87 dev_dbg(host, "%s: add: %s\n", dev_name(match), dev_name(&port->dev));
88
89 ctx = (struct cxl_walk_context){
90 .dev = host,
91 .root = pci_root->bus,
92 .port = port,
93 };
94 pci_walk_bus(pci_root->bus, match_add_root_ports, &ctx);
95
96 if (ctx.count == 0)
97 return -ENODEV;
Dan Williams40ba17a2021-06-09 09:43:29 -070098 if (ctx.error)
99 return ctx.error;
100
101 /* TODO: Scan CHBCR for HDM Decoder resources */
102
103 /*
104 * In the single-port host-bridge case there are no HDM decoders
105 * in the CHBCR and a 1:1 passthrough decode is implied.
106 */
107 if (ctx.count == 1) {
108 cxld = devm_cxl_add_passthrough_decoder(host, port);
109 if (IS_ERR(cxld))
110 return PTR_ERR(cxld);
111
112 dev_dbg(host, "add: %s\n", dev_name(&cxld->dev));
113 }
114
115 return 0;
Dan Williams3b94ce72021-06-09 09:01:51 -0700116}
117
Dan Williams7d4b5ca2021-06-09 09:01:46 -0700118static int add_host_bridge_dport(struct device *match, void *arg)
119{
120 int rc;
121 acpi_status status;
122 unsigned long long uid;
123 struct cxl_port *root_port = arg;
124 struct device *host = root_port->dev.parent;
125 struct acpi_device *bridge = to_cxl_host_bridge(match);
126
127 if (!bridge)
128 return 0;
129
130 status = acpi_evaluate_integer(bridge->handle, METHOD_NAME__UID, NULL,
131 &uid);
132 if (status != AE_OK) {
133 dev_err(host, "unable to retrieve _UID of %s\n",
134 dev_name(match));
135 return -ENODEV;
136 }
137
138 rc = cxl_add_dport(root_port, match, uid, CXL_RESOURCE_NONE);
139 if (rc) {
140 dev_err(host, "failed to add downstream port: %s\n",
141 dev_name(match));
142 return rc;
143 }
144 dev_dbg(host, "add dport%llu: %s\n", uid, dev_name(match));
145 return 0;
146}
147
Dan Williams4812be92021-06-09 09:01:35 -0700148static int cxl_acpi_probe(struct platform_device *pdev)
149{
Dan Williams3b94ce72021-06-09 09:01:51 -0700150 int rc;
Dan Williams4812be92021-06-09 09:01:35 -0700151 struct cxl_port *root_port;
152 struct device *host = &pdev->dev;
Dan Williams7d4b5ca2021-06-09 09:01:46 -0700153 struct acpi_device *adev = ACPI_COMPANION(host);
Dan Williams4812be92021-06-09 09:01:35 -0700154
155 root_port = devm_cxl_add_port(host, host, CXL_RESOURCE_NONE, NULL);
156 if (IS_ERR(root_port))
157 return PTR_ERR(root_port);
158 dev_dbg(host, "add: %s\n", dev_name(&root_port->dev));
159
Dan Williams3b94ce72021-06-09 09:01:51 -0700160 rc = bus_for_each_dev(adev->dev.bus, NULL, root_port,
161 add_host_bridge_dport);
162 if (rc)
163 return rc;
164
165 /*
166 * Root level scanned with host-bridge as dports, now scan host-bridges
167 * for their role as CXL uports to their CXL-capable PCIe Root Ports.
168 */
Dan Williams7d4b5ca2021-06-09 09:01:46 -0700169 return bus_for_each_dev(adev->dev.bus, NULL, root_port,
Dan Williams3b94ce72021-06-09 09:01:51 -0700170 add_host_bridge_uport);
Dan Williams4812be92021-06-09 09:01:35 -0700171}
172
173static const struct acpi_device_id cxl_acpi_ids[] = {
174 { "ACPI0017", 0 },
175 { "", 0 },
176};
177MODULE_DEVICE_TABLE(acpi, cxl_acpi_ids);
178
179static struct platform_driver cxl_acpi_driver = {
180 .probe = cxl_acpi_probe,
181 .driver = {
182 .name = KBUILD_MODNAME,
183 .acpi_match_table = cxl_acpi_ids,
184 },
185};
186
187module_platform_driver(cxl_acpi_driver);
188MODULE_LICENSE("GPL v2");
189MODULE_IMPORT_NS(CXL);