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