aboutsummaryrefslogtreecommitdiffstats
path: root/runlist_procfs.c
blob: f7f937dffeb15dc0d41795fd3b56d32facb95e17 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
#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

/* Print channel details using PCCSR (Programmable Channel Control System RAM?)
 * @param s      Pointer to state from seq_file subsystem to pass to seq_printf
 * @param g      Pointer to our internal GPU state
 * @param chid   ID of channel to print details on, range [0, 512)
 * @param prefix Text string to prefix each line with, or empty string
 */
#ifdef DETAILED_CHANNEL_INFO
static int runlist_detail_seq_show_chan(struct seq_file *s, struct nvdebug_state *g, uint32_t chid, char *prefix) {
	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, "%s+- Channel Info %-4d -+\n", prefix, chid);
	seq_printf(s, "%s| Enabled:           %d|\n", prefix, chan.enable);
	seq_printf(s, "%s| Next:              %d|\n", prefix, chan.next);
	seq_printf(s, "%s| Force CTX Reload:  %d|\n", prefix, chan.force_ctx_reload);
	seq_printf(s, "%s| Enable set:        %d|\n", prefix, chan.enable_set);
	seq_printf(s, "%s| Enable clear:      %d|\n", prefix, chan.enable_clear);
	seq_printf(s, "%s| PBDMA Faulted:     %d|\n", prefix, chan.pbdma_faulted);
	seq_printf(s, "%s| ENG Faulted:       %d|\n", prefix, chan.eng_faulted);
	seq_printf(s, "%s| Status:           %2d|\n", prefix, chan.status);
	seq_printf(s, "%s| Busy:              %d|\n", prefix, chan.busy);
	seq_printf(s, "%s| Instance PTR:       |\n", prefix);
	seq_printf(s, "%s| %#018llx  |\n", prefix, instance_ptr);
	seq_printf(s, "%s| %-20s|\n", prefix, loc_txt);
	seq_printf(s, "%s| Instance bound:    %d|\n", prefix, chan.inst_bind);
	// START TEMP
	// "runlist_id -1 is synonym for the ENGINE_GR_GK20A runlist id"
	// GR, GRCE, and ASYNC_CE
	// Note that this appears to be broken??
	// Peek into the channel instance RAM
	if (chan.inst_target == TARGET_SYS_MEM_COHERENT) {
		seq_printf(s, "%s| Target Engine:    %2d|\n", prefix, *(uint32_t*)phys_to_virt(instance_ptr + 4/*bytes for 32bits*/*43/*NV_RAMFC_TARGET*/) & 0x1f);
		seq_printf(s, "%s| PDB LO:   %#08x|\n", prefix, *(uint32_t*)phys_to_virt(instance_ptr + 4/*bytes for 32bits*/*128/*NV_RAMIN_PAGE_DIR_BASE_LO*/) & 0xfffff000);
		seq_printf(s, "%s| Num subcontexts:  %2ld|\n", prefix, hweight64(*(uint64_t*)phys_to_virt(instance_ptr + 4/*bytes for 32bits*/*166/*NV_RAMIN_SC_PDB_VALID*/)));
		// This appears to be unset on Xavier
		//seq_printf(s, "%s| PAS ID:     %8ld|\n", prefix, *(uint32_t*)phys_to_virt(instance_ptr + 4/*bytes for 32bits*/*135/*NV_RAMIN_PASID*/) & 0xfffff);
	}
	// END TEMP
	seq_printf(s, "%s+---------------------+\n", prefix);
	return 0;
}
#endif

