diff options
Diffstat (limited to 'device_info_procfs.c')
-rw-r--r-- | device_info_procfs.c | 126 |
1 files changed, 126 insertions, 0 deletions
diff --git a/device_info_procfs.c b/device_info_procfs.c new file mode 100644 index 0000000..cd6c53c --- /dev/null +++ b/device_info_procfs.c | |||
@@ -0,0 +1,126 @@ | |||
1 | #include "nvdebug.h" | ||
2 | #include <linux/seq_file.h> // For seq_* functions and types | ||
3 | #include <linux/uaccess.h> // For copy_to_user() | ||
4 | |||
5 | // Generic register printing function, used for PTOP_*_NUM registers (+more) | ||
6 | // @param f File being read from. `data` field is register offset to read. | ||
7 | // @param buf User buffer for result | ||
8 | // @param size Length of user buffer | ||
9 | // @param off Requested offset. Updated by number of characters written. | ||
10 | // @return -errno on error, otherwise number of bytes written to *buf | ||
11 | // Note: Parent `data` field MUST be the GPU index | ||
12 | static ssize_t nvdebug_reg32_read(struct file *f, char __user *buf, size_t size, loff_t *off) { | ||
13 | char out[16]; | ||
14 | int chars_written; | ||
15 | struct nvdebug_state *g = &g_nvdebug_state[file2parentgpuidx(f)]; | ||
16 | if (size < 16 || *off != 0) | ||
17 | return 0; | ||
18 | // 32 bit register will always take less than 16 characters to print | ||
19 | chars_written = scnprintf(out, 16, "%#0x\n", nvdebug_readl(g, (uintptr_t)PDE_DATA(file_inode(f)))); | ||
20 | if (copy_to_user(buf, out, chars_written)) | ||
21 | printk(KERN_WARNING "Unable to copy all data for %s\n", file_dentry(f)->d_name.name); | ||
22 | *off += chars_written; | ||
23 | return chars_written; | ||
24 | } | ||
25 | const struct file_operations nvdebug_read_reg32_file_ops = { | ||
26 | .read = nvdebug_reg32_read, | ||
27 | }; | ||
28 | |||
29 | //// ==v== PTOP_DEVICE_INFO ==v== //// | ||
30 | |||
31 | // Called to start or resume a sequence. Prior to 4.19, *pos is unreliable. | ||
32 | // Initializes iterator `idx` state and returns it. Ends sequence on NULL. | ||
33 | static void* device_info_file_seq_start(struct seq_file *s, loff_t *pos) { | ||
34 | static int idx; | ||
35 | // If start of sequence, reset `idx` | ||
36 | if (*pos == 0) | ||
37 | idx = 0; | ||
38 | // Number of possible info entries is fixed, and list is sparse | ||
39 | if (idx >= NV_PTOP_DEVICE_INFO__SIZE_1) | ||
40 | return NULL; | ||
41 | return &idx; | ||
42 | } | ||
43 | |||
44 | // Steps to next record. Returns new value of `idx`. | ||
45 | // Calls show() on non-NULL return | ||
46 | static void* device_info_file_seq_next(struct seq_file *s, void *idx, | ||
47 | loff_t *pos) { | ||
48 | (*pos)++; // Required by seq interface | ||
49 | // Number of possible info entries is fixed, and list is sparse | ||
50 | if ((*(int*)idx)++ >= NV_PTOP_DEVICE_INFO__SIZE_1) | ||
51 | return NULL; | ||
52 | return idx; | ||
53 | } | ||
54 | |||
55 | // Print info at index *idx. Returns non-zero on error. | ||
56 | static int device_info_file_seq_show(struct seq_file *s, void *idx) { | ||
57 | ptop_device_info_t curr_info; | ||
58 | struct nvdebug_state *g = &g_nvdebug_state[seq2gpuidx(s)]; | ||
59 | |||
60 | curr_info.raw = nvdebug_readl(g, NV_PTOP_DEVICE_INFO(*(int*)idx)); | ||
61 | // Check for read errors | ||
62 | if (curr_info.raw == -1) | ||
63 | return -EIO; | ||
64 | |||
65 | // Parse and print the data | ||
66 | switch(curr_info.info_type) { | ||
67 | case INFO_TYPE_DATA: | ||
68 | // As of early 2022, only the ENUM2 format of this entry exists | ||
69 | if (curr_info.is_not_enum2) | ||
70 | break; | ||
71 | seq_printf(s, "| BAR0 Base %#.8x\n" | ||
72 | "| instance %d\n", | ||
73 | curr_info.pri_base << 12, curr_info.inst_id); | ||
74 | if (curr_info.fault_id_is_valid) | ||
75 | seq_printf(s, "| Fault ID: %3d\n", curr_info.fault_id); | ||
76 | break; | ||
77 | case INFO_TYPE_ENUM: | ||
78 | if (curr_info.engine_is_valid) | ||
79 | seq_printf(s, "| Host's Engine ID: %2d\n", curr_info.engine_enum); | ||
80 | if (curr_info.runlist_is_valid) | ||
81 | seq_printf(s, "| Runlist ID: %2d\n", curr_info.runlist_enum); | ||
82 | if (curr_info.intr_is_valid) | ||
83 | seq_printf(s, "| Interrupt ID: %2d\n", curr_info.intr_enum); | ||
84 | if (curr_info.reset_is_valid) | ||
85 | seq_printf(s, "| Reset ID: %2d\n", curr_info.reset_enum); | ||
86 | break; | ||
87 | case INFO_TYPE_ENGINE_TYPE: | ||
88 | seq_printf(s, "| Engine Type: %2d (", curr_info.engine_type); | ||
89 | if (curr_info.engine_type < ENGINE_TYPES_LEN) | ||
90 | seq_printf(s, "%s)\n", ENGINE_TYPES_NAMES[curr_info.engine_type]); | ||
91 | else | ||
92 | seq_printf(s, "Unknown Engine, introduced post-Ampere)\n"); | ||
93 | break; | ||
94 | case INFO_TYPE_NOT_VALID: | ||
95 | default: | ||
96 | // Device info records are sparse, so skip unset or unknown ones | ||
97 | return 0; | ||
98 | } | ||
99 | |||
100 | // Draw a line between each device entry | ||
101 | if (!curr_info.has_next_entry) | ||
102 | seq_printf(s, "+---------------------+\n"); | ||
103 | return 0; | ||
104 | } | ||
105 | |||
106 | static void device_info_file_seq_stop(struct seq_file *s, void *idx) { | ||
107 | // No cleanup needed | ||
108 | } | ||
109 | |||
110 | static const struct seq_operations device_info_file_seq_ops = { | ||
111 | .start = device_info_file_seq_start, | ||
112 | .next = device_info_file_seq_next, | ||
113 | .stop = device_info_file_seq_stop, | ||
114 | .show = device_info_file_seq_show, | ||
115 | }; | ||
116 | |||
117 | static int device_info_file_open(struct inode *inode, struct file *f) { | ||
118 | return seq_open(f, &device_info_file_seq_ops); | ||
119 | } | ||
120 | |||
121 | const struct file_operations device_info_file_ops = { | ||
122 | .open = device_info_file_open, | ||
123 | .read = seq_read, | ||
124 | .llseek = seq_lseek, | ||
125 | .release = seq_release, | ||
126 | }; | ||