Release 4.14 arch/powerpc/platforms/powernv/npu-dma.c
/*
* 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.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
* License as published by the Free Software Foundation.
*/
#include <linux/slab.h>
#include <linux/mmu_notifier.h>
#include <linux/mmu_context.h>
#include <linux/of.h>
#include <linux/export.h>
#include <linux/pci.h>
#include <linux/memblock.h>
#include <linux/iommu.h>
#include <asm/tlb.h>
#include <asm/powernv.h>
#include <asm/reg.h>
#include <asm/opal.h>
#include <asm/io.h>
#include <asm/iommu.h>
#include <asm/pnv-pci.h>
#include <asm/msi_bitmap.h>
#include <asm/opal.h>
#include "powernv.h"
#include "pci.h"
#define npu_to_phb(x) container_of(x, struct pnv_phb, npu)
/*
* Other types of TCE cache invalidation are not functional in the
* hardware.
*/
static struct pci_dev *get_pci_dev(struct device_node *dn)
{
return PCI_DN(dn)->pcidev;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Alistair Popple | 21 | 100.00% | 1 | 100.00% |
Total | 21 | 100.00% | 1 | 100.00% |
/* 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;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Alistair Popple | 87 | 100.00% | 2 | 100.00% |
Total | 87 | 100.00% | 2 | 100.00% |
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;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Alistair Popple | 88 | 100.00% | 3 | 100.00% |
Total | 88 | 100.00% | 3 | 100.00% |
EXPORT_SYMBOL(pnv_pci_get_npu_dev);
#define NPU_DMA_OP_UNSUPPORTED() \
dev_err_once(dev, "%s operation unsupported for NVLink devices\n", \
__func__)
static void *dma_npu_alloc(struct device *dev, size_t size,
dma_addr_t *dma_handle, gfp_t flag,
unsigned long attrs)
{
NPU_DMA_OP_UNSUPPORTED();
return NULL;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Alistair Popple | 30 | 93.75% | 1 | 50.00% |
Krzysztof Kozlowski | 2 | 6.25% | 1 | 50.00% |
Total | 32 | 100.00% | 2 | 100.00% |
static void dma_npu_free(struct device *dev, size_t size,
void *vaddr, dma_addr_t dma_handle,
unsigned long attrs)
{
NPU_DMA_OP_UNSUPPORTED();
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Alistair Popple | 26 | 92.86% | 1 | 50.00% |
Krzysztof Kozlowski | 2 | 7.14% | 1 | 50.00% |
Total | 28 | 100.00% | 2 | 100.00% |
static dma_addr_t dma_npu_map_page(struct device *dev, struct page *page,
unsigned long offset, size_t size,
enum dma_data_direction direction,
unsigned long attrs)
{
NPU_DMA_OP_UNSUPPORTED();
return 0;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Alistair Popple | 35 | 94.59% | 1 | 50.00% |
Krzysztof Kozlowski | 2 | 5.41% | 1 | 50.00% |
Total | 37 | 100.00% | 2 | 100.00% |
static int dma_npu_map_sg(struct device *dev, struct scatterlist *sglist,
int nelems, enum dma_data_direction direction,
unsigned long attrs)
{
NPU_DMA_OP_UNSUPPORTED();
return 0;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Alistair Popple | 31 | 93.94% | 1 | 50.00% |
Krzysztof Kozlowski | 2 | 6.06% | 1 | 50.00% |
Total | 33 | 100.00% | 2 | 100.00% |
static int dma_npu_dma_supported(struct device *dev, u64 mask)
{
NPU_DMA_OP_UNSUPPORTED();
return 0;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Alistair Popple | 20 | 100.00% | 1 | 100.00% |
Total | 20 | 100.00% | 1 | 100.00% |
static u64 dma_npu_get_required_mask(struct device *dev)
{
NPU_DMA_OP_UNSUPPORTED();
return 0;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Alistair Popple | 17 | 100.00% | 1 | 100.00% |
Total | 17 | 100.00% | 1 | 100.00% |
static const struct dma_map_ops dma_npu_ops = {
.map_page = dma_npu_map_page,
.map_sg = dma_npu_map_sg,
.alloc = dma_npu_alloc,
.free = dma_npu_free,
.dma_supported = dma_npu_dma_supported,
.get_required_mask = dma_npu_get_required_mask,
};
/*
* 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;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Alistair Popple | 126 | 100.00% | 1 | 100.00% |
Total | 126 | 100.00% | 1 | 100.00% |
long pnv_npu_set_window(struct pnv_ioda_pe *npe, int num,
struct iommu_table *tbl)
{
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;
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;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Alexey Kardashevskiy | 176 | 99.44% | 3 | 75.00% |
Alistair Popple | 1 | 0.56% | 1 | 25.00% |
Total | 177 | 100.00% | 4 | 100.00% |
long pnv_npu_unset_window(struct pnv_ioda_pe *npe, int num)
{
struct pnv_phb *phb = npe->phb;
int64_t rc;
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;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Alexey Kardashevskiy | 95 | 89.62% | 3 | 60.00% |
Alistair Popple | 11 | 10.38% | 2 | 40.00% |
Total | 106 | 100.00% | 5 | 100.00% |
/*
* Enables 32 bit DMA on NPU.
*/
static void pnv_npu_dma_set_32(struct pnv_ioda_pe *npe)
{
struct pci_dev *gpdev;
struct pnv_ioda_pe *gpe;
int64_t rc;
/*
* Find the assoicated PCI devices and get the dma window
* information from there.
*/
if (!npe->pdev || !(npe->flags & PNV_IODA_PE_DEV))
return;
gpe = get_gpu_pci_dev_and_pe(npe, &gpdev);
if (!gpe)
return;
rc = pnv_npu_set_window(npe, 0, gpe->table_group.tables[0]);
/*
* We don't initialise npu_pe->tce32_table as we always use
* dma_npu_ops which are nops.
*/
set_dma_ops(&npe->pdev->dev, &dma_npu_ops);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Alistair Popple | 82 | 91.11% | 1 | 25.00% |
Alexey Kardashevskiy | 8 | 8.89% | 3 | 75.00% |
Total | 90 | 100.00% | 4 | 100.00% |
/*
* Enables bypass mode on the NPU. The NPU only supports one
* window per link, so bypass needs to be explicitly enabled or
* disabled. Unlike for a PHB3 bypass and non-bypass modes can't be
* active at the same time.
*/
static int pnv_npu_dma_set_bypass(struct pnv_ioda_pe *npe)
{
struct pnv_phb *phb = npe->phb;
int64_t rc = 0;
phys_addr_t top = memblock_end_of_DRAM();
if (phb->type != PNV_PHB_NPU || !npe->pdev)
return -EINVAL;
rc = pnv_npu_unset_window(npe, 0);
if (rc != OPAL_SUCCESS)
return rc;
/* Enable the bypass window */
top = roundup_pow_of_two(top);
dev_info(&npe->pdev->dev, "Enabling bypass for PE %x\n",
npe->pe_number);
rc = opal_pci_map_pe_dma_window_real(phb->opal_id,
npe->pe_number, npe->pe_number,
0 /* bypass base */, top);
if (rc == OPAL_SUCCESS)
pnv_pci_ioda2_tce_invalidate_entire(phb, false);
return rc;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Alistair Popple | 88 | 68.75% | 2 | 28.57% |
Alexey Kardashevskiy | 39 | 30.47% | 4 | 57.14% |
Russell Currey | 1 | 0.78% | 1 | 14.29% |
Total | 128 | 100.00% | 7 | 100.00% |
void pnv_npu_try_dma_set_bypass(struct pci_dev *gpdev, bool bypass)
{
int i;
struct pnv_phb *phb;
struct pci_dn *pdn;
struct pnv_ioda_pe *npe;
struct pci_dev *npdev;
for (i = 0; ; ++i) {
npdev = pnv_pci_get_npu_dev(gpdev, i);
if (!npdev)
break;
pdn = pci_get_pdn(npdev);
if (WARN_ON(!pdn || pdn->pe_number == IODA_INVALID_PE))
return;
phb = pci_bus_to_host(npdev->bus)->private_data;
/* We only do bypass if it's enabled on the linked device */
npe = &phb->ioda.pe_array[pdn->pe_number];
if (bypass) {
dev_info(&npdev->dev,
"Using 64-bit DMA iommu bypass\n");
pnv_npu_dma_set_bypass(npe);
} else {
dev_info(&npdev->dev, "Using 32-bit DMA via iommu\n");
pnv_npu_dma_set_32(npe);
}
}
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Alistair Popple | 86 | 57.33% | 1 | 50.00% |
Alexey Kardashevskiy | 64 | 42.67% | 1 | 50.00% |
Total | 150 | 100.00% | 2 | 100.00% |
/* Switch ownership from platform code to external user (e.g. VFIO) */
void pnv_npu_take_ownership(struct pnv_ioda_pe *npe)
{
struct pnv_phb *phb = npe->phb;
int64_t rc;
/*
* 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, 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);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Alexey Kardashevskiy | 91 | 98.91% | 1 | 50.00% |
Alistair Popple | 1 | 1.09% | 1 | 50.00% |
Total | 92 | 100.00% | 2 | 100.00% |
struct pnv_ioda_pe *pnv_pci_npu_setup_iommu(struct pnv_ioda_pe *npe)
{
struct pnv_phb *phb = npe->phb;
struct pci_bus *pbus = phb->hose->bus;
struct pci_dev *npdev, *gpdev = NULL, *gptmp;
struct pnv_ioda_pe *gpe = get_gpu_pci_dev_and_pe(npe, &gpdev);
if (!gpe || !gpdev)
return NULL;
list_for_each_entry(npdev, &pbus->devices, bus_list) {
gptmp = pnv_pci_get_gpu_dev(npdev);
if (gptmp != gpdev)
continue;
pe_info(gpe, "Attached NPU %s\n", dev_name(&npdev->dev));
iommu_group_add_device(gpe->table_group.group, &npdev->dev);
}
return gpe;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Alexey Kardashevskiy | 125 | 100.00% | 1 | 100.00% |
Total | 125 | 100.00% | 1 | 100.00% |
/* Maximum number of nvlinks per npu */
#define NV_MAX_LINKS 6
/* Maximum index of npu2 hosts in the system. Always < NV_MAX_NPUS */
static int max_npu2_index;
struct npu_context {
struct mm_struct *mm;
struct pci_dev *npdev[NV_MAX_NPUS][NV_MAX_LINKS];
struct mmu_notifier mn;
struct kref kref;
/* Callback to stop translation requests on a given GPU */
struct npu_context *(*release_cb)(struct npu_context *, void *);
/*
* Private pointer passed to the above callback for usage by
* device drivers.
*/
void *priv;
};
/*
* Find a free MMIO ATSD register and mark it in use. Return -ENOSPC
* if none are available.
*/
static int get_mmio_atsd_reg(struct npu *npu)
{
int i;
for (i = 0; i < npu->mmio_atsd_count; i++) {
if (!test_and_set_bit(i, &npu->mmio_atsd_usage))
return i;
}
return -ENOSPC;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Alistair Popple | 51 | 100.00% | 1 | 100.00% |
Total | 51 | 100.00% | 1 | 100.00% |
static void put_mmio_atsd_reg(struct npu *npu, int reg)
{
clear_bit(reg, &npu->mmio_atsd_usage);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Alistair Popple | 24 | 100.00% | 1 | 100.00% |
Total | 24 | 100.00% | 1 | 100.00% |
/* MMIO ATSD register offsets */
#define XTS_ATSD_AVA 1
#define XTS_ATSD_STAT 2
static int mmio_launch_invalidate(struct npu *npu, unsigned long launch,
unsigned long va)
{
int mmio_atsd_reg;
do {
mmio_atsd_reg = get_mmio_atsd_reg(npu);
cpu_relax();
} while (mmio_atsd_reg < 0);
__raw_writeq(cpu_to_be64(va),
npu->mmio_atsd_regs[mmio_atsd_reg] + XTS_ATSD_AVA);
eieio();
__raw_writeq(cpu_to_be64(launch), npu->mmio_atsd_regs[mmio_atsd_reg]);
return mmio_atsd_reg;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Alistair Popple | 80 | 100.00% | 1 | 100.00% |
Total | 80 | 100.00% | 1 | 100.00% |
static int mmio_invalidate_pid(struct npu *npu, unsigned long pid, bool flush)
{
unsigned long launch;
/* IS set to invalidate matching PID */
launch = PPC_BIT(12);
/* PRS set to process-scoped */
launch |= PPC_BIT(13);
/* AP */
launch |= (u64) mmu_get_ap(mmu_virtual_psize) << PPC_BITLSHIFT(17);
/* PID */
launch |= pid << PPC_BITLSHIFT(38);
/* No flush */
launch |= !flush << PPC_BITLSHIFT(39);
/* Invalidating the entire process doesn't use a va */
return mmio_launch_invalidate(npu, launch, 0);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Alistair Popple | 86 | 100.00% | 2 | 100.00% |
Total | 86 | 100.00% | 2 | 100.00% |
static int mmio_invalidate_va(struct npu *npu, unsigned long va,
unsigned long pid, bool flush)
{
unsigned long launch;
/* IS set to invalidate target VA */
launch = 0;
/* PRS set to process scoped */
launch |= PPC_BIT(13);
/* AP */
launch |= (u64) mmu_get_ap(mmu_virtual_psize) << PPC_BITLSHIFT(17);
/* PID */
launch |= pid << PPC_BITLSHIFT(38);
/* No flush */
launch |= !flush << PPC_BITLSHIFT(39);
return mmio_launch_invalidate(npu, launch, va);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Alistair Popple | 86 | 100.00% | 2 | 100.00% |
Total | 86 | 100.00% | 2 | 100.00% |
#define mn_to_npu_context(x) container_of(x, struct npu_context, mn)
struct mmio_atsd_reg {
struct npu *npu;
int reg;
};
static void mmio_invalidate_wait(
struct mmio_atsd_reg mmio_atsd_reg[NV_MAX_NPUS], bool flush)
{
struct npu *npu;
int i, reg;
/* Wait for all invalidations to complete */
for (i = 0; i <= max_npu2_index; i++) {
if (mmio_atsd_reg[i].reg < 0)
continue;
/* Wait for completion */
npu = mmio_atsd_reg[i].npu;
reg = mmio_atsd_reg[i].reg;
while (__raw_readq(npu->mmio_atsd_regs[reg] + XTS_ATSD_STAT))
cpu_relax();
put_mmio_atsd_reg(npu, reg);
/*
* The GPU requires two flush ATSDs to ensure all entries have
* been flushed. We use PID 0 as it will never be used for a
* process on the GPU.
*/
if (flush)
mmio_invalidate_pid(npu, 0, true);
}
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Alistair Popple | 111 | 100.00% | 1 | 100.00% |
Total | 111 | 100.00% | 1 | 100.00% |
/*
* Invalidate either a single address or an entire PID depending on
* the value of va.
*/
static void mmio_invalidate(struct npu_context *npu_context, int va,
unsigned long address, bool flush)
{
int i, j;
struct npu *npu;
struct pnv_phb *nphb;
struct pci_dev *npdev;
struct mmio_atsd_reg mmio_atsd_reg[NV_MAX_NPUS];
unsigned long pid = npu_context->mm->context.id;
/*
* Unfortunately the nest mmu does not support flushing specific
* addresses so we have to flush the whole mm.
*/
flush_tlb_mm(npu_context->mm);
/*
* Loop over all the NPUs this process is active on and launch
* an invalidate.
*/
for (i = 0; i <= max_npu2_index; i++) {
mmio_atsd_reg[i].reg = -1;
for (j = 0; j < NV_MAX_LINKS; j++) {
npdev = npu_context->npdev[i][j];
if (!npdev)
continue;
nphb = pci_bus_to_host(npdev->bus)->private_data;
npu = &nphb->npu;
mmio_atsd_reg[i].npu = npu;
if (va)
mmio_atsd_reg[i].reg =
mmio_invalidate_va(npu, address, pid,
flush);
else
mmio_atsd_reg[i].reg =
mmio_invalidate_pid(npu, pid, flush);
/*
* The NPU hardware forwards the shootdown to all GPUs
* so we only have to launch one shootdown per NPU.
*/
break;
}
}
mmio_invalidate_wait(mmio_atsd_reg, flush);
if (flush)
/* Wait for the flush to complete */
mmio_invalidate_wait(mmio_atsd_reg, false);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Alistair Popple | 214 | 100.00% | 3 | 100.00% |
Total | 214 | 100.00% | 3 | 100.00% |
static void pnv_npu2_mn_release(struct mmu_notifier *mn,
struct mm_struct *mm)
{
struct npu_context *npu_context = mn_to_npu_context(mn);
/* Call into device driver to stop requests to the NMMU */
if (npu_context->release_cb)
npu_context->release_cb(npu_context, npu_context->priv);
/*
* There should be no more translation requests for this PID, but we
* need to ensure any entries for it are removed from the TLB.
*/
mmio_invalidate(npu_context, 0, 0, true);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Alistair Popple | 56 | 100.00% | 2 | 100.00% |
Total | 56 | 100.00% | 2 | 100.00% |
static void pnv_npu2_mn_change_pte(struct mmu_notifier *mn,
struct mm_struct *mm,
unsigned long address,
pte_t pte)
{
struct npu_context *npu_context = mn_to_npu_context(mn);
mmio_invalidate(npu_context, 1, address, true);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Alistair Popple | 44 | 100.00% | 2 | 100.00% |
Total | 44 | 100.00% | 2 | 100.00% |
static void pnv_npu2_mn_invalidate_range(struct mmu_notifier *mn,
struct mm_struct *mm,
unsigned long start, unsigned long end)
{
struct npu_context *npu_context = mn_to_npu_context(mn);
unsigned long address;
for (address = start; address < end; address += PAGE_SIZE)
mmio_invalidate(npu_context, 1, address, false);
/* Do the flush only on the final addess == end */
mmio_invalidate(npu_context, 1, address, true);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Alistair Popple | 75 | 100.00% | 2 | 100.00% |
Total | 75 | 100.00% | 2 | 100.00% |
static const struct mmu_notifier_ops nv_nmmu_notifier_ops = {
.release = pnv_npu2_mn_release,
.change_pte = pnv_npu2_mn_change_pte,
.invalidate_range = pnv_npu2_mn_invalidate_range,
};
/*
* Call into OPAL to setup the nmmu context for the current task in
* the NPU. This must be called to setup the context tables before the
* GPU issues ATRs. pdev should be a pointed to PCIe GPU device.
*
* A release callback should be registered to allow a device driver to
* be notified that it should not launch any new translation requests
* as the final TLB invalidate is about to occur.
*
* Returns an error if there no contexts are currently available or a
* npu_context which should be passed to pnv_npu2_handle_fault().
*
* mmap_sem must be held in write mode.
*/
struct npu_context *pnv_npu2_init_context(struct pci_dev *gpdev,
unsigned long flags,
struct npu_context *(*cb)(struct npu_context *, void *),
void *priv)
{
int rc;
u32 nvlink_index;
struct device_node *nvlink_dn;
struct mm_struct *mm = current->mm;
struct pnv_phb *nphb;
struct npu *npu;
struct npu_context *npu_context;
/*
* At present we don't support GPUs connected to multiple NPUs and I'm
* not sure the hardware does either.
*/
struct pci_dev *npdev = pnv_pci_get_npu_dev(gpdev, 0);
if (!firmware_has_feature(FW_FEATURE_OPAL))
return ERR_PTR(-ENODEV);
if (!npdev)
/* No nvlink associated with this GPU device */
return ERR_PTR(-ENODEV);
if (!mm || mm->context.id == 0) {
/*
* Kernel thread contexts are not supported and context id 0 is
* reserved on the GPU.
*/
return ERR_PTR(-EINVAL);
}
nphb = pci_bus_to_host(npdev->bus)->private_data;
npu = &nphb->npu;
/*
* Setup the NPU context table for a particular GPU. These need to be
* per-GPU as we need the tables to filter ATSDs when there are no
* active contexts on a particular GPU.
*/
rc = opal_npu_init_context(nphb->opal_id, mm->context.id, flags,
PCI_DEVID(gpdev->bus->number, gpdev->devfn));
if (rc < 0)
return ERR_PTR(-ENOSPC);
/*
* We store the npu pci device so we can more easily get at the
* associated npus.
*/
npu_context = mm->context.npu_context;
if (!npu_context) {
npu_context = kzalloc(sizeof(struct npu_context), GFP_KERNEL);
if (!npu_context)
return ERR_PTR(-ENOMEM);
mm->context.npu_context = npu_context;
npu_context->mm = mm;
npu_context->mn.ops = &nv_nmmu_notifier_ops;
__mmu_notifier_register(&npu_context->mn, mm);
kref_init(&npu_context->kref);
} else {
kref_get(&npu_context->kref);
}
npu_context->release_cb = cb;
npu_context->priv = priv;
nvlink_dn = of_parse_phandle(npdev->dev.of_node, "ibm,nvlink", 0);
if (WARN_ON(of_property_read_u32(nvlink_dn, "ibm,npu-link-index",
&nvlink_index)))
return ERR_PTR(-ENODEV);
npu_context->npdev[npu->index][nvlink_index] = npdev;
return npu_context;
}
EXPORT_SYMBOL(pnv_npu2_init_context);
static void pnv_npu2_release_context(struct kref *kref)
{
struct npu_context *npu_context =
container_of(kref, struct npu_context, kref);
npu_context->mm->context.npu_context = NULL;
mmu_notifier_unregister(&npu_context->mn,
npu_context->mm);
kfree(npu_context);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Alistair Popple | 53 | 100.00% | 1 | 100.00% |
Total | 53 | 100.00% | 1 | 100.00% |
void pnv_npu2_destroy_context(struct npu_context *npu_context,
struct pci_dev *gpdev)
{
struct pnv_phb *nphb;
struct npu *npu;
struct pci_dev *npdev = pnv_pci_get_npu_dev(gpdev, 0);
struct device_node *nvlink_dn;
u32 nvlink_index;
if (WARN_ON(!npdev))
return;
if (!firmware_has_feature(FW_FEATURE_OPAL))
return;
nphb = pci_bus_to_host(npdev->bus)->private_data;
npu = &nphb->npu;
nvlink_dn = of_parse_phandle(npdev->dev.of_node, "ibm,nvlink", 0);
if (WARN_ON(of_property_read_u32(nvlink_dn, "ibm,npu-link-index",
&nvlink_index)))
return;
npu_context->npdev[npu->index][nvlink_index] = NULL;
opal_npu_destroy_context(nphb->opal_id, npu_context->mm->context.id,
PCI_DEVID(gpdev->bus->number, gpdev->devfn));
kref_put(&npu_context->kref, pnv_npu2_release_context);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Alistair Popple | 164 | 100.00% | 2 | 100.00% |
Total | 164 | 100.00% | 2 | 100.00% |
EXPORT_SYMBOL(pnv_npu2_destroy_context);
/*
* Assumes mmap_sem is held for the contexts associated mm.
*/
int pnv_npu2_handle_fault(struct npu_context *context, uintptr_t *ea,
unsigned long *flags, unsigned long *status, int count)
{
u64 rc = 0, result = 0;
int i, is_write;
struct page *page[1];
/* mmap_sem should be held so the struct_mm must be present */
struct mm_struct *mm = context->mm;
if (!firmware_has_feature(FW_FEATURE_OPAL))
return -ENODEV;
WARN_ON(!rwsem_is_locked(&mm->mmap_sem));
for (i = 0; i < count; i++) {
is_write = flags[i] & NPU2_WRITE;
rc = get_user_pages_remote(NULL, mm, ea[i], 1,
is_write ? FOLL_WRITE : 0,
page, NULL, NULL);
/*
* To support virtualised environments we will have to do an
* access to the page to ensure it gets faulted into the
* hypervisor. For the moment virtualisation is not supported in
* other areas so leave the access out.
*/
if (rc != 1) {
status[i] = rc;
result = -EFAULT;
continue;
}
status[i] = 0;
put_page(page[0]);
}
return result;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Alistair Popple | 175 | 100.00% | 1 | 100.00% |
Total | 175 | 100.00% | 1 | 100.00% |
EXPORT_SYMBOL(pnv_npu2_handle_fault);
int pnv_npu2_init(struct pnv_phb *phb)
{
unsigned int i;
u64 mmio_atsd;
struct device_node *dn;
struct pci_dev *gpdev;
static int npu_index;
uint64_t rc = 0;
for_each_child_of_node(phb->hose->dn, dn) {
gpdev = pnv_pci_get_gpu_dev(get_pci_dev(dn));
if (gpdev) {
rc = opal_npu_map_lpar(phb->opal_id,
PCI_DEVID(gpdev->bus->number, gpdev->devfn),
0, 0);
if (rc)
dev_err(&gpdev->dev,
"Error %lld mapping device to LPAR\n",
rc);
}
}
for (i = 0; !of_property_read_u64_index(phb->hose->dn, "ibm,mmio-atsd",
i, &mmio_atsd); i++)
phb->npu.mmio_atsd_regs[i] = ioremap(mmio_atsd, 32);
pr_info("NPU%lld: Found %d MMIO ATSD registers", phb->opal_id, i);
phb->npu.mmio_atsd_count = i;
phb->npu.mmio_atsd_usage = 0;
npu_index++;
if (WARN_ON(npu_index >= NV_MAX_NPUS))
return -ENOSPC;
max_npu2_index = npu_index;
phb->npu.index = npu_index;
return 0;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Alistair Popple | 202 | 100.00% | 1 | 100.00% |
Total | 202 | 100.00% | 1 | 100.00% |
Overall Contributors
Person | Tokens | Prop | Commits | CommitProp |
Alistair Popple | 2777 | 81.87% | 8 | 50.00% |
Alexey Kardashevskiy | 604 | 17.81% | 4 | 25.00% |
Krzysztof Kozlowski | 8 | 0.24% | 1 | 6.25% |
Daniel Axtens | 1 | 0.03% | 1 | 6.25% |
Russell Currey | 1 | 0.03% | 1 | 6.25% |
Bart Van Assche | 1 | 0.03% | 1 | 6.25% |
Total | 3392 | 100.00% | 16 | 100.00% |
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.