#include <linux/seq_file.h> // For seq_* functions and types
#include <linux/version.h> // 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,
};