631 lines
16 KiB
C
631 lines
16 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* This file implements the DMA operations for NVLink devices. The NPU
|
|
* devices all point to the same iommu table as the parent PCI device.
|
|
*
|
|
* Copyright Alistair Popple, IBM Corporation 2015.
|
|
*/
|
|
|
|
#include <linux/mmu_notifier.h>
|
|
#include <linux/mmu_context.h>
|
|
#include <linux/of.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/memblock.h>
|
|
#include <linux/sizes.h>
|
|
|
|
#include <asm/debugfs.h>
|
|
#include <asm/powernv.h>
|
|
#include <asm/opal.h>
|
|
|
|
#include "pci.h"
|
|
|
|
static struct pci_dev *get_pci_dev(struct device_node *dn)
|
|
{
|
|
struct pci_dn *pdn = PCI_DN(dn);
|
|
struct pci_dev *pdev;
|
|
|
|
pdev = pci_get_domain_bus_and_slot(pci_domain_nr(pdn->phb->bus),
|
|
pdn->busno, pdn->devfn);
|
|
|
|
/*
|
|
* pci_get_domain_bus_and_slot() increased the reference count of
|
|
* the PCI device, but callers don't need that actually as the PE
|
|
* already holds a reference to the device. Since callers aren't
|
|
* aware of the reference count change, call pci_dev_put() now to
|
|
* avoid leaks.
|
|
*/
|
|
if (pdev)
|
|
pci_dev_put(pdev);
|
|
|
|
return pdev;
|
|
}
|
|
|
|
/* Given a NPU device get the associated PCI device. */
|
|
struct pci_dev *pnv_pci_get_gpu_dev(struct pci_dev *npdev)
|
|
{
|
|
struct device_node *dn;
|
|
struct pci_dev *gpdev;
|
|
|
|
if (WARN_ON(!npdev))
|
|
return NULL;
|
|
|
|
if (WARN_ON(!npdev->dev.of_node))
|
|
return NULL;
|
|
|
|
/* Get assoicated PCI device */
|
|
dn = of_parse_phandle(npdev->dev.of_node, "ibm,gpu", 0);
|
|
if (!dn)
|
|
return NULL;
|
|
|
|
gpdev = get_pci_dev(dn);
|
|
of_node_put(dn);
|
|
|
|
return gpdev;
|
|
}
|
|
EXPORT_SYMBOL(pnv_pci_get_gpu_dev);
|
|
|
|
/* Given the real PCI device get a linked NPU device. */
|
|
struct pci_dev *pnv_pci_get_npu_dev(struct pci_dev *gpdev, int index)
|
|
{
|
|
struct device_node *dn;
|
|
struct pci_dev *npdev;
|
|
|
|
if (WARN_ON(!gpdev))
|
|
return NULL;
|
|
|
|
/* Not all PCI devices have device-tree nodes */
|
|
if (!gpdev->dev.of_node)
|
|
return NULL;
|
|
|
|
/* Get assoicated PCI device */
|
|
dn = of_parse_phandle(gpdev->dev.of_node, "ibm,npu", index);
|
|
if (!dn)
|
|
return NULL;
|
|
|
|
npdev = get_pci_dev(dn);
|
|
of_node_put(dn);
|
|
|
|
return npdev;
|
|
}
|
|
EXPORT_SYMBOL(pnv_pci_get_npu_dev);
|
|
|
|
#ifdef CONFIG_IOMMU_API
|
|
/*
|
|
* Returns the PE assoicated with the PCI device of the given
|
|
* NPU. Returns the linked pci device if pci_dev != NULL.
|
|
*/
|
|
static struct pnv_ioda_pe *get_gpu_pci_dev_and_pe(struct pnv_ioda_pe *npe,
|
|
struct pci_dev **gpdev)
|
|
{
|
|
struct pnv_phb *phb;
|
|
struct pci_controller *hose;
|
|
struct pci_dev *pdev;
|
|
struct pnv_ioda_pe *pe;
|
|
struct pci_dn *pdn;
|
|
|
|
pdev = pnv_pci_get_gpu_dev(npe->pdev);
|
|
if (!pdev)
|
|
return NULL;
|
|
|
|
pdn = pci_get_pdn(pdev);
|
|
if (WARN_ON(!pdn || pdn->pe_number == IODA_INVALID_PE))
|
|
return NULL;
|
|
|
|
hose = pci_bus_to_host(pdev->bus);
|
|
phb = hose->private_data;
|
|
pe = &phb->ioda.pe_array[pdn->pe_number];
|
|
|
|
if (gpdev)
|
|
*gpdev = pdev;
|
|
|
|
return pe;
|
|
}
|
|
|
|
static long pnv_npu_unset_window(struct iommu_table_group *table_group,
|
|
int num);
|
|
|
|
static long pnv_npu_set_window(struct iommu_table_group *table_group, int num,
|
|
struct iommu_table *tbl)
|
|
{
|
|
struct pnv_ioda_pe *npe = container_of(table_group, struct pnv_ioda_pe,
|
|
table_group);
|
|
struct pnv_phb *phb = npe->phb;
|
|
int64_t rc;
|
|
const unsigned long size = tbl->it_indirect_levels ?
|
|
tbl->it_level_size : tbl->it_size;
|
|
const __u64 start_addr = tbl->it_offset << tbl->it_page_shift;
|
|
const __u64 win_size = tbl->it_size << tbl->it_page_shift;
|
|
int num2 = (num == 0) ? 1 : 0;
|
|
|
|
/* NPU has just one TVE so if there is another table, remove it first */
|
|
if (npe->table_group.tables[num2])
|
|
pnv_npu_unset_window(&npe->table_group, num2);
|
|
|
|
pe_info(npe, "Setting up window %llx..%llx pg=%lx\n",
|
|
start_addr, start_addr + win_size - 1,
|
|
IOMMU_PAGE_SIZE(tbl));
|
|
|
|
rc = opal_pci_map_pe_dma_window(phb->opal_id,
|
|
npe->pe_number,
|
|
npe->pe_number,
|
|
tbl->it_indirect_levels + 1,
|
|
__pa(tbl->it_base),
|
|
size << 3,
|
|
IOMMU_PAGE_SIZE(tbl));
|
|
if (rc) {
|
|
pe_err(npe, "Failed to configure TCE table, err %lld\n", rc);
|
|
return rc;
|
|
}
|
|
pnv_pci_ioda2_tce_invalidate_entire(phb, false);
|
|
|
|
/* Add the table to the list so its TCE cache will get invalidated */
|
|
pnv_pci_link_table_and_group(phb->hose->node, num,
|
|
tbl, &npe->table_group);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static long pnv_npu_unset_window(struct iommu_table_group *table_group, int num)
|
|
{
|
|
struct pnv_ioda_pe *npe = container_of(table_group, struct pnv_ioda_pe,
|
|
table_group);
|
|
struct pnv_phb *phb = npe->phb;
|
|
int64_t rc;
|
|
|
|
if (!npe->table_group.tables[num])
|
|
return 0;
|
|
|
|
pe_info(npe, "Removing DMA window\n");
|
|
|
|
rc = opal_pci_map_pe_dma_window(phb->opal_id, npe->pe_number,
|
|
npe->pe_number,
|
|
0/* levels */, 0/* table address */,
|
|
0/* table size */, 0/* page size */);
|
|
if (rc) {
|
|
pe_err(npe, "Unmapping failed, ret = %lld\n", rc);
|
|
return rc;
|
|
}
|
|
pnv_pci_ioda2_tce_invalidate_entire(phb, false);
|
|
|
|
pnv_pci_unlink_table_and_group(npe->table_group.tables[num],
|
|
&npe->table_group);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Switch ownership from platform code to external user (e.g. VFIO) */
|
|
static void pnv_npu_take_ownership(struct iommu_table_group *table_group)
|
|
{
|
|
struct pnv_ioda_pe *npe = container_of(table_group, struct pnv_ioda_pe,
|
|
table_group);
|
|
struct pnv_phb *phb = npe->phb;
|
|
int64_t rc;
|
|
struct pci_dev *gpdev = NULL;
|
|
|
|
/*
|
|
* Note: NPU has just a single TVE in the hardware which means that
|
|
* while used by the kernel, it can have either 32bit window or
|
|
* DMA bypass but never both. So we deconfigure 32bit window only
|
|
* if it was enabled at the moment of ownership change.
|
|
*/
|
|
if (npe->table_group.tables[0]) {
|
|
pnv_npu_unset_window(&npe->table_group, 0);
|
|
return;
|
|
}
|
|
|
|
/* Disable bypass */
|
|
rc = opal_pci_map_pe_dma_window_real(phb->opal_id,
|
|
npe->pe_number, npe->pe_number,
|
|
0 /* bypass base */, 0);
|
|
if (rc) {
|
|
pe_err(npe, "Failed to disable bypass, err %lld\n", rc);
|
|
return;
|
|
}
|
|
pnv_pci_ioda2_tce_invalidate_entire(npe->phb, false);
|
|
|
|
get_gpu_pci_dev_and_pe(npe, &gpdev);
|
|
if (gpdev)
|
|
pnv_npu2_unmap_lpar_dev(gpdev);
|
|
}
|
|
|
|
static void pnv_npu_release_ownership(struct iommu_table_group *table_group)
|
|
{
|
|
struct pnv_ioda_pe *npe = container_of(table_group, struct pnv_ioda_pe,
|
|
table_group);
|
|
struct pci_dev *gpdev = NULL;
|
|
|
|
get_gpu_pci_dev_and_pe(npe, &gpdev);
|
|
if (gpdev)
|
|
pnv_npu2_map_lpar_dev(gpdev, 0, MSR_DR | MSR_PR | MSR_HV);
|
|
}
|
|
|
|
static struct iommu_table_group_ops pnv_pci_npu_ops = {
|
|
.set_window = pnv_npu_set_window,
|
|
.unset_window = pnv_npu_unset_window,
|
|
.take_ownership = pnv_npu_take_ownership,
|
|
.release_ownership = pnv_npu_release_ownership,
|
|
};
|
|
#endif /* !CONFIG_IOMMU_API */
|
|
|
|
/*
|
|
* NPU2 ATS
|
|
*/
|
|
/* Maximum possible number of ATSD MMIO registers per NPU */
|
|
#define NV_NMMU_ATSD_REGS 8
|
|
#define NV_NPU_MAX_PE_NUM 16
|
|
|
|
/*
|
|
* A compound NPU IOMMU group which might consist of 1 GPU + 2xNPUs (POWER8) or
|
|
* up to 3 x (GPU + 2xNPUs) (POWER9).
|
|
*/
|
|
struct npu_comp {
|
|
struct iommu_table_group table_group;
|
|
int pe_num;
|
|
struct pnv_ioda_pe *pe[NV_NPU_MAX_PE_NUM];
|
|
};
|
|
|
|
/* An NPU descriptor, valid for POWER9 only */
|
|
struct npu {
|
|
int index;
|
|
struct npu_comp npucomp;
|
|
};
|
|
|
|
#ifdef CONFIG_IOMMU_API
|
|
static long pnv_npu_peers_create_table_userspace(
|
|
struct iommu_table_group *table_group,
|
|
int num, __u32 page_shift, __u64 window_size, __u32 levels,
|
|
struct iommu_table **ptbl)
|
|
{
|
|
struct npu_comp *npucomp = container_of(table_group, struct npu_comp,
|
|
table_group);
|
|
|
|
if (!npucomp->pe_num || !npucomp->pe[0] ||
|
|
!npucomp->pe[0]->table_group.ops ||
|
|
!npucomp->pe[0]->table_group.ops->create_table)
|
|
return -EFAULT;
|
|
|
|
return npucomp->pe[0]->table_group.ops->create_table(
|
|
&npucomp->pe[0]->table_group, num, page_shift,
|
|
window_size, levels, ptbl);
|
|
}
|
|
|
|
static long pnv_npu_peers_set_window(struct iommu_table_group *table_group,
|
|
int num, struct iommu_table *tbl)
|
|
{
|
|
int i, j;
|
|
long ret = 0;
|
|
struct npu_comp *npucomp = container_of(table_group, struct npu_comp,
|
|
table_group);
|
|
|
|
for (i = 0; i < npucomp->pe_num; ++i) {
|
|
struct pnv_ioda_pe *pe = npucomp->pe[i];
|
|
|
|
if (!pe->table_group.ops->set_window)
|
|
continue;
|
|
|
|
ret = pe->table_group.ops->set_window(&pe->table_group,
|
|
num, tbl);
|
|
if (ret)
|
|
break;
|
|
}
|
|
|
|
if (ret) {
|
|
for (j = 0; j < i; ++j) {
|
|
struct pnv_ioda_pe *pe = npucomp->pe[j];
|
|
|
|
if (!pe->table_group.ops->unset_window)
|
|
continue;
|
|
|
|
ret = pe->table_group.ops->unset_window(
|
|
&pe->table_group, num);
|
|
if (ret)
|
|
break;
|
|
}
|
|
} else {
|
|
table_group->tables[num] = iommu_tce_table_get(tbl);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static long pnv_npu_peers_unset_window(struct iommu_table_group *table_group,
|
|
int num)
|
|
{
|
|
int i, j;
|
|
long ret = 0;
|
|
struct npu_comp *npucomp = container_of(table_group, struct npu_comp,
|
|
table_group);
|
|
|
|
for (i = 0; i < npucomp->pe_num; ++i) {
|
|
struct pnv_ioda_pe *pe = npucomp->pe[i];
|
|
|
|
WARN_ON(npucomp->table_group.tables[num] !=
|
|
table_group->tables[num]);
|
|
if (!npucomp->table_group.tables[num])
|
|
continue;
|
|
|
|
if (!pe->table_group.ops->unset_window)
|
|
continue;
|
|
|
|
ret = pe->table_group.ops->unset_window(&pe->table_group, num);
|
|
if (ret)
|
|
break;
|
|
}
|
|
|
|
if (ret) {
|
|
for (j = 0; j < i; ++j) {
|
|
struct pnv_ioda_pe *pe = npucomp->pe[j];
|
|
|
|
if (!npucomp->table_group.tables[num])
|
|
continue;
|
|
|
|
if (!pe->table_group.ops->set_window)
|
|
continue;
|
|
|
|
ret = pe->table_group.ops->set_window(&pe->table_group,
|
|
num, table_group->tables[num]);
|
|
if (ret)
|
|
break;
|
|
}
|
|
} else if (table_group->tables[num]) {
|
|
iommu_tce_table_put(table_group->tables[num]);
|
|
table_group->tables[num] = NULL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void pnv_npu_peers_take_ownership(struct iommu_table_group *table_group)
|
|
{
|
|
int i;
|
|
struct npu_comp *npucomp = container_of(table_group, struct npu_comp,
|
|
table_group);
|
|
|
|
for (i = 0; i < npucomp->pe_num; ++i) {
|
|
struct pnv_ioda_pe *pe = npucomp->pe[i];
|
|
|
|
if (!pe->table_group.ops->take_ownership)
|
|
continue;
|
|
pe->table_group.ops->take_ownership(&pe->table_group);
|
|
}
|
|
}
|
|
|
|
static void pnv_npu_peers_release_ownership(
|
|
struct iommu_table_group *table_group)
|
|
{
|
|
int i;
|
|
struct npu_comp *npucomp = container_of(table_group, struct npu_comp,
|
|
table_group);
|
|
|
|
for (i = 0; i < npucomp->pe_num; ++i) {
|
|
struct pnv_ioda_pe *pe = npucomp->pe[i];
|
|
|
|
if (!pe->table_group.ops->release_ownership)
|
|
continue;
|
|
pe->table_group.ops->release_ownership(&pe->table_group);
|
|
}
|
|
}
|
|
|
|
static struct iommu_table_group_ops pnv_npu_peers_ops = {
|
|
.get_table_size = pnv_pci_ioda2_get_table_size,
|
|
.create_table = pnv_npu_peers_create_table_userspace,
|
|
.set_window = pnv_npu_peers_set_window,
|
|
.unset_window = pnv_npu_peers_unset_window,
|
|
.take_ownership = pnv_npu_peers_take_ownership,
|
|
.release_ownership = pnv_npu_peers_release_ownership,
|
|
};
|
|
|
|
static void pnv_comp_attach_table_group(struct npu_comp *npucomp,
|
|
struct pnv_ioda_pe *pe)
|
|
{
|
|
if (WARN_ON(npucomp->pe_num == NV_NPU_MAX_PE_NUM))
|
|
return;
|
|
|
|
npucomp->pe[npucomp->pe_num] = pe;
|
|
++npucomp->pe_num;
|
|
}
|
|
|
|
struct iommu_table_group *pnv_try_setup_npu_table_group(struct pnv_ioda_pe *pe)
|
|
{
|
|
struct iommu_table_group *table_group;
|
|
struct npu_comp *npucomp;
|
|
struct pci_dev *gpdev = NULL;
|
|
struct pci_controller *hose;
|
|
struct pci_dev *npdev = NULL;
|
|
|
|
list_for_each_entry(gpdev, &pe->pbus->devices, bus_list) {
|
|
npdev = pnv_pci_get_npu_dev(gpdev, 0);
|
|
if (npdev)
|
|
break;
|
|
}
|
|
|
|
if (!npdev)
|
|
/* It is not an NPU attached device, skip */
|
|
return NULL;
|
|
|
|
hose = pci_bus_to_host(npdev->bus);
|
|
|
|
if (hose->npu) {
|
|
table_group = &hose->npu->npucomp.table_group;
|
|
|
|
if (!table_group->group) {
|
|
table_group->ops = &pnv_npu_peers_ops;
|
|
iommu_register_group(table_group,
|
|
hose->global_number,
|
|
pe->pe_number);
|
|
}
|
|
} else {
|
|
/* Create a group for 1 GPU and attached NPUs for POWER8 */
|
|
pe->npucomp = kzalloc(sizeof(*pe->npucomp), GFP_KERNEL);
|
|
table_group = &pe->npucomp->table_group;
|
|
table_group->ops = &pnv_npu_peers_ops;
|
|
iommu_register_group(table_group, hose->global_number,
|
|
pe->pe_number);
|
|
}
|
|
|
|
/* Steal capabilities from a GPU PE */
|
|
table_group->max_dynamic_windows_supported =
|
|
pe->table_group.max_dynamic_windows_supported;
|
|
table_group->tce32_start = pe->table_group.tce32_start;
|
|
table_group->tce32_size = pe->table_group.tce32_size;
|
|
table_group->max_levels = pe->table_group.max_levels;
|
|
if (!table_group->pgsizes)
|
|
table_group->pgsizes = pe->table_group.pgsizes;
|
|
|
|
npucomp = container_of(table_group, struct npu_comp, table_group);
|
|
pnv_comp_attach_table_group(npucomp, pe);
|
|
|
|
return table_group;
|
|
}
|
|
|
|
struct iommu_table_group *pnv_npu_compound_attach(struct pnv_ioda_pe *pe)
|
|
{
|
|
struct iommu_table_group *table_group;
|
|
struct npu_comp *npucomp;
|
|
struct pci_dev *gpdev = NULL;
|
|
struct pci_dev *npdev;
|
|
struct pnv_ioda_pe *gpe = get_gpu_pci_dev_and_pe(pe, &gpdev);
|
|
|
|
WARN_ON(!(pe->flags & PNV_IODA_PE_DEV));
|
|
if (!gpe)
|
|
return NULL;
|
|
|
|
/*
|
|
* IODA2 bridges get this set up from pci_controller_ops::setup_bridge
|
|
* but NPU bridges do not have this hook defined so we do it here.
|
|
* We do not setup other table group parameters as they won't be used
|
|
* anyway - NVLink bridges are subordinate PEs.
|
|
*/
|
|
pe->table_group.ops = &pnv_pci_npu_ops;
|
|
|
|
table_group = iommu_group_get_iommudata(
|
|
iommu_group_get(&gpdev->dev));
|
|
|
|
/*
|
|
* On P9 NPU PHB and PCI PHB support different page sizes,
|
|
* keep only matching. We expect here that NVLink bridge PE pgsizes is
|
|
* initialized by the caller.
|
|
*/
|
|
table_group->pgsizes &= pe->table_group.pgsizes;
|
|
npucomp = container_of(table_group, struct npu_comp, table_group);
|
|
pnv_comp_attach_table_group(npucomp, pe);
|
|
|
|
list_for_each_entry(npdev, &pe->phb->hose->bus->devices, bus_list) {
|
|
struct pci_dev *gpdevtmp = pnv_pci_get_gpu_dev(npdev);
|
|
|
|
if (gpdevtmp != gpdev)
|
|
continue;
|
|
|
|
iommu_add_device(table_group, &npdev->dev);
|
|
}
|
|
|
|
return table_group;
|
|
}
|
|
#endif /* CONFIG_IOMMU_API */
|
|
|
|
int pnv_npu2_init(struct pci_controller *hose)
|
|
{
|
|
static int npu_index;
|
|
struct npu *npu;
|
|
int ret;
|
|
|
|
npu = kzalloc(sizeof(*npu), GFP_KERNEL);
|
|
if (!npu)
|
|
return -ENOMEM;
|
|
|
|
npu_index++;
|
|
if (WARN_ON(npu_index >= NV_MAX_NPUS)) {
|
|
ret = -ENOSPC;
|
|
goto fail_exit;
|
|
}
|
|
npu->index = npu_index;
|
|
hose->npu = npu;
|
|
|
|
return 0;
|
|
|
|
fail_exit:
|
|
kfree(npu);
|
|
return ret;
|
|
}
|
|
|
|
int pnv_npu2_map_lpar_dev(struct pci_dev *gpdev, unsigned int lparid,
|
|
unsigned long msr)
|
|
{
|
|
int ret;
|
|
struct pci_dev *npdev = pnv_pci_get_npu_dev(gpdev, 0);
|
|
struct pci_controller *hose;
|
|
struct pnv_phb *nphb;
|
|
|
|
if (!npdev)
|
|
return -ENODEV;
|
|
|
|
hose = pci_bus_to_host(npdev->bus);
|
|
nphb = hose->private_data;
|
|
|
|
dev_dbg(&gpdev->dev, "Map LPAR opalid=%llu lparid=%u\n",
|
|
nphb->opal_id, lparid);
|
|
/*
|
|
* Currently we only support radix and non-zero LPCR only makes sense
|
|
* for hash tables so skiboot expects the LPCR parameter to be a zero.
|
|
*/
|
|
ret = opal_npu_map_lpar(nphb->opal_id, pci_dev_id(gpdev), lparid,
|
|
0 /* LPCR bits */);
|
|
if (ret) {
|
|
dev_err(&gpdev->dev, "Error %d mapping device to LPAR\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
dev_dbg(&gpdev->dev, "init context opalid=%llu msr=%lx\n",
|
|
nphb->opal_id, msr);
|
|
ret = opal_npu_init_context(nphb->opal_id, 0/*__unused*/, msr,
|
|
pci_dev_id(gpdev));
|
|
if (ret < 0)
|
|
dev_err(&gpdev->dev, "Failed to init context: %d\n", ret);
|
|
else
|
|
ret = 0;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(pnv_npu2_map_lpar_dev);
|
|
|
|
void pnv_npu2_map_lpar(struct pnv_ioda_pe *gpe, unsigned long msr)
|
|
{
|
|
struct pci_dev *gpdev;
|
|
|
|
list_for_each_entry(gpdev, &gpe->pbus->devices, bus_list)
|
|
pnv_npu2_map_lpar_dev(gpdev, 0, msr);
|
|
}
|
|
|
|
int pnv_npu2_unmap_lpar_dev(struct pci_dev *gpdev)
|
|
{
|
|
int ret;
|
|
struct pci_dev *npdev = pnv_pci_get_npu_dev(gpdev, 0);
|
|
struct pci_controller *hose;
|
|
struct pnv_phb *nphb;
|
|
|
|
if (!npdev)
|
|
return -ENODEV;
|
|
|
|
hose = pci_bus_to_host(npdev->bus);
|
|
nphb = hose->private_data;
|
|
|
|
dev_dbg(&gpdev->dev, "destroy context opalid=%llu\n",
|
|
nphb->opal_id);
|
|
ret = opal_npu_destroy_context(nphb->opal_id, 0/*__unused*/,
|
|
pci_dev_id(gpdev));
|
|
if (ret < 0) {
|
|
dev_err(&gpdev->dev, "Failed to destroy context: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Set LPID to 0 anyway, just to be safe */
|
|
dev_dbg(&gpdev->dev, "Map LPAR opalid=%llu lparid=0\n", nphb->opal_id);
|
|
ret = opal_npu_map_lpar(nphb->opal_id, pci_dev_id(gpdev), 0 /*LPID*/,
|
|
0 /* LPCR bits */);
|
|
if (ret)
|
|
dev_err(&gpdev->dev, "Error %d mapping device to LPAR\n", ret);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(pnv_npu2_unmap_lpar_dev);
|