| 1 | /* |
| 2 | * CXL ACPI Implementation |
| 3 | * |
| 4 | * Copyright(C) 2020 Intel Corporation. |
| 5 | * |
| 6 | * This library is free software; you can redistribute it and/or |
| 7 | * modify it under the terms of the GNU Lesser General Public |
| 8 | * License as published by the Free Software Foundation; either |
| 9 | * version 2 of the License, or (at your option) any later version. |
| 10 | * |
| 11 | * This library is distributed in the hope that it will be useful, |
| 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 14 | * Lesser General Public License for more details. |
| 15 | * |
| 16 | * You should have received a copy of the GNU Lesser General Public |
| 17 | * License along with this library; if not, see <http://www.gnu.org/licenses/> |
| 18 | */ |
| 19 | |
| 20 | #include "qemu/osdep.h" |
| 21 | #include "hw/core/sysbus.h" |
| 22 | #include "hw/pci/pci_bridge.h" |
| 23 | #include "hw/pci/pci_host.h" |
| 24 | #include "hw/cxl/cxl.h" |
| 25 | #include "hw/cxl/cxl_host.h" |
| 26 | #include "hw/mem/memory-device.h" |
| 27 | #include "hw/acpi/acpi.h" |
| 28 | #include "hw/acpi/aml-build.h" |
| 29 | #include "hw/acpi/bios-linker-loader.h" |
| 30 | #include "hw/acpi/cxl.h" |
| 31 | #include "qapi/error.h" |
| 32 | #include "qemu/uuid.h" |
| 33 | |
| 34 | void build_cxl_dsm_method(Aml *dev) |
| 35 | { |
| 36 | Aml *method, *ifctx, *ifctx2; |
| 37 | |
| 38 | method = aml_method("_DSM", 4, AML_SERIALIZED); |
| 39 | { |
| 40 | Aml *function, *uuid; |
| 41 | |
| 42 | uuid = aml_arg(0); |
| 43 | function = aml_arg(2); |
| 44 | /* CXL spec v3.0 9.17.3.1 _DSM Function for Retrieving QTG ID */ |
| 45 | ifctx = aml_if(aml_equal( |
| 46 | uuid, aml_touuid("F365F9A6-A7DE-4071-A66A-B40C0B4F8E52"))); |
| 47 | |
| 48 | /* Function 0, standard DSM query function */ |
| 49 | ifctx2 = aml_if(aml_equal(function, aml_int(0))); |
| 50 | { |
| 51 | uint8_t byte_list[1] = { 0x01 }; /* function 1 only */ |
| 52 | |
| 53 | aml_append(ifctx2, |
| 54 | aml_return(aml_buffer(sizeof(byte_list), byte_list))); |
| 55 | } |
| 56 | aml_append(ifctx, ifctx2); |
| 57 | |
| 58 | /* |
| 59 | * Function 1 |
| 60 | * Creating a package with static values. The max supported QTG ID will |
| 61 | * be 1 and recommended QTG IDs are 0 and then 1. |
| 62 | * The values here are statically created to simplify emulation. Values |
| 63 | * from a real BIOS would be determined by the performance of all the |
| 64 | * present CXL memory and then assigned. |
| 65 | */ |
| 66 | ifctx2 = aml_if(aml_equal(function, aml_int(1))); |
| 67 | { |
| 68 | Aml *pak, *pak1; |
| 69 | |
| 70 | /* |
| 71 | * Return: A package containing two elements - a WORD that returns |
| 72 | * the maximum throttling group that the platform supports, and a |
| 73 | * package containing the QTG ID(s) that the platform recommends. |
| 74 | * Package { |
| 75 | * Max Supported QTG ID |
| 76 | * Package {QTG Recommendations} |
| 77 | * } |
| 78 | * |
| 79 | * While the SPEC specified WORD that hints at the value being |
| 80 | * 16bit, the ACPI dump of BIOS DSDT table showed that the values |
| 81 | * are integers with no specific size specification. aml_int() will |
| 82 | * be used for the values. |
| 83 | */ |
| 84 | pak1 = aml_package(2); |
| 85 | /* Set QTG ID of 0 */ |
| 86 | aml_append(pak1, aml_int(0)); |
| 87 | /* Set QTG ID of 1 */ |
| 88 | aml_append(pak1, aml_int(1)); |
| 89 | |
| 90 | pak = aml_package(2); |
| 91 | /* Set Max QTG 1 */ |
| 92 | aml_append(pak, aml_int(1)); |
| 93 | aml_append(pak, pak1); |
| 94 | |
| 95 | aml_append(ifctx2, aml_return(pak)); |
| 96 | } |
| 97 | aml_append(ifctx, ifctx2); |
| 98 | } |
| 99 | aml_append(method, ifctx); |
| 100 | aml_append(dev, method); |
| 101 | } |
| 102 | |
| 103 | static void cedt_build_chbs(GArray *table_data, PXBCXLDev *cxl) |
| 104 | { |
| 105 | PXBDev *pxb = PXB_DEV(cxl); |
| 106 | SysBusDevice *sbd = SYS_BUS_DEVICE(cxl->cxl_host_bridge); |
| 107 | struct MemoryRegion *mr = sbd->mmio[0].memory; |
| 108 | |
| 109 | /* Type */ |
| 110 | build_append_int_noprefix(table_data, 0, 1); |
| 111 | |
| 112 | /* Reserved */ |
| 113 | build_append_int_noprefix(table_data, 0, 1); |
| 114 | |
| 115 | /* Record Length */ |
| 116 | build_append_int_noprefix(table_data, 32, 2); |
| 117 | |
| 118 | /* UID - currently equal to bus number */ |
| 119 | build_append_int_noprefix(table_data, pxb->bus_nr, 4); |
| 120 | |
| 121 | /* Version */ |
| 122 | build_append_int_noprefix(table_data, 1, 4); |
| 123 | |
| 124 | /* Reserved */ |
| 125 | build_append_int_noprefix(table_data, 0, 4); |
| 126 | |
| 127 | /* Base - subregion within a container that is in PA space */ |
| 128 | build_append_int_noprefix(table_data, mr->container->addr + mr->addr, 8); |
| 129 | |
| 130 | /* Length */ |
| 131 | build_append_int_noprefix(table_data, memory_region_size(mr), 8); |
| 132 | } |
| 133 | |
| 134 | /* |
| 135 | * CFMWS entries in CXL 2.0 ECN: CEDT CFMWS & QTG _DSM. |
| 136 | * Interleave ways encoding in CXL 2.0 ECN: 3, 6, 12 and 16-way memory |
| 137 | * interleaving. |
| 138 | */ |
| 139 | static void cedt_build_cfmws(CXLFixedWindow *fw, Aml *cedt) |
| 140 | { |
| 141 | GArray *table_data = cedt->buf; |
| 142 | int i; |
| 143 | |
| 144 | /* Type */ |
| 145 | build_append_int_noprefix(table_data, 1, 1); |
| 146 | |
| 147 | /* Reserved */ |
| 148 | build_append_int_noprefix(table_data, 0, 1); |
| 149 | |
| 150 | /* Record Length */ |
| 151 | build_append_int_noprefix(table_data, 36 + 4 * fw->num_targets, 2); |
| 152 | |
| 153 | /* Reserved */ |
| 154 | build_append_int_noprefix(table_data, 0, 4); |
| 155 | |
| 156 | /* Base HPA */ |
| 157 | build_append_int_noprefix(table_data, fw->mr.addr, 8); |
| 158 | |
| 159 | /* Window Size */ |
| 160 | build_append_int_noprefix(table_data, fw->size, 8); |
| 161 | |
| 162 | /* Host Bridge Interleave Ways */ |
| 163 | build_append_int_noprefix(table_data, fw->enc_int_ways, 1); |
| 164 | |
| 165 | /* Host Bridge Interleave Arithmetic */ |
| 166 | build_append_int_noprefix(table_data, 0, 1); |
| 167 | |
| 168 | /* Reserved */ |
| 169 | build_append_int_noprefix(table_data, 0, 2); |
| 170 | |
| 171 | /* Host Bridge Interleave Granularity */ |
| 172 | build_append_int_noprefix(table_data, fw->enc_int_gran, 4); |
| 173 | |
| 174 | /* Window Restrictions */ |
| 175 | build_append_int_noprefix(table_data, 0x2f, 2); |
| 176 | |
| 177 | /* QTG ID */ |
| 178 | build_append_int_noprefix(table_data, 0, 2); |
| 179 | |
| 180 | /* Host Bridge List (list of UIDs - currently bus_nr) */ |
| 181 | for (i = 0; i < fw->num_targets; i++) { |
| 182 | g_assert(fw->target_hbs[i]); |
| 183 | build_append_int_noprefix(table_data, |
| 184 | PXB_DEV(fw->target_hbs[i])->bus_nr, 4); |
| 185 | } |
| 186 | } |
| 187 | |
| 188 | static int cxl_foreach_pxb_hb(Object *obj, void *opaque) |
| 189 | { |
| 190 | Aml *cedt = opaque; |
| 191 | |
| 192 | if (object_dynamic_cast(obj, TYPE_PXB_CXL_DEV)) { |
| 193 | cedt_build_chbs(cedt->buf, PXB_CXL_DEV(obj)); |
| 194 | } |
| 195 | |
| 196 | return 0; |
| 197 | } |
| 198 | |
| 199 | void cxl_build_cedt(GArray *table_offsets, GArray *table_data, |
| 200 | BIOSLinker *linker, const char *oem_id, |
| 201 | const char *oem_table_id, CXLState *cxl_state) |
| 202 | { |
| 203 | GSList *cfmws_list, *iter; |
| 204 | Aml *cedt; |
| 205 | AcpiTable table = { .sig = "CEDT", .rev = 1, .oem_id = oem_id, |
| 206 | .oem_table_id = oem_table_id }; |
| 207 | |
| 208 | acpi_add_table(table_offsets, table_data); |
| 209 | acpi_table_begin(&table, table_data); |
| 210 | cedt = init_aml_allocator(); |
| 211 | |
| 212 | /* reserve space for CEDT header */ |
| 213 | |
| 214 | object_child_foreach_recursive(object_get_root(), cxl_foreach_pxb_hb, cedt); |
| 215 | |
| 216 | cfmws_list = cxl_fmws_get_all_sorted(); |
| 217 | for (iter = cfmws_list; iter; iter = iter->next) { |
| 218 | cedt_build_cfmws(CXL_FMW(iter->data), cedt); |
| 219 | } |
| 220 | g_slist_free(cfmws_list); |
| 221 | |
| 222 | /* copy AML table into ACPI tables blob and patch header there */ |
| 223 | g_array_append_vals(table_data, cedt->buf->data, cedt->buf->len); |
| 224 | free_aml_allocator(); |
| 225 | |
| 226 | acpi_table_end(linker, &table); |
| 227 | } |
| 228 | |
| 229 | static Aml *__build_cxl_osc_method(void) |
| 230 | { |
| 231 | Aml *method, *if_uuid, *else_uuid, *if_arg1_not_1, *if_cxl, *if_caps_masked; |
| 232 | Aml *a_ctrl = aml_local(0); |
| 233 | Aml *a_cdw1 = aml_name("CDW1"); |
| 234 | |
| 235 | method = aml_method("_OSC", 4, AML_NOTSERIALIZED); |
| 236 | /* CDW1 is used for the return value so is present whether or not a match occurs */ |
| 237 | aml_append(method, aml_create_dword_field(aml_arg(3), aml_int(0), "CDW1")); |
| 238 | |
| 239 | /* |
| 240 | * Generate shared section between: |
| 241 | * CXL 2.0 - 9.14.2.1.4 and |
| 242 | * PCI Firmware Specification 3.0 |
| 243 | * 4.5.1. _OSC Interface for PCI Host Bridge Devices |
| 244 | * The _OSC interface for a PCI/PCI-X/PCI Express hierarchy is |
| 245 | * identified by the Universal Unique IDentifier (UUID) |
| 246 | * 33DB4D5B-1FF7-401C-9657-7441C03DD766 |
| 247 | * The _OSC interface for a CXL Host bridge is |
| 248 | * identified by the UUID 68F2D50B-C469-4D8A-BD3D-941A103FD3FC |
| 249 | * A CXL Host bridge is compatible with a PCI host bridge so |
| 250 | * for the shared section match both. |
| 251 | */ |
| 252 | if_uuid = aml_if( |
| 253 | aml_lor(aml_equal(aml_arg(0), |
| 254 | aml_touuid("33DB4D5B-1FF7-401C-9657-7441C03DD766")), |
| 255 | aml_equal(aml_arg(0), |
| 256 | aml_touuid("68F2D50B-C469-4D8A-BD3D-941A103FD3FC")))); |
| 257 | aml_append(if_uuid, aml_create_dword_field(aml_arg(3), aml_int(4), "CDW2")); |
| 258 | aml_append(if_uuid, aml_create_dword_field(aml_arg(3), aml_int(8), "CDW3")); |
| 259 | |
| 260 | aml_append(if_uuid, aml_store(aml_name("CDW3"), a_ctrl)); |
| 261 | |
| 262 | /* |
| 263 | * |
| 264 | * Allows OS control for all 5 features: |
| 265 | * PCIeHotplug SHPCHotplug PME AER PCIeCapability |
| 266 | */ |
| 267 | aml_append(if_uuid, aml_and(a_ctrl, aml_int(0x1F), a_ctrl)); |
| 268 | |
| 269 | /* |
| 270 | * Check _OSC revision. |
| 271 | * PCI Firmware specification 3.3 and CXL 2.0 both use revision 1 |
| 272 | * Unknown Revision is CDW1 - BIT (3) |
| 273 | */ |
| 274 | if_arg1_not_1 = aml_if(aml_lnot(aml_equal(aml_arg(1), aml_int(0x1)))); |
| 275 | aml_append(if_arg1_not_1, aml_or(a_cdw1, aml_int(0x08), a_cdw1)); |
| 276 | aml_append(if_uuid, if_arg1_not_1); |
| 277 | |
| 278 | if_caps_masked = aml_if(aml_lnot(aml_equal(aml_name("CDW3"), a_ctrl))); |
| 279 | |
| 280 | /* Capability bits were masked */ |
| 281 | aml_append(if_caps_masked, aml_or(a_cdw1, aml_int(0x10), a_cdw1)); |
| 282 | aml_append(if_uuid, if_caps_masked); |
| 283 | |
| 284 | aml_append(if_uuid, aml_store(aml_name("CDW2"), aml_name("SUPP"))); |
| 285 | aml_append(if_uuid, aml_store(aml_name("CDW3"), aml_name("CTRL"))); |
| 286 | |
| 287 | /* Update DWORD3 (the return value) */ |
| 288 | aml_append(if_uuid, aml_store(a_ctrl, aml_name("CDW3"))); |
| 289 | |
| 290 | /* CXL only section as per CXL 2.0 - 9.14.2.1.4 */ |
| 291 | if_cxl = aml_if(aml_equal( |
| 292 | aml_arg(0), aml_touuid("68F2D50B-C469-4D8A-BD3D-941A103FD3FC"))); |
| 293 | /* CXL support field */ |
| 294 | aml_append(if_cxl, aml_create_dword_field(aml_arg(3), aml_int(12), "CDW4")); |
| 295 | /* CXL capabilities */ |
| 296 | aml_append(if_cxl, aml_create_dword_field(aml_arg(3), aml_int(16), "CDW5")); |
| 297 | aml_append(if_cxl, aml_store(aml_name("CDW4"), aml_name("SUPC"))); |
| 298 | aml_append(if_cxl, aml_store(aml_name("CDW5"), aml_name("CTRC"))); |
| 299 | |
| 300 | /* CXL 2.0 Port/Device Register access */ |
| 301 | aml_append(if_cxl, |
| 302 | aml_or(aml_name("CDW5"), aml_int(0x1), aml_name("CDW5"))); |
| 303 | aml_append(if_uuid, if_cxl); |
| 304 | |
| 305 | aml_append(if_uuid, aml_return(aml_arg(3))); |
| 306 | aml_append(method, if_uuid); |
| 307 | |
| 308 | /* |
| 309 | * If no UUID matched, return Unrecognized UUID via Arg3 DWord 1 |
| 310 | * ACPI 6.4 - 6.2.11 |
| 311 | * Unrecognised UUID - BIT(2) |
| 312 | */ |
| 313 | else_uuid = aml_else(); |
| 314 | |
| 315 | aml_append(else_uuid, |
| 316 | aml_or(aml_name("CDW1"), aml_int(0x4), aml_name("CDW1"))); |
| 317 | aml_append(else_uuid, aml_return(aml_arg(3))); |
| 318 | aml_append(method, else_uuid); |
| 319 | |
| 320 | return method; |
| 321 | } |
| 322 | |
| 323 | void build_cxl_osc_method(Aml *dev) |
| 324 | { |
| 325 | aml_append(dev, aml_name_decl("SUPP", aml_int(0))); |
| 326 | aml_append(dev, aml_name_decl("CTRL", aml_int(0))); |
| 327 | aml_append(dev, aml_name_decl("SUPC", aml_int(0))); |
| 328 | aml_append(dev, aml_name_decl("CTRC", aml_int(0))); |
| 329 | aml_append(dev, __build_cxl_osc_method()); |
| 330 | } |