aboutsummaryrefslogtreecommitdiffstats
path: root/device_info_procfs.c
blob: b1e58b1db7d900490c0cd764fced2cdf344b6986 (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
#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_previous(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;
}

static void* device_info_file_seq_start_ampere(struct seq_file *s, loff_t *pos) {
        static int idx;
        // If start of sequence, reset `idx`
        if (*pos == 0)
                idx = 0;
        struct nvdebug_state *g = &g_nvdebug_state[seq2gpuidx(s)];
        // Number of possible info entries is fixed, and list is sparse
        if (idx >= (nvdebug_readl(g, 0x0224fc) >> 20))
                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_previous(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;
}

// Steps to next record. Returns new value of `idx`.
// Calls show() on non-NULL return
static void* device_info_file_seq_next_ampere(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
        struct nvdebug_state *g = &g_nvdebug_state[seq2gpuidx(s)];
        if ((*(int*)idx)++ >= (nvdebug_readl(g, 0x0224fc) >> 20))
                    return NULL;
        return idx;
}
/*
// Steps to next record on Ampere GPUs. Returns new value of `idx`.
static void* device_info_file_seq_next_ampere(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
        while(1) {
            struct nvdebug_state *g = &g_nvdebug_state[seq2gpuidx(s)];
            if ((*(int*)idx)++ >= (nvdebug_readl(g, 0x0224fc) >> 20))
                    return NULL;
            ptop_device_info_t curr_info;
            curr_info.raw = nvdebug_readl(g, NV_PTOP_DEVICE_INFO_AMPERE(*(int*)idx));
	    if(!curr_info.raw && !*info_type) continue;
	    (*info_type)++;
	    break;
        }
	while(1) {
	    struct nvdebug_state *g = &g_nvdebug_state[seq2gpuidx(s)];
            if ((*(int*)idx)++ >= (nvdebug_readl(g, 0x0224fc) >> 20))
                    return NULL;
            ptop_device_info_t curr_info;
            curr_info.raw = nvdebug_readl(g, NV_PTOP_DEVICE_INFO_AMPERE(*(int*)idx));
	    if(curr_info.raw & 0x80000000) continue;
	    break;
	}
        return idx;
}
*/
// Print info at index *idx. Returns non-zero on error.
static int device_info_file_seq_show_previous(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_PREVIOUS(*(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;
}

// Print info at index *idx for Ampere GPUs. Returns non-zero on error.
static int device_info_file_seq_show_ampere(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_AMPERE(*(int*)idx));
        // Check for read errors
        if (curr_info.raw == -1)
                return -EIO;
        int info_type = -1;
	if(curr_info.raw) {
		if(*(int*)idx < 1 || !nvdebug_readl(g, NV_PTOP_DEVICE_INFO_AMPERE(*(int*)idx) - 1)) info_type = 0;
                if(*(int*)idx < 2 || !nvdebug_readl(g, NV_PTOP_DEVICE_INFO_AMPERE(*(int*)idx) - 2)) info_type = 1;
                if(*(int*)idx < 3 || !nvdebug_readl(g, NV_PTOP_DEVICE_INFO_AMPERE(*(int*)idx) - 3)) info_type = 2;
	}
        // Parse and print the data
        switch(info_type) {
        case 0:
                seq_printf(s, "|           instance %d\n", curr_info.inst_id_ampere);
                seq_printf(s, "| Fault ID:        %3d\n", curr_info.fault_id_ampere);
		seq_printf(s, "| Engine Type:      %2d (", curr_info.engine_type_ampere);
                if (curr_info.engine_type_ampere < ENGINE_TYPES_LEN)
                        seq_printf(s, "%s)\n", ENGINE_TYPES_NAMES[curr_info.engine_type_ampere]);
                else
                        seq_printf(s, "Unknown Engine, introduced post-Ampere)\n");
                break;
        case 1:
                seq_printf(s, "| BAR0 Base %#.8x\n", curr_info.pri_base_ampere << 12);
                seq_printf(s, "| Reset ID:         %2d\n", curr_info.reset_enum_ampere);
                break;
        case 2:
                seq_printf(s, "| Host's Engine ID: %2d\n", curr_info.engine_enum_ampere);
                seq_printf(s, "| Runlist ID:       %2d\n", curr_info.runlist_enum_ampere);
                break;
        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_ampere) {
                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_previous = {
	.start = device_info_file_seq_start_previous,
	.next = device_info_file_seq_next_previous,
	.stop = device_info_file_seq_stop,
	.show = device_info_file_seq_show_previous,
};

static const struct seq_operations device_info_file_seq_ops_ampere = {
        .start = device_info_file_seq_start_ampere,
        .next = device_info_file_seq_next_ampere,
        .stop = device_info_file_seq_stop,
        .show = device_info_file_seq_show_ampere,
};

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

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

struct file_operations device_info_file_ops_previous = {
	.open = device_info_file_open_previous,
	.read = seq_read,
	.llseek = seq_lseek,
	.release = seq_release,
};

struct file_operations device_info_file_ops_ampere = {
        .open = device_info_file_open_ampere,
        .read = seq_read,
        .llseek = seq_lseek,
        .release = seq_release,
};