| 1 | /* |
| 2 | * NVDIMM ACPI Implementation |
| 3 | * |
| 4 | * Copyright(C) 2015 Intel Corporation. |
| 5 | * |
| 6 | * Author: |
| 7 | * Xiao Guangrong <guangrong.xiao@linux.intel.com> |
| 8 | * |
| 9 | * NFIT is defined in ACPI 6.0: 5.2.25 NVDIMM Firmware Interface Table (NFIT) |
| 10 | * and the DSM specification can be found at: |
| 11 | * http://pmem.io/documents/NVDIMM_DSM_Interface_Example.pdf |
| 12 | * |
| 13 | * Currently, it only supports PMEM Virtualization. |
| 14 | * |
| 15 | * This library is free software; you can redistribute it and/or |
| 16 | * modify it under the terms of the GNU Lesser General Public |
| 17 | * License as published by the Free Software Foundation; either |
| 18 | * version 2.1 of the License, or (at your option) any later version. |
| 19 | * |
| 20 | * This library is distributed in the hope that it will be useful, |
| 21 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 22 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 23 | * Lesser General Public License for more details. |
| 24 | * |
| 25 | * You should have received a copy of the GNU Lesser General Public |
| 26 | * License along with this library; if not, see <http://www.gnu.org/licenses/> |
| 27 | */ |
| 28 | |
| 29 | #include "qemu/osdep.h" |
| 30 | #include "qemu/uuid.h" |
| 31 | #include "qapi/error.h" |
| 32 | #include "hw/acpi/acpi.h" |
| 33 | #include "hw/acpi/aml-build.h" |
| 34 | #include "hw/acpi/bios-linker-loader.h" |
| 35 | #include "hw/nvram/fw_cfg.h" |
| 36 | #include "hw/mem/nvdimm.h" |
| 37 | #include "qemu/nvdimm-utils.h" |
| 38 | #include "system/physmem.h" |
| 39 | #include "trace.h" |
| 40 | #include "exec/cpu-common.h" |
| 41 | |
| 42 | /* |
| 43 | * define Byte Addressable Persistent Memory (PM) Region according to |
| 44 | * ACPI 6.0: 5.2.25.1 System Physical Address Range Structure. |
| 45 | */ |
| 46 | static const uint8_t nvdimm_nfit_spa_uuid[] = |
| 47 | UUID_LE(0x66f0d379, 0xb4f3, 0x4074, 0xac, 0x43, 0x0d, 0x33, |
| 48 | 0x18, 0xb7, 0x8c, 0xdb); |
| 49 | |
| 50 | /* |
| 51 | * define NFIT structures according to ACPI 6.0: 5.2.25 NVDIMM Firmware |
| 52 | * Interface Table (NFIT). |
| 53 | */ |
| 54 | |
| 55 | /* |
| 56 | * System Physical Address Range Structure |
| 57 | * |
| 58 | * It describes the system physical address ranges occupied by NVDIMMs and |
| 59 | * the types of the regions. |
| 60 | */ |
| 61 | struct NvdimmNfitSpa { |
| 62 | uint16_t type; |
| 63 | uint16_t length; |
| 64 | uint16_t spa_index; |
| 65 | uint16_t flags; |
| 66 | uint32_t reserved; |
| 67 | uint32_t proximity_domain; |
| 68 | uint8_t type_guid[16]; |
| 69 | uint64_t spa_base; |
| 70 | uint64_t spa_length; |
| 71 | uint64_t mem_attr; |
| 72 | } QEMU_PACKED; |
| 73 | typedef struct NvdimmNfitSpa NvdimmNfitSpa; |
| 74 | |
| 75 | /* |
| 76 | * Memory Device to System Physical Address Range Mapping Structure |
| 77 | * |
| 78 | * It enables identifying each NVDIMM region and the corresponding SPA |
| 79 | * describing the memory interleave |
| 80 | */ |
| 81 | struct NvdimmNfitMemDev { |
| 82 | uint16_t type; |
| 83 | uint16_t length; |
| 84 | uint32_t nfit_handle; |
| 85 | uint16_t phys_id; |
| 86 | uint16_t region_id; |
| 87 | uint16_t spa_index; |
| 88 | uint16_t dcr_index; |
| 89 | uint64_t region_len; |
| 90 | uint64_t region_offset; |
| 91 | uint64_t region_dpa; |
| 92 | uint16_t interleave_index; |
| 93 | uint16_t interleave_ways; |
| 94 | uint16_t flags; |
| 95 | uint16_t reserved; |
| 96 | } QEMU_PACKED; |
| 97 | typedef struct NvdimmNfitMemDev NvdimmNfitMemDev; |
| 98 | |
| 99 | #define ACPI_NFIT_MEM_NOT_ARMED (1 << 3) |
| 100 | |
| 101 | /* |
| 102 | * NVDIMM Control Region Structure |
| 103 | * |
| 104 | * It describes the NVDIMM and if applicable, Block Control Window. |
| 105 | */ |
| 106 | struct NvdimmNfitControlRegion { |
| 107 | uint16_t type; |
| 108 | uint16_t length; |
| 109 | uint16_t dcr_index; |
| 110 | uint16_t vendor_id; |
| 111 | uint16_t device_id; |
| 112 | uint16_t revision_id; |
| 113 | uint16_t sub_vendor_id; |
| 114 | uint16_t sub_device_id; |
| 115 | uint16_t sub_revision_id; |
| 116 | uint8_t reserved[6]; |
| 117 | uint32_t serial_number; |
| 118 | uint16_t fic; |
| 119 | uint16_t num_bcw; |
| 120 | uint64_t bcw_size; |
| 121 | uint64_t cmd_offset; |
| 122 | uint64_t cmd_size; |
| 123 | uint64_t status_offset; |
| 124 | uint64_t status_size; |
| 125 | uint16_t flags; |
| 126 | uint8_t reserved2[6]; |
| 127 | } QEMU_PACKED; |
| 128 | typedef struct NvdimmNfitControlRegion NvdimmNfitControlRegion; |
| 129 | |
| 130 | /* |
| 131 | * NVDIMM Platform Capabilities Structure |
| 132 | * |
| 133 | * Defined in section 5.2.25.9 of ACPI 6.2 Errata A, September 2017 |
| 134 | */ |
| 135 | struct NvdimmNfitPlatformCaps { |
| 136 | uint16_t type; |
| 137 | uint16_t length; |
| 138 | uint8_t highest_cap; |
| 139 | uint8_t reserved[3]; |
| 140 | uint32_t capabilities; |
| 141 | uint8_t reserved2[4]; |
| 142 | } QEMU_PACKED; |
| 143 | typedef struct NvdimmNfitPlatformCaps NvdimmNfitPlatformCaps; |
| 144 | |
| 145 | /* |
| 146 | * Module serial number is a unique number for each device. We use the |
| 147 | * slot id of NVDIMM device to generate this number so that each device |
| 148 | * associates with a different number. |
| 149 | * |
| 150 | * 0x123456 is a magic number we arbitrarily chose. |
| 151 | */ |
| 152 | static uint32_t nvdimm_slot_to_sn(int slot) |
| 153 | { |
| 154 | return 0x123456 + slot; |
| 155 | } |
| 156 | |
| 157 | /* |
| 158 | * handle is used to uniquely associate nfit_memdev structure with NVDIMM |
| 159 | * ACPI device - nfit_memdev.nfit_handle matches with the value returned |
| 160 | * by ACPI device _ADR method. |
| 161 | * |
| 162 | * We generate the handle with the slot id of NVDIMM device and reserve |
| 163 | * 0 for NVDIMM root device. |
| 164 | */ |
| 165 | static uint32_t nvdimm_slot_to_handle(int slot) |
| 166 | { |
| 167 | return slot + 1; |
| 168 | } |
| 169 | |
| 170 | /* |
| 171 | * index uniquely identifies the structure, 0 is reserved which indicates |
| 172 | * that the structure is not valid or the associated structure is not |
| 173 | * present. |
| 174 | * |
| 175 | * Each NVDIMM device needs two indexes, one for nfit_spa and another for |
| 176 | * nfit_dc which are generated by the slot id of NVDIMM device. |
| 177 | */ |
| 178 | static uint16_t nvdimm_slot_to_spa_index(int slot) |
| 179 | { |
| 180 | return (slot + 1) << 1; |
| 181 | } |
| 182 | |
| 183 | /* See the comments of nvdimm_slot_to_spa_index(). */ |
| 184 | static uint32_t nvdimm_slot_to_dcr_index(int slot) |
| 185 | { |
| 186 | return nvdimm_slot_to_spa_index(slot) + 1; |
| 187 | } |
| 188 | |
| 189 | static NVDIMMDevice *nvdimm_get_device_by_handle(uint32_t handle) |
| 190 | { |
| 191 | NVDIMMDevice *nvdimm = NULL; |
| 192 | GSList *list, *device_list = nvdimm_get_device_list(); |
| 193 | |
| 194 | for (list = device_list; list; list = list->next) { |
| 195 | NVDIMMDevice *nvd = list->data; |
| 196 | int slot = object_property_get_int(OBJECT(nvd), PC_DIMM_SLOT_PROP, |
| 197 | NULL); |
| 198 | |
| 199 | if (nvdimm_slot_to_handle(slot) == handle) { |
| 200 | nvdimm = nvd; |
| 201 | break; |
| 202 | } |
| 203 | } |
| 204 | |
| 205 | g_slist_free(device_list); |
| 206 | return nvdimm; |
| 207 | } |
| 208 | |
| 209 | /* ACPI 6.0: 5.2.25.1 System Physical Address Range Structure */ |
| 210 | static void |
| 211 | nvdimm_build_structure_spa(GArray *structures, DeviceState *dev) |
| 212 | { |
| 213 | NvdimmNfitSpa *nfit_spa; |
| 214 | uint64_t addr = object_property_get_uint(OBJECT(dev), PC_DIMM_ADDR_PROP, |
| 215 | NULL); |
| 216 | uint64_t size = object_property_get_uint(OBJECT(dev), PC_DIMM_SIZE_PROP, |
| 217 | NULL); |
| 218 | uint32_t node = object_property_get_uint(OBJECT(dev), PC_DIMM_NODE_PROP, |
| 219 | NULL); |
| 220 | int slot = object_property_get_int(OBJECT(dev), PC_DIMM_SLOT_PROP, |
| 221 | NULL); |
| 222 | |
| 223 | nfit_spa = acpi_data_push(structures, sizeof(*nfit_spa)); |
| 224 | |
| 225 | nfit_spa->type = cpu_to_le16(0 /* System Physical Address Range |
| 226 | Structure */); |
| 227 | nfit_spa->length = cpu_to_le16(sizeof(*nfit_spa)); |
| 228 | nfit_spa->spa_index = cpu_to_le16(nvdimm_slot_to_spa_index(slot)); |
| 229 | |
| 230 | /* |
| 231 | * Control region is strict as all the device info, such as SN, index, |
| 232 | * is associated with slot id. |
| 233 | */ |
| 234 | nfit_spa->flags = cpu_to_le16(1 /* Control region is strictly for |
| 235 | management during hot add/online |
| 236 | operation */ | |
| 237 | 2 /* Data in Proximity Domain field is |
| 238 | valid*/); |
| 239 | |
| 240 | /* NUMA node. */ |
| 241 | nfit_spa->proximity_domain = cpu_to_le32(node); |
| 242 | /* the region reported as PMEM. */ |
| 243 | memcpy(nfit_spa->type_guid, nvdimm_nfit_spa_uuid, |
| 244 | sizeof(nvdimm_nfit_spa_uuid)); |
| 245 | |
| 246 | nfit_spa->spa_base = cpu_to_le64(addr); |
| 247 | nfit_spa->spa_length = cpu_to_le64(size); |
| 248 | |
| 249 | /* It is the PMEM and can be cached as writeback. */ |
| 250 | nfit_spa->mem_attr = cpu_to_le64(0x8ULL /* EFI_MEMORY_WB */ | |
| 251 | 0x8000ULL /* EFI_MEMORY_NV */); |
| 252 | } |
| 253 | |
| 254 | /* |
| 255 | * ACPI 6.0: 5.2.25.2 Memory Device to System Physical Address Range Mapping |
| 256 | * Structure |
| 257 | */ |
| 258 | static void |
| 259 | nvdimm_build_structure_memdev(GArray *structures, DeviceState *dev) |
| 260 | { |
| 261 | NvdimmNfitMemDev *nfit_memdev; |
| 262 | NVDIMMDevice *nvdimm = NVDIMM(OBJECT(dev)); |
| 263 | uint64_t size = object_property_get_uint(OBJECT(dev), PC_DIMM_SIZE_PROP, |
| 264 | NULL); |
| 265 | int slot = object_property_get_int(OBJECT(dev), PC_DIMM_SLOT_PROP, |
| 266 | NULL); |
| 267 | uint32_t handle = nvdimm_slot_to_handle(slot); |
| 268 | |
| 269 | nfit_memdev = acpi_data_push(structures, sizeof(*nfit_memdev)); |
| 270 | |
| 271 | nfit_memdev->type = cpu_to_le16(1 /* Memory Device to System Address |
| 272 | Range Map Structure*/); |
| 273 | nfit_memdev->length = cpu_to_le16(sizeof(*nfit_memdev)); |
| 274 | nfit_memdev->nfit_handle = cpu_to_le32(handle); |
| 275 | |
| 276 | /* |
| 277 | * associate memory device with System Physical Address Range |
| 278 | * Structure. |
| 279 | */ |
| 280 | nfit_memdev->spa_index = cpu_to_le16(nvdimm_slot_to_spa_index(slot)); |
| 281 | /* associate memory device with Control Region Structure. */ |
| 282 | nfit_memdev->dcr_index = cpu_to_le16(nvdimm_slot_to_dcr_index(slot)); |
| 283 | |
| 284 | /* The memory region on the device. */ |
| 285 | nfit_memdev->region_len = cpu_to_le64(size); |
| 286 | /* The device address starts from 0. */ |
| 287 | nfit_memdev->region_dpa = cpu_to_le64(0); |
| 288 | |
| 289 | /* Only one interleave for PMEM. */ |
| 290 | nfit_memdev->interleave_ways = cpu_to_le16(1); |
| 291 | |
| 292 | if (nvdimm->unarmed) { |
| 293 | nfit_memdev->flags |= cpu_to_le16(ACPI_NFIT_MEM_NOT_ARMED); |
| 294 | } |
| 295 | } |
| 296 | |
| 297 | /* |
| 298 | * ACPI 6.0: 5.2.25.5 NVDIMM Control Region Structure. |
| 299 | */ |
| 300 | static void nvdimm_build_structure_dcr(GArray *structures, DeviceState *dev) |
| 301 | { |
| 302 | NvdimmNfitControlRegion *nfit_dcr; |
| 303 | int slot = object_property_get_int(OBJECT(dev), PC_DIMM_SLOT_PROP, |
| 304 | NULL); |
| 305 | uint32_t sn = nvdimm_slot_to_sn(slot); |
| 306 | |
| 307 | nfit_dcr = acpi_data_push(structures, sizeof(*nfit_dcr)); |
| 308 | |
| 309 | nfit_dcr->type = cpu_to_le16(4 /* NVDIMM Control Region Structure */); |
| 310 | nfit_dcr->length = cpu_to_le16(sizeof(*nfit_dcr)); |
| 311 | nfit_dcr->dcr_index = cpu_to_le16(nvdimm_slot_to_dcr_index(slot)); |
| 312 | |
| 313 | /* vendor: Intel. */ |
| 314 | nfit_dcr->vendor_id = cpu_to_le16(0x8086); |
| 315 | nfit_dcr->device_id = cpu_to_le16(1); |
| 316 | |
| 317 | /* The _DSM method is following Intel's DSM specification. */ |
| 318 | nfit_dcr->revision_id = cpu_to_le16(1 /* Current Revision supported |
| 319 | in ACPI 6.0 is 1. */); |
| 320 | nfit_dcr->serial_number = cpu_to_le32(sn); |
| 321 | nfit_dcr->fic = cpu_to_le16(0x301 /* Format Interface Code: |
| 322 | Byte addressable, no energy backed. |
| 323 | See ACPI 6.2, sect 5.2.25.6 and |
| 324 | JEDEC Annex L Release 3. */); |
| 325 | } |
| 326 | |
| 327 | /* |
| 328 | * ACPI 6.2 Errata A: 5.2.25.9 NVDIMM Platform Capabilities Structure |
| 329 | */ |
| 330 | static void |
| 331 | nvdimm_build_structure_caps(GArray *structures, uint32_t capabilities) |
| 332 | { |
| 333 | NvdimmNfitPlatformCaps *nfit_caps; |
| 334 | |
| 335 | nfit_caps = acpi_data_push(structures, sizeof(*nfit_caps)); |
| 336 | |
| 337 | nfit_caps->type = cpu_to_le16(7 /* NVDIMM Platform Capabilities */); |
| 338 | nfit_caps->length = cpu_to_le16(sizeof(*nfit_caps)); |
| 339 | nfit_caps->highest_cap = 31 - clz32(capabilities); |
| 340 | nfit_caps->capabilities = cpu_to_le32(capabilities); |
| 341 | } |
| 342 | |
| 343 | static GArray *nvdimm_build_device_structure(NVDIMMState *state) |
| 344 | { |
| 345 | GSList *device_list, *list = nvdimm_get_device_list(); |
| 346 | GArray *structures = g_array_new(false, true /* clear */, 1); |
| 347 | |
| 348 | for (device_list = list; device_list; device_list = device_list->next) { |
| 349 | DeviceState *dev = device_list->data; |
| 350 | |
| 351 | /* build System Physical Address Range Structure. */ |
| 352 | nvdimm_build_structure_spa(structures, dev); |
| 353 | |
| 354 | /* |
| 355 | * build Memory Device to System Physical Address Range Mapping |
| 356 | * Structure. |
| 357 | */ |
| 358 | nvdimm_build_structure_memdev(structures, dev); |
| 359 | |
| 360 | /* build NVDIMM Control Region Structure. */ |
| 361 | nvdimm_build_structure_dcr(structures, dev); |
| 362 | } |
| 363 | g_slist_free(list); |
| 364 | |
| 365 | if (state->persistence) { |
| 366 | nvdimm_build_structure_caps(structures, state->persistence); |
| 367 | } |
| 368 | |
| 369 | return structures; |
| 370 | } |
| 371 | |
| 372 | static void nvdimm_init_fit_buffer(NvdimmFitBuffer *fit_buf) |
| 373 | { |
| 374 | fit_buf->fit = g_array_new(false, true /* clear */, 1); |
| 375 | } |
| 376 | |
| 377 | static void nvdimm_build_fit_buffer(NVDIMMState *state) |
| 378 | { |
| 379 | NvdimmFitBuffer *fit_buf = &state->fit_buf; |
| 380 | |
| 381 | g_array_free(fit_buf->fit, true); |
| 382 | fit_buf->fit = nvdimm_build_device_structure(state); |
| 383 | fit_buf->dirty = true; |
| 384 | } |
| 385 | |
| 386 | void nvdimm_plug(NVDIMMState *state) |
| 387 | { |
| 388 | nvdimm_build_fit_buffer(state); |
| 389 | } |
| 390 | |
| 391 | /* |
| 392 | * NVDIMM Firmware Interface Table |
| 393 | * @signature: "NFIT" |
| 394 | * |
| 395 | * It provides information that allows OSPM to enumerate NVDIMM present in |
| 396 | * the platform and associate system physical address ranges created by the |
| 397 | * NVDIMMs. |
| 398 | * |
| 399 | * It is defined in ACPI 6.0: 5.2.25 NVDIMM Firmware Interface Table (NFIT) |
| 400 | */ |
| 401 | |
| 402 | static void nvdimm_build_nfit(NVDIMMState *state, GArray *table_offsets, |
| 403 | GArray *table_data, BIOSLinker *linker, |
| 404 | const char *oem_id, const char *oem_table_id) |
| 405 | { |
| 406 | NvdimmFitBuffer *fit_buf = &state->fit_buf; |
| 407 | AcpiTable table = { .sig = "NFIT", .rev = 1, |
| 408 | .oem_id = oem_id, .oem_table_id = oem_table_id }; |
| 409 | |
| 410 | acpi_add_table(table_offsets, table_data); |
| 411 | |
| 412 | acpi_table_begin(&table, table_data); |
| 413 | /* Reserved */ |
| 414 | build_append_int_noprefix(table_data, 0, 4); |
| 415 | /* NVDIMM device structures. */ |
| 416 | g_array_append_vals(table_data, fit_buf->fit->data, fit_buf->fit->len); |
| 417 | acpi_table_end(linker, &table); |
| 418 | } |
| 419 | |
| 420 | #define NVDIMM_DSM_MEMORY_SIZE 4096 |
| 421 | |
| 422 | struct NvdimmDsmIn { |
| 423 | uint32_t handle; |
| 424 | uint32_t revision; |
| 425 | uint32_t function; |
| 426 | /* the remaining size in the page is used by arg3. */ |
| 427 | union { |
| 428 | uint8_t arg3[4084]; |
| 429 | }; |
| 430 | } QEMU_PACKED; |
| 431 | typedef struct NvdimmDsmIn NvdimmDsmIn; |
| 432 | QEMU_BUILD_BUG_ON(sizeof(NvdimmDsmIn) != NVDIMM_DSM_MEMORY_SIZE); |
| 433 | |
| 434 | struct NvdimmDsmOut { |
| 435 | /* the size of buffer filled by QEMU. */ |
| 436 | uint32_t len; |
| 437 | uint8_t data[4092]; |
| 438 | } QEMU_PACKED; |
| 439 | typedef struct NvdimmDsmOut NvdimmDsmOut; |
| 440 | QEMU_BUILD_BUG_ON(sizeof(NvdimmDsmOut) != NVDIMM_DSM_MEMORY_SIZE); |
| 441 | |
| 442 | struct NvdimmDsmFunc0Out { |
| 443 | /* the size of buffer filled by QEMU. */ |
| 444 | uint32_t len; |
| 445 | uint32_t supported_func; |
| 446 | } QEMU_PACKED; |
| 447 | typedef struct NvdimmDsmFunc0Out NvdimmDsmFunc0Out; |
| 448 | |
| 449 | struct NvdimmDsmFuncNoPayloadOut { |
| 450 | /* the size of buffer filled by QEMU. */ |
| 451 | uint32_t len; |
| 452 | uint32_t func_ret_status; |
| 453 | } QEMU_PACKED; |
| 454 | typedef struct NvdimmDsmFuncNoPayloadOut NvdimmDsmFuncNoPayloadOut; |
| 455 | |
| 456 | struct NvdimmFuncGetLabelSizeOut { |
| 457 | /* the size of buffer filled by QEMU. */ |
| 458 | uint32_t len; |
| 459 | uint32_t func_ret_status; /* return status code. */ |
| 460 | uint32_t label_size; /* the size of label data area. */ |
| 461 | /* |
| 462 | * Maximum size of the namespace label data length supported by |
| 463 | * the platform in Get/Set Namespace Label Data functions. |
| 464 | */ |
| 465 | uint32_t max_xfer; |
| 466 | } QEMU_PACKED; |
| 467 | typedef struct NvdimmFuncGetLabelSizeOut NvdimmFuncGetLabelSizeOut; |
| 468 | QEMU_BUILD_BUG_ON(sizeof(NvdimmFuncGetLabelSizeOut) > NVDIMM_DSM_MEMORY_SIZE); |
| 469 | |
| 470 | struct NvdimmFuncGetLabelDataIn { |
| 471 | uint32_t offset; /* the offset in the namespace label data area. */ |
| 472 | uint32_t length; /* the size of data is to be read via the function. */ |
| 473 | } QEMU_PACKED; |
| 474 | typedef struct NvdimmFuncGetLabelDataIn NvdimmFuncGetLabelDataIn; |
| 475 | QEMU_BUILD_BUG_ON(sizeof(NvdimmFuncGetLabelDataIn) + |
| 476 | offsetof(NvdimmDsmIn, arg3) > NVDIMM_DSM_MEMORY_SIZE); |
| 477 | |
| 478 | struct NvdimmFuncGetLabelDataOut { |
| 479 | /* the size of buffer filled by QEMU. */ |
| 480 | uint32_t len; |
| 481 | uint32_t func_ret_status; /* return status code. */ |
| 482 | uint8_t out_buf[]; /* the data got via Get Namespace Label function. */ |
| 483 | } QEMU_PACKED; |
| 484 | typedef struct NvdimmFuncGetLabelDataOut NvdimmFuncGetLabelDataOut; |
| 485 | QEMU_BUILD_BUG_ON(sizeof(NvdimmFuncGetLabelDataOut) > NVDIMM_DSM_MEMORY_SIZE); |
| 486 | |
| 487 | struct NvdimmFuncSetLabelDataIn { |
| 488 | uint32_t offset; /* the offset in the namespace label data area. */ |
| 489 | uint32_t length; /* the size of data is to be written via the function. */ |
| 490 | uint8_t in_buf[]; /* the data written to label data area. */ |
| 491 | } QEMU_PACKED; |
| 492 | typedef struct NvdimmFuncSetLabelDataIn NvdimmFuncSetLabelDataIn; |
| 493 | QEMU_BUILD_BUG_ON(sizeof(NvdimmFuncSetLabelDataIn) + |
| 494 | offsetof(NvdimmDsmIn, arg3) > NVDIMM_DSM_MEMORY_SIZE); |
| 495 | |
| 496 | struct NvdimmFuncReadFITIn { |
| 497 | uint32_t offset; /* the offset into FIT buffer. */ |
| 498 | } QEMU_PACKED; |
| 499 | typedef struct NvdimmFuncReadFITIn NvdimmFuncReadFITIn; |
| 500 | QEMU_BUILD_BUG_ON(sizeof(NvdimmFuncReadFITIn) + |
| 501 | offsetof(NvdimmDsmIn, arg3) > NVDIMM_DSM_MEMORY_SIZE); |
| 502 | |
| 503 | struct NvdimmFuncReadFITOut { |
| 504 | /* the size of buffer filled by QEMU. */ |
| 505 | uint32_t len; |
| 506 | uint32_t func_ret_status; /* return status code. */ |
| 507 | uint8_t fit[]; /* the FIT data. */ |
| 508 | } QEMU_PACKED; |
| 509 | typedef struct NvdimmFuncReadFITOut NvdimmFuncReadFITOut; |
| 510 | QEMU_BUILD_BUG_ON(sizeof(NvdimmFuncReadFITOut) > NVDIMM_DSM_MEMORY_SIZE); |
| 511 | |
| 512 | static void |
| 513 | nvdimm_dsm_function0(uint32_t supported_func, hwaddr dsm_mem_addr) |
| 514 | { |
| 515 | NvdimmDsmFunc0Out func0 = { |
| 516 | .len = cpu_to_le32(sizeof(func0)), |
| 517 | .supported_func = cpu_to_le32(supported_func), |
| 518 | }; |
| 519 | physical_memory_write(dsm_mem_addr, &func0, sizeof(func0)); |
| 520 | } |
| 521 | |
| 522 | static void |
| 523 | nvdimm_dsm_no_payload(uint32_t func_ret_status, hwaddr dsm_mem_addr) |
| 524 | { |
| 525 | NvdimmDsmFuncNoPayloadOut out = { |
| 526 | .len = cpu_to_le32(sizeof(out)), |
| 527 | .func_ret_status = cpu_to_le32(func_ret_status), |
| 528 | }; |
| 529 | physical_memory_write(dsm_mem_addr, &out, sizeof(out)); |
| 530 | } |
| 531 | |
| 532 | #define NVDIMM_DSM_RET_STATUS_SUCCESS 0 /* Success */ |
| 533 | #define NVDIMM_DSM_RET_STATUS_UNSUPPORT 1 /* Not Supported */ |
| 534 | #define NVDIMM_DSM_RET_STATUS_NOMEMDEV 2 /* Non-Existing Memory Device */ |
| 535 | #define NVDIMM_DSM_RET_STATUS_INVALID 3 /* Invalid Input Parameters */ |
| 536 | #define NVDIMM_DSM_RET_STATUS_FIT_CHANGED 0x100 /* FIT Changed */ |
| 537 | |
| 538 | #define NVDIMM_QEMU_RSVD_HANDLE_ROOT 0x10000 |
| 539 | |
| 540 | /* Read FIT data, defined in docs/specs/acpi_nvdimm.rst. */ |
| 541 | static void nvdimm_dsm_func_read_fit(NVDIMMState *state, NvdimmDsmIn *in, |
| 542 | hwaddr dsm_mem_addr) |
| 543 | { |
| 544 | NvdimmFitBuffer *fit_buf = &state->fit_buf; |
| 545 | NvdimmFuncReadFITIn *read_fit; |
| 546 | NvdimmFuncReadFITOut *read_fit_out; |
| 547 | GArray *fit; |
| 548 | uint32_t read_len = 0, func_ret_status; |
| 549 | int size; |
| 550 | |
| 551 | read_fit = (NvdimmFuncReadFITIn *)in->arg3; |
| 552 | read_fit->offset = le32_to_cpu(read_fit->offset); |
| 553 | |
| 554 | fit = fit_buf->fit; |
| 555 | |
| 556 | trace_acpi_nvdimm_read_fit(read_fit->offset, fit->len, |
| 557 | fit_buf->dirty ? "Yes" : "No"); |
| 558 | |
| 559 | if (read_fit->offset > fit->len) { |
| 560 | func_ret_status = NVDIMM_DSM_RET_STATUS_INVALID; |
| 561 | goto exit; |
| 562 | } |
| 563 | |
| 564 | /* It is the first time to read FIT. */ |
| 565 | if (!read_fit->offset) { |
| 566 | fit_buf->dirty = false; |
| 567 | } else if (fit_buf->dirty) { /* FIT has been changed during RFIT. */ |
| 568 | func_ret_status = NVDIMM_DSM_RET_STATUS_FIT_CHANGED; |
| 569 | goto exit; |
| 570 | } |
| 571 | |
| 572 | func_ret_status = NVDIMM_DSM_RET_STATUS_SUCCESS; |
| 573 | read_len = MIN(fit->len - read_fit->offset, |
| 574 | NVDIMM_DSM_MEMORY_SIZE - sizeof(NvdimmFuncReadFITOut)); |
| 575 | |
| 576 | exit: |
| 577 | size = sizeof(NvdimmFuncReadFITOut) + read_len; |
| 578 | read_fit_out = g_malloc(size); |
| 579 | |
| 580 | read_fit_out->len = cpu_to_le32(size); |
| 581 | read_fit_out->func_ret_status = cpu_to_le32(func_ret_status); |
| 582 | memcpy(read_fit_out->fit, fit->data + read_fit->offset, read_len); |
| 583 | |
| 584 | physical_memory_write(dsm_mem_addr, read_fit_out, size); |
| 585 | |
| 586 | g_free(read_fit_out); |
| 587 | } |
| 588 | |
| 589 | static void |
| 590 | nvdimm_dsm_handle_reserved_root_method(NVDIMMState *state, |
| 591 | NvdimmDsmIn *in, hwaddr dsm_mem_addr) |
| 592 | { |
| 593 | switch (in->function) { |
| 594 | case 0x0: |
| 595 | nvdimm_dsm_function0(0x1 | 1 << 1 /* Read FIT */, dsm_mem_addr); |
| 596 | return; |
| 597 | case 0x1 /* Read FIT */: |
| 598 | nvdimm_dsm_func_read_fit(state, in, dsm_mem_addr); |
| 599 | return; |
| 600 | } |
| 601 | |
| 602 | nvdimm_dsm_no_payload(NVDIMM_DSM_RET_STATUS_UNSUPPORT, dsm_mem_addr); |
| 603 | } |
| 604 | |
| 605 | static void nvdimm_dsm_root(NvdimmDsmIn *in, hwaddr dsm_mem_addr) |
| 606 | { |
| 607 | /* |
| 608 | * function 0 is called to inquire which functions are supported by |
| 609 | * OSPM |
| 610 | */ |
| 611 | if (!in->function) { |
| 612 | nvdimm_dsm_function0(0 /* No function supported other than |
| 613 | function 0 */, dsm_mem_addr); |
| 614 | return; |
| 615 | } |
| 616 | |
| 617 | /* No function except function 0 is supported yet. */ |
| 618 | nvdimm_dsm_no_payload(NVDIMM_DSM_RET_STATUS_UNSUPPORT, dsm_mem_addr); |
| 619 | } |
| 620 | |
| 621 | /* |
| 622 | * the max transfer size is the max size transferred by both a |
| 623 | * 'Get Namespace Label Data' function and a 'Set Namespace Label Data' |
| 624 | * function. |
| 625 | */ |
| 626 | static uint32_t nvdimm_get_max_xfer_label_size(void) |
| 627 | { |
| 628 | uint32_t max_get_size, max_set_size, dsm_memory_size; |
| 629 | |
| 630 | dsm_memory_size = NVDIMM_DSM_MEMORY_SIZE; |
| 631 | |
| 632 | /* |
| 633 | * the max data ACPI can read one time which is transferred by |
| 634 | * the response of 'Get Namespace Label Data' function. |
| 635 | */ |
| 636 | max_get_size = dsm_memory_size - sizeof(NvdimmFuncGetLabelDataOut); |
| 637 | |
| 638 | /* |
| 639 | * the max data ACPI can write one time which is transferred by |
| 640 | * 'Set Namespace Label Data' function. |
| 641 | */ |
| 642 | max_set_size = dsm_memory_size - offsetof(NvdimmDsmIn, arg3) - |
| 643 | sizeof(NvdimmFuncSetLabelDataIn); |
| 644 | |
| 645 | return MIN(max_get_size, max_set_size); |
| 646 | } |
| 647 | |
| 648 | /* |
| 649 | * DSM Spec Rev1 4.4 Get Namespace Label Size (Function Index 4). |
| 650 | * |
| 651 | * It gets the size of Namespace Label data area and the max data size |
| 652 | * that Get/Set Namespace Label Data functions can transfer. |
| 653 | */ |
| 654 | static void nvdimm_dsm_label_size(NVDIMMDevice *nvdimm, hwaddr dsm_mem_addr) |
| 655 | { |
| 656 | NvdimmFuncGetLabelSizeOut label_size_out = { |
| 657 | .len = cpu_to_le32(sizeof(label_size_out)), |
| 658 | }; |
| 659 | uint32_t label_size, mxfer; |
| 660 | |
| 661 | label_size = nvdimm->label_size; |
| 662 | mxfer = nvdimm_get_max_xfer_label_size(); |
| 663 | |
| 664 | trace_acpi_nvdimm_label_info(label_size, mxfer); |
| 665 | |
| 666 | label_size_out.func_ret_status = cpu_to_le32(NVDIMM_DSM_RET_STATUS_SUCCESS); |
| 667 | label_size_out.label_size = cpu_to_le32(label_size); |
| 668 | label_size_out.max_xfer = cpu_to_le32(mxfer); |
| 669 | |
| 670 | physical_memory_write(dsm_mem_addr, &label_size_out, |
| 671 | sizeof(label_size_out)); |
| 672 | } |
| 673 | |
| 674 | static uint32_t nvdimm_rw_label_data_check(NVDIMMDevice *nvdimm, |
| 675 | uint32_t offset, uint32_t length, |
| 676 | bool is_write) |
| 677 | { |
| 678 | uint32_t ret = NVDIMM_DSM_RET_STATUS_INVALID; |
| 679 | |
| 680 | if (offset + length < offset) { |
| 681 | trace_acpi_nvdimm_label_overflow(offset, length); |
| 682 | return ret; |
| 683 | } |
| 684 | |
| 685 | if (nvdimm->label_size < offset + length) { |
| 686 | trace_acpi_nvdimm_label_oversize(offset + length, nvdimm->label_size); |
| 687 | return ret; |
| 688 | } |
| 689 | |
| 690 | if (length > nvdimm_get_max_xfer_label_size()) { |
| 691 | trace_acpi_nvdimm_label_xfer_exceed(length, |
| 692 | nvdimm_get_max_xfer_label_size()); |
| 693 | return ret; |
| 694 | } |
| 695 | |
| 696 | if (is_write && nvdimm->readonly) { |
| 697 | return NVDIMM_DSM_RET_STATUS_UNSUPPORT; |
| 698 | } |
| 699 | |
| 700 | return NVDIMM_DSM_RET_STATUS_SUCCESS; |
| 701 | } |
| 702 | |
| 703 | /* |
| 704 | * DSM Spec Rev1 4.5 Get Namespace Label Data (Function Index 5). |
| 705 | */ |
| 706 | static void nvdimm_dsm_get_label_data(NVDIMMDevice *nvdimm, NvdimmDsmIn *in, |
| 707 | hwaddr dsm_mem_addr) |
| 708 | { |
| 709 | NVDIMMClass *nvc = NVDIMM_GET_CLASS(nvdimm); |
| 710 | NvdimmFuncGetLabelDataIn *get_label_data; |
| 711 | NvdimmFuncGetLabelDataOut *get_label_data_out; |
| 712 | uint32_t status; |
| 713 | int size; |
| 714 | |
| 715 | get_label_data = (NvdimmFuncGetLabelDataIn *)in->arg3; |
| 716 | get_label_data->offset = le32_to_cpu(get_label_data->offset); |
| 717 | get_label_data->length = le32_to_cpu(get_label_data->length); |
| 718 | |
| 719 | trace_acpi_nvdimm_read_label(get_label_data->offset, |
| 720 | get_label_data->length); |
| 721 | |
| 722 | status = nvdimm_rw_label_data_check(nvdimm, get_label_data->offset, |
| 723 | get_label_data->length, false); |
| 724 | if (status != NVDIMM_DSM_RET_STATUS_SUCCESS) { |
| 725 | nvdimm_dsm_no_payload(status, dsm_mem_addr); |
| 726 | return; |
| 727 | } |
| 728 | |
| 729 | size = sizeof(*get_label_data_out) + get_label_data->length; |
| 730 | assert(size <= NVDIMM_DSM_MEMORY_SIZE); |
| 731 | get_label_data_out = g_malloc(size); |
| 732 | |
| 733 | get_label_data_out->len = cpu_to_le32(size); |
| 734 | get_label_data_out->func_ret_status = |
| 735 | cpu_to_le32(NVDIMM_DSM_RET_STATUS_SUCCESS); |
| 736 | nvc->read_label_data(nvdimm, get_label_data_out->out_buf, |
| 737 | get_label_data->length, get_label_data->offset); |
| 738 | |
| 739 | physical_memory_write(dsm_mem_addr, get_label_data_out, size); |
| 740 | g_free(get_label_data_out); |
| 741 | } |
| 742 | |
| 743 | /* |
| 744 | * DSM Spec Rev1 4.6 Set Namespace Label Data (Function Index 6). |
| 745 | */ |
| 746 | static void nvdimm_dsm_set_label_data(NVDIMMDevice *nvdimm, NvdimmDsmIn *in, |
| 747 | hwaddr dsm_mem_addr) |
| 748 | { |
| 749 | NVDIMMClass *nvc = NVDIMM_GET_CLASS(nvdimm); |
| 750 | NvdimmFuncSetLabelDataIn *set_label_data; |
| 751 | uint32_t status; |
| 752 | |
| 753 | set_label_data = (NvdimmFuncSetLabelDataIn *)in->arg3; |
| 754 | |
| 755 | set_label_data->offset = le32_to_cpu(set_label_data->offset); |
| 756 | set_label_data->length = le32_to_cpu(set_label_data->length); |
| 757 | |
| 758 | trace_acpi_nvdimm_write_label(set_label_data->offset, |
| 759 | set_label_data->length); |
| 760 | |
| 761 | status = nvdimm_rw_label_data_check(nvdimm, set_label_data->offset, |
| 762 | set_label_data->length, true); |
| 763 | if (status != NVDIMM_DSM_RET_STATUS_SUCCESS) { |
| 764 | nvdimm_dsm_no_payload(status, dsm_mem_addr); |
| 765 | return; |
| 766 | } |
| 767 | |
| 768 | assert(offsetof(NvdimmDsmIn, arg3) + sizeof(*set_label_data) + |
| 769 | set_label_data->length <= NVDIMM_DSM_MEMORY_SIZE); |
| 770 | |
| 771 | nvc->write_label_data(nvdimm, set_label_data->in_buf, |
| 772 | set_label_data->length, set_label_data->offset); |
| 773 | nvdimm_dsm_no_payload(NVDIMM_DSM_RET_STATUS_SUCCESS, dsm_mem_addr); |
| 774 | } |
| 775 | |
| 776 | static void nvdimm_dsm_device(NvdimmDsmIn *in, hwaddr dsm_mem_addr) |
| 777 | { |
| 778 | NVDIMMDevice *nvdimm = nvdimm_get_device_by_handle(in->handle); |
| 779 | |
| 780 | /* See the comments in nvdimm_dsm_root(). */ |
| 781 | if (!in->function) { |
| 782 | uint32_t supported_func = 0; |
| 783 | |
| 784 | if (nvdimm && nvdimm->label_size) { |
| 785 | supported_func |= 0x1 /* Bit 0 indicates whether there is |
| 786 | support for any functions other |
| 787 | than function 0. */ | |
| 788 | 1 << 4 /* Get Namespace Label Size */ | |
| 789 | 1 << 5 /* Get Namespace Label Data */ | |
| 790 | 1 << 6 /* Set Namespace Label Data */; |
| 791 | } |
| 792 | nvdimm_dsm_function0(supported_func, dsm_mem_addr); |
| 793 | return; |
| 794 | } |
| 795 | |
| 796 | if (!nvdimm) { |
| 797 | nvdimm_dsm_no_payload(NVDIMM_DSM_RET_STATUS_NOMEMDEV, |
| 798 | dsm_mem_addr); |
| 799 | return; |
| 800 | } |
| 801 | |
| 802 | /* Encode DSM function according to DSM Spec Rev1. */ |
| 803 | switch (in->function) { |
| 804 | case 4 /* Get Namespace Label Size */: |
| 805 | if (nvdimm->label_size) { |
| 806 | nvdimm_dsm_label_size(nvdimm, dsm_mem_addr); |
| 807 | return; |
| 808 | } |
| 809 | break; |
| 810 | case 5 /* Get Namespace Label Data */: |
| 811 | if (nvdimm->label_size) { |
| 812 | nvdimm_dsm_get_label_data(nvdimm, in, dsm_mem_addr); |
| 813 | return; |
| 814 | } |
| 815 | break; |
| 816 | case 0x6 /* Set Namespace Label Data */: |
| 817 | if (nvdimm->label_size) { |
| 818 | nvdimm_dsm_set_label_data(nvdimm, in, dsm_mem_addr); |
| 819 | return; |
| 820 | } |
| 821 | break; |
| 822 | } |
| 823 | |
| 824 | nvdimm_dsm_no_payload(NVDIMM_DSM_RET_STATUS_UNSUPPORT, dsm_mem_addr); |
| 825 | } |
| 826 | |
| 827 | static uint64_t |
| 828 | nvdimm_dsm_read(void *opaque, hwaddr addr, unsigned size) |
| 829 | { |
| 830 | trace_acpi_nvdimm_read_io_port(); |
| 831 | return 0; |
| 832 | } |
| 833 | |
| 834 | static void |
| 835 | nvdimm_dsm_write(void *opaque, hwaddr addr, uint64_t val, unsigned size) |
| 836 | { |
| 837 | NVDIMMState *state = opaque; |
| 838 | NvdimmDsmIn *in; |
| 839 | hwaddr dsm_mem_addr = val; |
| 840 | |
| 841 | trace_acpi_nvdimm_dsm_mem_addr(dsm_mem_addr); |
| 842 | |
| 843 | /* |
| 844 | * The DSM memory is mapped to guest address space so an evil guest |
| 845 | * can change its content while we are doing DSM emulation. Avoid |
| 846 | * this by copying DSM memory to QEMU local memory. |
| 847 | */ |
| 848 | in = g_new(NvdimmDsmIn, 1); |
| 849 | physical_memory_read(dsm_mem_addr, in, sizeof(*in)); |
| 850 | |
| 851 | in->revision = le32_to_cpu(in->revision); |
| 852 | in->function = le32_to_cpu(in->function); |
| 853 | in->handle = le32_to_cpu(in->handle); |
| 854 | |
| 855 | trace_acpi_nvdimm_dsm_info(in->revision, in->handle, in->function); |
| 856 | |
| 857 | if (in->revision != 0x1 /* Currently we only support DSM Spec Rev1. */) { |
| 858 | trace_acpi_nvdimm_invalid_revision(in->revision); |
| 859 | nvdimm_dsm_no_payload(NVDIMM_DSM_RET_STATUS_UNSUPPORT, dsm_mem_addr); |
| 860 | goto exit; |
| 861 | } |
| 862 | |
| 863 | if (in->handle == NVDIMM_QEMU_RSVD_HANDLE_ROOT) { |
| 864 | nvdimm_dsm_handle_reserved_root_method(state, in, dsm_mem_addr); |
| 865 | goto exit; |
| 866 | } |
| 867 | |
| 868 | /* Handle 0 is reserved for NVDIMM Root Device. */ |
| 869 | if (!in->handle) { |
| 870 | nvdimm_dsm_root(in, dsm_mem_addr); |
| 871 | goto exit; |
| 872 | } |
| 873 | |
| 874 | nvdimm_dsm_device(in, dsm_mem_addr); |
| 875 | |
| 876 | exit: |
| 877 | g_free(in); |
| 878 | } |
| 879 | |
| 880 | static const MemoryRegionOps nvdimm_dsm_ops = { |
| 881 | .read = nvdimm_dsm_read, |
| 882 | .write = nvdimm_dsm_write, |
| 883 | .endianness = DEVICE_LITTLE_ENDIAN, |
| 884 | .valid = { |
| 885 | .min_access_size = 4, |
| 886 | .max_access_size = 4, |
| 887 | }, |
| 888 | }; |
| 889 | |
| 890 | void nvdimm_acpi_plug_cb(HotplugHandler *hotplug_dev, DeviceState *dev) |
| 891 | { |
| 892 | if (dev->hotplugged) { |
| 893 | acpi_send_event(DEVICE(hotplug_dev), ACPI_NVDIMM_HOTPLUG_STATUS); |
| 894 | } |
| 895 | } |
| 896 | |
| 897 | void nvdimm_init_acpi_state(NVDIMMState *state, MemoryRegion *io, |
| 898 | struct AcpiGenericAddress dsm_io, |
| 899 | FWCfgState *fw_cfg, Object *owner) |
| 900 | { |
| 901 | state->dsm_io = dsm_io; |
| 902 | memory_region_init_io(&state->io_mr, owner, &nvdimm_dsm_ops, state, |
| 903 | "nvdimm-acpi-io", dsm_io.bit_width >> 3); |
| 904 | memory_region_add_subregion(io, dsm_io.address, &state->io_mr); |
| 905 | |
| 906 | state->dsm_mem = g_array_new(false, true /* clear */, 1); |
| 907 | acpi_data_push(state->dsm_mem, sizeof(NvdimmDsmIn)); |
| 908 | fw_cfg_add_file(fw_cfg, NVDIMM_DSM_MEM_FILE, state->dsm_mem->data, |
| 909 | state->dsm_mem->len); |
| 910 | |
| 911 | nvdimm_init_fit_buffer(&state->fit_buf); |
| 912 | } |
| 913 | |
| 914 | #define NVDIMM_COMMON_DSM "NCAL" |
| 915 | #define NVDIMM_ACPI_MEM_ADDR "MEMA" |
| 916 | |
| 917 | #define NVDIMM_DSM_MEMORY "NRAM" |
| 918 | #define NVDIMM_DSM_IOPORT "NPIO" |
| 919 | |
| 920 | #define NVDIMM_DSM_NOTIFY "NTFI" |
| 921 | #define NVDIMM_DSM_HANDLE "HDLE" |
| 922 | #define NVDIMM_DSM_REVISION "REVS" |
| 923 | #define NVDIMM_DSM_FUNCTION "FUNC" |
| 924 | #define NVDIMM_DSM_ARG3 "FARG" |
| 925 | |
| 926 | #define NVDIMM_DSM_OUT_BUF_SIZE "RLEN" |
| 927 | #define NVDIMM_DSM_OUT_BUF "ODAT" |
| 928 | |
| 929 | #define NVDIMM_DSM_RFIT_STATUS "RSTA" |
| 930 | |
| 931 | #define NVDIMM_QEMU_RSVD_UUID "648B9CF2-CDA1-4312-8AD9-49C4AF32BD62" |
| 932 | #define NVDIMM_DEVICE_DSM_UUID "4309AC30-0D11-11E4-9191-0800200C9A66" |
| 933 | |
| 934 | static void nvdimm_build_common_dsm(Aml *dev, |
| 935 | NVDIMMState *nvdimm_state) |
| 936 | { |
| 937 | Aml *method, *ifctx, *function, *handle, *uuid, *dsm_mem, *elsectx2; |
| 938 | Aml *elsectx, *unsupport, *unpatched, *expected_uuid, *uuid_invalid; |
| 939 | Aml *pckg, *pckg_index, *pckg_buf, *field, *dsm_out_buf, *dsm_out_buf_size; |
| 940 | Aml *whilectx, *offset; |
| 941 | uint8_t byte_list[1]; |
| 942 | AmlRegionSpace rs; |
| 943 | |
| 944 | method = aml_method(NVDIMM_COMMON_DSM, 5, AML_SERIALIZED); |
| 945 | uuid = aml_arg(0); |
| 946 | function = aml_arg(2); |
| 947 | handle = aml_arg(4); |
| 948 | dsm_mem = aml_local(6); |
| 949 | dsm_out_buf = aml_local(7); |
| 950 | |
| 951 | aml_append(method, aml_store(aml_name(NVDIMM_ACPI_MEM_ADDR), dsm_mem)); |
| 952 | |
| 953 | if (nvdimm_state->dsm_io.space_id == AML_AS_SYSTEM_IO) { |
| 954 | rs = AML_SYSTEM_IO; |
| 955 | } else { |
| 956 | rs = AML_SYSTEM_MEMORY; |
| 957 | } |
| 958 | |
| 959 | /* map DSM memory and IO into ACPI namespace. */ |
| 960 | aml_append(method, aml_operation_region(NVDIMM_DSM_IOPORT, rs, |
| 961 | aml_int(nvdimm_state->dsm_io.address), |
| 962 | nvdimm_state->dsm_io.bit_width >> 3)); |
| 963 | aml_append(method, aml_operation_region(NVDIMM_DSM_MEMORY, |
| 964 | AML_SYSTEM_MEMORY, dsm_mem, sizeof(NvdimmDsmIn))); |
| 965 | |
| 966 | /* |
| 967 | * DSM notifier: |
| 968 | * NVDIMM_DSM_NOTIFY: write the address of DSM memory and notify QEMU to |
| 969 | * emulate the access. |
| 970 | * |
| 971 | * It is the IO port so that accessing them will cause VM-exit, the |
| 972 | * control will be transferred to QEMU. |
| 973 | */ |
| 974 | field = aml_field(NVDIMM_DSM_IOPORT, AML_DWORD_ACC, AML_NOLOCK, |
| 975 | AML_PRESERVE); |
| 976 | aml_append(field, aml_named_field(NVDIMM_DSM_NOTIFY, |
| 977 | nvdimm_state->dsm_io.bit_width)); |
| 978 | aml_append(method, field); |
| 979 | |
| 980 | /* |
| 981 | * DSM input: |
| 982 | * NVDIMM_DSM_HANDLE: store device's handle, it's zero if the _DSM call |
| 983 | * happens on NVDIMM Root Device. |
| 984 | * NVDIMM_DSM_REVISION: store the Arg1 of _DSM call. |
| 985 | * NVDIMM_DSM_FUNCTION: store the Arg2 of _DSM call. |
| 986 | * NVDIMM_DSM_ARG3: store the Arg3 of _DSM call which is a Package |
| 987 | * containing function-specific arguments. |
| 988 | * |
| 989 | * They are RAM mapping on host so that these accesses never cause |
| 990 | * VM-EXIT. |
| 991 | */ |
| 992 | field = aml_field(NVDIMM_DSM_MEMORY, AML_DWORD_ACC, AML_NOLOCK, |
| 993 | AML_PRESERVE); |
| 994 | aml_append(field, aml_named_field(NVDIMM_DSM_HANDLE, |
| 995 | sizeof(typeof_field(NvdimmDsmIn, handle)) * BITS_PER_BYTE)); |
| 996 | aml_append(field, aml_named_field(NVDIMM_DSM_REVISION, |
| 997 | sizeof(typeof_field(NvdimmDsmIn, revision)) * BITS_PER_BYTE)); |
| 998 | aml_append(field, aml_named_field(NVDIMM_DSM_FUNCTION, |
| 999 | sizeof(typeof_field(NvdimmDsmIn, function)) * BITS_PER_BYTE)); |
| 1000 | aml_append(field, aml_named_field(NVDIMM_DSM_ARG3, |
| 1001 | (sizeof(NvdimmDsmIn) - offsetof(NvdimmDsmIn, arg3)) * BITS_PER_BYTE)); |
| 1002 | aml_append(method, field); |
| 1003 | |
| 1004 | /* |
| 1005 | * DSM output: |
| 1006 | * NVDIMM_DSM_OUT_BUF_SIZE: the size of the buffer filled by QEMU. |
| 1007 | * NVDIMM_DSM_OUT_BUF: the buffer QEMU uses to store the result. |
| 1008 | * |
| 1009 | * Since the page is reused by both input and out, the input data |
| 1010 | * will be lost after storing new result into ODAT so we should fetch |
| 1011 | * all the input data before writing the result. |
| 1012 | */ |
| 1013 | field = aml_field(NVDIMM_DSM_MEMORY, AML_DWORD_ACC, AML_NOLOCK, |
| 1014 | AML_PRESERVE); |
| 1015 | aml_append(field, aml_named_field(NVDIMM_DSM_OUT_BUF_SIZE, |
| 1016 | sizeof(typeof_field(NvdimmDsmOut, len)) * BITS_PER_BYTE)); |
| 1017 | aml_append(field, aml_named_field(NVDIMM_DSM_OUT_BUF, |
| 1018 | (sizeof(NvdimmDsmOut) - offsetof(NvdimmDsmOut, data)) * BITS_PER_BYTE)); |
| 1019 | aml_append(method, field); |
| 1020 | |
| 1021 | /* |
| 1022 | * do not support any method if DSM memory address has not been |
| 1023 | * patched. |
| 1024 | */ |
| 1025 | unpatched = aml_equal(dsm_mem, aml_int(0x0)); |
| 1026 | |
| 1027 | expected_uuid = aml_local(0); |
| 1028 | |
| 1029 | ifctx = aml_if(aml_equal(handle, aml_int(0x0))); |
| 1030 | aml_append(ifctx, aml_store( |
| 1031 | aml_touuid("2F10E7A4-9E91-11E4-89D3-123B93F75CBA") |
| 1032 | /* UUID for NVDIMM Root Device */, expected_uuid)); |
| 1033 | aml_append(method, ifctx); |
| 1034 | elsectx = aml_else(); |
| 1035 | ifctx = aml_if(aml_equal(handle, aml_int(NVDIMM_QEMU_RSVD_HANDLE_ROOT))); |
| 1036 | aml_append(ifctx, aml_store(aml_touuid(NVDIMM_QEMU_RSVD_UUID |
| 1037 | /* UUID for QEMU internal use */), expected_uuid)); |
| 1038 | aml_append(elsectx, ifctx); |
| 1039 | elsectx2 = aml_else(); |
| 1040 | aml_append(elsectx2, aml_store(aml_touuid(NVDIMM_DEVICE_DSM_UUID) |
| 1041 | /* UUID for NVDIMM Devices */, expected_uuid)); |
| 1042 | aml_append(elsectx, elsectx2); |
| 1043 | aml_append(method, elsectx); |
| 1044 | |
| 1045 | uuid_invalid = aml_lnot(aml_equal(uuid, expected_uuid)); |
| 1046 | |
| 1047 | unsupport = aml_if(aml_lor(unpatched, uuid_invalid)); |
| 1048 | |
| 1049 | /* |
| 1050 | * function 0 is called to inquire what functions are supported by |
| 1051 | * OSPM |
| 1052 | */ |
| 1053 | ifctx = aml_if(aml_equal(function, aml_int(0))); |
| 1054 | byte_list[0] = 0 /* No function Supported */; |
| 1055 | aml_append(ifctx, aml_return(aml_buffer(1, byte_list))); |
| 1056 | aml_append(unsupport, ifctx); |
| 1057 | |
| 1058 | /* No function is supported yet. */ |
| 1059 | byte_list[0] = NVDIMM_DSM_RET_STATUS_UNSUPPORT; |
| 1060 | aml_append(unsupport, aml_return(aml_buffer(1, byte_list))); |
| 1061 | aml_append(method, unsupport); |
| 1062 | |
| 1063 | /* |
| 1064 | * The HDLE indicates the DSM function is issued from which device, |
| 1065 | * it reserves 0 for root device and is the handle for NVDIMM devices. |
| 1066 | * See the comments in nvdimm_slot_to_handle(). |
| 1067 | */ |
| 1068 | aml_append(method, aml_store(handle, aml_name(NVDIMM_DSM_HANDLE))); |
| 1069 | aml_append(method, aml_store(aml_arg(1), aml_name(NVDIMM_DSM_REVISION))); |
| 1070 | aml_append(method, aml_store(function, aml_name(NVDIMM_DSM_FUNCTION))); |
| 1071 | |
| 1072 | /* |
| 1073 | * The fourth parameter (Arg3) of _DSM is a package which contains |
| 1074 | * a buffer, the layout of the buffer is specified by UUID (Arg0), |
| 1075 | * Revision ID (Arg1) and Function Index (Arg2) which are documented |
| 1076 | * in the DSM Spec. |
| 1077 | */ |
| 1078 | pckg = aml_arg(3); |
| 1079 | ifctx = aml_if(aml_land(aml_equal(aml_object_type(pckg), |
| 1080 | aml_int(4 /* Package */)) /* It is a Package? */, |
| 1081 | aml_equal(aml_sizeof(pckg), aml_int(1)) /* 1 element? */)); |
| 1082 | |
| 1083 | pckg_index = aml_local(2); |
| 1084 | pckg_buf = aml_local(3); |
| 1085 | aml_append(ifctx, aml_store(aml_index(pckg, aml_int(0)), pckg_index)); |
| 1086 | aml_append(ifctx, aml_store(aml_derefof(pckg_index), pckg_buf)); |
| 1087 | aml_append(ifctx, aml_store(pckg_buf, aml_name(NVDIMM_DSM_ARG3))); |
| 1088 | aml_append(method, ifctx); |
| 1089 | |
| 1090 | /* |
| 1091 | * tell QEMU about the real address of DSM memory, then QEMU |
| 1092 | * gets the control and fills the result in DSM memory. |
| 1093 | */ |
| 1094 | aml_append(method, aml_store(dsm_mem, aml_name(NVDIMM_DSM_NOTIFY))); |
| 1095 | |
| 1096 | dsm_out_buf_size = aml_local(1); |
| 1097 | /* RLEN is not included in the payload returned to guest. */ |
| 1098 | aml_append(method, aml_subtract(aml_name(NVDIMM_DSM_OUT_BUF_SIZE), |
| 1099 | aml_int(4), dsm_out_buf_size)); |
| 1100 | |
| 1101 | /* |
| 1102 | * As per ACPI spec 6.3, Table 19-419 Object Conversion Rules, if |
| 1103 | * the Buffer Field <= to the size of an Integer (in bits), it will |
| 1104 | * be treated as an integer. Moreover, the integer size depends on |
| 1105 | * DSDT tables revision number. If revision number is < 2, integer |
| 1106 | * size is 32 bits, otherwise it is 64 bits. |
| 1107 | * Because of this CreateField() cannot be used if RLEN < Integer Size. |
| 1108 | * |
| 1109 | * Also please note that APCI ASL operator SizeOf() doesn't support |
| 1110 | * Integer and there isn't any other way to figure out the Integer |
| 1111 | * size. Hence we assume 8 byte as Integer size and if RLEN < 8 bytes, |
| 1112 | * build dsm_out_buf byte by byte. |
| 1113 | */ |
| 1114 | ifctx = aml_if(aml_lless(dsm_out_buf_size, aml_int(8))); |
| 1115 | offset = aml_local(2); |
| 1116 | aml_append(ifctx, aml_store(aml_int(0), offset)); |
| 1117 | aml_append(ifctx, aml_name_decl("TBUF", aml_buffer(1, NULL))); |
| 1118 | aml_append(ifctx, aml_store(aml_buffer(0, NULL), dsm_out_buf)); |
| 1119 | |
| 1120 | whilectx = aml_while(aml_lless(offset, dsm_out_buf_size)); |
| 1121 | /* Copy 1 byte at offset from ODAT to temporary buffer(TBUF). */ |
| 1122 | aml_append(whilectx, aml_store(aml_derefof(aml_index( |
| 1123 | aml_name(NVDIMM_DSM_OUT_BUF), offset)), |
| 1124 | aml_index(aml_name("TBUF"), aml_int(0)))); |
| 1125 | aml_append(whilectx, aml_concatenate(dsm_out_buf, aml_name("TBUF"), |
| 1126 | dsm_out_buf)); |
| 1127 | aml_append(whilectx, aml_increment(offset)); |
| 1128 | aml_append(ifctx, whilectx); |
| 1129 | |
| 1130 | aml_append(ifctx, aml_return(dsm_out_buf)); |
| 1131 | aml_append(method, ifctx); |
| 1132 | |
| 1133 | /* If RLEN >= Integer size, just use CreateField() operator */ |
| 1134 | aml_append(method, aml_store(aml_shiftleft(dsm_out_buf_size, aml_int(3)), |
| 1135 | dsm_out_buf_size)); |
| 1136 | aml_append(method, aml_create_field(aml_name(NVDIMM_DSM_OUT_BUF), |
| 1137 | aml_int(0), dsm_out_buf_size, "OBUF")); |
| 1138 | aml_append(method, aml_return(aml_name("OBUF"))); |
| 1139 | |
| 1140 | aml_append(dev, method); |
| 1141 | } |
| 1142 | |
| 1143 | static void nvdimm_build_device_dsm(Aml *dev, uint32_t handle) |
| 1144 | { |
| 1145 | Aml *method; |
| 1146 | |
| 1147 | method = aml_method("_DSM", 4, AML_NOTSERIALIZED); |
| 1148 | aml_append(method, aml_return(aml_call5(NVDIMM_COMMON_DSM, aml_arg(0), |
| 1149 | aml_arg(1), aml_arg(2), aml_arg(3), |
| 1150 | aml_int(handle)))); |
| 1151 | aml_append(dev, method); |
| 1152 | } |
| 1153 | |
| 1154 | static void nvdimm_build_fit(Aml *dev) |
| 1155 | { |
| 1156 | Aml *method, *pkg, *buf, *buf_size, *offset, *call_result; |
| 1157 | Aml *whilectx, *ifcond, *ifctx, *elsectx, *fit; |
| 1158 | |
| 1159 | buf = aml_local(0); |
| 1160 | buf_size = aml_local(1); |
| 1161 | fit = aml_local(2); |
| 1162 | |
| 1163 | aml_append(dev, aml_name_decl(NVDIMM_DSM_RFIT_STATUS, aml_int(0))); |
| 1164 | |
| 1165 | /* build helper function, RFIT. */ |
| 1166 | method = aml_method("RFIT", 1, AML_SERIALIZED); |
| 1167 | aml_append(method, aml_name_decl("OFST", aml_int(0))); |
| 1168 | |
| 1169 | /* prepare input package. */ |
| 1170 | pkg = aml_package(1); |
| 1171 | aml_append(method, aml_store(aml_arg(0), aml_name("OFST"))); |
| 1172 | aml_append(pkg, aml_name("OFST")); |
| 1173 | |
| 1174 | /* call Read_FIT function. */ |
| 1175 | call_result = aml_call5(NVDIMM_COMMON_DSM, |
| 1176 | aml_touuid(NVDIMM_QEMU_RSVD_UUID), |
| 1177 | aml_int(1) /* Revision 1 */, |
| 1178 | aml_int(0x1) /* Read FIT */, |
| 1179 | pkg, aml_int(NVDIMM_QEMU_RSVD_HANDLE_ROOT)); |
| 1180 | aml_append(method, aml_store(call_result, buf)); |
| 1181 | |
| 1182 | /* handle _DSM result. */ |
| 1183 | aml_append(method, aml_create_dword_field(buf, |
| 1184 | aml_int(0) /* offset at byte 0 */, "STAU")); |
| 1185 | |
| 1186 | aml_append(method, aml_store(aml_name("STAU"), |
| 1187 | aml_name(NVDIMM_DSM_RFIT_STATUS))); |
| 1188 | |
| 1189 | /* if something is wrong during _DSM. */ |
| 1190 | ifcond = aml_equal(aml_int(NVDIMM_DSM_RET_STATUS_SUCCESS), |
| 1191 | aml_name("STAU")); |
| 1192 | ifctx = aml_if(aml_lnot(ifcond)); |
| 1193 | aml_append(ifctx, aml_return(aml_buffer(0, NULL))); |
| 1194 | aml_append(method, ifctx); |
| 1195 | |
| 1196 | aml_append(method, aml_store(aml_sizeof(buf), buf_size)); |
| 1197 | aml_append(method, aml_subtract(buf_size, |
| 1198 | aml_int(4) /* the size of "STAU" */, |
| 1199 | buf_size)); |
| 1200 | |
| 1201 | /* if we read the end of fit. */ |
| 1202 | ifctx = aml_if(aml_equal(buf_size, aml_int(0))); |
| 1203 | aml_append(ifctx, aml_return(aml_buffer(0, NULL))); |
| 1204 | aml_append(method, ifctx); |
| 1205 | |
| 1206 | aml_append(method, aml_create_field(buf, |
| 1207 | aml_int(4 * BITS_PER_BYTE), /* offset at byte 4.*/ |
| 1208 | aml_shiftleft(buf_size, aml_int(3)), "BUFF")); |
| 1209 | aml_append(method, aml_return(aml_name("BUFF"))); |
| 1210 | aml_append(dev, method); |
| 1211 | |
| 1212 | /* build _FIT. */ |
| 1213 | method = aml_method("_FIT", 0, AML_SERIALIZED); |
| 1214 | offset = aml_local(3); |
| 1215 | |
| 1216 | aml_append(method, aml_store(aml_buffer(0, NULL), fit)); |
| 1217 | aml_append(method, aml_store(aml_int(0), offset)); |
| 1218 | |
| 1219 | whilectx = aml_while(aml_int(1)); |
| 1220 | aml_append(whilectx, aml_store(aml_call1("RFIT", offset), buf)); |
| 1221 | aml_append(whilectx, aml_store(aml_sizeof(buf), buf_size)); |
| 1222 | |
| 1223 | /* |
| 1224 | * if fit buffer was changed during RFIT, read from the beginning |
| 1225 | * again. |
| 1226 | */ |
| 1227 | ifctx = aml_if(aml_equal(aml_name(NVDIMM_DSM_RFIT_STATUS), |
| 1228 | aml_int(NVDIMM_DSM_RET_STATUS_FIT_CHANGED))); |
| 1229 | aml_append(ifctx, aml_store(aml_buffer(0, NULL), fit)); |
| 1230 | aml_append(ifctx, aml_store(aml_int(0), offset)); |
| 1231 | aml_append(whilectx, ifctx); |
| 1232 | |
| 1233 | elsectx = aml_else(); |
| 1234 | |
| 1235 | /* finish fit read if no data is read out. */ |
| 1236 | ifctx = aml_if(aml_equal(buf_size, aml_int(0))); |
| 1237 | aml_append(ifctx, aml_return(fit)); |
| 1238 | aml_append(elsectx, ifctx); |
| 1239 | |
| 1240 | /* update the offset. */ |
| 1241 | aml_append(elsectx, aml_add(offset, buf_size, offset)); |
| 1242 | /* append the data we read out to the fit buffer. */ |
| 1243 | aml_append(elsectx, aml_concatenate(fit, buf, fit)); |
| 1244 | aml_append(whilectx, elsectx); |
| 1245 | aml_append(method, whilectx); |
| 1246 | |
| 1247 | aml_append(dev, method); |
| 1248 | } |
| 1249 | |
| 1250 | static void nvdimm_build_nvdimm_devices(Aml *root_dev, uint32_t ram_slots) |
| 1251 | { |
| 1252 | uint32_t slot; |
| 1253 | Aml *method, *pkg, *field, *com_call; |
| 1254 | |
| 1255 | for (slot = 0; slot < ram_slots; slot++) { |
| 1256 | uint32_t handle = nvdimm_slot_to_handle(slot); |
| 1257 | Aml *nvdimm_dev; |
| 1258 | |
| 1259 | nvdimm_dev = aml_device("NV%02X", slot); |
| 1260 | |
| 1261 | /* |
| 1262 | * ACPI 6.0: 9.20 NVDIMM Devices: |
| 1263 | * |
| 1264 | * _ADR object that is used to supply OSPM with unique address |
| 1265 | * of the NVDIMM device. This is done by returning the NFIT Device |
| 1266 | * handle that is used to identify the associated entries in ACPI |
| 1267 | * table NFIT or _FIT. |
| 1268 | */ |
| 1269 | aml_append(nvdimm_dev, aml_name_decl("_ADR", aml_int(handle))); |
| 1270 | |
| 1271 | /* |
| 1272 | * ACPI v6.4: Section 6.5.10 NVDIMM Label Methods |
| 1273 | */ |
| 1274 | /* _LSI */ |
| 1275 | method = aml_method("_LSI", 0, AML_SERIALIZED); |
| 1276 | com_call = aml_call5(NVDIMM_COMMON_DSM, |
| 1277 | aml_touuid(NVDIMM_DEVICE_DSM_UUID), |
| 1278 | aml_int(1), aml_int(4), aml_int(0), |
| 1279 | aml_int(handle)); |
| 1280 | aml_append(method, aml_store(com_call, aml_local(0))); |
| 1281 | |
| 1282 | aml_append(method, aml_create_dword_field(aml_local(0), |
| 1283 | aml_int(0), "STTS")); |
| 1284 | aml_append(method, aml_create_dword_field(aml_local(0), aml_int(4), |
| 1285 | "SLSA")); |
| 1286 | aml_append(method, aml_create_dword_field(aml_local(0), aml_int(8), |
| 1287 | "MAXT")); |
| 1288 | |
| 1289 | pkg = aml_package(3); |
| 1290 | aml_append(pkg, aml_name("STTS")); |
| 1291 | aml_append(pkg, aml_name("SLSA")); |
| 1292 | aml_append(pkg, aml_name("MAXT")); |
| 1293 | aml_append(method, aml_store(pkg, aml_local(1))); |
| 1294 | aml_append(method, aml_return(aml_local(1))); |
| 1295 | |
| 1296 | aml_append(nvdimm_dev, method); |
| 1297 | |
| 1298 | /* _LSR */ |
| 1299 | method = aml_method("_LSR", 2, AML_SERIALIZED); |
| 1300 | aml_append(method, aml_name_decl("INPT", aml_buffer(8, NULL))); |
| 1301 | |
| 1302 | aml_append(method, aml_create_dword_field(aml_name("INPT"), |
| 1303 | aml_int(0), "OFST")); |
| 1304 | aml_append(method, aml_create_dword_field(aml_name("INPT"), |
| 1305 | aml_int(4), "LEN")); |
| 1306 | aml_append(method, aml_store(aml_arg(0), aml_name("OFST"))); |
| 1307 | aml_append(method, aml_store(aml_arg(1), aml_name("LEN"))); |
| 1308 | |
| 1309 | pkg = aml_package(1); |
| 1310 | aml_append(pkg, aml_name("INPT")); |
| 1311 | aml_append(method, aml_store(pkg, aml_local(0))); |
| 1312 | |
| 1313 | com_call = aml_call5(NVDIMM_COMMON_DSM, |
| 1314 | aml_touuid(NVDIMM_DEVICE_DSM_UUID), |
| 1315 | aml_int(1), aml_int(5), aml_local(0), |
| 1316 | aml_int(handle)); |
| 1317 | aml_append(method, aml_store(com_call, aml_local(3))); |
| 1318 | field = aml_create_dword_field(aml_local(3), aml_int(0), "STTS"); |
| 1319 | aml_append(method, field); |
| 1320 | field = aml_create_field(aml_local(3), aml_int(32), |
| 1321 | aml_shiftleft(aml_name("LEN"), aml_int(3)), |
| 1322 | "LDAT"); |
| 1323 | aml_append(method, field); |
| 1324 | aml_append(method, aml_name_decl("LSA", aml_buffer(0, NULL))); |
| 1325 | aml_append(method, aml_to_buffer(aml_name("LDAT"), aml_name("LSA"))); |
| 1326 | |
| 1327 | pkg = aml_package(2); |
| 1328 | aml_append(pkg, aml_name("STTS")); |
| 1329 | aml_append(pkg, aml_name("LSA")); |
| 1330 | |
| 1331 | aml_append(method, aml_store(pkg, aml_local(1))); |
| 1332 | aml_append(method, aml_return(aml_local(1))); |
| 1333 | |
| 1334 | aml_append(nvdimm_dev, method); |
| 1335 | |
| 1336 | /* _LSW */ |
| 1337 | method = aml_method("_LSW", 3, AML_SERIALIZED); |
| 1338 | aml_append(method, aml_store(aml_arg(2), aml_local(2))); |
| 1339 | aml_append(method, aml_name_decl("INPT", aml_buffer(8, NULL))); |
| 1340 | field = aml_create_dword_field(aml_name("INPT"), |
| 1341 | aml_int(0), "OFST"); |
| 1342 | aml_append(method, field); |
| 1343 | field = aml_create_dword_field(aml_name("INPT"), |
| 1344 | aml_int(4), "TLEN"); |
| 1345 | aml_append(method, field); |
| 1346 | aml_append(method, aml_store(aml_arg(0), aml_name("OFST"))); |
| 1347 | aml_append(method, aml_store(aml_arg(1), aml_name("TLEN"))); |
| 1348 | |
| 1349 | aml_append(method, aml_concatenate(aml_name("INPT"), aml_local(2), |
| 1350 | aml_name("INPT"))); |
| 1351 | pkg = aml_package(1); |
| 1352 | aml_append(pkg, aml_name("INPT")); |
| 1353 | aml_append(method, aml_store(pkg, aml_local(0))); |
| 1354 | com_call = aml_call5(NVDIMM_COMMON_DSM, |
| 1355 | aml_touuid(NVDIMM_DEVICE_DSM_UUID), |
| 1356 | aml_int(1), aml_int(6), aml_local(0), |
| 1357 | aml_int(handle)); |
| 1358 | aml_append(method, aml_store(com_call, aml_local(3))); |
| 1359 | field = aml_create_dword_field(aml_local(3), aml_int(0), "STTS"); |
| 1360 | aml_append(method, field); |
| 1361 | aml_append(method, aml_return(aml_name("STTS"))); |
| 1362 | |
| 1363 | aml_append(nvdimm_dev, method); |
| 1364 | |
| 1365 | nvdimm_build_device_dsm(nvdimm_dev, handle); |
| 1366 | aml_append(root_dev, nvdimm_dev); |
| 1367 | } |
| 1368 | } |
| 1369 | |
| 1370 | static void nvdimm_build_ssdt(GArray *table_offsets, GArray *table_data, |
| 1371 | BIOSLinker *linker, |
| 1372 | NVDIMMState *nvdimm_state, |
| 1373 | uint32_t ram_slots, const char *oem_id) |
| 1374 | { |
| 1375 | int mem_addr_offset; |
| 1376 | Aml *ssdt, *sb_scope, *dev; |
| 1377 | AcpiTable table = { .sig = "SSDT", .rev = 1, |
| 1378 | .oem_id = oem_id, .oem_table_id = "NVDIMM" }; |
| 1379 | |
| 1380 | acpi_add_table(table_offsets, table_data); |
| 1381 | |
| 1382 | acpi_table_begin(&table, table_data); |
| 1383 | ssdt = init_aml_allocator(); |
| 1384 | sb_scope = aml_scope("\\_SB"); |
| 1385 | |
| 1386 | dev = aml_device("NVDR"); |
| 1387 | |
| 1388 | /* |
| 1389 | * ACPI 6.0: 9.20 NVDIMM Devices: |
| 1390 | * |
| 1391 | * The ACPI Name Space device uses _HID of ACPI0012 to identify the root |
| 1392 | * NVDIMM interface device. Platform firmware is required to contain one |
| 1393 | * such device in _SB scope if NVDIMMs support is exposed by platform to |
| 1394 | * OSPM. |
| 1395 | * For each NVDIMM present or intended to be supported by platform, |
| 1396 | * platform firmware also exposes an ACPI Namespace Device under the |
| 1397 | * root device. |
| 1398 | */ |
| 1399 | aml_append(dev, aml_name_decl("_HID", aml_string("ACPI0012"))); |
| 1400 | |
| 1401 | nvdimm_build_common_dsm(dev, nvdimm_state); |
| 1402 | |
| 1403 | /* 0 is reserved for root device. */ |
| 1404 | nvdimm_build_device_dsm(dev, 0); |
| 1405 | nvdimm_build_fit(dev); |
| 1406 | |
| 1407 | nvdimm_build_nvdimm_devices(dev, ram_slots); |
| 1408 | |
| 1409 | aml_append(sb_scope, dev); |
| 1410 | aml_append(ssdt, sb_scope); |
| 1411 | |
| 1412 | /* copy AML table into ACPI tables blob and patch header there */ |
| 1413 | g_array_append_vals(table_data, ssdt->buf->data, ssdt->buf->len); |
| 1414 | mem_addr_offset = build_append_named_dword(table_data, |
| 1415 | NVDIMM_ACPI_MEM_ADDR); |
| 1416 | |
| 1417 | bios_linker_loader_alloc(linker, |
| 1418 | NVDIMM_DSM_MEM_FILE, nvdimm_state->dsm_mem, |
| 1419 | sizeof(NvdimmDsmIn), false /* high memory */); |
| 1420 | bios_linker_loader_add_pointer(linker, |
| 1421 | ACPI_BUILD_TABLE_FILE, mem_addr_offset, sizeof(uint32_t), |
| 1422 | NVDIMM_DSM_MEM_FILE, 0); |
| 1423 | free_aml_allocator(); |
| 1424 | /* |
| 1425 | * must be executed as the last so that pointer patching command above |
| 1426 | * would be executed by guest before it recalculated checksum which were |
| 1427 | * scheduled by acpi_table_end() |
| 1428 | */ |
| 1429 | acpi_table_end(linker, &table); |
| 1430 | } |
| 1431 | |
| 1432 | void nvdimm_build_srat(GArray *table_data) |
| 1433 | { |
| 1434 | GSList *device_list, *list = nvdimm_get_device_list(); |
| 1435 | |
| 1436 | for (device_list = list; device_list; device_list = device_list->next) { |
| 1437 | DeviceState *dev = device_list->data; |
| 1438 | Object *obj = OBJECT(dev); |
| 1439 | uint64_t addr, size; |
| 1440 | int node; |
| 1441 | |
| 1442 | node = object_property_get_int(obj, PC_DIMM_NODE_PROP, &error_abort); |
| 1443 | addr = object_property_get_uint(obj, PC_DIMM_ADDR_PROP, &error_abort); |
| 1444 | size = object_property_get_uint(obj, PC_DIMM_SIZE_PROP, &error_abort); |
| 1445 | |
| 1446 | build_srat_memory(table_data, addr, size, node, |
| 1447 | MEM_AFFINITY_ENABLED | MEM_AFFINITY_NON_VOLATILE); |
| 1448 | } |
| 1449 | g_slist_free(list); |
| 1450 | } |
| 1451 | |
| 1452 | void nvdimm_build_acpi(GArray *table_offsets, GArray *table_data, |
| 1453 | BIOSLinker *linker, NVDIMMState *state, |
| 1454 | uint32_t ram_slots, const char *oem_id, |
| 1455 | const char *oem_table_id) |
| 1456 | { |
| 1457 | GSList *device_list; |
| 1458 | |
| 1459 | /* no nvdimm device can be plugged. */ |
| 1460 | if (!ram_slots) { |
| 1461 | return; |
| 1462 | } |
| 1463 | |
| 1464 | nvdimm_build_ssdt(table_offsets, table_data, linker, state, |
| 1465 | ram_slots, oem_id); |
| 1466 | |
| 1467 | device_list = nvdimm_get_device_list(); |
| 1468 | /* no NVDIMM device is plugged. */ |
| 1469 | if (!device_list) { |
| 1470 | return; |
| 1471 | } |
| 1472 | |
| 1473 | nvdimm_build_nfit(state, table_offsets, table_data, linker, |
| 1474 | oem_id, oem_table_id); |
| 1475 | g_slist_free(device_list); |
| 1476 | } |