xref: /illumos-gate/usr/src/uts/common/io/cxgbe/t4nex/t4_debug.c (revision ba52565b00cef6c84d9c58d122f646c8f30b4a38)
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