aboutsummaryrefslogtreecommitdiffstats
path: root/device_info_procfs.c
blob: 1fc0586852043fb29680410c51fa2eb980cf77f5 (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
#include "nvdebug.h"
#include <linux/seq_file.h> // For seq_* functions and types
#include <linux/uaccess.h> // For copy_to_user()

// Generic register printing function, used for PTOP_*_NUM registers (+more)
// @param f    File being read from. `data` field is register offset to read.
// @param buf  User buffer for result
// @param size Length of user buffer
// @param off  Requested offset. Updated by number of characters written.
// @return -errno on error, otherwise number of bytes written to *buf
// Note: Parent `data` field MUST be the GPU index
static ssize_t nvdebug_reg32_read(struct file *f, char __user *buf, size_t size, loff_t *off) {
	char out[16];
	int chars_written;
	struct nvdebug_state *g = &g_nvdebug_state[file2parentgpuidx(f)];
	if (size < 16 || *off != 0)
		return 0;
	// 32 bit register will always take less than 16 characters to print
	chars_written = scnprintf(out, 16, "%#0x\n", nvdebug_readl(g, (uintptr_t)PDE_DATA(file_inode(f))));
	if (copy_to_user(buf, out, chars_written))
		printk(KERN_WARNING "Unable to copy all data for %s\n", file_dentry(f)->d_name.name);
	*off += chars_written;
	return chars_written;
}
struct file_operations nvdebug_read_reg32_file_ops = {
	.read = nvdebug_reg32_read,
	.llseek = default_llseek,
};

//// ==v== PTOP_DEVICE_INFO ==v== ////

// Called to start or resume a sequence. Prior to 4.19, *pos is unreliable.
// Initializes iterator `idx` state and returns it. Ends sequence on NULL.
static void* device_info_file_seq_start(struct seq_file *s, loff_t *pos) {
	static int idx;
	// If start of sequence, reset `idx`
	if (*pos == 0)
		idx = 0;
	// Number of possible info entries is fixed, and list is sparse
	if (idx >= NV_PTOP_DEVICE_INFO__SIZE_1)
		return NULL;
	return &idx;
}

// Steps to next record. Returns new value of `idx`.
// Calls show() on non-NULL return
static void* device_info_file_seq_next(struct seq_file *s, void *idx,
				       loff_t *pos) {
	(*pos)++; // Required by seq interface
	// Number of possible info entries is fixed, and list is sparse
	if ((*(int*)idx)++ >= NV_PTOP_DEVICE_INFO__SIZE_1)
		return NULL;
	return idx;
}

// Print info at index *idx. Returns non-zero on error.
static int device_info_file_seq_show(struct seq_file *s, void *idx) {
	ptop_device_info_t curr_info;
	struct nvdebug_state *g = &g_nvdebug_state[seq2gpuidx(s)];
	
	curr_info.raw = nvdebug_readl(g, NV_PTOP_DEVICE_INFO(*(int*)idx));
	// Check for read errors
	if (curr_info.raw == -1)
		return -EIO;

	// Parse and print the data
	switch(curr_info.info_type) {
	case INFO_TYPE_DATA:
		// As of early 2022, only the ENUM2 format of this entry exists
		if (curr_info.is_not_enum2)
			break;
		seq_printf(s, "| BAR0 Base %#.8x\n"
			      "|           instance %d\n",
			curr_info.pri_base << 12, curr_info.inst_id);
		if (curr_info.fault_id_is_valid)
			seq_printf(s, "| Fault ID:        %3d\n", curr_info.fault_id);
		break;
	case INFO_TYPE_ENUM:
		if (curr_info.engine_is_valid)
			seq_printf(s, "| Host's Engine ID: %2d\n", curr_info.engine_enum);
		if (curr_info.runlist_is_valid)
			seq_printf(s, "| Runlist ID:       %2d\n", curr_info.runlist_enum);
		if (curr_info.intr_is_valid)
			seq_printf(s, "| Interrupt ID:     %2d\n", curr_info.intr_enum);
		if (curr_info.reset_is_valid)
			seq_printf(s, "| Reset ID:         %2d\n", curr_info.reset_enum);
		break;
	case INFO_TYPE_ENGINE_TYPE:
		seq_printf(s, "| Engine Type:      %2d (", curr_info.engine_type);
		if (curr_info.engine_type < ENGINE_TYPES_LEN)
			seq_printf(s, "%s)\n", ENGINE_TYPES_NAMES[curr_info.engine_type]);
		else
			seq_printf(s, "Unknown Engine, introduced post-Ampere)\n");
		break;
	case INFO_TYPE_NOT_VALID:
	default:
		// Device info records are sparse, so skip unset or unknown ones
		return 0;
	}

	// Draw a line between each device entry
	if (!curr_info.has_next_entry)
		seq_printf(s, "+---------------------+\n");
	return 0;
}

static void device_info_file_seq_stop(struct seq_file *s, void *idx) {
        // No cleanup needed
}

static const struct seq_operations device_info_file_seq_ops = {
	.start = device_info_file_seq_start,
	.next = device_info_file_seq_next,
	.stop = device_info_file_seq_stop,
	.show = device_info_file_seq_show,
};

static int device_info_file_open(struct inode *inode, struct file *f) {
	return seq_open(f, &device_info_file_seq_ops);
}

struct file_operations device_info_file_ops = {
	.open = device_info_file_open,
	.read = seq_read,
	.llseek = seq_lseek,
	.release = seq_release,
};