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