]>
Commit | Line | Data |
---|---|---|
b94d5230 DW |
1 | /* |
2 | * Copyright(c) 2013-2015 Intel Corporation. All rights reserved. | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or modify | |
5 | * it under the terms of version 2 of the GNU General Public License as | |
6 | * published by the Free Software Foundation. | |
7 | * | |
8 | * This program is distributed in the hope that it will be useful, but | |
9 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
11 | * General Public License for more details. | |
12 | */ | |
13 | #include <linux/list_sort.h> | |
14 | #include <linux/libnvdimm.h> | |
15 | #include <linux/module.h> | |
16 | #include <linux/list.h> | |
17 | #include <linux/acpi.h> | |
18 | #include "nfit.h" | |
19 | ||
20 | static u8 nfit_uuid[NFIT_UUID_MAX][16]; | |
21 | ||
22 | static const u8 *to_nfit_uuid(enum nfit_uuids id) | |
23 | { | |
24 | return nfit_uuid[id]; | |
25 | } | |
26 | ||
27 | static int acpi_nfit_ctl(struct nvdimm_bus_descriptor *nd_desc, | |
28 | struct nvdimm *nvdimm, unsigned int cmd, void *buf, | |
29 | unsigned int buf_len) | |
30 | { | |
31 | return -ENOTTY; | |
32 | } | |
33 | ||
34 | static const char *spa_type_name(u16 type) | |
35 | { | |
36 | static const char *to_name[] = { | |
37 | [NFIT_SPA_VOLATILE] = "volatile", | |
38 | [NFIT_SPA_PM] = "pmem", | |
39 | [NFIT_SPA_DCR] = "dimm-control-region", | |
40 | [NFIT_SPA_BDW] = "block-data-window", | |
41 | [NFIT_SPA_VDISK] = "volatile-disk", | |
42 | [NFIT_SPA_VCD] = "volatile-cd", | |
43 | [NFIT_SPA_PDISK] = "persistent-disk", | |
44 | [NFIT_SPA_PCD] = "persistent-cd", | |
45 | ||
46 | }; | |
47 | ||
48 | if (type > NFIT_SPA_PCD) | |
49 | return "unknown"; | |
50 | ||
51 | return to_name[type]; | |
52 | } | |
53 | ||
54 | static int nfit_spa_type(struct acpi_nfit_system_address *spa) | |
55 | { | |
56 | int i; | |
57 | ||
58 | for (i = 0; i < NFIT_UUID_MAX; i++) | |
59 | if (memcmp(to_nfit_uuid(i), spa->range_guid, 16) == 0) | |
60 | return i; | |
61 | return -1; | |
62 | } | |
63 | ||
64 | static bool add_spa(struct acpi_nfit_desc *acpi_desc, | |
65 | struct acpi_nfit_system_address *spa) | |
66 | { | |
67 | struct device *dev = acpi_desc->dev; | |
68 | struct nfit_spa *nfit_spa = devm_kzalloc(dev, sizeof(*nfit_spa), | |
69 | GFP_KERNEL); | |
70 | ||
71 | if (!nfit_spa) | |
72 | return false; | |
73 | INIT_LIST_HEAD(&nfit_spa->list); | |
74 | nfit_spa->spa = spa; | |
75 | list_add_tail(&nfit_spa->list, &acpi_desc->spas); | |
76 | dev_dbg(dev, "%s: spa index: %d type: %s\n", __func__, | |
77 | spa->range_index, | |
78 | spa_type_name(nfit_spa_type(spa))); | |
79 | return true; | |
80 | } | |
81 | ||
82 | static bool add_memdev(struct acpi_nfit_desc *acpi_desc, | |
83 | struct acpi_nfit_memory_map *memdev) | |
84 | { | |
85 | struct device *dev = acpi_desc->dev; | |
86 | struct nfit_memdev *nfit_memdev = devm_kzalloc(dev, | |
87 | sizeof(*nfit_memdev), GFP_KERNEL); | |
88 | ||
89 | if (!nfit_memdev) | |
90 | return false; | |
91 | INIT_LIST_HEAD(&nfit_memdev->list); | |
92 | nfit_memdev->memdev = memdev; | |
93 | list_add_tail(&nfit_memdev->list, &acpi_desc->memdevs); | |
94 | dev_dbg(dev, "%s: memdev handle: %#x spa: %d dcr: %d\n", | |
95 | __func__, memdev->device_handle, memdev->range_index, | |
96 | memdev->region_index); | |
97 | return true; | |
98 | } | |
99 | ||
100 | static bool add_dcr(struct acpi_nfit_desc *acpi_desc, | |
101 | struct acpi_nfit_control_region *dcr) | |
102 | { | |
103 | struct device *dev = acpi_desc->dev; | |
104 | struct nfit_dcr *nfit_dcr = devm_kzalloc(dev, sizeof(*nfit_dcr), | |
105 | GFP_KERNEL); | |
106 | ||
107 | if (!nfit_dcr) | |
108 | return false; | |
109 | INIT_LIST_HEAD(&nfit_dcr->list); | |
110 | nfit_dcr->dcr = dcr; | |
111 | list_add_tail(&nfit_dcr->list, &acpi_desc->dcrs); | |
112 | dev_dbg(dev, "%s: dcr index: %d windows: %d\n", __func__, | |
113 | dcr->region_index, dcr->windows); | |
114 | return true; | |
115 | } | |
116 | ||
117 | static bool add_bdw(struct acpi_nfit_desc *acpi_desc, | |
118 | struct acpi_nfit_data_region *bdw) | |
119 | { | |
120 | struct device *dev = acpi_desc->dev; | |
121 | struct nfit_bdw *nfit_bdw = devm_kzalloc(dev, sizeof(*nfit_bdw), | |
122 | GFP_KERNEL); | |
123 | ||
124 | if (!nfit_bdw) | |
125 | return false; | |
126 | INIT_LIST_HEAD(&nfit_bdw->list); | |
127 | nfit_bdw->bdw = bdw; | |
128 | list_add_tail(&nfit_bdw->list, &acpi_desc->bdws); | |
129 | dev_dbg(dev, "%s: bdw dcr: %d windows: %d\n", __func__, | |
130 | bdw->region_index, bdw->windows); | |
131 | return true; | |
132 | } | |
133 | ||
134 | static void *add_table(struct acpi_nfit_desc *acpi_desc, void *table, | |
135 | const void *end) | |
136 | { | |
137 | struct device *dev = acpi_desc->dev; | |
138 | struct acpi_nfit_header *hdr; | |
139 | void *err = ERR_PTR(-ENOMEM); | |
140 | ||
141 | if (table >= end) | |
142 | return NULL; | |
143 | ||
144 | hdr = table; | |
145 | switch (hdr->type) { | |
146 | case ACPI_NFIT_TYPE_SYSTEM_ADDRESS: | |
147 | if (!add_spa(acpi_desc, table)) | |
148 | return err; | |
149 | break; | |
150 | case ACPI_NFIT_TYPE_MEMORY_MAP: | |
151 | if (!add_memdev(acpi_desc, table)) | |
152 | return err; | |
153 | break; | |
154 | case ACPI_NFIT_TYPE_CONTROL_REGION: | |
155 | if (!add_dcr(acpi_desc, table)) | |
156 | return err; | |
157 | break; | |
158 | case ACPI_NFIT_TYPE_DATA_REGION: | |
159 | if (!add_bdw(acpi_desc, table)) | |
160 | return err; | |
161 | break; | |
162 | /* TODO */ | |
163 | case ACPI_NFIT_TYPE_INTERLEAVE: | |
164 | dev_dbg(dev, "%s: idt\n", __func__); | |
165 | break; | |
166 | case ACPI_NFIT_TYPE_FLUSH_ADDRESS: | |
167 | dev_dbg(dev, "%s: flush\n", __func__); | |
168 | break; | |
169 | case ACPI_NFIT_TYPE_SMBIOS: | |
170 | dev_dbg(dev, "%s: smbios\n", __func__); | |
171 | break; | |
172 | default: | |
173 | dev_err(dev, "unknown table '%d' parsing nfit\n", hdr->type); | |
174 | break; | |
175 | } | |
176 | ||
177 | return table + hdr->length; | |
178 | } | |
179 | ||
180 | static void nfit_mem_find_spa_bdw(struct acpi_nfit_desc *acpi_desc, | |
181 | struct nfit_mem *nfit_mem) | |
182 | { | |
183 | u32 device_handle = __to_nfit_memdev(nfit_mem)->device_handle; | |
184 | u16 dcr = nfit_mem->dcr->region_index; | |
185 | struct nfit_spa *nfit_spa; | |
186 | ||
187 | list_for_each_entry(nfit_spa, &acpi_desc->spas, list) { | |
188 | u16 range_index = nfit_spa->spa->range_index; | |
189 | int type = nfit_spa_type(nfit_spa->spa); | |
190 | struct nfit_memdev *nfit_memdev; | |
191 | ||
192 | if (type != NFIT_SPA_BDW) | |
193 | continue; | |
194 | ||
195 | list_for_each_entry(nfit_memdev, &acpi_desc->memdevs, list) { | |
196 | if (nfit_memdev->memdev->range_index != range_index) | |
197 | continue; | |
198 | if (nfit_memdev->memdev->device_handle != device_handle) | |
199 | continue; | |
200 | if (nfit_memdev->memdev->region_index != dcr) | |
201 | continue; | |
202 | ||
203 | nfit_mem->spa_bdw = nfit_spa->spa; | |
204 | return; | |
205 | } | |
206 | } | |
207 | ||
208 | dev_dbg(acpi_desc->dev, "SPA-BDW not found for SPA-DCR %d\n", | |
209 | nfit_mem->spa_dcr->range_index); | |
210 | nfit_mem->bdw = NULL; | |
211 | } | |
212 | ||
213 | static int nfit_mem_add(struct acpi_nfit_desc *acpi_desc, | |
214 | struct nfit_mem *nfit_mem, struct acpi_nfit_system_address *spa) | |
215 | { | |
216 | u16 dcr = __to_nfit_memdev(nfit_mem)->region_index; | |
217 | struct nfit_dcr *nfit_dcr; | |
218 | struct nfit_bdw *nfit_bdw; | |
219 | ||
220 | list_for_each_entry(nfit_dcr, &acpi_desc->dcrs, list) { | |
221 | if (nfit_dcr->dcr->region_index != dcr) | |
222 | continue; | |
223 | nfit_mem->dcr = nfit_dcr->dcr; | |
224 | break; | |
225 | } | |
226 | ||
227 | if (!nfit_mem->dcr) { | |
228 | dev_dbg(acpi_desc->dev, "SPA %d missing:%s%s\n", | |
229 | spa->range_index, __to_nfit_memdev(nfit_mem) | |
230 | ? "" : " MEMDEV", nfit_mem->dcr ? "" : " DCR"); | |
231 | return -ENODEV; | |
232 | } | |
233 | ||
234 | /* | |
235 | * We've found enough to create an nvdimm, optionally | |
236 | * find an associated BDW | |
237 | */ | |
238 | list_add(&nfit_mem->list, &acpi_desc->dimms); | |
239 | ||
240 | list_for_each_entry(nfit_bdw, &acpi_desc->bdws, list) { | |
241 | if (nfit_bdw->bdw->region_index != dcr) | |
242 | continue; | |
243 | nfit_mem->bdw = nfit_bdw->bdw; | |
244 | break; | |
245 | } | |
246 | ||
247 | if (!nfit_mem->bdw) | |
248 | return 0; | |
249 | ||
250 | nfit_mem_find_spa_bdw(acpi_desc, nfit_mem); | |
251 | return 0; | |
252 | } | |
253 | ||
254 | static int nfit_mem_dcr_init(struct acpi_nfit_desc *acpi_desc, | |
255 | struct acpi_nfit_system_address *spa) | |
256 | { | |
257 | struct nfit_mem *nfit_mem, *found; | |
258 | struct nfit_memdev *nfit_memdev; | |
259 | int type = nfit_spa_type(spa); | |
260 | u16 dcr; | |
261 | ||
262 | switch (type) { | |
263 | case NFIT_SPA_DCR: | |
264 | case NFIT_SPA_PM: | |
265 | break; | |
266 | default: | |
267 | return 0; | |
268 | } | |
269 | ||
270 | list_for_each_entry(nfit_memdev, &acpi_desc->memdevs, list) { | |
271 | int rc; | |
272 | ||
273 | if (nfit_memdev->memdev->range_index != spa->range_index) | |
274 | continue; | |
275 | found = NULL; | |
276 | dcr = nfit_memdev->memdev->region_index; | |
277 | list_for_each_entry(nfit_mem, &acpi_desc->dimms, list) | |
278 | if (__to_nfit_memdev(nfit_mem)->region_index == dcr) { | |
279 | found = nfit_mem; | |
280 | break; | |
281 | } | |
282 | ||
283 | if (found) | |
284 | nfit_mem = found; | |
285 | else { | |
286 | nfit_mem = devm_kzalloc(acpi_desc->dev, | |
287 | sizeof(*nfit_mem), GFP_KERNEL); | |
288 | if (!nfit_mem) | |
289 | return -ENOMEM; | |
290 | INIT_LIST_HEAD(&nfit_mem->list); | |
291 | } | |
292 | ||
293 | if (type == NFIT_SPA_DCR) { | |
294 | /* multiple dimms may share a SPA when interleaved */ | |
295 | nfit_mem->spa_dcr = spa; | |
296 | nfit_mem->memdev_dcr = nfit_memdev->memdev; | |
297 | } else { | |
298 | /* | |
299 | * A single dimm may belong to multiple SPA-PM | |
300 | * ranges, record at least one in addition to | |
301 | * any SPA-DCR range. | |
302 | */ | |
303 | nfit_mem->memdev_pmem = nfit_memdev->memdev; | |
304 | } | |
305 | ||
306 | if (found) | |
307 | continue; | |
308 | ||
309 | rc = nfit_mem_add(acpi_desc, nfit_mem, spa); | |
310 | if (rc) | |
311 | return rc; | |
312 | } | |
313 | ||
314 | return 0; | |
315 | } | |
316 | ||
317 | static int nfit_mem_cmp(void *priv, struct list_head *_a, struct list_head *_b) | |
318 | { | |
319 | struct nfit_mem *a = container_of(_a, typeof(*a), list); | |
320 | struct nfit_mem *b = container_of(_b, typeof(*b), list); | |
321 | u32 handleA, handleB; | |
322 | ||
323 | handleA = __to_nfit_memdev(a)->device_handle; | |
324 | handleB = __to_nfit_memdev(b)->device_handle; | |
325 | if (handleA < handleB) | |
326 | return -1; | |
327 | else if (handleA > handleB) | |
328 | return 1; | |
329 | return 0; | |
330 | } | |
331 | ||
332 | static int nfit_mem_init(struct acpi_nfit_desc *acpi_desc) | |
333 | { | |
334 | struct nfit_spa *nfit_spa; | |
335 | ||
336 | /* | |
337 | * For each SPA-DCR or SPA-PMEM address range find its | |
338 | * corresponding MEMDEV(s). From each MEMDEV find the | |
339 | * corresponding DCR. Then, if we're operating on a SPA-DCR, | |
340 | * try to find a SPA-BDW and a corresponding BDW that references | |
341 | * the DCR. Throw it all into an nfit_mem object. Note, that | |
342 | * BDWs are optional. | |
343 | */ | |
344 | list_for_each_entry(nfit_spa, &acpi_desc->spas, list) { | |
345 | int rc; | |
346 | ||
347 | rc = nfit_mem_dcr_init(acpi_desc, nfit_spa->spa); | |
348 | if (rc) | |
349 | return rc; | |
350 | } | |
351 | ||
352 | list_sort(NULL, &acpi_desc->dimms, nfit_mem_cmp); | |
353 | ||
354 | return 0; | |
355 | } | |
356 | ||
45def22c DW |
357 | static ssize_t revision_show(struct device *dev, |
358 | struct device_attribute *attr, char *buf) | |
359 | { | |
360 | struct nvdimm_bus *nvdimm_bus = to_nvdimm_bus(dev); | |
361 | struct nvdimm_bus_descriptor *nd_desc = to_nd_desc(nvdimm_bus); | |
362 | struct acpi_nfit_desc *acpi_desc = to_acpi_desc(nd_desc); | |
363 | ||
364 | return sprintf(buf, "%d\n", acpi_desc->nfit->header.revision); | |
365 | } | |
366 | static DEVICE_ATTR_RO(revision); | |
367 | ||
368 | static struct attribute *acpi_nfit_attributes[] = { | |
369 | &dev_attr_revision.attr, | |
370 | NULL, | |
371 | }; | |
372 | ||
373 | static struct attribute_group acpi_nfit_attribute_group = { | |
374 | .name = "nfit", | |
375 | .attrs = acpi_nfit_attributes, | |
376 | }; | |
377 | ||
378 | static const struct attribute_group *acpi_nfit_attribute_groups[] = { | |
379 | &nvdimm_bus_attribute_group, | |
380 | &acpi_nfit_attribute_group, | |
381 | NULL, | |
382 | }; | |
383 | ||
b94d5230 DW |
384 | static int acpi_nfit_init(struct acpi_nfit_desc *acpi_desc, acpi_size sz) |
385 | { | |
386 | struct device *dev = acpi_desc->dev; | |
387 | const void *end; | |
388 | u8 *data; | |
389 | ||
390 | INIT_LIST_HEAD(&acpi_desc->spas); | |
391 | INIT_LIST_HEAD(&acpi_desc->dcrs); | |
392 | INIT_LIST_HEAD(&acpi_desc->bdws); | |
393 | INIT_LIST_HEAD(&acpi_desc->memdevs); | |
394 | INIT_LIST_HEAD(&acpi_desc->dimms); | |
395 | ||
396 | data = (u8 *) acpi_desc->nfit; | |
397 | end = data + sz; | |
398 | data += sizeof(struct acpi_table_nfit); | |
399 | while (!IS_ERR_OR_NULL(data)) | |
400 | data = add_table(acpi_desc, data, end); | |
401 | ||
402 | if (IS_ERR(data)) { | |
403 | dev_dbg(dev, "%s: nfit table parsing error: %ld\n", __func__, | |
404 | PTR_ERR(data)); | |
405 | return PTR_ERR(data); | |
406 | } | |
407 | ||
408 | if (nfit_mem_init(acpi_desc) != 0) | |
409 | return -ENOMEM; | |
410 | ||
411 | return 0; | |
412 | } | |
413 | ||
414 | static int acpi_nfit_add(struct acpi_device *adev) | |
415 | { | |
416 | struct nvdimm_bus_descriptor *nd_desc; | |
417 | struct acpi_nfit_desc *acpi_desc; | |
418 | struct device *dev = &adev->dev; | |
419 | struct acpi_table_header *tbl; | |
420 | acpi_status status = AE_OK; | |
421 | acpi_size sz; | |
422 | int rc; | |
423 | ||
424 | status = acpi_get_table_with_size("NFIT", 0, &tbl, &sz); | |
425 | if (ACPI_FAILURE(status)) { | |
426 | dev_err(dev, "failed to find NFIT\n"); | |
427 | return -ENXIO; | |
428 | } | |
429 | ||
430 | acpi_desc = devm_kzalloc(dev, sizeof(*acpi_desc), GFP_KERNEL); | |
431 | if (!acpi_desc) | |
432 | return -ENOMEM; | |
433 | ||
434 | dev_set_drvdata(dev, acpi_desc); | |
435 | acpi_desc->dev = dev; | |
436 | acpi_desc->nfit = (struct acpi_table_nfit *) tbl; | |
437 | nd_desc = &acpi_desc->nd_desc; | |
438 | nd_desc->provider_name = "ACPI.NFIT"; | |
439 | nd_desc->ndctl = acpi_nfit_ctl; | |
45def22c | 440 | nd_desc->attr_groups = acpi_nfit_attribute_groups; |
b94d5230 DW |
441 | |
442 | acpi_desc->nvdimm_bus = nvdimm_bus_register(dev, nd_desc); | |
443 | if (!acpi_desc->nvdimm_bus) | |
444 | return -ENXIO; | |
445 | ||
446 | rc = acpi_nfit_init(acpi_desc, sz); | |
447 | if (rc) { | |
448 | nvdimm_bus_unregister(acpi_desc->nvdimm_bus); | |
449 | return rc; | |
450 | } | |
451 | return 0; | |
452 | } | |
453 | ||
454 | static int acpi_nfit_remove(struct acpi_device *adev) | |
455 | { | |
456 | struct acpi_nfit_desc *acpi_desc = dev_get_drvdata(&adev->dev); | |
457 | ||
458 | nvdimm_bus_unregister(acpi_desc->nvdimm_bus); | |
459 | return 0; | |
460 | } | |
461 | ||
462 | static const struct acpi_device_id acpi_nfit_ids[] = { | |
463 | { "ACPI0012", 0 }, | |
464 | { "", 0 }, | |
465 | }; | |
466 | MODULE_DEVICE_TABLE(acpi, acpi_nfit_ids); | |
467 | ||
468 | static struct acpi_driver acpi_nfit_driver = { | |
469 | .name = KBUILD_MODNAME, | |
470 | .ids = acpi_nfit_ids, | |
471 | .ops = { | |
472 | .add = acpi_nfit_add, | |
473 | .remove = acpi_nfit_remove, | |
474 | }, | |
475 | }; | |
476 | ||
477 | static __init int nfit_init(void) | |
478 | { | |
479 | BUILD_BUG_ON(sizeof(struct acpi_table_nfit) != 40); | |
480 | BUILD_BUG_ON(sizeof(struct acpi_nfit_system_address) != 56); | |
481 | BUILD_BUG_ON(sizeof(struct acpi_nfit_memory_map) != 48); | |
482 | BUILD_BUG_ON(sizeof(struct acpi_nfit_interleave) != 20); | |
483 | BUILD_BUG_ON(sizeof(struct acpi_nfit_smbios) != 9); | |
484 | BUILD_BUG_ON(sizeof(struct acpi_nfit_control_region) != 80); | |
485 | BUILD_BUG_ON(sizeof(struct acpi_nfit_data_region) != 40); | |
486 | ||
487 | acpi_str_to_uuid(UUID_VOLATILE_MEMORY, nfit_uuid[NFIT_SPA_VOLATILE]); | |
488 | acpi_str_to_uuid(UUID_PERSISTENT_MEMORY, nfit_uuid[NFIT_SPA_PM]); | |
489 | acpi_str_to_uuid(UUID_CONTROL_REGION, nfit_uuid[NFIT_SPA_DCR]); | |
490 | acpi_str_to_uuid(UUID_DATA_REGION, nfit_uuid[NFIT_SPA_BDW]); | |
491 | acpi_str_to_uuid(UUID_VOLATILE_VIRTUAL_DISK, nfit_uuid[NFIT_SPA_VDISK]); | |
492 | acpi_str_to_uuid(UUID_VOLATILE_VIRTUAL_CD, nfit_uuid[NFIT_SPA_VCD]); | |
493 | acpi_str_to_uuid(UUID_PERSISTENT_VIRTUAL_DISK, nfit_uuid[NFIT_SPA_PDISK]); | |
494 | acpi_str_to_uuid(UUID_PERSISTENT_VIRTUAL_CD, nfit_uuid[NFIT_SPA_PCD]); | |
495 | acpi_str_to_uuid(UUID_NFIT_BUS, nfit_uuid[NFIT_DEV_BUS]); | |
496 | acpi_str_to_uuid(UUID_NFIT_DIMM, nfit_uuid[NFIT_DEV_DIMM]); | |
497 | ||
498 | return acpi_bus_register_driver(&acpi_nfit_driver); | |
499 | } | |
500 | ||
501 | static __exit void nfit_exit(void) | |
502 | { | |
503 | acpi_bus_unregister_driver(&acpi_nfit_driver); | |
504 | } | |
505 | ||
506 | module_init(nfit_init); | |
507 | module_exit(nfit_exit); | |
508 | MODULE_LICENSE("GPL v2"); | |
509 | MODULE_AUTHOR("Intel Corporation"); |