| /* |
| * 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 "runl.h" |
| #include "cgrp.h" |
| #include "chan.h" |
| #include "chid.h" |
| #include "priv.h" |
| #include "runq.h" |
| |
| #include <core/gpuobj.h> |
| #include <subdev/timer.h> |
| #include <subdev/top.h> |
| |
| static struct nvkm_cgrp * |
| nvkm_engn_cgrp_get(struct nvkm_engn *engn, unsigned long *pirqflags) |
| { |
| struct nvkm_cgrp *cgrp = NULL; |
| struct nvkm_chan *chan; |
| bool cgid; |
| int id; |
| |
| id = engn->func->cxid(engn, &cgid); |
| if (id < 0) |
| return NULL; |
| |
| if (!cgid) { |
| chan = nvkm_runl_chan_get_chid(engn->runl, id, pirqflags); |
| if (chan) |
| cgrp = chan->cgrp; |
| } else { |
| cgrp = nvkm_runl_cgrp_get_cgid(engn->runl, id, pirqflags); |
| } |
| |
| WARN_ON(!cgrp); |
| return cgrp; |
| } |
| |
| static void |
| nvkm_runl_rc(struct nvkm_runl *runl) |
| { |
| struct nvkm_fifo *fifo = runl->fifo; |
| struct nvkm_cgrp *cgrp, *gtmp; |
| struct nvkm_chan *chan, *ctmp; |
| struct nvkm_engn *engn; |
| unsigned long flags; |
| int rc, state, i; |
| bool reset; |
| |
| /* Runlist is blocked before scheduling recovery - fetch count. */ |
| BUG_ON(!mutex_is_locked(&runl->mutex)); |
| rc = atomic_xchg(&runl->rc_pending, 0); |
| if (!rc) |
| return; |
| |
| /* Look for channel groups flagged for RC. */ |
| nvkm_runl_foreach_cgrp_safe(cgrp, gtmp, runl) { |
| state = atomic_cmpxchg(&cgrp->rc, NVKM_CGRP_RC_PENDING, NVKM_CGRP_RC_RUNNING); |
| if (state == NVKM_CGRP_RC_PENDING) { |
| /* Disable all channels in them, and remove from runlist. */ |
| nvkm_cgrp_foreach_chan_safe(chan, ctmp, cgrp) { |
| nvkm_chan_error(chan, false); |
| nvkm_chan_remove_locked(chan); |
| } |
| } |
| } |
| |
| /* On GPUs with runlist preempt, wait for PBDMA(s) servicing runlist to go idle. */ |
| if (runl->func->preempt) { |
| for (i = 0; i < runl->runq_nr; i++) { |
| struct nvkm_runq *runq = runl->runq[i]; |
| |
| if (runq) { |
| nvkm_msec(fifo->engine.subdev.device, 2000, |
| if (runq->func->idle(runq)) |
| break; |
| ); |
| } |
| } |
| } |
| |
| /* Look for engines that are still on flagged channel groups - reset them. */ |
| nvkm_runl_foreach_engn_cond(engn, runl, engn->func->cxid) { |
| cgrp = nvkm_engn_cgrp_get(engn, &flags); |
| if (!cgrp) { |
| ENGN_DEBUG(engn, "cxid not valid"); |
| continue; |
| } |
| |
| reset = atomic_read(&cgrp->rc) == NVKM_CGRP_RC_RUNNING; |
| nvkm_cgrp_put(&cgrp, flags); |
| if (!reset) { |
| ENGN_DEBUG(engn, "cxid not in recovery"); |
| continue; |
| } |
| |
| ENGN_DEBUG(engn, "resetting..."); |
| /*TODO: can we do something less of a potential catastrophe on failure? */ |
| WARN_ON(nvkm_engine_reset(engn->engine)); |
| } |
| |
| /* Submit runlist update, and clear any remaining exception state. */ |
| runl->func->update(runl); |
| if (runl->func->fault_clear) |
| runl->func->fault_clear(runl); |
| |
| /* Unblock runlist processing. */ |
| while (rc--) |
| nvkm_runl_allow(runl); |
| runl->func->wait(runl); |
| } |
| |
| static void |
| nvkm_runl_rc_runl(struct nvkm_runl *runl) |
| { |
| RUNL_ERROR(runl, "rc scheduled"); |
| |
| nvkm_runl_block(runl); |
| if (runl->func->preempt) |
| runl->func->preempt(runl); |
| |
| atomic_inc(&runl->rc_pending); |
| schedule_work(&runl->work); |
| } |
| |
| void |
| nvkm_runl_rc_cgrp(struct nvkm_cgrp *cgrp) |
| { |
| if (atomic_cmpxchg(&cgrp->rc, NVKM_CGRP_RC_NONE, NVKM_CGRP_RC_PENDING) != NVKM_CGRP_RC_NONE) |
| return; |
| |
| CGRP_ERROR(cgrp, "rc scheduled"); |
| nvkm_runl_rc_runl(cgrp->runl); |
| } |
| |
| void |
| nvkm_runl_rc_engn(struct nvkm_runl *runl, struct nvkm_engn *engn) |
| { |
| struct nvkm_cgrp *cgrp; |
| unsigned long flags; |
| |
| /* Lookup channel group currently on engine. */ |
| cgrp = nvkm_engn_cgrp_get(engn, &flags); |
| if (!cgrp) { |
| ENGN_DEBUG(engn, "rc skipped, not on channel"); |
| return; |
| } |
| |
| nvkm_runl_rc_cgrp(cgrp); |
| nvkm_cgrp_put(&cgrp, flags); |
| } |
| |
| static void |
| nvkm_runl_work(struct work_struct *work) |
| { |
| struct nvkm_runl *runl = container_of(work, typeof(*runl), work); |
| |
| mutex_lock(&runl->mutex); |
| nvkm_runl_rc(runl); |
| mutex_unlock(&runl->mutex); |
| |
| } |
| |
| struct nvkm_chan * |
| nvkm_runl_chan_get_inst(struct nvkm_runl *runl, u64 inst, unsigned long *pirqflags) |
| { |
| struct nvkm_chid *chid = runl->chid; |
| struct nvkm_chan *chan; |
| unsigned long flags; |
| int id; |
| |
| spin_lock_irqsave(&chid->lock, flags); |
| for_each_set_bit(id, chid->used, chid->nr) { |
| chan = chid->data[id]; |
| if (likely(chan)) { |
| if (chan->inst->addr == inst) { |
| spin_lock(&chan->cgrp->lock); |
| *pirqflags = flags; |
| spin_unlock(&chid->lock); |
| return chan; |
| } |
| } |
| } |
| spin_unlock_irqrestore(&chid->lock, flags); |
| return NULL; |
| } |
| |
| struct nvkm_chan * |
| nvkm_runl_chan_get_chid(struct nvkm_runl *runl, int id, unsigned long *pirqflags) |
| { |
| struct nvkm_chid *chid = runl->chid; |
| struct nvkm_chan *chan; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&chid->lock, flags); |
| if (!WARN_ON(id >= chid->nr)) { |
| chan = chid->data[id]; |
| if (likely(chan)) { |
| spin_lock(&chan->cgrp->lock); |
| *pirqflags = flags; |
| spin_unlock(&chid->lock); |
| return chan; |
| } |
| } |
| spin_unlock_irqrestore(&chid->lock, flags); |
| return NULL; |
| } |
| |
| struct nvkm_cgrp * |
| nvkm_runl_cgrp_get_cgid(struct nvkm_runl *runl, int id, unsigned long *pirqflags) |
| { |
| struct nvkm_chid *cgid = runl->cgid; |
| struct nvkm_cgrp *cgrp; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&cgid->lock, flags); |
| if (!WARN_ON(id >= cgid->nr)) { |
| cgrp = cgid->data[id]; |
| if (likely(cgrp)) { |
| spin_lock(&cgrp->lock); |
| *pirqflags = flags; |
| spin_unlock(&cgid->lock); |
| return cgrp; |
| } |
| } |
| spin_unlock_irqrestore(&cgid->lock, flags); |
| return NULL; |
| } |
| |
| int |
| nvkm_runl_preempt_wait(struct nvkm_runl *runl) |
| { |
| return nvkm_msec(runl->fifo->engine.subdev.device, runl->fifo->timeout.chan_msec, |
| if (!runl->func->preempt_pending(runl)) |
| break; |
| |
| nvkm_runl_rc(runl); |
| usleep_range(1, 2); |
| ) < 0 ? -ETIMEDOUT : 0; |
| } |
| |
| bool |
| nvkm_runl_update_pending(struct nvkm_runl *runl) |
| { |
| if (!runl->func->pending(runl)) |
| return false; |
| |
| nvkm_runl_rc(runl); |
| return true; |
| } |
| |
| void |
| nvkm_runl_update_locked(struct nvkm_runl *runl, bool wait) |
| { |
| if (atomic_xchg(&runl->changed, 0) && runl->func->update) { |
| runl->func->update(runl); |
| if (wait) |
| runl->func->wait(runl); |
| } |
| } |
| |
| void |
| nvkm_runl_allow(struct nvkm_runl *runl) |
| { |
| struct nvkm_fifo *fifo = runl->fifo; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&fifo->lock, flags); |
| if (!--runl->blocked) { |
| RUNL_TRACE(runl, "running"); |
| runl->func->allow(runl, ~0); |
| } |
| spin_unlock_irqrestore(&fifo->lock, flags); |
| } |
| |
| void |
| nvkm_runl_block(struct nvkm_runl *runl) |
| { |
| struct nvkm_fifo *fifo = runl->fifo; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&fifo->lock, flags); |
| if (!runl->blocked++) { |
| RUNL_TRACE(runl, "stopped"); |
| runl->func->block(runl, ~0); |
| } |
| spin_unlock_irqrestore(&fifo->lock, flags); |
| } |
| |
| void |
| nvkm_runl_fini(struct nvkm_runl *runl) |
| { |
| if (runl->func->fini) |
| runl->func->fini(runl); |
| |
| flush_work(&runl->work); |
| } |
| |
| void |
| nvkm_runl_del(struct nvkm_runl *runl) |
| { |
| struct nvkm_engn *engn, *engt; |
| |
| nvkm_memory_unref(&runl->mem); |
| |
| list_for_each_entry_safe(engn, engt, &runl->engns, head) { |
| list_del(&engn->head); |
| kfree(engn); |
| } |
| |
| nvkm_chid_unref(&runl->chid); |
| nvkm_chid_unref(&runl->cgid); |
| |
| list_del(&runl->head); |
| mutex_destroy(&runl->mutex); |
| kfree(runl); |
| } |
| |
| struct nvkm_engn * |
| nvkm_runl_add(struct nvkm_runl *runl, int engi, const struct nvkm_engn_func *func, |
| enum nvkm_subdev_type type, int inst) |
| { |
| struct nvkm_fifo *fifo = runl->fifo; |
| struct nvkm_device *device = fifo->engine.subdev.device; |
| struct nvkm_engine *engine; |
| struct nvkm_engn *engn; |
| |
| engine = nvkm_device_engine(device, type, inst); |
| if (!engine) { |
| RUNL_DEBUG(runl, "engn %d.%d[%s] not found", engi, inst, nvkm_subdev_type[type]); |
| return NULL; |
| } |
| |
| if (!(engn = kzalloc(sizeof(*engn), GFP_KERNEL))) |
| return NULL; |
| |
| engn->func = func; |
| engn->runl = runl; |
| engn->id = engi; |
| engn->engine = engine; |
| engn->fault = -1; |
| list_add_tail(&engn->head, &runl->engns); |
| |
| /* Lookup MMU engine ID for fault handling. */ |
| if (device->top) |
| engn->fault = nvkm_top_fault_id(device, engine->subdev.type, engine->subdev.inst); |
| |
| if (engn->fault < 0 && fifo->func->mmu_fault) { |
| const struct nvkm_enum *map = fifo->func->mmu_fault->engine; |
| |
| while (map->name) { |
| if (map->data2 == engine->subdev.type && map->inst == engine->subdev.inst) { |
| engn->fault = map->value; |
| break; |
| } |
| map++; |
| } |
| } |
| |
| return engn; |
| } |
| |
| struct nvkm_runl * |
| nvkm_runl_get(struct nvkm_fifo *fifo, int runi, u32 addr) |
| { |
| struct nvkm_runl *runl; |
| |
| nvkm_runl_foreach(runl, fifo) { |
| if ((runi >= 0 && runl->id == runi) || (runi < 0 && runl->addr == addr)) |
| return runl; |
| } |
| |
| return NULL; |
| } |
| |
| struct nvkm_runl * |
| nvkm_runl_new(struct nvkm_fifo *fifo, int runi, u32 addr, int id_nr) |
| { |
| struct nvkm_subdev *subdev = &fifo->engine.subdev; |
| struct nvkm_runl *runl; |
| int ret; |
| |
| if (!(runl = kzalloc(sizeof(*runl), GFP_KERNEL))) |
| return ERR_PTR(-ENOMEM); |
| |
| runl->func = fifo->func->runl; |
| runl->fifo = fifo; |
| runl->id = runi; |
| runl->addr = addr; |
| INIT_LIST_HEAD(&runl->engns); |
| INIT_LIST_HEAD(&runl->cgrps); |
| atomic_set(&runl->changed, 0); |
| mutex_init(&runl->mutex); |
| INIT_WORK(&runl->work, nvkm_runl_work); |
| atomic_set(&runl->rc_triggered, 0); |
| atomic_set(&runl->rc_pending, 0); |
| list_add_tail(&runl->head, &fifo->runls); |
| |
| if (!fifo->chid) { |
| if ((ret = nvkm_chid_new(&nvkm_chan_event, subdev, id_nr, 0, id_nr, &runl->cgid)) || |
| (ret = nvkm_chid_new(&nvkm_chan_event, subdev, id_nr, 0, id_nr, &runl->chid))) { |
| RUNL_ERROR(runl, "cgid/chid: %d", ret); |
| nvkm_runl_del(runl); |
| return ERR_PTR(ret); |
| } |
| } else { |
| runl->cgid = nvkm_chid_ref(fifo->cgid); |
| runl->chid = nvkm_chid_ref(fifo->chid); |
| } |
| |
| return runl; |
| } |