| /* |
| * QEMU sPAPR PCI for NVLink2 pass through |
| * |
| * Copyright (c) 2019 Alexey Kardashevskiy, IBM Corporation. |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a copy |
| * of this software and associated documentation files (the "Software"), to deal |
| * in the Software without restriction, including without limitation the rights |
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| * copies of the Software, and to permit persons to whom the Software is |
| * furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be included in |
| * all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| * THE SOFTWARE. |
| */ |
| #include "qemu/osdep.h" |
| #include "qapi/error.h" |
| #include "qemu-common.h" |
| #include "hw/pci/pci.h" |
| #include "hw/pci-host/spapr.h" |
| #include "hw/ppc/spapr_numa.h" |
| #include "qemu/error-report.h" |
| #include "hw/ppc/fdt.h" |
| #include "hw/pci/pci_bridge.h" |
| |
| #define PHANDLE_PCIDEV(phb, pdev) (0x12000000 | \ |
| (((phb)->index) << 16) | ((pdev)->devfn)) |
| #define PHANDLE_GPURAM(phb, n) (0x110000FF | ((n) << 8) | \ |
| (((phb)->index) << 16)) |
| #define PHANDLE_NVLINK(phb, gn, nn) (0x00130000 | (((phb)->index) << 8) | \ |
| ((gn) << 4) | (nn)) |
| |
| typedef struct SpaprPhbPciNvGpuSlot { |
| uint64_t tgt; |
| uint64_t gpa; |
| unsigned numa_id; |
| PCIDevice *gpdev; |
| int linknum; |
| struct { |
| uint64_t atsd_gpa; |
| PCIDevice *npdev; |
| uint32_t link_speed; |
| } links[NVGPU_MAX_LINKS]; |
| } SpaprPhbPciNvGpuSlot; |
| |
| struct SpaprPhbPciNvGpuConfig { |
| uint64_t nv2_ram_current; |
| uint64_t nv2_atsd_current; |
| int num; /* number of non empty (i.e. tgt!=0) entries in slots[] */ |
| SpaprPhbPciNvGpuSlot slots[NVGPU_MAX_NUM]; |
| Error *err; |
| }; |
| |
| static SpaprPhbPciNvGpuSlot * |
| spapr_nvgpu_get_slot(SpaprPhbPciNvGpuConfig *nvgpus, uint64_t tgt) |
| { |
| int i; |
| |
| /* Search for partially collected "slot" */ |
| for (i = 0; i < nvgpus->num; ++i) { |
| if (nvgpus->slots[i].tgt == tgt) { |
| return &nvgpus->slots[i]; |
| } |
| } |
| |
| if (nvgpus->num == ARRAY_SIZE(nvgpus->slots)) { |
| return NULL; |
| } |
| |
| i = nvgpus->num; |
| nvgpus->slots[i].tgt = tgt; |
| ++nvgpus->num; |
| |
| return &nvgpus->slots[i]; |
| } |
| |
| static void spapr_pci_collect_nvgpu(SpaprPhbPciNvGpuConfig *nvgpus, |
| PCIDevice *pdev, uint64_t tgt, |
| MemoryRegion *mr, Error **errp) |
| { |
| MachineState *machine = MACHINE(qdev_get_machine()); |
| SpaprMachineState *spapr = SPAPR_MACHINE(machine); |
| SpaprPhbPciNvGpuSlot *nvslot = spapr_nvgpu_get_slot(nvgpus, tgt); |
| |
| if (!nvslot) { |
| error_setg(errp, "Found too many GPUs per vPHB"); |
| return; |
| } |
| g_assert(!nvslot->gpdev); |
| nvslot->gpdev = pdev; |
| |
| nvslot->gpa = nvgpus->nv2_ram_current; |
| nvgpus->nv2_ram_current += memory_region_size(mr); |
| nvslot->numa_id = spapr->gpu_numa_id; |
| ++spapr->gpu_numa_id; |
| } |
| |
| static void spapr_pci_collect_nvnpu(SpaprPhbPciNvGpuConfig *nvgpus, |
| PCIDevice *pdev, uint64_t tgt, |
| MemoryRegion *mr, Error **errp) |
| { |
| SpaprPhbPciNvGpuSlot *nvslot = spapr_nvgpu_get_slot(nvgpus, tgt); |
| int j; |
| |
| if (!nvslot) { |
| error_setg(errp, "Found too many NVLink bridges per vPHB"); |
| return; |
| } |
| |
| j = nvslot->linknum; |
| if (j == ARRAY_SIZE(nvslot->links)) { |
| error_setg(errp, "Found too many NVLink bridges per GPU"); |
| return; |
| } |
| ++nvslot->linknum; |
| |
| g_assert(!nvslot->links[j].npdev); |
| nvslot->links[j].npdev = pdev; |
| nvslot->links[j].atsd_gpa = nvgpus->nv2_atsd_current; |
| nvgpus->nv2_atsd_current += memory_region_size(mr); |
| nvslot->links[j].link_speed = |
| object_property_get_uint(OBJECT(pdev), "nvlink2-link-speed", NULL); |
| } |
| |
| static void spapr_phb_pci_collect_nvgpu(PCIBus *bus, PCIDevice *pdev, |
| void *opaque) |
| { |
| PCIBus *sec_bus; |
| Object *po = OBJECT(pdev); |
| uint64_t tgt = object_property_get_uint(po, "nvlink2-tgt", NULL); |
| |
| if (tgt) { |
| Error *local_err = NULL; |
| SpaprPhbPciNvGpuConfig *nvgpus = opaque; |
| Object *mr_gpu = object_property_get_link(po, "nvlink2-mr[0]", NULL); |
| Object *mr_npu = object_property_get_link(po, "nvlink2-atsd-mr[0]", |
| NULL); |
| |
| g_assert(mr_gpu || mr_npu); |
| if (mr_gpu) { |
| spapr_pci_collect_nvgpu(nvgpus, pdev, tgt, MEMORY_REGION(mr_gpu), |
| &local_err); |
| } else { |
| spapr_pci_collect_nvnpu(nvgpus, pdev, tgt, MEMORY_REGION(mr_npu), |
| &local_err); |
| } |
| error_propagate(&nvgpus->err, local_err); |
| } |
| if ((pci_default_read_config(pdev, PCI_HEADER_TYPE, 1) != |
| PCI_HEADER_TYPE_BRIDGE)) { |
| return; |
| } |
| |
| sec_bus = pci_bridge_get_sec_bus(PCI_BRIDGE(pdev)); |
| if (!sec_bus) { |
| return; |
| } |
| |
| pci_for_each_device(sec_bus, pci_bus_num(sec_bus), |
| spapr_phb_pci_collect_nvgpu, opaque); |
| } |
| |
| void spapr_phb_nvgpu_setup(SpaprPhbState *sphb, Error **errp) |
| { |
| int i, j, valid_gpu_num; |
| PCIBus *bus; |
| |
| /* Search for GPUs and NPUs */ |
| if (!sphb->nv2_gpa_win_addr || !sphb->nv2_atsd_win_addr) { |
| return; |
| } |
| |
| sphb->nvgpus = g_new0(SpaprPhbPciNvGpuConfig, 1); |
| sphb->nvgpus->nv2_ram_current = sphb->nv2_gpa_win_addr; |
| sphb->nvgpus->nv2_atsd_current = sphb->nv2_atsd_win_addr; |
| |
| bus = PCI_HOST_BRIDGE(sphb)->bus; |
| pci_for_each_device(bus, pci_bus_num(bus), |
| spapr_phb_pci_collect_nvgpu, sphb->nvgpus); |
| |
| if (sphb->nvgpus->err) { |
| error_propagate(errp, sphb->nvgpus->err); |
| sphb->nvgpus->err = NULL; |
| goto cleanup_exit; |
| } |
| |
| /* Add found GPU RAM and ATSD MRs if found */ |
| for (i = 0, valid_gpu_num = 0; i < sphb->nvgpus->num; ++i) { |
| Object *nvmrobj; |
| SpaprPhbPciNvGpuSlot *nvslot = &sphb->nvgpus->slots[i]; |
| |
| if (!nvslot->gpdev) { |
| continue; |
| } |
| nvmrobj = object_property_get_link(OBJECT(nvslot->gpdev), |
| "nvlink2-mr[0]", NULL); |
| /* ATSD is pointless without GPU RAM MR so skip those */ |
| if (!nvmrobj) { |
| continue; |
| } |
| |
| ++valid_gpu_num; |
| memory_region_add_subregion(get_system_memory(), nvslot->gpa, |
| MEMORY_REGION(nvmrobj)); |
| |
| for (j = 0; j < nvslot->linknum; ++j) { |
| Object *atsdmrobj; |
| |
| atsdmrobj = object_property_get_link(OBJECT(nvslot->links[j].npdev), |
| "nvlink2-atsd-mr[0]", NULL); |
| if (!atsdmrobj) { |
| continue; |
| } |
| memory_region_add_subregion(get_system_memory(), |
| nvslot->links[j].atsd_gpa, |
| MEMORY_REGION(atsdmrobj)); |
| } |
| } |
| |
| if (valid_gpu_num) { |
| return; |
| } |
| /* We did not find any interesting GPU */ |
| cleanup_exit: |
| g_free(sphb->nvgpus); |
| sphb->nvgpus = NULL; |
| } |
| |
| void spapr_phb_nvgpu_free(SpaprPhbState *sphb) |
| { |
| int i, j; |
| |
| if (!sphb->nvgpus) { |
| return; |
| } |
| |
| for (i = 0; i < sphb->nvgpus->num; ++i) { |
| SpaprPhbPciNvGpuSlot *nvslot = &sphb->nvgpus->slots[i]; |
| Object *nv_mrobj = object_property_get_link(OBJECT(nvslot->gpdev), |
| "nvlink2-mr[0]", NULL); |
| |
| if (nv_mrobj) { |
| memory_region_del_subregion(get_system_memory(), |
| MEMORY_REGION(nv_mrobj)); |
| } |
| for (j = 0; j < nvslot->linknum; ++j) { |
| PCIDevice *npdev = nvslot->links[j].npdev; |
| Object *atsd_mrobj; |
| atsd_mrobj = object_property_get_link(OBJECT(npdev), |
| "nvlink2-atsd-mr[0]", NULL); |
| if (atsd_mrobj) { |
| memory_region_del_subregion(get_system_memory(), |
| MEMORY_REGION(atsd_mrobj)); |
| } |
| } |
| } |
| g_free(sphb->nvgpus); |
| sphb->nvgpus = NULL; |
| } |
| |
| void spapr_phb_nvgpu_populate_dt(SpaprPhbState *sphb, void *fdt, int bus_off, |
| Error **errp) |
| { |
| int i, j, atsdnum = 0; |
| uint64_t atsd[8]; /* The existing limitation of known guests */ |
| |
| if (!sphb->nvgpus) { |
| return; |
| } |
| |
| for (i = 0; (i < sphb->nvgpus->num) && (atsdnum < ARRAY_SIZE(atsd)); ++i) { |
| SpaprPhbPciNvGpuSlot *nvslot = &sphb->nvgpus->slots[i]; |
| |
| if (!nvslot->gpdev) { |
| continue; |
| } |
| for (j = 0; j < nvslot->linknum; ++j) { |
| if (!nvslot->links[j].atsd_gpa) { |
| continue; |
| } |
| |
| if (atsdnum == ARRAY_SIZE(atsd)) { |
| error_report("Only %"PRIuPTR" ATSD registers supported", |
| ARRAY_SIZE(atsd)); |
| break; |
| } |
| atsd[atsdnum] = cpu_to_be64(nvslot->links[j].atsd_gpa); |
| ++atsdnum; |
| } |
| } |
| |
| if (!atsdnum) { |
| error_setg(errp, "No ATSD registers found"); |
| return; |
| } |
| |
| if (!spapr_phb_eeh_available(sphb)) { |
| /* |
| * ibm,mmio-atsd contains ATSD registers; these belong to an NPU PHB |
| * which we do not emulate as a separate device. Instead we put |
| * ibm,mmio-atsd to the vPHB with GPU and make sure that we do not |
| * put GPUs from different IOMMU groups to the same vPHB to ensure |
| * that the guest will use ATSDs from the corresponding NPU. |
| */ |
| error_setg(errp, "ATSD requires separate vPHB per GPU IOMMU group"); |
| return; |
| } |
| |
| _FDT((fdt_setprop(fdt, bus_off, "ibm,mmio-atsd", atsd, |
| atsdnum * sizeof(atsd[0])))); |
| } |
| |
| void spapr_phb_nvgpu_ram_populate_dt(SpaprPhbState *sphb, void *fdt) |
| { |
| int i, j, linkidx, npuoff; |
| char *npuname; |
| |
| if (!sphb->nvgpus) { |
| return; |
| } |
| |
| npuname = g_strdup_printf("npuphb%d", sphb->index); |
| npuoff = fdt_add_subnode(fdt, 0, npuname); |
| _FDT(npuoff); |
| _FDT(fdt_setprop_cell(fdt, npuoff, "#address-cells", 1)); |
| _FDT(fdt_setprop_cell(fdt, npuoff, "#size-cells", 0)); |
| /* Advertise NPU as POWER9 so the guest can enable NPU2 contexts */ |
| _FDT((fdt_setprop_string(fdt, npuoff, "compatible", "ibm,power9-npu"))); |
| g_free(npuname); |
| |
| for (i = 0, linkidx = 0; i < sphb->nvgpus->num; ++i) { |
| for (j = 0; j < sphb->nvgpus->slots[i].linknum; ++j) { |
| char *linkname = g_strdup_printf("link@%d", linkidx); |
| int off = fdt_add_subnode(fdt, npuoff, linkname); |
| |
| _FDT(off); |
| /* _FDT((fdt_setprop_cell(fdt, off, "reg", linkidx))); */ |
| _FDT((fdt_setprop_string(fdt, off, "compatible", |
| "ibm,npu-link"))); |
| _FDT((fdt_setprop_cell(fdt, off, "phandle", |
| PHANDLE_NVLINK(sphb, i, j)))); |
| _FDT((fdt_setprop_cell(fdt, off, "ibm,npu-link-index", linkidx))); |
| g_free(linkname); |
| ++linkidx; |
| } |
| } |
| |
| /* Add memory nodes for GPU RAM and mark them unusable */ |
| for (i = 0; i < sphb->nvgpus->num; ++i) { |
| SpaprPhbPciNvGpuSlot *nvslot = &sphb->nvgpus->slots[i]; |
| Object *nv_mrobj = object_property_get_link(OBJECT(nvslot->gpdev), |
| "nvlink2-mr[0]", |
| &error_abort); |
| uint64_t size = object_property_get_uint(nv_mrobj, "size", NULL); |
| uint64_t mem_reg[2] = { cpu_to_be64(nvslot->gpa), cpu_to_be64(size) }; |
| char *mem_name = g_strdup_printf("memory@%"PRIx64, nvslot->gpa); |
| int off = fdt_add_subnode(fdt, 0, mem_name); |
| |
| _FDT(off); |
| _FDT((fdt_setprop_string(fdt, off, "device_type", "memory"))); |
| _FDT((fdt_setprop(fdt, off, "reg", mem_reg, sizeof(mem_reg)))); |
| |
| spapr_numa_write_associativity_dt(SPAPR_MACHINE(qdev_get_machine()), |
| fdt, off, nvslot->numa_id); |
| |
| _FDT((fdt_setprop_string(fdt, off, "compatible", |
| "ibm,coherent-device-memory"))); |
| |
| mem_reg[1] = cpu_to_be64(0); |
| _FDT((fdt_setprop(fdt, off, "linux,usable-memory", mem_reg, |
| sizeof(mem_reg)))); |
| _FDT((fdt_setprop_cell(fdt, off, "phandle", |
| PHANDLE_GPURAM(sphb, i)))); |
| g_free(mem_name); |
| } |
| |
| } |
| |
| void spapr_phb_nvgpu_populate_pcidev_dt(PCIDevice *dev, void *fdt, int offset, |
| SpaprPhbState *sphb) |
| { |
| int i, j; |
| |
| if (!sphb->nvgpus) { |
| return; |
| } |
| |
| for (i = 0; i < sphb->nvgpus->num; ++i) { |
| SpaprPhbPciNvGpuSlot *nvslot = &sphb->nvgpus->slots[i]; |
| |
| /* Skip "slot" without attached GPU */ |
| if (!nvslot->gpdev) { |
| continue; |
| } |
| if (dev == nvslot->gpdev) { |
| uint32_t npus[nvslot->linknum]; |
| |
| for (j = 0; j < nvslot->linknum; ++j) { |
| PCIDevice *npdev = nvslot->links[j].npdev; |
| |
| npus[j] = cpu_to_be32(PHANDLE_PCIDEV(sphb, npdev)); |
| } |
| _FDT(fdt_setprop(fdt, offset, "ibm,npu", npus, |
| j * sizeof(npus[0]))); |
| _FDT((fdt_setprop_cell(fdt, offset, "phandle", |
| PHANDLE_PCIDEV(sphb, dev)))); |
| continue; |
| } |
| |
| for (j = 0; j < nvslot->linknum; ++j) { |
| if (dev != nvslot->links[j].npdev) { |
| continue; |
| } |
| |
| _FDT((fdt_setprop_cell(fdt, offset, "phandle", |
| PHANDLE_PCIDEV(sphb, dev)))); |
| _FDT(fdt_setprop_cell(fdt, offset, "ibm,gpu", |
| PHANDLE_PCIDEV(sphb, nvslot->gpdev))); |
| _FDT((fdt_setprop_cell(fdt, offset, "ibm,nvlink", |
| PHANDLE_NVLINK(sphb, i, j)))); |
| /* |
| * If we ever want to emulate GPU RAM at the same location as on |
| * the host - here is the encoding GPA->TGT: |
| * |
| * gta = ((sphb->nv2_gpa >> 42) & 0x1) << 42; |
| * gta |= ((sphb->nv2_gpa >> 45) & 0x3) << 43; |
| * gta |= ((sphb->nv2_gpa >> 49) & 0x3) << 45; |
| * gta |= sphb->nv2_gpa & ((1UL << 43) - 1); |
| */ |
| _FDT(fdt_setprop_cell(fdt, offset, "memory-region", |
| PHANDLE_GPURAM(sphb, i))); |
| _FDT(fdt_setprop_u64(fdt, offset, "ibm,device-tgt-addr", |
| nvslot->tgt)); |
| _FDT(fdt_setprop_cell(fdt, offset, "ibm,nvlink-speed", |
| nvslot->links[j].link_speed)); |
| } |
| } |
| } |