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 STAT_INIT(inv_cmdseq_err);
93
94 /* Errors reported by asynchronous events */
95 STAT_INIT(diagfail_event);
96 STAT_INIT(persistent_event);
97 STAT_INIT(transient_event);
98 STAT_INIT(fw_load_event);
99 STAT_INIT(reliability_event);
100 STAT_INIT(temperature_event);
101 STAT_INIT(spare_event);
102 STAT_INIT(vendor_event);
103 STAT_INIT(notice_event);
104 STAT_INIT(unknown_event);
105
106 #undef STAT_INIT
107
108 kstat_install(nvme->n_device_kstat);
109 return (true);
110 }
111
112 static void
nvme_stat_admin_cleanup(nvme_t * nvme)113 nvme_stat_admin_cleanup(nvme_t *nvme)
114 {
115 if (nvme->n_admin_kstat != NULL) {
116 kstat_delete(nvme->n_admin_kstat);
117 nvme->n_admin_kstat = NULL;
118 mutex_destroy(&nvme->n_admin_stat_mutex);
119 }
120 }
121
122 static boolean_t
nvme_stat_admin_init(nvme_t * nvme)123 nvme_stat_admin_init(nvme_t *nvme)
124 {
125 kstat_t *ksp = kstat_create(NVME_MODULE_NAME,
126 ddi_get_instance(nvme->n_dip), "admin", "controller",
127 KSTAT_TYPE_NAMED,
128 sizeof (nvme_admin_stat_t) / sizeof (kstat_named_t),
129 KSTAT_FLAG_VIRTUAL);
130 nvme_admin_stat_t *nas = &nvme->n_admin_stat;
131
132 if (ksp == NULL) {
133 dev_err(nvme->n_dip, CE_WARN,
134 "!failed to create admin kstats");
135 return (false);
136 }
137
138 nvme->n_admin_kstat = ksp;
139 ksp->ks_data = nas;
140 mutex_init(&nvme->n_admin_stat_mutex, NULL, MUTEX_DRIVER, NULL);
141
142 #define STAT_INIT_ONE(stat, index, postfix) \
143 kstat_named_init(&nas->nas_ ## stat[index], #stat postfix, \
144 KSTAT_DATA_UINT64)
145
146 #define STAT_INIT(stat) \
147 do { \
148 STAT_INIT_ONE(stat, NAS_CNT, "_cnt"); \
149 STAT_INIT_ONE(stat, NAS_AVG, "_avg"); \
150 STAT_INIT_ONE(stat, NAS_MAX, "_max"); \
151 } while (0)
152
153 STAT_INIT(getlogpage);
154 STAT_INIT(identify);
155 STAT_INIT(abort);
156 STAT_INIT(fwactivate);
157 STAT_INIT(fwimgload);
158 STAT_INIT(nsformat);
159 STAT_INIT(vendor);
160 STAT_INIT(other);
161
162 #undef STAT_INIT
163 #undef STAT_INIT_ONE
164
165 kstat_install(nvme->n_admin_kstat);
166 return (true);
167 }
168
169 void
nvme_stat_cleanup(nvme_t * nvme)170 nvme_stat_cleanup(nvme_t *nvme)
171 {
172 nvme_stat_device_cleanup(nvme);
173 nvme_stat_admin_cleanup(nvme);
174 }
175
176 boolean_t
nvme_stat_init(nvme_t * nvme)177 nvme_stat_init(nvme_t *nvme)
178 {
179 if (!nvme_stat_device_init(nvme) || !nvme_stat_admin_init(nvme)) {
180 nvme_stat_cleanup(nvme);
181 return (B_FALSE);
182 }
183 return (B_TRUE);
184 }
185
186 void
nvme_admin_stat_cmd(nvme_t * nvme,nvme_cmd_t * cmd)187 nvme_admin_stat_cmd(nvme_t *nvme, nvme_cmd_t *cmd)
188 {
189 hrtime_t t;
190 uint64_t cnt, avg;
191 kstat_named_t *data, *cntd, *avgd, *maxd;
192
193 switch (cmd->nc_sqe.sqe_opc) {
194 case NVME_OPC_DELETE_SQUEUE:
195 case NVME_OPC_CREATE_SQUEUE:
196 case NVME_OPC_DELETE_CQUEUE:
197 case NVME_OPC_CREATE_CQUEUE:
198 /* No statistics are kept for these opcodes */
199 return;
200 case NVME_OPC_GET_LOG_PAGE:
201 data = nvme->n_admin_stat.nas_getlogpage;
202 break;
203 case NVME_OPC_IDENTIFY:
204 data = nvme->n_admin_stat.nas_identify;
205 break;
206 case NVME_OPC_ABORT:
207 data = nvme->n_admin_stat.nas_abort;
208 break;
209 case NVME_OPC_FW_ACTIVATE:
210 data = nvme->n_admin_stat.nas_fwactivate;
211 break;
212 case NVME_OPC_FW_IMAGE_LOAD:
213 data = nvme->n_admin_stat.nas_fwimgload;
214 break;
215 case NVME_OPC_NVM_FORMAT:
216 data = nvme->n_admin_stat.nas_nsformat;
217 break;
218 case NVME_OPC_VENDOR_LOW ... NVME_OPC_VENDOR_HIGH:
219 data = nvme->n_admin_stat.nas_vendor;
220 break;
221 default:
222 data = nvme->n_admin_stat.nas_other;
223 break;
224 }
225
226 t = gethrtime() - cmd->nc_submit_ts;
227
228 cntd = &data[NAS_CNT];
229 avgd = &data[NAS_AVG];
230 maxd = &data[NAS_MAX];
231
232 mutex_enter(&nvme->n_admin_stat_mutex);
233 cnt = cntd->value.ui64;
234 avg = avgd->value.ui64;
235
236 /*
237 * Update the cumulative rolling average.
238 * Since `t` and `avg` are orders of magnitude greater than `cnt` it
239 * is sufficient to directly adjust the current average towards the
240 * new value.
241 */
242 if (t > avg)
243 avg += (t - avg) / (cnt + 1);
244 else
245 avg -= (avg - t) / (cnt + 1);
246 cntd->value.ui64++;
247 avgd->value.ui64 = avg;
248 if (t > maxd->value.ui64)
249 maxd->value.ui64 = t;
250 mutex_exit(&nvme->n_admin_stat_mutex);
251 }
252