/* * Copyright (c) 2018-2021, NVIDIA CORPORATION. All rights reserved. * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common/gr/ctx_priv.h" #include #include #include "hal/fifo/tsg_gk20a.h" #include "hal/init/hal_gv11b.h" #include "../nvgpu-fifo-common.h" #include "nvgpu-tsg.h" #ifdef TSG_UNIT_DEBUG #undef unit_verbose #define unit_verbose unit_info #else #define unit_verbose(unit, msg, ...) \ do { \ if (0) { \ unit_info(unit, msg, ##__VA_ARGS__); \ } \ } while (0) #endif struct tsg_unit_ctx { u32 branches; }; static struct tsg_unit_ctx unit_ctx; #define MAX_STUB 4 struct stub_ctx { const char *name; u32 count; u32 chid; u32 tsgid; u32 runlist_mask; u32 runlist_state; }; struct stub_ctx stub[MAX_STUB]; static void subtest_setup(u32 branches) { u32 i; unit_ctx.branches = branches; memset(stub, 0, sizeof(stub)); for (i = 0; i < MAX_STUB; i++) { stub[i].name = ""; stub[i].count = 0; stub[i].chid = NVGPU_INVALID_CHANNEL_ID; stub[i].tsgid = NVGPU_INVALID_TSG_ID; } } #define pruned test_fifo_subtest_pruned #define branches_str test_fifo_flags_str #define F_TSG_OPEN_ACQUIRE_CH_FAIL BIT(0) #define F_TSG_OPEN_SM_FAIL BIT(1) #define F_TSG_OPEN_ALLOC_SM_FAIL BIT(2) #define F_TSG_OPEN_ALLOC_SM_KZALLOC_FAIL BIT(3) #define F_TSG_OPEN_ALLOC_GR_FAIL BIT(4) #define F_TSG_OPEN_NO_INIT_BUF BIT(5) #define F_TSG_OPEN_INIT_BUF_FAIL BIT(6) #define F_TSG_OPEN_NO_OPEN_HAL BIT(7) #define F_TSG_OPEN_OPEN_HAL_FAIL BIT(8) #define F_TSG_OPEN_LAST BIT(9) static int stub_tsg_init_eng_method_buffers(struct gk20a *g, struct nvgpu_tsg *tsg) { if (unit_ctx.branches & F_TSG_OPEN_INIT_BUF_FAIL) { return -ENOMEM; } return 0; } static int stub_tsg_open(struct nvgpu_tsg *tsg) { if (unit_ctx.branches & F_TSG_OPEN_OPEN_HAL_FAIL) { return -EINVAL; } return 0; } static u32 stub_gr_init_get_no_of_sm_0(struct gk20a *g) { return 0; } int test_tsg_open(struct unit_module *m, struct gk20a *g, void *args) { struct nvgpu_fifo *f = &g->fifo; struct gpu_ops gops = g->ops; u32 num_channels = f->num_channels; struct nvgpu_tsg *tsg = NULL; struct nvgpu_tsg *next_tsg = NULL; struct nvgpu_posix_fault_inj *kmem_fi; u32 branches = 0U; int ret = UNIT_FAIL; u32 fail = F_TSG_OPEN_ACQUIRE_CH_FAIL | F_TSG_OPEN_SM_FAIL | F_TSG_OPEN_ALLOC_SM_FAIL | F_TSG_OPEN_ALLOC_SM_KZALLOC_FAIL | F_TSG_OPEN_ALLOC_GR_FAIL | F_TSG_OPEN_INIT_BUF_FAIL | F_TSG_OPEN_OPEN_HAL_FAIL; u32 prune = fail; u32 tsgid; const char *labels[] = { "acquire_ch_fail", "sm_fail", "alloc_sm_fail", "alloc_sm_kzalloc_fail", "alloc_gr_fail", "no_init_buf", "init_buf_fail", "no_open_hal", "open_hal_fail" }; kmem_fi = nvgpu_kmem_get_fault_injection(); unit_assert(nvgpu_tsg_default_timeslice_us(g) == NVGPU_TSG_TIMESLICE_DEFAULT_US, goto done); unit_assert(nvgpu_tsg_check_and_get_from_id(g, NVGPU_INVALID_TSG_ID) == NULL, goto done); for (branches = 0U; branches < F_TSG_OPEN_LAST; branches++) { if (pruned(branches, prune)) { unit_verbose(m, "%s branches=%s (pruned)\n", __func__, branches_str(branches, labels)); continue; } unit_verbose(m, "%s branches=%s\n", __func__, branches_str(branches, labels)); subtest_setup(branches); /* find next tsg (if acquire succeeds) */ next_tsg = NULL; for (tsgid = 0U; tsgid < f->num_channels; tsgid++) { if (!f->tsg[tsgid].in_use) { next_tsg = &f->tsg[tsgid]; break; } } unit_assert(next_tsg != NULL, goto done); f->num_channels = branches & F_TSG_OPEN_ACQUIRE_CH_FAIL ? 0U : num_channels; g->ops.gr.init.get_no_of_sm = branches & F_TSG_OPEN_SM_FAIL ? stub_gr_init_get_no_of_sm_0 : gops.gr.init.get_no_of_sm; next_tsg->sm_error_states = branches & F_TSG_OPEN_ALLOC_SM_FAIL ? (void*)1 : NULL; g->ops.tsg.init_eng_method_buffers = branches & F_TSG_OPEN_NO_INIT_BUF ? NULL : stub_tsg_init_eng_method_buffers; g->ops.tsg.open = branches & F_TSG_OPEN_NO_OPEN_HAL ? NULL : stub_tsg_open; nvgpu_posix_enable_fault_injection(kmem_fi, false, 0); if (branches & F_TSG_OPEN_ALLOC_SM_KZALLOC_FAIL) { nvgpu_posix_enable_fault_injection(kmem_fi, true, 0); } if (branches & F_TSG_OPEN_ALLOC_GR_FAIL) { nvgpu_posix_enable_fault_injection(kmem_fi, true, 1); } tsg = nvgpu_tsg_open(g, getpid()); f->tsg[tsgid].sm_error_states = NULL; if (branches & fail) { f->num_channels = num_channels; unit_assert(tsg == NULL, goto done); } else { unit_assert(tsg != NULL, goto done); unit_assert(nvgpu_tsg_get_from_id(g, tsg->tsgid) == tsg, goto done); unit_assert(nvgpu_tsg_check_and_get_from_id(g, tsg->tsgid) == tsg, goto done); nvgpu_ref_put(&tsg->refcount, nvgpu_tsg_release); tsg = NULL; } } ret = UNIT_SUCCESS; done: if (ret != UNIT_SUCCESS) { unit_err(m, "%s branches=%s\n", __func__, branches_str(branches, labels)); } nvgpu_posix_enable_fault_injection(kmem_fi, false, 0); if (tsg != NULL) { nvgpu_ref_put(&tsg->refcount, nvgpu_tsg_release); } g->ops = gops; f->num_channels = num_channels; return ret; } #define F_TSG_BIND_CHANNEL_CH_BOUND BIT(0) #define F_TSG_BIND_CHANNEL_RL_MISMATCH BIT(1) #define F_TSG_BIND_CHANNEL_ACTIVE BIT(2) #define F_TSG_BIND_CHANNEL_BIND_HAL BIT(3) #define F_TSG_BIND_CHANNEL_BIND_HAL_ERR BIT(4) #define F_TSG_BIND_CHANNEL_ENG_METHOD_BUFFER BIT(5) #define F_TSG_BIND_CHANNEL_ASYNC_ID BIT(6) #define F_TSG_BIND_CHANNEL_LAST BIT(7) static const char *f_tsg_bind[] = { "ch_bound", "rl_mismatch", "active", "bind_hal", "eng_method_buffer", }; static int stub_tsg_bind_channel(struct nvgpu_tsg *tsg, struct nvgpu_channel *ch) { if (unit_ctx.branches & F_TSG_BIND_CHANNEL_BIND_HAL_ERR) { return -EINVAL; } return 0; } int test_tsg_bind_channel(struct unit_module *m, struct gk20a *g, void *args) { struct gpu_ops gops = g->ops; struct nvgpu_tsg *tsg = NULL; struct nvgpu_tsg tsg_save; struct nvgpu_channel *chA = NULL; struct nvgpu_channel *chB = NULL; struct nvgpu_channel *ch = NULL; struct nvgpu_runlist *runlist = NULL; u32 branches = 0U; int ret = UNIT_FAIL; int err; u32 fail = F_TSG_BIND_CHANNEL_CH_BOUND | F_TSG_BIND_CHANNEL_RL_MISMATCH | F_TSG_BIND_CHANNEL_ACTIVE | F_TSG_BIND_CHANNEL_BIND_HAL_ERR; u32 prune = fail; tsg = nvgpu_tsg_open(g, getpid()); unit_assert(tsg != NULL, goto done); chA = nvgpu_channel_open_new(g, ~0U, false, getpid(), getpid()); unit_assert(chA != NULL, goto done); chB = nvgpu_channel_open_new(g, ~0U, false, getpid(), getpid()); unit_assert(chB != NULL, goto done); err = nvgpu_tsg_bind_channel(tsg, chA); unit_assert(err == 0, goto done); tsg_save = *tsg; for (branches = 0U; branches < F_TSG_BIND_CHANNEL_LAST; branches++) { if (pruned(branches, prune)) { unit_verbose(m, "%s branches=%s (pruned)\n", __func__, branches_str(branches, f_tsg_bind)); continue; } subtest_setup(branches); ch = chB; if (branches & F_TSG_BIND_CHANNEL_ASYNC_ID) { ch->subctx_id = CHANNEL_INFO_VEID0 + 1; } else { ch->subctx_id = 0U; } /* ch already bound */ if (branches & F_TSG_BIND_CHANNEL_CH_BOUND) { ch = chA; } /* runlist id mismatch */ tsg->runlist = branches & F_TSG_BIND_CHANNEL_RL_MISMATCH ? NULL : tsg_save.runlist; /* ch already already active */ runlist = tsg->runlist; if (branches & F_TSG_BIND_CHANNEL_ACTIVE) { nvgpu_set_bit(ch->chid, runlist->active_channels); } else { nvgpu_clear_bit(ch->chid, runlist->active_channels); } if ((branches & F_TSG_BIND_CHANNEL_BIND_HAL) || (branches & F_TSG_BIND_CHANNEL_BIND_HAL_ERR)) { g->ops.tsg.bind_channel = stub_tsg_bind_channel; } else { g->ops.tsg.bind_channel = NULL; } g->ops.tsg.bind_channel_eng_method_buffers = branches & F_TSG_BIND_CHANNEL_ENG_METHOD_BUFFER ? gops.tsg.bind_channel_eng_method_buffers : NULL; unit_verbose(m, "%s branches=%s\n", __func__, branches_str(branches, f_tsg_bind)); err = nvgpu_tsg_bind_channel(tsg, ch); if (branches & fail) { if (!(branches & F_TSG_BIND_CHANNEL_CH_BOUND)) { unit_assert(nvgpu_tsg_from_ch(ch) == NULL, goto done); } unit_assert(err != 0, goto done); } else { unit_assert(err == 0, goto done); unit_assert(!nvgpu_list_empty(&tsg->ch_list), goto done); unit_assert(nvgpu_tsg_from_ch(ch) == tsg, goto done); err = nvgpu_tsg_force_unbind_channel(tsg, ch); unit_assert(err == 0, goto done); unit_assert(ch->tsgid == NVGPU_INVALID_TSG_ID, goto done); } } ret = UNIT_SUCCESS; done: if (ret != UNIT_SUCCESS) { unit_err(m, "%s branches=%s\n", __func__, branches_str(branches, f_tsg_bind)); } if (chA != NULL) { nvgpu_channel_close(chA); } if (chB != NULL) { nvgpu_channel_close(chB); } if (tsg != NULL) { nvgpu_ref_put(&tsg->refcount, nvgpu_tsg_release); } g->ops = gops; return ret; } #define F_TSG_UNBIND_CHANNEL_ABORT_RUNLIST_UPDATE_FAIL BIT(0) #define F_TSG_UNBIND_CHANNEL_UNSERVICEABLE BIT(1) #define F_TSG_UNBIND_CHANNEL_PREEMPT_TSG_FAIL BIT(2) #define F_TSG_UNBIND_CHANNEL_CHECK_HW_STATE BIT(3) #define F_TSG_UNBIND_CHANNEL_CHECK_HW_STATE_FAIL BIT(4) #define F_TSG_UNBIND_CHANNEL_RUNLIST_UPDATE_FAIL BIT(5) #define F_TSG_UNBIND_CHANNEL_UNBIND_HAL BIT(6) #define F_TSG_UNBIND_CHANNEL_UNBIND_HAL_FAIL BIT(7) #define F_TSG_UNBIND_CHANNEL_ABORT_CLEAN_UP_NULL BIT(8) #define F_TSG_UNBIND_CHANNEL_LAST BIT(9) static int stub_fifo_preempt_tsg_EINVAL( struct gk20a *g, struct nvgpu_tsg *tsg) { return -EINVAL; } static int stub_tsg_unbind_channel_check_hw_state( struct nvgpu_tsg *tsg, struct nvgpu_channel *ch) { if (unit_ctx.branches & F_TSG_UNBIND_CHANNEL_CHECK_HW_STATE_FAIL) { return -EINVAL; } return 0; } static int stub_tsg_unbind_channel(struct nvgpu_tsg *tsg, struct nvgpu_channel *ch) { if (unit_ctx.branches & F_TSG_UNBIND_CHANNEL_UNBIND_HAL_FAIL) { return -EINVAL; } return 0; } static int stub_runlist_update_EINVAL( struct gk20a *g, struct nvgpu_runlist *rl, struct nvgpu_channel *ch, bool add, bool wait_for_finish) { stub[0].count++; if (stub[0].count == 1 && (unit_ctx.branches & F_TSG_UNBIND_CHANNEL_RUNLIST_UPDATE_FAIL)) { return -EINVAL; } if (stub[0].count == 2 && (unit_ctx.branches & F_TSG_UNBIND_CHANNEL_ABORT_RUNLIST_UPDATE_FAIL)) { return -EINVAL; } return 0; } int test_tsg_unbind_channel(struct unit_module *m, struct gk20a *g, void *args) { struct gpu_ops gops = g->ops; struct nvgpu_tsg *tsg = NULL; struct nvgpu_channel *chA = NULL; struct nvgpu_channel *chB = NULL; u32 branches = 0U; int ret = UNIT_FAIL; int err; const char *labels[] = { "abort_runlist_update_fail", "unserviceable", "preempt_tsg_fail", "check_hw_state", "check_hw_state_fail", "runlist_update_fail", "unbind_hal", "unbind_hal_fail", "abort_cleanup_null" }; u32 fail = F_TSG_UNBIND_CHANNEL_PREEMPT_TSG_FAIL | F_TSG_UNBIND_CHANNEL_CHECK_HW_STATE_FAIL | F_TSG_UNBIND_CHANNEL_RUNLIST_UPDATE_FAIL | F_TSG_UNBIND_CHANNEL_UNBIND_HAL_FAIL; /* * do not prune F_TSG_UNBIND_CHANNEL_UNBIND_HAL_FAIL, to * exercise following abort path. */ u32 prune = F_TSG_UNBIND_CHANNEL_PREEMPT_TSG_FAIL | F_TSG_UNBIND_CHANNEL_CHECK_HW_STATE_FAIL | F_TSG_UNBIND_CHANNEL_RUNLIST_UPDATE_FAIL; for (branches = 0U; branches < F_TSG_UNBIND_CHANNEL_LAST; branches++) { if (pruned(branches, prune)) { unit_verbose(m, "%s branches=%s (pruned)\n", __func__, branches_str(branches, labels)); continue; } subtest_setup(branches); unit_verbose(m, "%s branches=%s\n", __func__, branches_str(branches, labels)); /* * tsg unbind tears down TSG in case of failure: * we need to create tsg + bind channel for each test */ tsg = nvgpu_tsg_open(g, getpid()); unit_assert(tsg != NULL, goto done); chA = nvgpu_channel_open_new(g, ~0U, false, getpid(), getpid()); unit_assert(chA != NULL, goto done); chB = nvgpu_channel_open_new(g, ~0U, false, getpid(), getpid()); unit_assert(chB != NULL, goto done); err = nvgpu_tsg_bind_channel(tsg, chA); unit_assert(err == 0, goto done); err = nvgpu_tsg_bind_channel(tsg, chB); unit_assert(err == 0, goto done); chA->unserviceable = branches & F_TSG_UNBIND_CHANNEL_UNSERVICEABLE ? true : false; g->ops.fifo.preempt_tsg = branches & F_TSG_UNBIND_CHANNEL_PREEMPT_TSG_FAIL ? stub_fifo_preempt_tsg_EINVAL : gops.fifo.preempt_tsg; if ((branches & F_TSG_UNBIND_CHANNEL_CHECK_HW_STATE) || (branches & F_TSG_UNBIND_CHANNEL_CHECK_HW_STATE_FAIL)) { g->ops.tsg.unbind_channel_check_hw_state = stub_tsg_unbind_channel_check_hw_state; } else { g->ops.tsg.unbind_channel_check_hw_state = NULL; } g->ops.runlist.update = branches & F_TSG_UNBIND_CHANNEL_RUNLIST_UPDATE_FAIL ? stub_runlist_update_EINVAL : gops.runlist.update; if (branches & F_TSG_UNBIND_CHANNEL_RUNLIST_UPDATE_FAIL || branches & F_TSG_UNBIND_CHANNEL_ABORT_RUNLIST_UPDATE_FAIL) { g->ops.runlist.update = stub_runlist_update_EINVAL; } if ((branches & F_TSG_UNBIND_CHANNEL_UNBIND_HAL) || (branches & F_TSG_UNBIND_CHANNEL_UNBIND_HAL_FAIL)) { g->ops.tsg.unbind_channel = stub_tsg_unbind_channel; } else { g->ops.tsg.unbind_channel = NULL; } g->ops.channel.abort_clean_up = branches & F_TSG_UNBIND_CHANNEL_ABORT_CLEAN_UP_NULL ? NULL : gops.channel.abort_clean_up; err = nvgpu_tsg_force_unbind_channel(tsg, chA); if (branches & fail) { /* check that TSG has been torn down */ unit_assert(err != 0, goto done); unit_assert(chA->unserviceable, goto done); unit_assert(chB->unserviceable, goto done); unit_assert(chA->tsgid == NVGPU_INVALID_TSG_ID, goto done); } else { if (branches & F_TSG_UNBIND_CHANNEL_ABORT_CLEAN_UP_NULL) { gops.channel.abort_clean_up(chA); } unit_assert(chA->tsgid == NVGPU_INVALID_TSG_ID, goto done); unit_assert(nvgpu_list_empty(&chA->ch_entry), goto done); /* check that TSG has not been torn down */ unit_assert(!chB->unserviceable, goto done); unit_assert(!nvgpu_list_empty(&chB->ch_entry), goto done); unit_assert(!nvgpu_list_empty(&tsg->ch_list), goto done); } nvgpu_channel_close(chA); nvgpu_channel_close(chB); nvgpu_ref_put(&tsg->refcount, nvgpu_tsg_release); chA = NULL; chB = NULL; tsg = NULL; } ret = UNIT_SUCCESS; done: if (ret == UNIT_FAIL) { unit_err(m, "%s branches=%s\n", __func__, branches_str(branches, labels)); } if (chA != NULL) { nvgpu_channel_close(chA); } if (chB != NULL) { nvgpu_channel_close(chB); } if (tsg != NULL) { nvgpu_ref_put(&tsg->refcount, nvgpu_tsg_release); } g->ops = gops; return ret; } #define F_TSG_RELEASE_NO_RELEASE_HAL BIT(0) #define F_TSG_RELEASE_GR_CTX BIT(1) #define F_TSG_RELEASE_MEM BIT(2) #define F_TSG_RELEASE_VM BIT(3) #define F_TSG_RELEASE_ENG_BUFS BIT(4) #define F_TSG_RELEASE_SM_ERR_STATES BIT(5) #define F_TSG_RELEASE_LAST BIT(6) static void stub_tsg_release(struct nvgpu_tsg *tsg) { } static void stub_tsg_deinit_eng_method_buffers(struct gk20a *g, struct nvgpu_tsg *tsg) { stub[0].name = __func__; stub[0].tsgid = tsg->tsgid; } static void stub_gr_setup_free_gr_ctx(struct gk20a *g, struct vm_gk20a *vm, struct nvgpu_gr_ctx *gr_ctx) { stub[1].name = __func__; stub[1].count++; } int test_tsg_release(struct unit_module *m, struct gk20a *g, void *args) { struct nvgpu_fifo *f = &g->fifo; struct gpu_ops gops = g->ops; struct nvgpu_tsg *tsg = NULL; struct vm_gk20a vm; u32 branches = 0U; int ret = UNIT_FAIL; struct nvgpu_mem mem; u32 free_gr_ctx_mask = F_TSG_RELEASE_GR_CTX|F_TSG_RELEASE_MEM|F_TSG_RELEASE_VM; const char *labels[] = { "no_release_hal", "gr_ctx", "mem", "vm", "eng_bufs", "sm_err_states" }; for (branches = 0U; branches < F_TSG_RELEASE_LAST; branches++) { if (!(branches & F_TSG_RELEASE_GR_CTX) && (branches & F_TSG_RELEASE_MEM)) { unit_verbose(m, "%s branches=%s (pruned)\n", __func__, branches_str(branches, labels)); continue; } subtest_setup(branches); unit_verbose(m, "%s branches=%s\n", __func__, branches_str(branches, labels)); tsg = nvgpu_tsg_open(g, getpid()); unit_assert(tsg != NULL, goto done); unit_assert(tsg->gr_ctx != NULL, goto done); unit_assert(tsg->gr_ctx->mem.aperture == APERTURE_INVALID, goto done); g->ops.tsg.release = branches & F_TSG_RELEASE_NO_RELEASE_HAL ? NULL : stub_tsg_release; if (!(branches & F_TSG_RELEASE_GR_CTX)) { nvgpu_free_gr_ctx_struct(g, tsg->gr_ctx); tsg->gr_ctx = NULL; } if (branches & F_TSG_RELEASE_MEM) { nvgpu_dma_alloc(g, NVGPU_CPU_PAGE_SIZE, &mem); tsg->gr_ctx->mem = mem; } if (branches & F_TSG_RELEASE_VM) { tsg->vm = &vm; /* prevent nvgpu_vm_remove */ nvgpu_ref_init(&vm.ref); nvgpu_ref_get(&vm.ref); } else { tsg->vm = NULL; } if ((branches & free_gr_ctx_mask) == free_gr_ctx_mask) { g->ops.gr.setup.free_gr_ctx = stub_gr_setup_free_gr_ctx; } g->ops.tsg.deinit_eng_method_buffers = branches & F_TSG_RELEASE_ENG_BUFS ? stub_tsg_deinit_eng_method_buffers : NULL; if (branches & F_TSG_RELEASE_SM_ERR_STATES) { unit_assert(tsg->sm_error_states != NULL, goto done); } else { nvgpu_kfree(g, tsg->sm_error_states); tsg->sm_error_states = NULL; } nvgpu_ref_put(&tsg->refcount, nvgpu_tsg_release); if ((branches & free_gr_ctx_mask) == free_gr_ctx_mask) { unit_assert(tsg->gr_ctx == NULL, goto done); } else { g->ops.gr.setup.free_gr_ctx = gops.gr.setup.free_gr_ctx; if (branches & F_TSG_RELEASE_MEM) { nvgpu_dma_free(g, &mem); } if (tsg->gr_ctx != NULL) { nvgpu_free_gr_ctx_struct(g, tsg->gr_ctx); tsg->gr_ctx = NULL; } unit_assert(stub[1].count == 0, goto done); } if (branches & F_TSG_RELEASE_ENG_BUFS) { unit_assert(stub[0].tsgid == tsg->tsgid, goto done); } unit_assert(!f->tsg[tsg->tsgid].in_use, goto done); unit_assert(tsg->gr_ctx == NULL, goto done); unit_assert(tsg->vm == NULL, goto done); unit_assert(tsg->sm_error_states == NULL, goto done); } ret = UNIT_SUCCESS; done: if (ret != UNIT_SUCCESS) { unit_err(m, "%s branches=%s\n", __func__, branches_str(branches, labels)); } g->ops = gops; return ret; } #define F_TSG_UNBIND_CHANNEL_CHECK_HW_NEXT BIT(0) #define F_TSG_UNBIND_CHANNEL_CHECK_HW_CTX_RELOAD BIT(1) #define F_TSG_UNBIND_CHANNEL_CHECK_HW_ENG_FAULTED BIT(2) #define F_TSG_UNBIND_CHANNEL_CHECK_HW_LAST BIT(3) static const char *f_tsg_unbind_channel_check_hw[] = { "next", "ctx_reload", "eng_faulted", }; static void stub_channel_read_state_NEXT(struct gk20a *g, struct nvgpu_channel *ch, struct nvgpu_channel_hw_state *state) { state->next = true; } int test_tsg_unbind_channel_check_hw_state(struct unit_module *m, struct gk20a *g, void *args) { struct gpu_ops gops = g->ops; struct nvgpu_channel *ch = NULL; struct nvgpu_tsg *tsg = NULL; u32 branches = 0U; int ret = UNIT_FAIL; int err; u32 prune = F_TSG_UNBIND_CHANNEL_CHECK_HW_NEXT; tsg = nvgpu_tsg_open(g, getpid()); unit_assert(tsg != NULL, goto done); ch = nvgpu_channel_open_new(g, ~0U, false, getpid(), getpid()); unit_assert(ch != NULL, goto done); err = nvgpu_tsg_bind_channel(tsg, ch); unit_assert(err == 0, goto done); for (branches = 0; branches < F_TSG_UNBIND_CHANNEL_CHECK_HW_LAST; branches++) { if (pruned(branches, prune)) { unit_verbose(m, "%s branches=%s (pruned)\n", __func__, branches_str(branches, f_tsg_unbind_channel_check_hw)); continue; } subtest_setup(branches); unit_verbose(m, "%s branches=%s\n", __func__, branches_str(branches, f_tsg_unbind_channel_check_hw)); g->ops.channel.read_state = branches & F_TSG_UNBIND_CHANNEL_CHECK_HW_NEXT ? stub_channel_read_state_NEXT : gops.channel.read_state; g->ops.tsg.unbind_channel_check_ctx_reload = branches & F_TSG_UNBIND_CHANNEL_CHECK_HW_CTX_RELOAD ? gops.tsg.unbind_channel_check_ctx_reload : NULL; g->ops.tsg.unbind_channel_check_eng_faulted = branches & F_TSG_UNBIND_CHANNEL_CHECK_HW_ENG_FAULTED ? gops.tsg.unbind_channel_check_eng_faulted : NULL; err = nvgpu_tsg_unbind_channel_check_hw_state(tsg, ch); if (branches & F_TSG_UNBIND_CHANNEL_CHECK_HW_NEXT) { unit_assert(err != 0, goto done); } else { unit_assert(err == 0, goto done); } } ret = UNIT_SUCCESS; done: if (ret == UNIT_FAIL) { unit_err(m, "%s branches=%s\n", __func__, branches_str(branches, f_tsg_unbind_channel_check_hw)); } if (ch != NULL) { nvgpu_channel_close(ch); } if (tsg != NULL) { nvgpu_ref_put(&tsg->refcount, nvgpu_tsg_release); } g->ops = gops; return ret; } #define F_UNBIND_CHANNEL_CHECK_CTX_RELOAD_SET BIT(0) #define F_UNBIND_CHANNEL_CHECK_CTX_RELOAD_CHID_MATCH BIT(1) #define F_UNBIND_CHANNEL_CHECK_CTX_RELOAD_LAST BIT(2) static const char *f_unbind_channel_check_ctx_reload[] = { "reload_set", "chid_match", }; static void stub_channel_force_ctx_reload(struct nvgpu_channel *ch) { stub[0].name = __func__; stub[0].chid = ch->chid; } int test_tsg_unbind_channel_check_ctx_reload(struct unit_module *m, struct gk20a *g, void *args) { struct gpu_ops gops = g->ops; u32 branches = 0U; int ret = UNIT_FAIL; struct nvgpu_channel_hw_state hw_state; struct nvgpu_tsg *tsg = NULL; struct nvgpu_channel *chA = NULL; struct nvgpu_channel *chB = NULL; int err; tsg = nvgpu_tsg_open(g, getpid()); unit_assert(tsg != NULL, goto done); chA = nvgpu_channel_open_new(g, ~0U, false, getpid(), getpid()); unit_assert(chA != NULL, goto done); chB = nvgpu_channel_open_new(g, ~0U, false, getpid(), getpid()); unit_assert(chB != NULL, goto done); err = nvgpu_tsg_bind_channel(tsg, chA); unit_assert(err == 0, goto done); g->ops.channel.force_ctx_reload = stub_channel_force_ctx_reload; for (branches = 0; branches < F_UNBIND_CHANNEL_CHECK_CTX_RELOAD_LAST; branches++) { subtest_setup(branches); unit_verbose(m, "%s branches=%s\n", __func__, branches_str(branches, f_unbind_channel_check_ctx_reload)); hw_state.ctx_reload = branches & F_UNBIND_CHANNEL_CHECK_CTX_RELOAD_SET ? true : false; if ((branches & F_UNBIND_CHANNEL_CHECK_CTX_RELOAD_SET) && (branches & F_UNBIND_CHANNEL_CHECK_CTX_RELOAD_CHID_MATCH)) { unit_assert(nvgpu_tsg_bind_channel(tsg, chB) == 0, goto done); } nvgpu_tsg_unbind_channel_check_ctx_reload(tsg, chA, &hw_state); if ((branches & F_UNBIND_CHANNEL_CHECK_CTX_RELOAD_SET) && (branches & F_UNBIND_CHANNEL_CHECK_CTX_RELOAD_CHID_MATCH)) { nvgpu_tsg_force_unbind_channel(tsg, chB); unit_assert(stub[0].chid == chB->chid, goto done); } } ret = UNIT_SUCCESS; done: if (ret != UNIT_SUCCESS) { unit_err(m, "%s branches=%s\n", __func__, branches_str(branches, f_unbind_channel_check_ctx_reload)); } if (chA != NULL) { nvgpu_channel_close(chA); } if (chB != NULL) { nvgpu_channel_close(chB); } if (tsg != NULL) { nvgpu_ref_put(&tsg->refcount, nvgpu_tsg_release); } g->ops = gops; return ret; } #define F_TSG_ENABLE_CH BIT(0) #define F_TSG_ENABLE_STUB BIT(1) #define F_TSG_ENABLE_LAST BIT(2) static const char *f_tsg_enable[] = { "ch", "stub" }; static void stub_channel_enable(struct nvgpu_channel *ch) { stub[0].name = __func__; stub[0].chid = ch->chid; stub[0].count++; } static void stub_usermode_ring_doorbell(struct nvgpu_channel *ch) { stub[1].name = __func__; stub[1].chid = ch->chid; stub[1].count++; } static void stub_channel_disable(struct nvgpu_channel *ch) { stub[2].name = __func__; stub[2].chid = ch->chid; stub[2].count++; } int test_tsg_enable(struct unit_module *m, struct gk20a *g, void *args) { struct gpu_ops gops = g->ops; struct nvgpu_tsg *tsgA = NULL; struct nvgpu_tsg *tsgB = NULL; struct nvgpu_tsg *tsg = NULL; struct nvgpu_channel *chA = NULL; u32 branches = 0U; int ret = UNIT_FAIL; int err; tsgA = nvgpu_tsg_open(g, getpid()); unit_assert(tsgA != NULL, goto done); tsgB = nvgpu_tsg_open(g, getpid()); unit_assert(tsgB != NULL, goto done); chA = nvgpu_channel_open_new(g, ~0U, false, getpid(), getpid()); unit_assert(chA != NULL, goto done); err = nvgpu_tsg_bind_channel(tsgA, chA); unit_assert(err == 0, goto done); g->ops.channel.disable = stub_channel_disable; for (branches = 0U; branches < F_TSG_ENABLE_LAST; branches++) { subtest_setup(branches); unit_verbose(m, "%s branches=%s\n", __func__, branches_str(branches, f_tsg_enable)); tsg = branches & F_TSG_ENABLE_CH ? tsgA : tsgB; g->ops.channel.enable = branches & F_TSG_ENABLE_STUB ? stub_channel_enable : gops.channel.enable; g->ops.usermode.ring_doorbell = branches & F_TSG_ENABLE_STUB ? stub_usermode_ring_doorbell : gops.usermode.ring_doorbell; g->ops.tsg.enable(tsg); if (branches & F_TSG_ENABLE_STUB) { if (tsg == tsgB) { unit_assert(stub[0].count == 0, goto done); unit_assert(stub[1].count == 0, goto done); } if (tsg == tsgA) { unit_assert(stub[0].chid == chA->chid, goto done); unit_assert(stub[1].count > 0, goto done); } } g->ops.channel.disable = branches & F_TSG_ENABLE_STUB ? stub_channel_disable : gops.channel.disable; g->ops.tsg.disable(tsg); if (branches & F_TSG_ENABLE_STUB) { if (tsg == tsgB) { unit_assert(stub[2].count == 0, goto done); } if (tsg == tsgA) { unit_assert(stub[2].chid == chA->chid, goto done); } } } ret = UNIT_SUCCESS; done: if (ret != UNIT_SUCCESS) { unit_err(m, "%s branches=%s\n", __func__, branches_str(branches, f_tsg_enable)); } if (chA != NULL) { nvgpu_channel_close(chA); } if (tsgA != NULL) { nvgpu_ref_put(&tsgA->refcount, nvgpu_tsg_release); } if (tsgB != NULL) { nvgpu_ref_put(&tsgB->refcount, nvgpu_tsg_release); } g->ops = gops; return ret; } int test_tsg_check_and_get_from_id(struct unit_module *m, struct gk20a *g, void *args) { struct nvgpu_tsg *tsg; int ret = UNIT_FAIL; tsg = nvgpu_tsg_check_and_get_from_id(g, NVGPU_INVALID_TSG_ID); unit_assert(tsg == NULL, goto done); tsg = nvgpu_tsg_open(g, getpid()); unit_assert(tsg != NULL, goto done); unit_assert(nvgpu_tsg_check_and_get_from_id(g, tsg->tsgid) == tsg, goto done); nvgpu_ref_put(&tsg->refcount, nvgpu_tsg_release); ret = UNIT_SUCCESS; done: return ret; } #define F_TSG_ABORT_CH_ABORT_CLEANUP_NULL BIT(0) #define F_TSG_ABORT_PREEMPT BIT(1) #define F_TSG_ABORT_CH BIT(2) #define F_TSG_ABORT_NON_ABORTABLE BIT(3) #define F_TSG_ABORT_CH_NON_REFERENCABLE BIT(4) #define F_TSG_ABORT_LAST BIT(5) static const char *f_tsg_abort[] = { "preempt", "ch", "ch_abort_cleanup_null", "non_abortable", "non_referenceable" }; static int stub_fifo_preempt_tsg(struct gk20a *g, struct nvgpu_tsg *tsg) { stub[0].tsgid = tsg->tsgid; return 0; } static void stub_channel_abort_clean_up(struct nvgpu_channel *ch) { stub[1].chid = ch->chid; } int test_tsg_abort(struct unit_module *m, struct gk20a *g, void *args) { struct gpu_ops gops = g->ops; struct nvgpu_tsg *tsgA = NULL; struct nvgpu_tsg *tsgB = NULL; struct nvgpu_tsg *tsg = NULL; struct nvgpu_channel *chA = NULL; bool preempt = false; u32 branches = 0U; int ret = UNIT_FAIL; u32 prune = F_TSG_ABORT_NON_ABORTABLE; int err; tsgA = nvgpu_tsg_open(g, getpid()); unit_assert(tsgA != NULL, goto done); tsgB = nvgpu_tsg_open(g, getpid()); unit_assert(tsgB != NULL, goto done); chA = nvgpu_channel_open_new(g, ~0U, false, getpid(), getpid()); unit_assert(chA != NULL, goto done); for (branches = 0U; branches < F_TSG_ABORT_LAST; branches++) { if (pruned(branches, prune)) { unit_verbose(m, "%s branches=%s (pruned)\n", __func__, branches_str(branches, f_tsg_abort)); continue; } subtest_setup(branches); unit_verbose(m, "%s branches=%s\n", __func__, branches_str(branches, f_tsg_abort)); g->ops.channel.abort_clean_up = branches & F_TSG_ABORT_CH_ABORT_CLEANUP_NULL ? NULL : stub_channel_abort_clean_up; g->ops.fifo.preempt_tsg = stub_fifo_preempt_tsg; tsg = branches & F_TSG_ABORT_CH ? tsgA : tsgB; tsg->abortable = branches & F_TSG_ABORT_NON_ABORTABLE ? false : true; preempt = branches & F_TSG_ABORT_PREEMPT ? true : false; if (branches & F_TSG_ABORT_CH_ABORT_CLEANUP_NULL) { g->ops.channel.abort_clean_up = NULL; } if (branches & F_TSG_ABORT_CH_NON_REFERENCABLE) { chA->referenceable = false; } if (chA->tsgid == NVGPU_INVALID_TSG_ID) { err = nvgpu_tsg_bind_channel(tsgA, chA); unit_assert(err == 0, goto done); } nvgpu_tsg_abort(g, tsg, preempt); unit_assert(preempt == (stub[0].tsgid == tsg->tsgid), goto done); unit_assert(chA->unserviceable == ((tsg == tsgA) && (chA->referenceable)), goto done); if (!((branches & F_TSG_ABORT_CH_ABORT_CLEANUP_NULL) || (branches & F_TSG_ABORT_CH_NON_REFERENCABLE))) { unit_assert((stub[1].chid == chA->chid) == (tsg == tsgA), goto done); unit_assert((stub[1].chid == NVGPU_INVALID_CHANNEL_ID) == (tsg == tsgB), goto done); } tsg->abortable = true; chA->unserviceable = false; chA->referenceable = true; } ret = UNIT_SUCCESS; done: if (ret == UNIT_FAIL) { unit_err(m, "%s branches=%s\n", __func__, branches_str(branches, f_tsg_abort)); } if (chA != NULL) { nvgpu_channel_close(chA); } if (tsgA != NULL) { nvgpu_ref_put(&tsgA->refcount, nvgpu_tsg_release); } if (tsgB != NULL) { nvgpu_ref_put(&tsgB->refcount, nvgpu_tsg_release); } g->ops = gops; return ret; } #define F_TSG_SETUP_SW_VZALLOC_FAIL BIT(0) #define F_TSG_SETUP_SW_LAST BIT(1) static const char *f_tsg_setup_sw[] = { "vzalloc_fail", }; int test_tsg_setup_sw(struct unit_module *m, struct gk20a *g, void *args) { struct gpu_ops gops = g->ops; struct nvgpu_posix_fault_inj *kmem_fi; u32 branches = 0U; int ret = UNIT_FAIL; int err; u32 fail = F_TSG_SETUP_SW_VZALLOC_FAIL; u32 prune = fail; kmem_fi = nvgpu_kmem_get_fault_injection(); for (branches = 0U; branches < F_TSG_SETUP_SW_LAST; branches++) { if (pruned(branches, prune)) { unit_verbose(m, "%s branches=%s (pruned)\n", __func__, branches_str(branches, f_tsg_setup_sw)); continue; } subtest_setup(branches); unit_verbose(m, "%s branches=%s\n", __func__, branches_str(branches, f_tsg_setup_sw)); nvgpu_posix_enable_fault_injection(kmem_fi, branches & F_TSG_SETUP_SW_VZALLOC_FAIL ? true : false, 0); err = nvgpu_tsg_setup_sw(g); if (branches & fail) { unit_assert(err != 0, goto done); } else { unit_assert(err == 0, goto done); nvgpu_tsg_cleanup_sw(g); } } ret = UNIT_SUCCESS; done: nvgpu_posix_enable_fault_injection(kmem_fi, false, 0); if (ret != UNIT_SUCCESS) { unit_err(m, "%s branches=%s\n", __func__, branches_str(branches, f_tsg_setup_sw)); } g->ops = gops; return ret; } #define F_TSG_MARK_ERROR_NO_CHANNEL BIT(0) #define F_TSG_MARK_ERROR_NON_REFERENCABLE BIT(1) #define F_TSG_MARK_ERROR_VERBOSE BIT(2) #define F_TSG_MARK_ERROR_LAST BIT(3) int test_tsg_mark_error(struct unit_module *m, struct gk20a *g, void *args) { struct gpu_ops gops = g->ops; struct nvgpu_tsg *tsg = NULL; struct nvgpu_channel *ch = NULL; int ret = UNIT_FAIL; bool verbose; int err; u32 branches; static const char *labels[] = { "no_channel", "non_referencable", "verbose", }; u32 prune = F_TSG_MARK_ERROR_NO_CHANNEL | F_TSG_MARK_ERROR_NON_REFERENCABLE; struct nvgpu_posix_channel ch_priv; for (branches = 0U; branches < F_TSG_MARK_ERROR_LAST; branches++) { if (pruned(branches, prune)) { unit_verbose(m, "%s branches=%s (pruned)\n", __func__, branches_str(branches, labels)); continue; } subtest_setup(branches); unit_verbose(m, "%s branches=%s\n", __func__, branches_str(branches, labels)); tsg = nvgpu_tsg_open(g, getpid()); unit_assert(tsg != NULL, goto done); ch = nvgpu_channel_open_new(g, ~0U, false, getpid(), getpid()); unit_assert(ch != NULL, goto done); ch->os_priv = &ch_priv; ch_priv.err_notifier.error = U32_MAX; ch_priv.err_notifier.status = 0; if ((branches & F_TSG_MARK_ERROR_NO_CHANNEL) == 0) { err = nvgpu_tsg_bind_channel(tsg, ch); unit_assert(err == 0, goto done); } if (branches & F_TSG_MARK_ERROR_NON_REFERENCABLE) { ch->referenceable = false; } ch->ctxsw_timeout_debug_dump = branches & F_TSG_MARK_ERROR_VERBOSE ? true : false; nvgpu_tsg_set_error_notifier(g, tsg, NVGPU_ERR_NOTIFIER_FIFO_ERROR_IDLE_TIMEOUT); verbose = nvgpu_tsg_mark_error(g, tsg); if ((branches & F_TSG_MARK_ERROR_NO_CHANNEL) || (branches & F_TSG_MARK_ERROR_NON_REFERENCABLE)) { unit_assert(!verbose, goto done); } if (branches & F_TSG_MARK_ERROR_VERBOSE) { unit_assert(verbose, goto done); } else { unit_assert(!verbose, goto done); } nvgpu_channel_close(ch); ch = NULL; nvgpu_ref_put(&tsg->refcount, nvgpu_tsg_release); tsg = NULL; } ret = UNIT_SUCCESS; done: if (ret != UNIT_SUCCESS) { unit_err(m, "%s branches=%s\n", __func__, branches_str(branches, labels)); } if (ch != NULL) { nvgpu_channel_close(ch); } if (tsg != NULL) { nvgpu_ref_put(&tsg->refcount, nvgpu_tsg_release); } g->ops = gops; return ret; } int test_tsg_set_ctx_mmu_error(struct unit_module *m, struct gk20a *g, void *args) { struct nvgpu_tsg *tsg = NULL; struct nvgpu_channel *ch = NULL; int ret = UNIT_FAIL; struct nvgpu_posix_channel ch_priv; int err; tsg = nvgpu_tsg_open(g, getpid()); unit_assert(tsg != NULL, goto done); ch = nvgpu_channel_open_new(g, ~0U, false, getpid(), getpid()); unit_assert(ch != NULL, goto done); err = nvgpu_tsg_bind_channel(tsg, ch); unit_assert(err == 0, goto done); ch->os_priv = &ch_priv; ch_priv.err_notifier.error = U32_MAX; ch_priv.err_notifier.status = 0; nvgpu_tsg_set_ctx_mmu_error(g, tsg); unit_assert(ch_priv.err_notifier.error == NVGPU_ERR_NOTIFIER_FIFO_ERROR_MMU_ERR_FLT, goto done); unit_assert(ch_priv.err_notifier.status != 0, goto done); ret = UNIT_SUCCESS; done: if (ch != NULL) { nvgpu_channel_close(ch); } if (tsg != NULL) { nvgpu_ref_put(&tsg->refcount, nvgpu_tsg_release); } return ret; } #define F_TSG_RESET_FAULTED_NO_RESET_HAL BIT(0) #define F_TSG_RESET_FAULTED_TSG_NULL BIT(1) #define F_TSG_RESET_FAULTED_LAST BIT(2) static void stub_channel_reset_faulted(struct gk20a *g, struct nvgpu_channel *ch, bool eng, bool pbdma) { stub[0].name = __func__; stub[0].chid = ch->chid; } int test_tsg_reset_faulted_eng_pbdma(struct unit_module *m, struct gk20a *g, void *args) { struct nvgpu_tsg *tsg = NULL; struct nvgpu_channel *ch = NULL; int ret = UNIT_FAIL; struct gpu_ops gops = g->ops; int err; u32 branches; static const char *labels[] = { "no_reset_hal", "tsg_null" }; u32 fail = F_TSG_RESET_FAULTED_NO_RESET_HAL | F_TSG_RESET_FAULTED_TSG_NULL; u32 prune = fail; tsg = nvgpu_tsg_open(g, getpid()); unit_assert(tsg != NULL, goto done); ch = nvgpu_channel_open_new(g, ~0U, false, getpid(), getpid()); unit_assert(ch != NULL, goto done); err = nvgpu_tsg_bind_channel(tsg, ch); unit_assert(err == 0, goto done); for (branches = 0U; branches < F_TSG_MARK_ERROR_LAST; branches++) { if (pruned(branches, prune)) { unit_verbose(m, "%s branches=%s (pruned)\n", __func__, branches_str(branches, labels)); continue; } subtest_setup(branches); unit_verbose(m, "%s branches=%s\n", __func__, branches_str(branches, labels)); g->ops.channel.reset_faulted = branches & F_TSG_RESET_FAULTED_NO_RESET_HAL ? NULL : stub_channel_reset_faulted; if (branches & F_TSG_RESET_FAULTED_TSG_NULL) { nvgpu_tsg_reset_faulted_eng_pbdma(g, NULL, true, true); } else { nvgpu_tsg_reset_faulted_eng_pbdma(g, tsg, true, true); } if (branches & fail) { unit_assert(stub[0].chid != ch->chid, goto done); } else { unit_assert(stub[0].chid == ch->chid, goto done); } } ret = UNIT_SUCCESS; done: if (ch != NULL) { nvgpu_channel_close(ch); } if (tsg != NULL) { nvgpu_ref_put(&tsg->refcount, nvgpu_tsg_release); } g->ops = gops; return ret; } struct unit_module_test nvgpu_tsg_tests[] = { UNIT_TEST(setup_sw, test_tsg_setup_sw, &unit_ctx, 0), UNIT_TEST(init_support, test_fifo_init_support, &unit_ctx, 0), UNIT_TEST(open, test_tsg_open, &unit_ctx, 0), UNIT_TEST(release, test_tsg_release, &unit_ctx, 0), UNIT_TEST(get_from_id, test_tsg_check_and_get_from_id, &unit_ctx, 0), UNIT_TEST(bind_channel, test_tsg_bind_channel, &unit_ctx, 2), UNIT_TEST(unbind_channel, test_tsg_unbind_channel, &unit_ctx, 0), UNIT_TEST(unbind_channel_check_hw_state, test_tsg_unbind_channel_check_hw_state, &unit_ctx, 0), UNIT_TEST(unbind_channel_check_ctx_reload, test_tsg_unbind_channel_check_ctx_reload, &unit_ctx, 0), UNIT_TEST(enable_disable, test_tsg_enable, &unit_ctx, 0), UNIT_TEST(abort, test_tsg_abort, &unit_ctx, 0), UNIT_TEST(mark_error, test_tsg_mark_error, &unit_ctx, 0), UNIT_TEST(set_ctx_mmu_error, test_tsg_set_ctx_mmu_error, &unit_ctx, 0), UNIT_TEST(reset_faulted_eng_pbdma, test_tsg_reset_faulted_eng_pbdma, &unit_ctx, 0), UNIT_TEST(remove_support, test_fifo_remove_support, &unit_ctx, 0), }; UNIT_MODULE(nvgpu_tsg, nvgpu_tsg_tests, UNIT_PRIO_NVGPU_TEST);