/*
* Copyright 2021 Red Hat Inc.
*
* 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 COPYRIGHT HOLDER(S) OR AUTHOR(S) 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 "cgrp.h"
#include "chan.h"
#include "chid.h"
#include "runl.h"
#include "priv.h"
#include <core/gpuobj.h>
#include <subdev/mmu.h>
static void
nvkm_cgrp_ectx_put(struct nvkm_cgrp *cgrp, struct nvkm_ectx **pectx)
{
struct nvkm_ectx *ectx = *pectx;
if (ectx) {
struct nvkm_engn *engn = ectx->engn;
if (refcount_dec_and_test(&ectx->refs)) {
CGRP_TRACE(cgrp, "dtor ectx %d[%s]", engn->id, engn->engine->subdev.name);
nvkm_object_del(&ectx->object);
list_del(&ectx->head);
kfree(ectx);
}
*pectx = NULL;
}
}
static int
nvkm_cgrp_ectx_get(struct nvkm_cgrp *cgrp, struct nvkm_engn *engn, struct nvkm_ectx **pectx,
struct nvkm_chan *chan, struct nvkm_client *client)
{
struct nvkm_engine *engine = engn->engine;
struct nvkm_oclass cclass = {
.client = client,
.engine = engine,
};
struct nvkm_ectx *ectx;
int ret = 0;
/* Look for an existing context for this engine in the channel group. */
ectx = nvkm_list_find(ectx, &cgrp->ectxs, head, ectx->engn == engn);
if (ectx) {
refcount_inc(&ectx->refs);
*pectx = ectx;
return 0;
}
/* Nope - create a fresh one. */
CGRP_TRACE(cgrp, "ctor ectx %d[%s]", engn->id, engn->engine->subdev.name);
if (!(ectx = *pectx = kzalloc(sizeof(*ectx), GFP_KERNEL)))
return -ENOMEM;
ectx->engn = engn;
refcount_set(&ectx->refs, 1);
refcount_set(&ectx->uses, 0);
list_add_tail(&ectx->head, &cgrp->ectxs);
/* Allocate the HW structures. */
if (engine->func->fifo.cclass)
ret = engine->func->fifo.cclass(chan, &cclass, &ectx->object);
else if (engine->func->cclass)
ret = nvkm_object_new_(engine->func->cclass, &cclass, NULL, 0, &ectx->object);
if (ret)
nvkm_cgrp_ectx_put(cgrp, pectx);
return ret;
}
void
nvkm_cgrp_vctx_put(struct nvkm_cgrp *cgrp, struct nvkm_vctx **pvctx)
{
struct nvkm_vctx *vctx = *pvctx;
if (vctx) {
struct nvkm_engn *engn = vctx->ectx->engn;
if (refcount_dec_and_test(&vctx->refs)) {
CGRP_TRACE(cgrp, "dtor vctx %d[%s]", engn->id, engn->engine->subdev.name);
nvkm_vmm_put(vctx->vmm, &vctx->vma);
nvkm_gpuobj_del(&vctx->inst);
nvkm_cgrp_ectx_put(cgrp, &vctx->ectx);
if (vctx->vmm) {
atomic_dec(&vctx->vmm->engref[engn->engine->subdev.type]);
nvkm_vmm_unref(&vctx->vmm);
}
list_del(&vctx->head);
kfree(vctx);
}
*pvctx = NULL;
}
}
int
nvkm_cgrp_vctx_get(struct nvkm_cgrp *cgrp, struct nvkm_engn *engn, struct nvkm_chan *chan,
struct nvkm_vctx **pvctx, struct nvkm_client *client)
{
struct nvkm_ectx *ectx;
struct nvkm_vctx *vctx;
int ret;
/* Look for an existing sub-context for this engine+VEID in the channel group. */
vctx = nvkm_list_find(vctx, &cgrp->vctxs, head,
vctx->ectx->engn == engn && vctx->vmm == chan->vmm);
if (vctx) {
refcount_inc(&vctx->refs);
*pvctx = vctx;
return 0;
}
/* Nope - create a fresh one. But, context first. */
ret = nvkm_cgrp_ectx_get(cgrp, engn, &ectx, chan, client);
if (ret) {
CGRP_ERROR(cgrp, "ectx %d[%s]: %d", engn->id, engn->engine->subdev.name, ret);
return ret;
}
/* Now, create the sub-context. */
CGRP_TRACE(cgrp, "ctor vctx %d[%s]", engn->id, engn->engine->subdev.name);
if (!(vctx = *pvctx = kzalloc(sizeof(*vctx), GFP_KERNEL))) {
nvkm_cgrp_ectx_put(cgrp, &ectx);
return -ENOMEM;
}
vctx->ectx = ectx;
vctx->vmm = nvkm_vmm_ref(chan->vmm);
refcount_set(&vctx->refs, 1);
list_add_tail(&vctx->head, &cgrp->vctxs);
/* MMU on some GPUs needs to know engine usage for TLB invalidation. */
if (vctx->vmm)
atomic_inc(&vctx->vmm->engref[engn->engine->subdev.type]);
/* Allocate the HW structures. */
if (engn->func->ctor2) {
ret = engn->func->ctor2(engn, vctx, chan);
} else
if (engn->func->bind) {
ret = nvkm_object_bind(vctx->ectx->object, NULL, 0, &vctx->inst);
if (ret == 0 && engn->func->ctor)
ret = engn->func->ctor(engn, vctx);
}
if (ret)
nvkm_cgrp_vctx_put(cgrp, pvctx);
return ret;
}
static void
nvkm_cgrp_del(struct kref *kref)
{
struct nvkm_cgrp *cgrp = container_of(kref, typeof(*cgrp), kref);
struct nvkm_runl *runl = cgrp->runl;
if (runl->cgid)
nvkm_chid_put(runl->cgid, cgrp->id, &cgrp->lock);
mutex_destroy(&cgrp->mutex);
nvkm_vmm_unref(&cgrp->vmm);
kfree(cgrp);
}
void
nvkm_cgrp_unref(struct nvkm_cgrp **pcgrp)
{
struct nvkm_cgrp *cgrp = *pcgrp;
if (!cgrp)
return;
kref_put(&cgrp->kref, nvkm_cgrp_del);
*pcgrp = NULL;
}
struct nvkm_cgrp *
nvkm_cgrp_ref(struct nvkm_cgrp *cgrp)
{
if (cgrp)
kref_get(&cgrp->kref);
return cgrp;
}
void
nvkm_cgrp_put(struct nvkm_cgrp **pcgrp, unsigned long irqflags)
{
struct nvkm_cgrp *cgrp = *pcgrp;
if (!cgrp)
return;
*pcgrp = NULL;
spin_unlock_irqrestore(&cgrp->lock, irqflags);
}
int
nvkm_cgrp_new(struct nvkm_runl *runl, const char *name, struct nvkm_vmm *vmm, bool hw,
struct nvkm_cgrp **pcgrp)
{
struct nvkm_cgrp *cgrp;
if (!(cgrp = *pcgrp = kmalloc(sizeof(*cgrp), GFP_KERNEL)))
return -ENOMEM;
cgrp->func = runl->fifo->func->cgrp.func;
strscpy(cgrp->name, name, sizeof(cgrp->name));
cgrp->runl = runl;
cgrp->vmm = nvkm_vmm_ref(vmm);
cgrp->hw = hw;
cgrp->id = -1;
kref_init(&cgrp->kref);
INIT_LIST_HEAD(&cgrp->chans);
cgrp->chan_nr = 0;
spin_lock_init(&cgrp->lock);
INIT_LIST_HEAD(&cgrp->ectxs);
INIT_LIST_HEAD(&cgrp->vctxs);
mutex_init(&cgrp->mutex);
atomic_set(&cgrp->rc, NVKM_CGRP_RC_NONE);
if (runl->cgid) {
cgrp->id = nvkm_chid_get(runl->cgid, cgrp);
if (cgrp->id < 0) {
RUNL_ERROR(runl, "!cgids");
nvkm_cgrp_unref(pcgrp);
return -ENOSPC;
}
}
return 0;
}