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 2024 Oxide Computer Company
14 */
15
16 #include "nvme_reg.h"
17 #include "nvme_var.h"
18
19 static void
nvme_stat_device_cleanup(nvme_t * nvme)20 nvme_stat_device_cleanup(nvme_t *nvme)
21 {
22 if (nvme->n_device_kstat != NULL) {
23 kstat_delete(nvme->n_device_kstat);
24 nvme->n_device_kstat = NULL;
25 }
26 }
27
28 static boolean_t
nvme_stat_device_init(nvme_t * nvme)29 nvme_stat_device_init(nvme_t *nvme)
30 {
31 kstat_t *ksp = kstat_create(NVME_MODULE_NAME,
32 ddi_get_instance(nvme->n_dip), "device", "controller",
33 KSTAT_TYPE_NAMED,
34 sizeof (nvme_device_stat_t) / sizeof (kstat_named_t),
35 KSTAT_FLAG_VIRTUAL);
36 nvme_device_stat_t *nds = &nvme->n_device_stat;
37
38 if (ksp == NULL) {
39 dev_err(nvme->n_dip, CE_WARN,
40 "!failed to create device kstats");
41 return (false);
42 }
43
44 nvme->n_device_kstat = ksp;
45 ksp->ks_data = nds;
46
47 #define STAT_INIT(stat) \
48 kstat_named_init(&nds->nds_ ## stat, #stat, KSTAT_DATA_UINT64)
49
50 /* Errors detected by driver */
51 STAT_INIT(dma_bind_err);
52 STAT_INIT(abort_timeout);
53 STAT_INIT(abort_failed);
54 STAT_INIT(abort_successful);
55 STAT_INIT(abort_unsuccessful);
56 STAT_INIT(cmd_timeout);
57 STAT_INIT(wrong_logpage);
58 STAT_INIT(unknown_logpage);
59 STAT_INIT(too_many_cookies);
60 STAT_INIT(unknown_cid);
61
62 /* Errors detected by hardware */
63 STAT_INIT(inv_cmd_err);
64 STAT_INIT(inv_field_err);
65 STAT_INIT(inv_nsfmt_err);
66 STAT_INIT(data_xfr_err);
67 STAT_INIT(internal_err);
68 STAT_INIT(abort_rq_err);
69 STAT_INIT(abort_pwrloss_err);
70 STAT_INIT(abort_sq_del);
71 STAT_INIT(nvm_cap_exc);
72 STAT_INIT(nvm_ns_notrdy);
73 STAT_INIT(nvm_ns_formatting);
74 STAT_INIT(inv_cq_err);
75 STAT_INIT(inv_qid_err);
76 STAT_INIT(max_qsz_exc);
77 STAT_INIT(inv_int_vect);
78 STAT_INIT(inv_log_page);
79 STAT_INIT(inv_format);
80 STAT_INIT(inv_q_del);
81 STAT_INIT(cnfl_attr);
82 STAT_INIT(inv_prot);
83 STAT_INIT(readonly);
84 STAT_INIT(inv_fwslot);
85 STAT_INIT(inv_fwimg);
86 STAT_INIT(fwact_creset);
87 STAT_INIT(fwact_nssr);
88 STAT_INIT(fwact_reset);
89 STAT_INIT(fwact_mtfa);
90 STAT_INIT(fwact_prohibited);
91 STAT_INIT(fw_overlap);
92
93 /* Errors reported by asynchronous events */
94 STAT_INIT(diagfail_event);
95 STAT_INIT(persistent_event);
96 STAT_INIT(transient_event);
97 STAT_INIT(fw_load_event);
98 STAT_INIT(reliability_event);
99 STAT_INIT(temperature_event);
100 STAT_INIT(spare_event);
101 STAT_INIT(vendor_event);
102 STAT_INIT(notice_event);
103 STAT_INIT(unknown_event);
104
105 #undef STAT_INIT
106
107 kstat_install(nvme->n_device_kstat);
108 return (true);
109 }
110
111 static void
nvme_stat_admin_cleanup(nvme_t * nvme)112 nvme_stat_admin_cleanup(nvme_t *nvme)
113 {
114 if (nvme->n_admin_kstat != NULL) {
115 kstat_delete(nvme->n_admin_kstat);
116 nvme->n_admin_kstat = NULL;
117 mutex_destroy(&nvme->n_admin_stat_mutex);
118 }
119 }
120
121 static boolean_t
nvme_stat_admin_init(nvme_t * nvme)122 nvme_stat_admin_init(nvme_t *nvme)
123 {
124 kstat_t *ksp = kstat_create(NVME_MODULE_NAME,
125 ddi_get_instance(nvme->n_dip), "admin", "controller",
126 KSTAT_TYPE_NAMED,
127 sizeof (nvme_admin_stat_t) / sizeof (kstat_named_t),
128 KSTAT_FLAG_VIRTUAL);
129 nvme_admin_stat_t *nas = &nvme->n_admin_stat;
130
131 if (ksp == NULL) {
132 dev_err(nvme->n_dip, CE_WARN,
133 "!failed to create admin kstats");
134 return (false);
135 }
136
137 nvme->n_admin_kstat = ksp;
138 ksp->ks_data = nas;
139 mutex_init(&nvme->n_admin_stat_mutex, NULL, MUTEX_DRIVER, NULL);
140
141 #define STAT_INIT_ONE(stat, index, postfix) \
142 kstat_named_init(&nas->nas_ ## stat[index], #stat postfix, \
143 KSTAT_DATA_UINT64)
144
145 #define STAT_INIT(stat) \
146 do { \
147 STAT_INIT_ONE(stat, NAS_CNT, "_cnt"); \
148 STAT_INIT_ONE(stat, NAS_AVG, "_avg"); \
149 STAT_INIT_ONE(stat, NAS_MAX, "_max"); \
150 } while (0)
151
152 STAT_INIT(getlogpage);
153 STAT_INIT(identify);
154 STAT_INIT(abort);
155 STAT_INIT(fwactivate);
156 STAT_INIT(fwimgload);
157 STAT_INIT(nsformat);
158 STAT_INIT(vendor);
159 STAT_INIT(other);
160
161 #undef STAT_INIT
162 #undef STAT_INIT_ONE
163
164 kstat_install(nvme->n_admin_kstat);
165 return (true);
166 }
167
168 void
nvme_stat_cleanup(nvme_t * nvme)169 nvme_stat_cleanup(nvme_t *nvme)
170 {
171 nvme_stat_device_cleanup(nvme);
172 nvme_stat_admin_cleanup(nvme);
173 }
174
175 boolean_t
nvme_stat_init(nvme_t * nvme)176 nvme_stat_init(nvme_t *nvme)
177 {
178 if (!nvme_stat_device_init(nvme) || !nvme_stat_admin_init(nvme)) {
179 nvme_stat_cleanup(nvme);
180 return (B_FALSE);
181 }
182 return (B_TRUE);
183 }
184
185 void
nvme_admin_stat_cmd(nvme_t * nvme,nvme_cmd_t * cmd)186 nvme_admin_stat_cmd(nvme_t *nvme, nvme_cmd_t *cmd)
187 {
188 hrtime_t t;
189 uint64_t cnt, avg;
190 kstat_named_t *data, *cntd, *avgd, *maxd;
191
192 switch (cmd->nc_sqe.sqe_opc) {
193 case NVME_OPC_DELETE_SQUEUE:
194 case NVME_OPC_CREATE_SQUEUE:
195 case NVME_OPC_DELETE_CQUEUE:
196 case NVME_OPC_CREATE_CQUEUE:
197 /* No statistics are kept for these opcodes */
198 return;
199 case NVME_OPC_GET_LOG_PAGE:
200 data = nvme->n_admin_stat.nas_getlogpage;
201 break;
202 case NVME_OPC_IDENTIFY:
203 data = nvme->n_admin_stat.nas_identify;
204 break;
205 case NVME_OPC_ABORT:
206 data = nvme->n_admin_stat.nas_abort;
207 break;
208 case NVME_OPC_FW_ACTIVATE:
209 data = nvme->n_admin_stat.nas_fwactivate;
210 break;
211 case NVME_OPC_FW_IMAGE_LOAD:
212 data = nvme->n_admin_stat.nas_fwimgload;
213 break;
214 case NVME_OPC_NVM_FORMAT:
215 data = nvme->n_admin_stat.nas_nsformat;
216 break;
217 case NVME_OPC_VENDOR_LOW ... NVME_OPC_VENDOR_HIGH:
218 data = nvme->n_admin_stat.nas_vendor;
219 break;
220 default:
221 data = nvme->n_admin_stat.nas_other;
222 break;
223 }
224
225 t = gethrtime() - cmd->nc_submit_ts;
226
227 cntd = &data[NAS_CNT];
228 avgd = &data[NAS_AVG];
229 maxd = &data[NAS_MAX];
230
231 mutex_enter(&nvme->n_admin_stat_mutex);
232 cnt = cntd->value.ui64;
233 avg = avgd->value.ui64;
234
235 /*
236 * Update the cumulative rolling average.
237 * Since `t` and `avg` are orders of magnitude greater than `cnt` it
238 * is sufficient to directly adjust the current average towards the
239 * new value.
240 */
241 if (t > avg)
242 avg += (t - avg) / (cnt + 1);
243 else
244 avg -= (avg - t) / (cnt + 1);
245 cntd->value.ui64++;
246 avgd->value.ui64 = avg;
247 if (t > maxd->value.ui64)
248 maxd->value.ui64 = t;
249 mutex_exit(&nvme->n_admin_stat_mutex);
250 }
251