#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;
	struct nvdebug_state *g = &g_nvdebug_state[file2parentgpuidx(s->file)];
	// *pos == 0 for first call after read of file
	if (*pos == 0) {
		int err = get_runlist_iter(g, seq2gpuidx(s), &rl_iter);
		if (err)
			return ERR_PTR(err);
		// Don't try to print an empty runlist
		if (rl_iter.rl_info.len <= 0)
			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;
	struct nvdebug_state *g = &g_nvdebug_state[file2parentgpuidx(s->file)];
	// Advance by one TSG or channel
	(*pos)++;
	rl_iter->curr_entry += NV_RL_ENTRY_SIZE(g);
	// 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) {
		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 runlist_iter *rl_iter = raw_rl_iter;
	void *entry = rl_iter->curr_entry;
	struct nvdebug_state *g = &g_nvdebug_state[file2parentgpuidx(s->file)];
	if (entry_type(g, entry) == ENTRY_TYPE_TSG) {
		if (rl_iter->channels_left_in_tsg) {
			printk(KERN_WARNING "[nvdebug] Found TSG ID%d @ %px when %d channels were still expected under the previous TSG in the runlist!\n", tsgid(g, entry), entry, rl_iter->channels_left_in_tsg);
			while (rl_iter->channels_left_in_tsg--)
				seq_printf(s, "[missing channel]\n");
		}
		rl_iter->channels_left_in_tsg = tsg_length(g, entry);
		seq_printf(s, "+---- TSG Entry %-3d---+\n", tsgid(g, entry));
		seq_printf(s, "| Scale: %-13d|\n", timeslice_scale(g, entry));
		seq_printf(s, "| Timeout: %-11d|\n", timeslice_timeout(g, entry));
		seq_printf(s, "| Length: %-12d|\n", tsg_length(g, entry));
		seq_printf(s, "+---------------------+\n");
	} else {
		char *indt = "";
#ifndef DETAILED_CHANNEL_INFO
		u64 instance_ptr = 0;
#endif
		if (rl_iter->channels_left_in_tsg) {
			indt = "  ";
			rl_iter->channels_left_in_tsg--;
		}
#ifdef DETAILED_CHANNEL_INFO
		runlist_detail_seq_show_chan(s, g, chid(g, entry), indt);
#else
		// Reconstruct pointer to channel instance block
		if (g->chip_id >= NV_CHIP_ID_VOLTA) {
			instance_ptr = ((struct gv100_runlist_chan*)entry)->inst_ptr_hi;
			instance_ptr <<= 32;
		}
		instance_ptr |= inst_ptr_lo(g, entry) << 12;

		seq_printf(s, "%s+- Channel Entry %-4d-+\n", indt, chid(g, entry));
		if (g->chip_id >= NV_CHIP_ID_VOLTA)
			seq_printf(s, "%s| Runqueue Selector: %d|\n", indt,
				   ((struct gv100_runlist_chan*)entry)->runqueue_selector);
		seq_printf(s, "%s| Instance PTR:       |\n", indt);
		seq_printf(s, "%s| %#018llx  |\n", indt, instance_ptr);
		seq_printf(s, "%s| %-20s|\n", indt, target_to_text(inst_target(g, entry)));
		seq_printf(s, "%s+---------------------+\n", indt);
#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);
}

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);
	struct nvdebug_state *g = &g_nvdebug_state[file2gpuidx(f)];
	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(g, target_tsgid);
	if (err)
		return err;

	return count;
}

struct file_operations preempt_tsg_file_ops = {
	.write = preempt_tsg_file_write,
	.llseek = default_llseek,
};

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;
	runlist_info_t rl_info;
	runlist_disable_t rl_disable;
	struct nvdebug_state *g = &g_nvdebug_state[file2gpuidx(f)];
	// 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;
	// disable sched
	rl_info.raw = nvdebug_readl(g, NV_PFIFO_RUNLIST);
	rl_disable.raw = nvdebug_readl(g, NV_PFIFO_SCHED_DISABLE);
	rl_disable.raw |= BIT(rl_info.id);
	nvdebug_writel(g, NV_PFIFO_SCHED_DISABLE, rl_disable.raw);
	// disable chan
	nvdebug_writeq(g, NV_PCCSR_CHANNEL_INST(target_channel), chan.raw);
	// enable sched
	rl_disable.raw &= ~BIT(rl_info.id);
	nvdebug_writel(g, NV_PFIFO_SCHED_DISABLE, rl_disable.raw);

	return count;
}

struct file_operations disable_channel_file_ops = {
	.write = disable_channel_file_write,
	.llseek = default_llseek,
};

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 nvdebug_state *g = &g_nvdebug_state[file2gpuidx(f)];
	// 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;
}

struct file_operations enable_channel_file_ops = {
	.write = enable_channel_file_write,
	.llseek = default_llseek,
};

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 gv100_runlist_chan* chan;
	channel_ctrl_t chan_ctl;
	struct runlist_iter rl_iter;
	int err;
	loff_t pos = 0;
	struct nvdebug_state *g = &g_nvdebug_state[file2gpuidx(f)];
	// 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(g, 0, &rl_iter);
	if (err)
		return err;

	// Iterate through all TSGs
	while (pos < rl_iter.rl_info.len) {
		if (tsgid(g, rl_iter.curr_entry) == target_tsgid) {
			// Enable channels of target TSG
			for_chan_in_tsg(g, chan, rl_iter.curr_entry) {
				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 {
			// XXX: Fix for bare channels. Maybe a "for_chan_until_tsg" macro?
			// Disable all other channels
			// (This is how the Jetson nvgpu driver disables TSGs)
			for_chan_in_tsg(g, chan, rl_iter.curr_entry) {
				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 + tsg_length(g, rl_iter.curr_entry);
		rl_iter.curr_entry = next_tsg(g, rl_iter.curr_entry);
	}
	// Switch to next TSG with active channels (should be our TSG)
	err = preempt_tsg(g, target_tsgid);
	if (err)
		return err;

	return count;
}

struct file_operations switch_to_tsg_file_ops = {
	.write = switch_to_tsg_file_write,
	.llseek = default_llseek,
};