#include // For seq_* functions and types #include // Macros to detect kernel version #include "nvdebug.h" #define RUNLIST_PROCFS_NAME "runlist" #define DETAILED_CHANNEL_INFO static int runlist_detail_seq_show_chan(struct seq_file *s, struct gk20a *g, uint32_t chid) { channel_ctrl_t chan; char *loc_txt; u64 instance_ptr; chan.raw = nvdebug_readq(g, NV_PCCSR_CHANNEL_INST(chid)); loc_txt = target_to_text(chan.inst_target); if (!loc_txt) return -EIO; instance_ptr = chan.inst_ptr; instance_ptr <<= 12; seq_printf(s, " +- Channel Info %-4d -+\n", chid); seq_printf(s, " | Enabled: %d|\n", chan.enable); seq_printf(s, " | Next: %d|\n", chan.next); seq_printf(s, " | Force CTX Reload: %d|\n", chan.force_ctx_reload); seq_printf(s, " | Enable set: %d|\n", chan.enable_set); seq_printf(s, " | Enable clear: %d|\n", chan.enable_clear); seq_printf(s, " | PBDMA Faulted: %d|\n", chan.pbdma_faulted); seq_printf(s, " | ENG Faulted: %d|\n", chan.eng_faulted); seq_printf(s, " | Status: %2d|\n", chan.status); seq_printf(s, " | Busy: %d|\n", chan.busy); seq_printf(s, " | Instance PTR: |\n"); seq_printf(s, " | %#018llx |\n", instance_ptr); seq_printf(s, " | %-20s|\n", loc_txt); seq_printf(s, " | Instance bound: %d|\n", chan.inst_bind); seq_printf(s, " +---------------------+\n"); return 0; } #if LINUX_VERSION_CODE < KERNEL_VERSION(4,19,0) // Bug workaround. See comment in runlist_file_seq_start() static loff_t pos_fixup; #endif static void *runlist_file_seq_start(struct seq_file *s, loff_t *pos) { static struct runlist_iter rl_iter; // *pos == 0 for first call after read of file if (*pos == 0) { int err = get_runlist_iter(&rl_iter); if (err) return NULL; return &rl_iter; } // If we're resuming an earlier print if (*pos < rl_iter.rl_info.len) { #if LINUX_VERSION_CODE < KERNEL_VERSION(4,19,0) // There's a nasty bug prior to 4.19-rc1 that if the buffer overflows, the // last update to `pos` is not saved. Work around that here by reloading a // saved copy of `pos`. if (!pos_fixup) return NULL; *pos = pos_fixup; #endif return &rl_iter; } // When called with *pos != 0, we already traversed the runlist return NULL; } static void* runlist_file_seq_next(struct seq_file *s, void *raw_rl_iter, loff_t *pos) { struct runlist_iter* rl_iter = raw_rl_iter; void *ret = NULL; // Advance by one TSG + channels under last TSG *pos += 1 + rl_iter->curr_tsg->tsg_length; // Verify we haven't reached the end of the runlist // rl_info.len is the num of tsg entries + total num of channel entries if (*pos < rl_iter->rl_info.len) { rl_iter->curr_tsg = next_tsg(rl_iter->curr_tsg); ret = rl_iter; } #if LINUX_VERSION_CODE < KERNEL_VERSION(4,19,0) // Bug workaround. See comment in runlist_file_seq_start() pos_fixup = ret ? *pos : 0; #endif return ret; } static void runlist_file_seq_stop(struct seq_file *s, void *raw_rl_iter) { // No cleanup needed } static int runlist_file_seq_show(struct seq_file *s, void *raw_rl_iter) { struct entry_tsg* tsg = ((struct runlist_iter*)raw_rl_iter)->curr_tsg; struct runlist_chan* chan; struct gk20a *g = get_live_gk20a(); if (!g) return -EIO; if (tsg->entry_type != ENTRY_TYPE_TSG) { printk(KERN_WARNING "[nvdebug] Attempted to print non-TSG in tsg print logic!\n"); return -EIO; } seq_printf(s, "+---- TSG Entry %-2d----+\n", tsg->tsgid); seq_printf(s, "| Scale: %-13d|\n", tsg->timeslice_scale); seq_printf(s, "| Timeout: %-11d|\n", tsg->timeslice_timeout); seq_printf(s, "+---------------------+\n"); for_chan_in_tsg(chan, tsg) { #ifndef DETAILED_CHANNEL_INFO char* loc_txt; u64 instance_ptr; #endif if (chan->entry_type != ENTRY_TYPE_CHAN) { printk(KERN_WARNING "[nvdebug] Attempted to print non-channel in channel print logic!\n"); return -EIO; } #ifdef DETAILED_CHANNEL_INFO runlist_detail_seq_show_chan(s, g, chan->chid); #else loc_txt = target_to_text(chan->inst_target); if (!loc_txt) { printk(KERN_WARNING "[nvdebug] Invalid apature in channel print logic!\n"); return -EIO; } // Reconstruct pointer to channel instance block instance_ptr = chan->inst_ptr_hi; instance_ptr <<= 32; instance_ptr |= chan->inst_ptr_lo << 12; seq_printf(s, " +- Channel Entry %-4d-+\n", chan->chid); seq_printf(s, " | Runqueue Selector: %d|\n", chan->runqueue_selector); seq_printf(s, " | Instance PTR: |\n"); seq_printf(s, " | %#018llx |\n", instance_ptr); seq_printf(s, " | %-20s|\n", loc_txt); seq_printf(s, " +---------------------+\n"); #endif } return 0; } static const struct seq_operations runlist_file_seq_ops = { .start = runlist_file_seq_start, .next = runlist_file_seq_next, .stop = runlist_file_seq_stop, .show = runlist_file_seq_show, }; static int runlist_file_open(struct inode *inode, struct file *f) { return seq_open(f, &runlist_file_seq_ops); } const struct file_operations runlist_file_ops = { .open = runlist_file_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release, }; ssize_t preempt_tsg_file_write(struct file *f, const char __user *buffer, size_t count, loff_t *off) { uint32_t target_tsgid; // Passing 0 as the base to kstrtou32 indicates autodetect hex/octal/dec int err = kstrtou32_from_user(buffer, count, 0, &target_tsgid); if (err) return err; // TSG IDs are a 12-bit field, so make sure the request is in-range if (target_tsgid > MAX_TSGID) return -ERANGE; // Execute preemption err = preempt_tsg(target_tsgid); if (err) return err; return count; } const struct file_operations preempt_tsg_file_ops = { .write = preempt_tsg_file_write, }; ssize_t disable_channel_file_write(struct file *f, const char __user *buffer, size_t count, loff_t *off) { uint32_t target_channel; channel_ctrl_t chan; int err; struct gk20a *g = get_live_gk20a(); if (!g) return -EIO; // Passing 0 as the base to kstrtou32 indicates autodetect hex/octal/dec err = kstrtou32_from_user(buffer, count, 0, &target_channel); if (err) return err; if (target_channel > MAX_CHID) return -ERANGE; // Disable channel chan.raw = nvdebug_readq(g, NV_PCCSR_CHANNEL_INST(target_channel)); chan.enable_clear = true; nvdebug_writeq(g, NV_PCCSR_CHANNEL_INST(target_channel), chan.raw); return count; } const struct file_operations disable_channel_file_ops = { .write = disable_channel_file_write, }; ssize_t enable_channel_file_write(struct file *f, const char __user *buffer, size_t count, loff_t *off) { uint32_t target_channel; channel_ctrl_t chan; int err; struct gk20a *g = get_live_gk20a(); if (!g) return -EIO; // Passing 0 as the base to kstrtou32 indicates autodetect hex/octal/dec err = kstrtou32_from_user(buffer, count, 0, &target_channel); if (err) return err; if (target_channel > MAX_CHID) return -ERANGE; // Disable channel chan.raw = nvdebug_readq(g, NV_PCCSR_CHANNEL_INST(target_channel)); chan.enable_set = true; nvdebug_writeq(g, NV_PCCSR_CHANNEL_INST(target_channel), chan.raw); return count; } const struct file_operations enable_channel_file_ops = { .write = enable_channel_file_write, }; ssize_t switch_to_tsg_file_write(struct file *f, const char __user *buffer, size_t count, loff_t *off) { uint32_t target_tsgid; struct runlist_chan* chan; channel_ctrl_t chan_ctl; struct runlist_iter rl_iter; int err; loff_t pos = 0; struct gk20a *g = get_live_gk20a(); if (!g) return -EIO; // Passing 0 as the base to kstrtou32 indicates autodetect hex/octal/dec err = kstrtou32_from_user(buffer, count, 0, &target_tsgid); if (err) return err; if (target_tsgid > MAX_TSGID) return -ERANGE; err = get_runlist_iter(&rl_iter); if (err) return err; // Iterate through all TSGs while (pos < rl_iter.rl_info.len) { if (rl_iter.curr_tsg->tsgid == target_tsgid) { // Enable channels of target TSG for_chan_in_tsg(chan, rl_iter.curr_tsg) { chan_ctl.raw = nvdebug_readq(g, NV_PCCSR_CHANNEL_INST(chan->chid)); chan_ctl.enable_set = true; nvdebug_writeq(g, NV_PCCSR_CHANNEL_INST(chan->chid), chan_ctl.raw); } } else { // Disable all other channels for_chan_in_tsg(chan, rl_iter.curr_tsg) { chan_ctl.raw = nvdebug_readq(g, NV_PCCSR_CHANNEL_INST(chan->chid)); chan_ctl.enable_clear = true; nvdebug_writeq(g, NV_PCCSR_CHANNEL_INST(chan->chid), chan_ctl.raw); } } pos += 1 + rl_iter.curr_tsg->tsg_length; rl_iter.curr_tsg = next_tsg(rl_iter.curr_tsg); } // Switch to next TSG with active channels (should be our TSG) err = preempt_tsg(target_tsgid); if (err) return err; return count; } const struct file_operations switch_to_tsg_file_ops = { .write = switch_to_tsg_file_write, };