1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright 2025 Oxide Computer Company
14 */
15
16
17 #include <sys/types.h>
18 #include <sys/mutex.h>
19 #include <sys/list.h>
20 #include <sys/time.h>
21 #include <sys/varargs.h>
22 #include <sys/debug.h>
23 #include <sys/systm.h>
24 #include <sys/sysmacros.h>
25 #include <sys/sunddi.h>
26 #include <sys/cmn_err.h>
27
28 static kmutex_t t4_debug_lock;
29 static list_t t4_debug_msgs;
30 static uint_t t4_debug_size;
31 /* Rough counter of allocation failures during cxgb_printf() */
32 static uint64_t t4_debug_alloc_fail;
33
34 /*
35 * Max ring buffer size for debug logs. Defaults to 16KiB.
36 *
37 * If set to 0, no debug messages will be stored, nor will the t4-dbgmsg SDT
38 * probe be fired.
39 *
40 * If set to < 0, then messages will be logged through the legacy cmn_err()
41 * behavior (and the SDT probe is also skipped).
42 */
43 int t4_debug_max_size = 16384;
44
45 typedef struct t4_dbgmsg {
46 list_node_t tdm_node;
47 hrtime_t tdm_when;
48 dev_info_t *tdm_dip;
49 char tdm_msg[];
50 } t4_dbgmsg_t;
51
52 static inline uint_t
t4_dbgmsg_sz(int sz)53 t4_dbgmsg_sz(int sz)
54 {
55 ASSERT(sz >= 0);
56 return (sizeof (t4_dbgmsg_t) + sz + 1);
57 }
58
59 static uint_t
t4_debug_free(t4_dbgmsg_t * msg)60 t4_debug_free(t4_dbgmsg_t *msg)
61 {
62 const uint_t free_sz = t4_dbgmsg_sz(strlen(msg->tdm_msg));
63 kmem_free(msg, free_sz);
64
65 return (free_sz);
66 }
67
68 void
cxgb_printf(dev_info_t * dip,int level,const char * fmt,...)69 cxgb_printf(dev_info_t *dip, int level, const char *fmt, ...)
70 {
71 va_list adx;
72
73 if (t4_debug_max_size == 0) {
74 /* User has opted out of debug messages completely */
75 return;
76 } else if (t4_debug_max_size < 0) {
77 /* User has opted into old cmn_err() behavior */
78 char pfmt[128];
79
80 (void) snprintf(pfmt, sizeof (pfmt), "%s%d: %s",
81 ddi_driver_name(dip), ddi_get_instance(dip), fmt);
82
83 va_start(adx, fmt);
84 vcmn_err(level, pfmt, adx);
85 va_end(adx);
86
87 return;
88 }
89
90 va_start(adx, fmt);
91 const int size = vsnprintf(NULL, 0, fmt, adx);
92 va_end(adx);
93
94 const uint_t alloc_sz = t4_dbgmsg_sz(size);
95 t4_dbgmsg_t *msg = kmem_alloc(alloc_sz, KM_NOSLEEP);
96 if (msg == NULL) {
97 /*
98 * Just note the failure and bail if the system is so pressed
99 * for memory.
100 */
101 DTRACE_PROBE1(t4__dbgmsg__alloc_fail, dev_info_t *, dip);
102 t4_debug_alloc_fail++;
103 return;
104 }
105 msg->tdm_when = gethrtime();
106 msg->tdm_dip = dip;
107
108 va_start(adx, fmt);
109 (void) vsnprintf(msg->tdm_msg, size + 1, fmt, adx);
110 va_end(adx);
111
112 DTRACE_PROBE2(t4__dbgmsg, dev_info_t *, dip, char *, msg->tdm_msg);
113
114 mutex_enter(&t4_debug_lock);
115 list_insert_tail(&t4_debug_msgs, msg);
116 t4_debug_size += alloc_sz;
117 while (t4_debug_size > t4_debug_max_size && t4_debug_size != 0) {
118 msg = list_remove_head(&t4_debug_msgs);
119 t4_debug_size -= t4_debug_free(msg);
120 }
121 mutex_exit(&t4_debug_lock);
122 }
123
124 void
t4_debug_init(void)125 t4_debug_init(void)
126 {
127 mutex_init(&t4_debug_lock, NULL, MUTEX_DEFAULT, NULL);
128 list_create(&t4_debug_msgs, sizeof (t4_dbgmsg_t),
129 offsetof(t4_dbgmsg_t, tdm_node));
130 }
131
132 void
t4_debug_fini(void)133 t4_debug_fini(void)
134 {
135 t4_dbgmsg_t *msg;
136
137 while ((msg = list_remove_head(&t4_debug_msgs)) != NULL) {
138 t4_debug_size -= t4_debug_free(msg);
139 }
140 mutex_destroy(&t4_debug_lock);
141 }
142