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,
};
|