Commit | Line | Data |
---|---|---|
8dd2bc0f BW |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* Copyright(c) 2022 Intel Corporation. All rights reserved. */ | |
cc2a4878 | 3 | #include <linux/debugfs.h> |
8dd2bc0f BW |
4 | #include <linux/device.h> |
5 | #include <linux/module.h> | |
6 | #include <linux/pci.h> | |
7 | ||
8 | #include "cxlmem.h" | |
9 | #include "cxlpci.h" | |
10 | ||
11 | /** | |
12 | * DOC: cxl mem | |
13 | * | |
14 | * CXL memory endpoint devices and switches are CXL capable devices that are | |
15 | * participating in CXL.mem protocol. Their functionality builds on top of the | |
16 | * CXL.io protocol that allows enumerating and configuring components via | |
17 | * standard PCI mechanisms. | |
18 | * | |
19 | * The cxl_mem driver owns kicking off the enumeration of this CXL.mem | |
20 | * capability. With the detection of a CXL capable endpoint, the driver will | |
21 | * walk up to find the platform specific port it is connected to, and determine | |
22 | * if there are intervening switches in the path. If there are switches, a | |
23 | * secondary action is to enumerate those (implemented in cxl_core). Finally the | |
24 | * cxl_mem driver adds the device it is bound to as a CXL endpoint-port for use | |
25 | * in higher level operations. | |
26 | */ | |
27 | ||
9ea4dcf4 DW |
28 | static void enable_suspend(void *data) |
29 | { | |
30 | cxl_mem_active_dec(); | |
31 | } | |
32 | ||
cc2a4878 DW |
33 | static void remove_debugfs(void *dentry) |
34 | { | |
35 | debugfs_remove_recursive(dentry); | |
36 | } | |
37 | ||
38 | static int cxl_mem_dpa_show(struct seq_file *file, void *data) | |
39 | { | |
40 | struct device *dev = file->private; | |
41 | struct cxl_memdev *cxlmd = to_cxl_memdev(dev); | |
42 | ||
43 | cxl_dpa_debug(file, cxlmd->cxlds); | |
44 | ||
45 | return 0; | |
46 | } | |
47 | ||
0a19bfc8 | 48 | static int devm_cxl_add_endpoint(struct device *host, struct cxl_memdev *cxlmd, |
7592d935 DW |
49 | struct cxl_dport *parent_dport) |
50 | { | |
51 | struct cxl_port *parent_port = parent_dport->port; | |
52 | struct cxl_dev_state *cxlds = cxlmd->cxlds; | |
53 | struct cxl_port *endpoint, *iter, *down; | |
54 | int rc; | |
55 | ||
56 | /* | |
57 | * Now that the path to the root is established record all the | |
58 | * intervening ports in the chain. | |
59 | */ | |
60 | for (iter = parent_port, down = NULL; !is_cxl_root(iter); | |
61 | down = iter, iter = to_cxl_port(iter->dev.parent)) { | |
62 | struct cxl_ep *ep; | |
63 | ||
64 | ep = cxl_ep_load(iter, cxlmd); | |
65 | ep->next = down; | |
66 | } | |
67 | ||
86917c56 RR |
68 | endpoint = devm_cxl_add_port(host, &cxlmd->dev, |
69 | cxlds->component_reg_phys, | |
0a19bfc8 | 70 | parent_dport); |
7592d935 DW |
71 | if (IS_ERR(endpoint)) |
72 | return PTR_ERR(endpoint); | |
73 | ||
74 | rc = cxl_endpoint_autoremove(cxlmd, endpoint); | |
75 | if (rc) | |
76 | return rc; | |
77 | ||
78 | if (!endpoint->dev.driver) { | |
79 | dev_err(&cxlmd->dev, "%s failed probe\n", | |
80 | dev_name(&endpoint->dev)); | |
81 | return -ENXIO; | |
82 | } | |
83 | ||
84 | return 0; | |
85 | } | |
86 | ||
50d527f5 AS |
87 | static int cxl_debugfs_poison_inject(void *data, u64 dpa) |
88 | { | |
89 | struct cxl_memdev *cxlmd = data; | |
90 | ||
91 | return cxl_inject_poison(cxlmd, dpa); | |
92 | } | |
93 | ||
94 | DEFINE_DEBUGFS_ATTRIBUTE(cxl_poison_inject_fops, NULL, | |
95 | cxl_debugfs_poison_inject, "%llx\n"); | |
96 | ||
97 | static int cxl_debugfs_poison_clear(void *data, u64 dpa) | |
98 | { | |
99 | struct cxl_memdev *cxlmd = data; | |
100 | ||
101 | return cxl_clear_poison(cxlmd, dpa); | |
102 | } | |
103 | ||
104 | DEFINE_DEBUGFS_ATTRIBUTE(cxl_poison_clear_fops, NULL, | |
105 | cxl_debugfs_poison_clear, "%llx\n"); | |
106 | ||
8dd2bc0f BW |
107 | static int cxl_mem_probe(struct device *dev) |
108 | { | |
109 | struct cxl_memdev *cxlmd = to_cxl_memdev(dev); | |
59f8d151 | 110 | struct cxl_memdev_state *mds = to_cxl_memdev_state(cxlmd->cxlds); |
f17b558d | 111 | struct cxl_dev_state *cxlds = cxlmd->cxlds; |
0a19bfc8 | 112 | struct device *endpoint_parent; |
8dd2bc0f | 113 | struct cxl_port *parent_port; |
1b58b4ca | 114 | struct cxl_dport *dport; |
cc2a4878 | 115 | struct dentry *dentry; |
8dd2bc0f BW |
116 | int rc; |
117 | ||
e764f122 DJ |
118 | if (!cxlds->media_ready) |
119 | return -EBUSY; | |
120 | ||
8dd2bc0f BW |
121 | /* |
122 | * Someone is trying to reattach this device after it lost its port | |
123 | * connection (an endpoint port previously registered by this memdev was | |
124 | * disabled). This racy check is ok because if the port is still gone, | |
125 | * no harm done, and if the port hierarchy comes back it will re-trigger | |
126 | * this probe. Port rescan and memdev detach work share the same | |
127 | * single-threaded workqueue. | |
128 | */ | |
129 | if (work_pending(&cxlmd->detach_work)) | |
130 | return -EBUSY; | |
131 | ||
cc2a4878 DW |
132 | dentry = cxl_debugfs_create_dir(dev_name(dev)); |
133 | debugfs_create_devm_seqfile(dev, "dpamem", dentry, cxl_mem_dpa_show); | |
50d527f5 | 134 | |
59f8d151 | 135 | if (test_bit(CXL_POISON_ENABLED_INJECT, mds->poison.enabled_cmds)) |
50d527f5 AS |
136 | debugfs_create_file("inject_poison", 0200, dentry, cxlmd, |
137 | &cxl_poison_inject_fops); | |
59f8d151 | 138 | if (test_bit(CXL_POISON_ENABLED_CLEAR, mds->poison.enabled_cmds)) |
50d527f5 AS |
139 | debugfs_create_file("clear_poison", 0200, dentry, cxlmd, |
140 | &cxl_poison_clear_fops); | |
141 | ||
cc2a4878 DW |
142 | rc = devm_add_action_or_reset(dev, remove_debugfs, dentry); |
143 | if (rc) | |
144 | return rc; | |
145 | ||
8dd2bc0f BW |
146 | rc = devm_cxl_enumerate_ports(cxlmd); |
147 | if (rc) | |
148 | return rc; | |
149 | ||
1b58b4ca | 150 | parent_port = cxl_mem_find_port(cxlmd, &dport); |
8dd2bc0f BW |
151 | if (!parent_port) { |
152 | dev_err(dev, "CXL port topology not found\n"); | |
153 | return -ENXIO; | |
154 | } | |
155 | ||
0a19bfc8 | 156 | if (dport->rch) |
7481653d | 157 | endpoint_parent = parent_port->uport_dev; |
0a19bfc8 DW |
158 | else |
159 | endpoint_parent = &parent_port->dev; | |
160 | ||
161 | device_lock(endpoint_parent); | |
162 | if (!endpoint_parent->driver) { | |
8dd2bc0f | 163 | dev_err(dev, "CXL port topology %s not enabled\n", |
0a19bfc8 | 164 | dev_name(endpoint_parent)); |
8dd2bc0f | 165 | rc = -ENXIO; |
76a4121e | 166 | goto unlock; |
8dd2bc0f BW |
167 | } |
168 | ||
0a19bfc8 | 169 | rc = devm_cxl_add_endpoint(endpoint_parent, cxlmd, dport); |
76a4121e | 170 | unlock: |
0a19bfc8 | 171 | device_unlock(endpoint_parent); |
8dd2bc0f | 172 | put_device(&parent_port->dev); |
76a4121e DW |
173 | if (rc) |
174 | return rc; | |
9ea4dcf4 | 175 | |
f17b558d DW |
176 | if (resource_size(&cxlds->pmem_res) && IS_ENABLED(CONFIG_CXL_PMEM)) { |
177 | rc = devm_cxl_add_nvdimm(cxlmd); | |
178 | if (rc == -ENODEV) | |
179 | dev_info(dev, "PMEM disabled by platform\n"); | |
180 | else | |
181 | return rc; | |
182 | } | |
183 | ||
9ea4dcf4 DW |
184 | /* |
185 | * The kernel may be operating out of CXL memory on this device, | |
186 | * there is no spec defined way to determine whether this device | |
187 | * preserves contents over suspend, and there is no simple way | |
188 | * to arrange for the suspend image to avoid CXL memory which | |
189 | * would setup a circular dependency between PCI resume and save | |
190 | * state restoration. | |
191 | * | |
192 | * TODO: support suspend when all the regions this device is | |
193 | * hosting are locked and covered by the system address map, | |
194 | * i.e. platform firmware owns restoring the HDM configuration | |
195 | * that it locked. | |
196 | */ | |
197 | cxl_mem_active_inc(); | |
198 | return devm_add_action_or_reset(dev, enable_suspend, NULL); | |
8dd2bc0f BW |
199 | } |
200 | ||
7ff6ad10 AS |
201 | static ssize_t trigger_poison_list_store(struct device *dev, |
202 | struct device_attribute *attr, | |
203 | const char *buf, size_t len) | |
204 | { | |
205 | bool trigger; | |
206 | int rc; | |
207 | ||
208 | if (kstrtobool(buf, &trigger) || !trigger) | |
209 | return -EINVAL; | |
210 | ||
211 | rc = cxl_trigger_poison_list(to_cxl_memdev(dev)); | |
212 | ||
213 | return rc ? rc : len; | |
214 | } | |
215 | static DEVICE_ATTR_WO(trigger_poison_list); | |
216 | ||
217 | static umode_t cxl_mem_visible(struct kobject *kobj, struct attribute *a, int n) | |
218 | { | |
219 | if (a == &dev_attr_trigger_poison_list.attr) { | |
220 | struct device *dev = kobj_to_dev(kobj); | |
59f8d151 DW |
221 | struct cxl_memdev *cxlmd = to_cxl_memdev(dev); |
222 | struct cxl_memdev_state *mds = | |
223 | to_cxl_memdev_state(cxlmd->cxlds); | |
7ff6ad10 AS |
224 | |
225 | if (!test_bit(CXL_POISON_ENABLED_LIST, | |
59f8d151 | 226 | mds->poison.enabled_cmds)) |
7ff6ad10 AS |
227 | return 0; |
228 | } | |
229 | return a->mode; | |
230 | } | |
231 | ||
232 | static struct attribute *cxl_mem_attrs[] = { | |
233 | &dev_attr_trigger_poison_list.attr, | |
234 | NULL | |
235 | }; | |
236 | ||
237 | static struct attribute_group cxl_mem_group = { | |
238 | .attrs = cxl_mem_attrs, | |
239 | .is_visible = cxl_mem_visible, | |
240 | }; | |
241 | ||
242 | __ATTRIBUTE_GROUPS(cxl_mem); | |
243 | ||
8dd2bc0f BW |
244 | static struct cxl_driver cxl_mem_driver = { |
245 | .name = "cxl_mem", | |
246 | .probe = cxl_mem_probe, | |
247 | .id = CXL_DEVICE_MEMORY_EXPANDER, | |
7ff6ad10 AS |
248 | .drv = { |
249 | .dev_groups = cxl_mem_groups, | |
250 | }, | |
8dd2bc0f BW |
251 | }; |
252 | ||
253 | module_cxl_driver(cxl_mem_driver); | |
254 | ||
255 | MODULE_LICENSE("GPL v2"); | |
256 | MODULE_IMPORT_NS(CXL); | |
257 | MODULE_ALIAS_CXL(CXL_DEVICE_MEMORY_EXPANDER); | |
258 | /* | |
259 | * create_endpoint() wants to validate port driver attach immediately after | |
260 | * endpoint registration. | |
261 | */ | |
262 | MODULE_SOFTDEP("pre: cxl_port"); |