| 1 | |
| 2 | #include <linux/kernel.h>
|
| 3 | #include <linux/errno.h>
|
| 4 | #include <linux/fs.h>
|
| 5 | #include <linux/file.h>
|
| 6 | #include <linux/proc_fs.h>
|
| 7 | #include <linux/seq_file.h>
|
| 8 | #include <linux/nospec.h>
|
| 9 | #include <linux/io_uring.h>
|
| 10 |
|
| 11 | #include <uapi/linux/io_uring.h>
|
| 12 |
|
| 13 | #include "filetable.h"
|
| 14 | #include "sqpoll.h"
|
| 15 | #include "fdinfo.h"
|
| 16 | #include "cancel.h"
|
| 17 | #include "rsrc.h"
|
| 18 | #include "opdef.h"
|
| 19 |
|
| 20 | #ifdef CONFIG_NET_RX_BUSY_POLL
|
| 21 | static __cold void common_tracking_show_fdinfo(struct io_ring_ctx *ctx,
|
| 22 | struct seq_file *m,
|
| 23 | const char *tracking_strategy)
|
| 24 | {
|
| 25 | seq_puts(m, "NAPI:\tenabled\n");
|
| 26 | seq_printf(m, "napi tracking:\t%s\n", tracking_strategy);
|
| 27 | seq_printf(m, "napi_busy_poll_dt:\t%llu\n", ctx->napi_busy_poll_dt);
|
| 28 | if (ctx->napi_prefer_busy_poll)
|
| 29 | seq_puts(m, "napi_prefer_busy_poll:\ttrue\n");
|
| 30 | else
|
| 31 | seq_puts(m, "napi_prefer_busy_poll:\tfalse\n");
|
| 32 | }
|
| 33 |
|
| 34 | static __cold void napi_show_fdinfo(struct io_ring_ctx *ctx,
|
| 35 | struct seq_file *m)
|
| 36 | {
|
| 37 | unsigned int mode = READ_ONCE(ctx->napi_track_mode);
|
| 38 |
|
| 39 | switch (mode) {
|
| 40 | case IO_URING_NAPI_TRACKING_INACTIVE:
|
| 41 | seq_puts(m, "NAPI:\tdisabled\n");
|
| 42 | break;
|
| 43 | case IO_URING_NAPI_TRACKING_DYNAMIC:
|
| 44 | common_tracking_show_fdinfo(ctx, m, "dynamic");
|
| 45 | break;
|
| 46 | case IO_URING_NAPI_TRACKING_STATIC:
|
| 47 | common_tracking_show_fdinfo(ctx, m, "static");
|
| 48 | break;
|
| 49 | default:
|
| 50 | seq_printf(m, "NAPI:\tunknown mode (%u)\n", mode);
|
| 51 | }
|
| 52 | }
|
| 53 | #else
|
| 54 | static inline void napi_show_fdinfo(struct io_ring_ctx *ctx,
|
| 55 | struct seq_file *m)
|
| 56 | {
|
| 57 | }
|
| 58 | #endif
|
| 59 |
|
| 60 | static void __io_uring_show_fdinfo(struct io_ring_ctx *ctx, struct seq_file *m)
|
| 61 | {
|
| 62 | struct io_overflow_cqe *ocqe;
|
| 63 | struct io_rings *r = ctx->rings;
|
| 64 | unsigned int sq_mask = ctx->sq_entries - 1, cq_mask = ctx->cq_entries - 1;
|
| 65 | unsigned int sq_head = READ_ONCE(r->sq.head);
|
| 66 | unsigned int sq_tail = READ_ONCE(r->sq.tail);
|
| 67 | unsigned int cq_head = READ_ONCE(r->cq.head);
|
| 68 | unsigned int cq_tail = READ_ONCE(r->cq.tail);
|
| 69 | unsigned int sq_shift = 0;
|
| 70 | unsigned int cq_entries, sq_entries;
|
| 71 | int sq_pid = -1, sq_cpu = -1;
|
| 72 | u64 sq_total_time = 0, sq_work_time = 0;
|
| 73 | unsigned int i;
|
| 74 |
|
| 75 | if (ctx->flags & IORING_SETUP_SQE128)
|
| 76 | sq_shift = 1;
|
| 77 |
|
| 78 | |
| 79 | * we may get imprecise sqe and cqe info if uring is actively running
|
| 80 | * since we get cached_sq_head and cached_cq_tail without uring_lock
|
| 81 | * and sq_tail and cq_head are changed by userspace. But it's ok since
|
| 82 | * we usually use these info when it is stuck.
|
| 83 |
|
| 84 | seq_printf(m, "SqMask:\t0x%x\n", sq_mask);
|
| 85 | seq_printf(m, "SqHead:\t%u\n", sq_head);
|
| 86 | seq_printf(m, "SqTail:\t%u\n", sq_tail);
|
| 87 | seq_printf(m, "CachedSqHead:\t%u\n", data_race(ctx->cached_sq_head));
|
| 88 | seq_printf(m, "CqMask:\t0x%x\n", cq_mask);
|
| 89 | seq_printf(m, "CqHead:\t%u\n", cq_head);
|
| 90 | seq_printf(m, "CqTail:\t%u\n", cq_tail);
|
| 91 | seq_printf(m, "CachedCqTail:\t%u\n", data_race(ctx->cached_cq_tail));
|
| 92 | seq_printf(m, "SQEs:\t%u\n", sq_tail - sq_head);
|
| 93 | sq_entries = min(sq_tail - sq_head, ctx->sq_entries);
|
| 94 | for (i = 0; i < sq_entries; i++) {
|
| 95 | unsigned int entry = i + sq_head;
|
| 96 | struct io_uring_sqe *sqe;
|
| 97 | unsigned int sq_idx;
|
| 98 | bool sqe128 = false;
|
| 99 | u8 opcode;
|
| 100 |
|
| 101 | if (ctx->flags & IORING_SETUP_NO_SQARRAY)
|
| 102 | sq_idx = entry & sq_mask;
|
| 103 | else
|
| 104 | sq_idx = READ_ONCE(ctx->sq_array[entry & sq_mask]);
|
| 105 | if (sq_idx > sq_mask)
|
| 106 | continue;
|
| 107 |
|
| 108 | sqe = &ctx->sq_sqes[sq_idx << sq_shift];
|
| 109 | opcode = READ_ONCE(sqe->opcode);
|
| 110 | if (opcode >= IORING_OP_LAST)
|
| 111 | continue;
|
| 112 | opcode = array_index_nospec(opcode, IORING_OP_LAST);
|
| 113 | if (sq_shift) {
|
| 114 | sqe128 = true;
|
| 115 | } else if (io_issue_defs[opcode].is_128) {
|
| 116 | if (!(ctx->flags & IORING_SETUP_SQE_MIXED)) {
|
| 117 | seq_printf(m,
|
| 118 | "%5u: invalid sqe, 128B entry on non-mixed sq\n",
|
| 119 | sq_idx);
|
| 120 | break;
|
| 121 | }
|
| 122 | if (sq_idx == sq_mask) {
|
| 123 | seq_printf(m,
|
| 124 | "%5u: corrupted sqe, wrapping 128B entry\n",
|
| 125 | sq_idx);
|
| 126 | break;
|
| 127 | }
|
| 128 | sq_head++;
|
| 129 | i++;
|
| 130 | sqe128 = true;
|
| 131 | }
|
| 132 | seq_printf(m, "%5u: opcode:%s, fd:%d, flags:%x, off:%llu, "
|
| 133 | "addr:0x%llx, rw_flags:0x%x, buf_index:%d "
|
| 134 | "user_data:%llu",
|
| 135 | sq_idx, io_uring_get_opcode(opcode), sqe->fd,
|
| 136 | sqe->flags, (unsigned long long) sqe->off,
|
| 137 | (unsigned long long) sqe->addr, sqe->rw_flags,
|
| 138 | sqe->buf_index, sqe->user_data);
|
| 139 | if (sqe128) {
|
| 140 | u64 *sqeb = (void *) (sqe + 1);
|
| 141 | int size = sizeof(struct io_uring_sqe) / sizeof(u64);
|
| 142 | int j;
|
| 143 |
|
| 144 | for (j = 0; j < size; j++) {
|
| 145 | seq_printf(m, ", e%d:0x%llx", j,
|
| 146 | (unsigned long long) *sqeb);
|
| 147 | sqeb++;
|
| 148 | }
|
| 149 | }
|
| 150 | seq_printf(m, "\n");
|
| 151 | cond_resched();
|
| 152 | }
|
| 153 | seq_printf(m, "CQEs:\t%u\n", cq_tail - cq_head);
|
| 154 | cq_entries = min(cq_tail - cq_head, ctx->cq_entries);
|
| 155 | for (i = 0; i < cq_entries; i++) {
|
| 156 | struct io_uring_cqe *cqe;
|
| 157 | bool cqe32 = false;
|
| 158 |
|
| 159 | cqe = &r->cqes[(cq_head & cq_mask)];
|
| 160 | if (cqe->flags & IORING_CQE_F_32 || ctx->flags & IORING_SETUP_CQE32)
|
| 161 | cqe32 = true;
|
| 162 | seq_printf(m, "%5u: user_data:%llu, res:%d, flags:%x",
|
| 163 | cq_head & cq_mask, cqe->user_data, cqe->res,
|
| 164 | cqe->flags);
|
| 165 | if (cqe32)
|
| 166 | seq_printf(m, ", extra1:%llu, extra2:%llu",
|
| 167 | cqe->big_cqe[0], cqe->big_cqe[1]);
|
| 168 | seq_printf(m, "\n");
|
| 169 | cq_head++;
|
| 170 | if (cqe32) {
|
| 171 | cq_head++;
|
| 172 | i++;
|
| 173 | }
|
| 174 | cond_resched();
|
| 175 | }
|
| 176 |
|
| 177 | if (ctx->flags & IORING_SETUP_SQPOLL) {
|
| 178 | struct io_sq_data *sq = ctx->sq_data;
|
| 179 | struct task_struct *tsk;
|
| 180 |
|
| 181 | rcu_read_lock();
|
| 182 | tsk = rcu_dereference(sq->thread);
|
| 183 | |
| 184 | * sq->thread might be NULL if we raced with the sqpoll
|
| 185 | * thread termination.
|
| 186 |
|
| 187 | if (tsk) {
|
| 188 | u64 usec;
|
| 189 |
|
| 190 | get_task_struct(tsk);
|
| 191 | rcu_read_unlock();
|
| 192 | usec = io_sq_cpu_usec(tsk);
|
| 193 | sq_pid = task_pid_nr_ns(tsk,
|
| 194 | proc_pid_ns(file_inode(m->file)->i_sb));
|
| 195 | put_task_struct(tsk);
|
| 196 | sq_cpu = sq->sq_cpu;
|
| 197 | sq_total_time = usec;
|
| 198 | sq_work_time = sq->work_time;
|
| 199 | } else {
|
| 200 | rcu_read_unlock();
|
| 201 | }
|
| 202 | }
|
| 203 |
|
| 204 | seq_printf(m, "SqThread:\t%d\n", sq_pid);
|
| 205 | seq_printf(m, "SqThreadCpu:\t%d\n", sq_cpu);
|
| 206 | seq_printf(m, "SqTotalTime:\t%llu\n", sq_total_time);
|
| 207 | seq_printf(m, "SqWorkTime:\t%llu\n", sq_work_time);
|
| 208 | seq_printf(m, "UserFiles:\t%u\n", ctx->file_table.data.nr);
|
| 209 | for (i = 0; i < ctx->file_table.data.nr; i++) {
|
| 210 | struct file *f = NULL;
|
| 211 |
|
| 212 | if (ctx->file_table.data.nodes[i])
|
| 213 | f = io_slot_file(ctx->file_table.data.nodes[i]);
|
| 214 | if (f) {
|
| 215 | seq_printf(m, "%5u: ", i);
|
| 216 | seq_file_path(m, f, " \t\n\\");
|
| 217 | seq_puts(m, "\n");
|
| 218 | }
|
| 219 | }
|
| 220 | seq_printf(m, "UserBufs:\t%u\n", ctx->buf_table.nr);
|
| 221 | for (i = 0; i < ctx->buf_table.nr; i++) {
|
| 222 | struct io_mapped_ubuf *buf = NULL;
|
| 223 |
|
| 224 | if (ctx->buf_table.nodes[i])
|
| 225 | buf = ctx->buf_table.nodes[i]->buf;
|
| 226 | if (buf)
|
| 227 | seq_printf(m, "%5u: 0x%llx/%zu\n", i, buf->ubuf, buf->len);
|
| 228 | else
|
| 229 | seq_printf(m, "%5u: <none>\n", i);
|
| 230 | }
|
| 231 |
|
| 232 | seq_puts(m, "PollList:\n");
|
| 233 | for (i = 0; i < (1U << ctx->cancel_table.hash_bits); i++) {
|
| 234 | struct io_hash_bucket *hb = &ctx->cancel_table.hbs[i];
|
| 235 | struct io_kiocb *req;
|
| 236 |
|
| 237 | hlist_for_each_entry(req, &hb->list, hash_node)
|
| 238 | seq_printf(m, " op=%d, task_works=%d\n", req->opcode,
|
| 239 | task_work_pending(req->tctx->task));
|
| 240 | }
|
| 241 |
|
| 242 | seq_puts(m, "CqOverflowList:\n");
|
| 243 | spin_lock(&ctx->completion_lock);
|
| 244 | list_for_each_entry(ocqe, &ctx->cq_overflow_list, list) {
|
| 245 | struct io_uring_cqe *cqe = &ocqe->cqe;
|
| 246 |
|
| 247 | seq_printf(m, " user_data=%llu, res=%d, flags=%x\n",
|
| 248 | cqe->user_data, cqe->res, cqe->flags);
|
| 249 |
|
| 250 | }
|
| 251 | spin_unlock(&ctx->completion_lock);
|
| 252 | napi_show_fdinfo(ctx, m);
|
| 253 | }
|
| 254 |
|
| 255 | |
| 256 | * Caller holds a reference to the file already, we don't need to do
|
| 257 | * anything else to get an extra reference.
|
| 258 |
|
| 259 | __cold void io_uring_show_fdinfo(struct seq_file *m, struct file *file)
|
| 260 | {
|
| 261 | struct io_ring_ctx *ctx = file->private_data;
|
| 262 |
|
| 263 | |
| 264 | * Avoid ABBA deadlock between the seq lock and the io_uring mutex,
|
| 265 | * since fdinfo case grabs it in the opposite direction of normal use
|
| 266 | * cases.
|
| 267 |
|
| 268 | if (mutex_trylock(&ctx->uring_lock)) {
|
| 269 | __io_uring_show_fdinfo(ctx, m);
|
| 270 | mutex_unlock(&ctx->uring_lock);
|
| 271 | }
|
| 272 | }
|