Fix formatting: leading spaces to tabs, and fix continued-line alignment.
[akaros.git] / kern / drivers / dev / kprof.c
1 /*
2  * This file is part of the UCB release of Plan 9. It is subject to the license
3  * terms in the LICENSE file found in the top-level directory of this
4  * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
5  * part of the UCB release of Plan 9, including this file, may be copied,
6  * modified, propagated, or distributed except according to the terms contained
7  * in the LICENSE file.
8  */
9
10 #include <ros/profiler_records.h>
11 #include <arch/time.h>
12 #include <vfs.h>
13 #include <slab.h>
14 #include <kmalloc.h>
15 #include <kref.h>
16 #include <atomic.h>
17 #include <kthread.h>
18 #include <string.h>
19 #include <stdio.h>
20 #include <assert.h>
21 #include <error.h>
22 #include <pmap.h>
23 #include <smp.h>
24 #include <time.h>
25 #include <circular_buffer.h>
26 #include <umem.h>
27 #include <profiler.h>
28 #include <kprof.h>
29
30 #define KTRACE_BUFFER_SIZE (128 * 1024)
31 #define TRACE_PRINTK_BUFFER_SIZE (8 * 1024)
32
33 enum {
34         Kprofdirqid = 0,
35         Kprofdataqid,
36         Kprofctlqid,
37         Kptraceqid,
38         Kprintxqid,
39         Kmpstatqid,
40         Kmpstatrawqid,
41 };
42
43 struct trace_printk_buffer {
44         atomic_t in_use;
45         char buffer[TRACE_PRINTK_BUFFER_SIZE];
46 };
47
48 struct kprof {
49         qlock_t lock;
50         struct alarm_waiter *alarms;
51         bool mpstat_ipi;
52         bool profiling;
53         char *pdata;
54         size_t psize;
55 };
56
57 struct dev kprofdevtab;
58 struct dirtab kproftab[] = {
59         {".",                   {Kprofdirqid,           0, QTDIR}, 0,   DMDIR|0550},
60         {"kpdata",              {Kprofdataqid},         0,      0600},
61         {"kpctl",               {Kprofctlqid},          0,      0600},
62         {"kptrace",             {Kptraceqid},           0,      0600},
63         {"kprintx",             {Kprintxqid},           0,      0600},
64         {"mpstat",              {Kmpstatqid},           0,      0600},
65         {"mpstat-raw",  {Kmpstatrawqid},        0,      0600},
66 };
67
68 static struct kprof kprof;
69 static bool ktrace_init_done = FALSE;
70 static spinlock_t ktrace_lock = SPINLOCK_INITIALIZER_IRQSAVE;
71 static struct circular_buffer ktrace_data;
72 static char ktrace_buffer[KTRACE_BUFFER_SIZE];
73 static int kprof_timer_period = 1000;
74
75 static size_t mpstat_len(void)
76 {
77         size_t each_row = 7 + NR_CPU_STATES * 26;
78
79         return each_row * (num_cores + 1) + 1;
80 }
81
82 static size_t mpstatraw_len(void)
83 {
84         size_t header_row = 27 + NR_CPU_STATES * 7 + 1;
85         size_t cpu_row = 7 + NR_CPU_STATES * 17;
86
87         return header_row + cpu_row * num_cores + 1;
88 }
89
90 static char *devname(void)
91 {
92         return kprofdevtab.name;
93 }
94
95 static void kprof_alarm_handler(struct alarm_waiter *waiter,
96                                 struct hw_trapframe *hw_tf)
97 {
98         int coreid = core_id();
99         struct timer_chain *tchain = &per_cpu_info[coreid].tchain;
100
101         profiler_add_hw_sample(hw_tf, PROF_MKINFO(PROF_DOM_TIMER,
102                                                                                           kprof_timer_period));
103         reset_alarm_rel(tchain, waiter, kprof_timer_period);
104 }
105
106 static struct chan *kprof_attach(char *spec)
107 {
108         if (!kprof.alarms)
109                 error(ENOMEM, ERROR_FIXME);
110
111         return devattach(devname(), spec);
112 }
113
114 static void kprof_enable_timer(int coreid, int on_off)
115 {
116         struct timer_chain *tchain = &per_cpu_info[coreid].tchain;
117         struct alarm_waiter *waiter = &kprof.alarms[coreid];
118
119         if (on_off) {
120                 /* Per CPU waiters already inited.  Will set/reset each time (1 ms
121                  * default). */
122                 reset_alarm_rel(tchain, waiter, kprof_timer_period);
123         } else {
124                 /* Since the alarm handler runs and gets reset within IRQ context, then
125                  * we should never fail to cancel the alarm if it was already running
126                  * (tchain locks synchronize us).  But it might not be set at all, which
127                  * is fine. */
128                 unset_alarm(tchain, waiter);
129         }
130 }
131
132 static void kprof_profdata_clear(void)
133 {
134         kfree(kprof.pdata);
135         kprof.pdata = NULL;
136         kprof.psize = 0;
137 }
138
139 static void kprof_start_profiler(void)
140 {
141         ERRSTACK(2);
142
143         qlock(&kprof.lock);
144         if (waserror()) {
145                 qunlock(&kprof.lock);
146                 nexterror();
147         }
148         if (!kprof.profiling) {
149                 profiler_setup();
150                 if (waserror()) {
151                         kprof.profiling = FALSE;
152                         profiler_cleanup();
153                         nexterror();
154                 }
155                 profiler_control_trace(1);
156                 kprof.profiling = TRUE;
157                 kprof_profdata_clear();
158                 poperror();
159         }
160         poperror();
161         qunlock(&kprof.lock);
162 }
163
164 static void kprof_fetch_profiler_data(void)
165 {
166         size_t psize = kprof.psize + profiler_size();
167         char *ndata = krealloc(kprof.pdata, psize, KMALLOC_WAIT);
168
169         if (!ndata)
170                 error(ENOMEM, ERROR_FIXME);
171         kprof.pdata = ndata;
172         while (kprof.psize < psize) {
173                 size_t csize = profiler_read(kprof.pdata + kprof.psize,
174                                              psize - kprof.psize);
175
176                 if (csize == 0)
177                         break;
178                 kprof.psize += csize;
179         }
180 }
181
182 static void kprof_stop_profiler(void)
183 {
184         ERRSTACK(1);
185
186         qlock(&kprof.lock);
187         if (waserror()) {
188                 qunlock(&kprof.lock);
189                 nexterror();
190         }
191         if (kprof.profiling) {
192                 profiler_control_trace(0);
193                 kprof_fetch_profiler_data();
194                 profiler_cleanup();
195
196                 kprof.profiling = FALSE;
197         }
198         poperror();
199         qunlock(&kprof.lock);
200 }
201
202 static void kprof_flush_profiler(void)
203 {
204         ERRSTACK(1);
205
206         qlock(&kprof.lock);
207         if (waserror()) {
208                 qunlock(&kprof.lock);
209                 nexterror();
210         }
211         if (kprof.profiling) {
212                 profiler_trace_data_flush();
213                 kprof_fetch_profiler_data();
214         }
215         poperror();
216         qunlock(&kprof.lock);
217 }
218
219 static void kprof_init(void)
220 {
221         int i;
222         ERRSTACK(1);
223
224         profiler_init();
225
226         qlock_init(&kprof.lock);
227         kprof.profiling = FALSE;
228         kprof.pdata = NULL;
229         kprof.psize = 0;
230
231         kprof.alarms = kzmalloc(sizeof(struct alarm_waiter) * num_cores,
232                                 KMALLOC_WAIT);
233         if (!kprof.alarms)
234                 error(ENOMEM, ERROR_FIXME);
235         if (waserror()) {
236                 kfree(kprof.alarms);
237                 kprof.alarms = NULL;
238                 nexterror();
239         }
240         for (i = 0; i < num_cores; i++)
241                 init_awaiter_irq(&kprof.alarms[i], kprof_alarm_handler);
242
243         for (i = 0; i < ARRAY_SIZE(kproftab); i++)
244                 kproftab[i].length = 0;
245
246         kprof.mpstat_ipi = TRUE;
247         kproftab[Kmpstatqid].length = mpstat_len();
248         kproftab[Kmpstatrawqid].length = mpstatraw_len();
249
250         poperror();
251 }
252
253 static void kprof_shutdown(void)
254 {
255         kprof_stop_profiler();
256         kprof_profdata_clear();
257
258         kfree(kprof.alarms);
259         kprof.alarms = NULL;
260 }
261
262 static void kprofclear(void)
263 {
264         qlock(&kprof.lock);
265         kprof_profdata_clear();
266         qunlock(&kprof.lock);
267 }
268
269 static struct walkqid *kprof_walk(struct chan *c, struct chan *nc, char **name,
270                                   int nname)
271 {
272         return devwalk(c, nc, name, nname, kproftab, ARRAY_SIZE(kproftab), devgen);
273 }
274
275 static size_t kprof_profdata_size(void)
276 {
277         return kprof.pdata != NULL ? kprof.psize : profiler_size();
278 }
279
280 static long kprof_profdata_read(void *dest, long size, int64_t off)
281 {
282         qlock(&kprof.lock);
283         if (kprof.pdata && off < kprof.psize) {
284                 size = MIN(kprof.psize - off, size);
285                 memcpy(dest, kprof.pdata + off, size);
286         } else {
287                 size = 0;
288         }
289         qunlock(&kprof.lock);
290
291         return size;
292 }
293
294 static int kprof_stat(struct chan *c, uint8_t *db, int n)
295 {
296         kproftab[Kprofdataqid].length = kprof_profdata_size();
297         kproftab[Kptraceqid].length = kprof_tracedata_size();
298
299         return devstat(c, db, n, kproftab, ARRAY_SIZE(kproftab), devgen);
300 }
301
302 static struct chan *kprof_open(struct chan *c, int omode)
303 {
304         if (c->qid.type & QTDIR) {
305                 if (openmode(omode) != O_READ)
306                         error(EPERM, ERROR_FIXME);
307         }
308         c->mode = openmode(omode);
309         c->flag |= COPEN;
310         c->offset = 0;
311         return c;
312 }
313
314 static void kprof_close(struct chan *c)
315 {
316 }
317
318 static long mpstat_read(void *va, long n, int64_t off)
319 {
320         size_t bufsz = mpstat_len();
321         char *buf = kmalloc(bufsz, KMALLOC_WAIT);
322         int len = 0;
323         struct per_cpu_info *pcpui;
324         uint64_t cpu_total;
325         struct timespec ts;
326
327         /* the IPI interferes with other cores, might want to disable that. */
328         if (kprof.mpstat_ipi)
329                 send_broadcast_ipi(I_POKE_CORE);
330
331         len += snprintf(buf + len, bufsz - len, "  CPU: ");
332         for (int j = 0; j < NR_CPU_STATES; j++)
333                 len += snprintf(buf + len, bufsz - len, "%23s%s", cpu_state_names[j],
334                                 j != NR_CPU_STATES - 1 ? "   " : "  \n");
335
336         for (int i = 0; i < num_cores; i++) {
337                 pcpui = &per_cpu_info[i];
338                 cpu_total = 0;
339                 len += snprintf(buf + len, bufsz - len, "%5d: ", i);
340                 for (int j = 0; j < NR_CPU_STATES; j++)
341                         cpu_total += pcpui->state_ticks[j];
342                 cpu_total = MAX(cpu_total, 1);  /* for the divide later */
343                 for (int j = 0; j < NR_CPU_STATES; j++) {
344                         tsc2timespec(pcpui->state_ticks[j], &ts);
345                         len += snprintf(buf + len, bufsz - len, "%10d.%06d (%3d%%)%s",
346                                         ts.tv_sec, ts.tv_nsec / 1000,
347                                         MIN((pcpui->state_ticks[j] * 100) / cpu_total, 100),
348                                         j != NR_CPU_STATES - 1 ? ", " : " \n");
349                 }
350         }
351         n = readstr(off, va, n, buf);
352         kfree(buf);
353         return n;
354 }
355
356 static long mpstatraw_read(void *va, long n, int64_t off)
357 {
358         size_t bufsz = mpstatraw_len();
359         char *buf = kmalloc(bufsz, KMALLOC_WAIT);
360         int len = 0;
361         struct per_cpu_info *pcpui;
362
363         /* could spit it all out in binary, though then it'd be harder to process
364          * the data across a mnt (if we export #K).  probably not a big deal. */
365
366         /* header line: version, num_cores, tsc freq, state names */
367         len += snprintf(buf + len, bufsz - len, "v%03d %5d %16llu", 1, num_cores,
368                         system_timing.tsc_freq);
369         for (int j = 0; j < NR_CPU_STATES; j++)
370                 len += snprintf(buf + len, bufsz - len, " %6s", cpu_state_names[j]);
371         len += snprintf(buf + len, bufsz - len, "\n");
372
373         for (int i = 0; i < num_cores; i++) {
374                 pcpui = &per_cpu_info[i];
375                 len += snprintf(buf + len, bufsz - len, "%5d: ", i);
376                 for (int j = 0; j < NR_CPU_STATES; j++) {
377                         len += snprintf(buf + len, bufsz - len, "%16llx%s",
378                                         pcpui->state_ticks[j],
379                                         j != NR_CPU_STATES - 1 ? " " : "\n");
380                 }
381         }
382         n = readstr(off, va, n, buf);
383         kfree(buf);
384         return n;
385 }
386
387 static long kprof_read(struct chan *c, void *va, long n, int64_t off)
388 {
389         uint64_t w, *bp;
390         char *a, *ea;
391         uintptr_t offset = off;
392         uint64_t pc;
393
394         switch ((int) c->qid.path) {
395         case Kprofdirqid:
396                 return devdirread(c, va, n, kproftab, ARRAY_SIZE(kproftab), devgen);
397         case Kprofdataqid:
398                 n = kprof_profdata_read(va, n, off);
399                 break;
400         case Kptraceqid:
401                 n = kprof_tracedata_read(va, n, off);
402                 break;
403         case Kprintxqid:
404                 n = readstr(offset, va, n, printx_on ? "on" : "off");
405                 break;
406         case Kmpstatqid:
407                 n = mpstat_read(va, n, offset);
408                 break;
409         case Kmpstatrawqid:
410                 n = mpstatraw_read(va, n, offset);
411                 break;
412         default:
413                 n = 0;
414                 break;
415         }
416         return n;
417 }
418
419 static void kprof_manage_timer(int coreid, struct cmdbuf *cb)
420 {
421         if (!strcmp(cb->f[2], "on")) {
422                 kprof_enable_timer(coreid, 1);
423         } else if (!strcmp(cb->f[2], "off")) {
424                 kprof_enable_timer(coreid, 0);
425         } else {
426                 error(EFAIL, "timer needs on|off");
427         }
428 }
429
430 static void kprof_usage_fail(void)
431 {
432         static const char *ctlstring = "clear|start|stop|flush|timer";
433         const char * const *cmds = profiler_configure_cmds();
434         char msgbuf[128];
435
436         strlcpy(msgbuf, ctlstring, sizeof(msgbuf));
437         for (int i = 0; cmds[i]; i++) {
438                 strlcat(msgbuf, "|", sizeof(msgbuf));
439                 strlcat(msgbuf, cmds[i], sizeof(msgbuf));
440         }
441
442         error(EFAIL, msgbuf);
443 }
444
445 static long kprof_write(struct chan *c, void *a, long n, int64_t unused)
446 {
447         ERRSTACK(1);
448         struct cmdbuf *cb = parsecmd(a, n);
449
450         if (waserror()) {
451                 kfree(cb);
452                 nexterror();
453         }
454         switch ((int) c->qid.path) {
455         case Kprofctlqid:
456                 if (cb->nf < 1)
457                         kprof_usage_fail();
458                 if (profiler_configure(cb))
459                         break;
460                 if (!strcmp(cb->f[0], "clear")) {
461                         kprofclear();
462                 } else if (!strcmp(cb->f[0], "timer")) {
463                         if (cb->nf < 3)
464                                 error(EFAIL, "timer {{all, N} {on, off}, period USEC}");
465                         if (!strcmp(cb->f[1], "period")) {
466                                 kprof_timer_period = strtoul(cb->f[2], 0, 10);
467                         } else if (!strcmp(cb->f[1], "all")) {
468                                 for (int i = 0; i < num_cores; i++)
469                                         kprof_manage_timer(i, cb);
470                         } else {
471                                 int pcoreid = strtoul(cb->f[1], 0, 10);
472
473                                 if (pcoreid >= num_cores)
474                                         error(EFAIL, "No such coreid %d", pcoreid);
475                                 kprof_manage_timer(pcoreid, cb);
476                         }
477                 } else if (!strcmp(cb->f[0], "start")) {
478                         kprof_start_profiler();
479                 } else if (!strcmp(cb->f[0], "flush")) {
480                         kprof_flush_profiler();
481                 } else if (!strcmp(cb->f[0], "stop")) {
482                         kprof_stop_profiler();
483                 } else {
484                         kprof_usage_fail();
485                 }
486                 break;
487         case Kprofdataqid:
488                 profiler_add_trace((uintptr_t) strtoul(a, 0, 0), 0);
489                 break;
490         case Kptraceqid:
491                 if (a && (n > 0)) {
492                         char *uptr = user_strdup_errno(current, a, n);
493
494                         if (uptr) {
495                                 trace_printk(false, "%s", uptr);
496                                 user_memdup_free(current, uptr);
497                         } else {
498                                 n = -1;
499                         }
500                 }
501                 break;
502         case Kprintxqid:
503                 if (!strncmp(a, "on", 2))
504                         set_printx(1);
505                 else if (!strncmp(a, "off", 3))
506                         set_printx(0);
507                 else if (!strncmp(a, "toggle", 6))
508                         set_printx(2);
509                 else
510                         error(EFAIL, "Invalid option to Kprintx %s\n", a);
511                 break;
512         case Kmpstatqid:
513         case Kmpstatrawqid:
514                 if (cb->nf < 1)
515                         error(EFAIL, "Bad mpstat option (reset|ipi|on|off)");
516                 if (!strcmp(cb->f[0], "reset")) {
517                         for (int i = 0; i < num_cores; i++)
518                                 reset_cpu_state_ticks(i);
519                 } else if (!strcmp(cb->f[0], "on")) {
520                         /* TODO: enable the ticks */ ;
521                 } else if (!strcmp(cb->f[0], "off")) {
522                         /* TODO: disable the ticks */ ;
523                 } else if (!strcmp(cb->f[0], "ipi")) {
524                         if (cb->nf < 2)
525                                 error(EFAIL, "Need another arg: ipi [on|off]");
526                         if (!strcmp(cb->f[1], "on"))
527                                 kprof.mpstat_ipi = TRUE;
528                         else if (!strcmp(cb->f[1], "off"))
529                                 kprof.mpstat_ipi = FALSE;
530                         else
531                                 error(EFAIL, "ipi [on|off]");
532                 } else {
533                         error(EFAIL, "Bad mpstat option (reset|ipi|on|off)");
534                 }
535                 break;
536         default:
537                 error(EBADFD, ERROR_FIXME);
538         }
539         kfree(cb);
540         poperror();
541         return n;
542 }
543
544 size_t kprof_tracedata_size(void)
545 {
546         return circular_buffer_size(&ktrace_data);
547 }
548
549 size_t kprof_tracedata_read(void *data, size_t size, size_t offset)
550 {
551         spin_lock_irqsave(&ktrace_lock);
552         if (likely(ktrace_init_done))
553                 size = circular_buffer_read(&ktrace_data, data, size, offset);
554         else
555                 size = 0;
556         spin_unlock_irqsave(&ktrace_lock);
557
558         return size;
559 }
560
561 void kprof_tracedata_write(const char *pretty_buf, size_t len)
562 {
563         spin_lock_irqsave(&ktrace_lock);
564         if (unlikely(!ktrace_init_done)) {
565                 circular_buffer_init(&ktrace_data, sizeof(ktrace_buffer),
566                                      ktrace_buffer);
567                 ktrace_init_done = TRUE;
568         }
569         circular_buffer_write(&ktrace_data, pretty_buf, len);
570         spin_unlock_irqsave(&ktrace_lock);
571 }
572
573 static struct trace_printk_buffer *kprof_get_printk_buffer(void)
574 {
575         static struct trace_printk_buffer boot_tpb;
576         static struct trace_printk_buffer *cpu_tpbs;
577         static atomic_t alloc_done;
578
579         if (unlikely(!num_cores))
580                 return &boot_tpb;
581         if (unlikely(!cpu_tpbs)) {
582                 /* Poor man per-CPU data structure. I really do no like littering global
583                  * data structures with module specific data.
584                  * We cannot take the ktrace_lock to protect the kzmalloc() call, as
585                  * that might trigger printk()s, and we would reenter here.
586                  * Let only one core into the kzmalloc() path, and let the others get
587                  * the boot_tpb until finished.
588                  */
589                 if (!atomic_cas(&alloc_done, 0, 1))
590                         return &boot_tpb;
591                 cpu_tpbs = kzmalloc(num_cores * sizeof(struct trace_printk_buffer), 0);
592         }
593
594         return cpu_tpbs + core_id_early();
595 }
596
597 void trace_vprintk(bool btrace, const char *fmt, va_list args)
598 {
599         struct print_buf {
600                 char *ptr;
601                 char *top;
602         };
603
604         void emit_print_buf_str(struct print_buf *pb, const char *str, ssize_t size)
605         {
606                 if (size < 0) {
607                         for (; *str && (pb->ptr < pb->top); str++)
608                                 *(pb->ptr++) = *str;
609                 } else {
610                         for (; (size > 0) && (pb->ptr < pb->top); str++, size--)
611                                 *(pb->ptr++) = *str;
612                 }
613         }
614
615         void bt_print(void *opaque, const char *str)
616         {
617                 struct print_buf *pb = (struct print_buf *) opaque;
618
619                 emit_print_buf_str(pb, "\t", 1);
620                 emit_print_buf_str(pb, str, -1);
621         }
622
623         static const size_t bufsz = TRACE_PRINTK_BUFFER_SIZE;
624         static const size_t usr_bufsz = (3 * bufsz) / 8;
625         static const size_t kp_bufsz = bufsz - usr_bufsz;
626         struct trace_printk_buffer *tpb = kprof_get_printk_buffer();
627         struct timespec ts_now = { 0, 0 };
628         struct print_buf pb;
629         char *usrbuf = tpb->buffer, *kpbuf = tpb->buffer + usr_bufsz;
630         const char *utop, *uptr;
631         char hdr[64];
632
633         if (!atomic_cas(&tpb->in_use, 0, 1))
634                 return;
635         if (likely(system_timing.tsc_freq))
636                 tsc2timespec(read_tsc(), &ts_now);
637         snprintf(hdr, sizeof(hdr), "[%lu.%09lu]:cpu%d: ", ts_now.tv_sec,
638                  ts_now.tv_nsec, core_id_early());
639
640         pb.ptr = usrbuf + vsnprintf(usrbuf, usr_bufsz, fmt, args);
641         pb.top = usrbuf + usr_bufsz;
642
643         if (pb.ptr[-1] != '\n')
644                 emit_print_buf_str(&pb, "\n", 1);
645         if (btrace) {
646                 emit_print_buf_str(&pb, "\tBacktrace:\n", -1);
647                 gen_backtrace(bt_print, &pb);
648         }
649         /* snprintf null terminates the buffer, and does not count that as part of
650          * the len.  If we maxed out the buffer, let's make sure it has a \n.
651          */
652         if (pb.ptr == pb.top)
653                 pb.ptr[-1] = '\n';
654         utop = pb.ptr;
655
656         pb.ptr = kpbuf;
657         pb.top = kpbuf + kp_bufsz;
658         for (uptr = usrbuf; uptr < utop;) {
659                 const char *nlptr = memchr(uptr, '\n', utop - uptr);
660
661                 if (nlptr == NULL)
662                         nlptr = utop;
663                 emit_print_buf_str(&pb, hdr, -1);
664                 emit_print_buf_str(&pb, uptr, (nlptr - uptr) + 1);
665                 uptr = nlptr + 1;
666         }
667         kprof_tracedata_write(kpbuf, pb.ptr - kpbuf);
668         atomic_set(&tpb->in_use, 0);
669 }
670
671 void trace_printk(bool btrace, const char *fmt, ...)
672 {
673         va_list args;
674
675         va_start(args, fmt);
676         trace_vprintk(btrace, fmt, args);
677         va_end(args);
678 }
679
680 struct dev kprofdevtab __devtab = {
681         .name = "kprof",
682
683         .reset = devreset,
684         .init = kprof_init,
685         .shutdown = kprof_shutdown,
686         .attach = kprof_attach,
687         .walk = kprof_walk,
688         .stat = kprof_stat,
689         .open = kprof_open,
690         .create = devcreate,
691         .close = kprof_close,
692         .read = kprof_read,
693         .bread = devbread,
694         .write = kprof_write,
695         .bwrite = devbwrite,
696         .remove = devremove,
697         .wstat = devwstat,
698 };