1/* SPDX-License-Identifier: BSD-3-Clause */
2/*  Copyright (c) 2024, Intel Corporation
3 *  All rights reserved.
4 *
5 *  Redistribution and use in source and binary forms, with or without
6 *  modification, are permitted provided that the following conditions are met:
7 *
8 *   1. Redistributions of source code must retain the above copyright notice,
9 *      this list of conditions and the following disclaimer.
10 *
11 *   2. Redistributions in binary form must reproduce the above copyright
12 *      notice, this list of conditions and the following disclaimer in the
13 *      documentation and/or other materials provided with the distribution.
14 *
15 *   3. Neither the name of the Intel Corporation nor the names of its
16 *      contributors may be used to endorse or promote products derived from
17 *      this software without specific prior written permission.
18 *
19 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 *  POSSIBILITY OF SUCH DAMAGE.
30 */
31
32/**
33 * @file ice_fw_logging.c
34 * @brief firmware logging sysctls
35 *
36 * Contains sysctls to enable and configure firmware logging debug support.
37 */
38
39#include "ice_lib.h"
40#include "ice_iflib.h"
41#include <sys/queue.h>
42#include <sys/sdt.h>
43
44/*
45 * SDT provider for DTrace probes related to firmware logging events
46 */
47SDT_PROVIDER_DEFINE(ice_fwlog);
48
49/*
50 * SDT DTrace probe fired when a firmware log message is received over the
51 * AdminQ. It passes the buffer of the firwmare log message along with its
52 * length in bytes to the DTrace framework.
53 */
54SDT_PROBE_DEFINE2(ice_fwlog, , , message, "uint8_t *", "int");
55
56/*
57 * Helper function prototypes
58 */
59static int ice_reconfig_fw_log(struct ice_softc *sc, struct ice_fwlog_cfg *cfg);
60
61/*
62 * dynamic sysctl handlers
63 */
64static int ice_sysctl_fwlog_set_cfg_options(SYSCTL_HANDLER_ARGS);
65static int ice_sysctl_fwlog_log_resolution(SYSCTL_HANDLER_ARGS);
66static int ice_sysctl_fwlog_register(SYSCTL_HANDLER_ARGS);
67static int ice_sysctl_fwlog_module_log_severity(SYSCTL_HANDLER_ARGS);
68
69/**
70 * ice_reconfig_fw_log - Re-program firmware logging configuration
71 * @sc: private softc structure
72 * @cfg: firmware log configuration to latch
73 *
74 * If the adminq is currently active, ask firmware to update the logging
75 * configuration. If the adminq is currently down, then do nothing. In this
76 * case, ice_init_hw() will re-configure firmware logging as soon as it brings
77 * up the adminq.
78 */
79static int
80ice_reconfig_fw_log(struct ice_softc *sc, struct ice_fwlog_cfg *cfg)
81{
82	enum ice_status status;
83
84	ice_fwlog_init(&sc->hw, cfg);
85
86	if (!ice_check_sq_alive(&sc->hw, &sc->hw.adminq))
87		return (0);
88
89	if (!ice_fwlog_supported(&sc->hw))
90		return (0);
91
92	status = ice_fwlog_set(&sc->hw, cfg);
93	if (status) {
94		device_printf(sc->dev,
95		    "Failed to reconfigure firmware logging, err %s aq_err %s\n",
96		    ice_status_str(status),
97		    ice_aq_str(sc->hw.adminq.sq_last_status));
98		return (ENODEV);
99	}
100
101	return (0);
102}
103
104#define ICE_SYSCTL_HELP_FWLOG_LOG_RESOLUTION				\
105"\nControl firmware message limit to send per ARQ event"		\
106"\t\nMin: 1"								\
107"\t\nMax: 128"
108
109#define ICE_SYSCTL_HELP_FWLOG_ARQ_ENA					\
110"\nControl whether to enable/disable reporting to admin Rx queue"	\
111"\n0 - Enable firmware reporting via ARQ"				\
112"\n1 - Disable firmware reporting via ARQ"
113
114#define ICE_SYSCTL_HELP_FWLOG_UART_ENA					\
115"\nControl whether to enable/disable reporting to UART"			\
116"\n0 - Enable firmware reporting via UART"				\
117"\n1 - Disable firmware reporting via UART"
118
119#define ICE_SYSCTL_HELP_FWLOG_ENABLE_ON_LOAD				\
120"\nControl whether to enable logging during the attach phase"		\
121"\n0 - Enable firmware logging during attach phase"			\
122"\n1 - Disable firmware logging during attach phase"
123
124#define ICE_SYSCTL_HELP_FWLOG_REGISTER					\
125"\nControl whether to enable/disable firmware logging"			\
126"\n0 - Enable firmware logging"						\
127"\n1 - Disable firmware logging"
128
129#define ICE_SYSCTL_HELP_FWLOG_MODULE_SEVERITY				\
130"\nControl the level of log output messages for this module"		\
131"\n\tverbose <4> - Verbose messages + (Error|Warning|Normal)"		\
132"\n\tnormal  <3> - Normal messages  + (Error|Warning)"			\
133"\n\twarning <2> - Warning messages + (Error)"				\
134"\n\terror   <1> - Error messages"					\
135"\n\tnone    <0> - Disables all logging for this module"
136
137/**
138 * ice_sysctl_fwlog_set_cfg_options - Sysctl for setting fwlog cfg options
139 * @oidp: sysctl oid structure
140 * @arg1: private softc structure
141 * @arg2: option to adjust
142 * @req: sysctl request pointer
143 *
144 * On read: displays whether firmware logging was reported during attachment
145 * On write: enables/disables firmware logging during attach phase
146 *
147 * This has no effect on the legacy (V1) version of firmware logging.
148 */
149static int
150ice_sysctl_fwlog_set_cfg_options(SYSCTL_HANDLER_ARGS)
151{
152	struct ice_softc *sc = (struct ice_softc *)arg1;
153	struct ice_fwlog_cfg *cfg = &sc->hw.fwlog_cfg;
154	int error;
155	u16 option = (u16)arg2;
156	bool enabled;
157
158	enabled = !!(cfg->options & option);
159
160	error = sysctl_handle_bool(oidp, &enabled, 0, req);
161	if ((error) || (req->newptr == NULL))
162		return (error);
163
164	if (enabled)
165		cfg->options |= option;
166	else
167		cfg->options &= ~option;
168
169	return ice_reconfig_fw_log(sc, cfg);
170}
171
172/**
173 * ice_sysctl_fwlog_log_resolution - Sysctl for setting log message resolution
174 * @oidp: sysctl oid structure
175 * @arg1: private softc structure
176 * @arg2: __unused__
177 * @req: sysctl request pointer
178 *
179 * On read: displays message queue limit before posting
180 * On write: sets message queue limit before posting
181 *
182 * This has no effect on the legacy (V1) version of firmware logging.
183 */
184static int
185ice_sysctl_fwlog_log_resolution(SYSCTL_HANDLER_ARGS)
186{
187	struct ice_softc *sc = (struct ice_softc *)arg1;
188	struct ice_fwlog_cfg *cfg = &sc->hw.fwlog_cfg;
189	int error;
190	u8 resolution;
191
192	UNREFERENCED_PARAMETER(arg2);
193
194	resolution = cfg->log_resolution;
195
196	error = sysctl_handle_8(oidp, &resolution, 0, req);
197	if ((error) || (req->newptr == NULL))
198		return (error);
199
200	if ((resolution < ICE_AQC_FW_LOG_MIN_RESOLUTION) ||
201	    (resolution > ICE_AQC_FW_LOG_MAX_RESOLUTION)) {
202		device_printf(sc->dev, "Log resolution out-of-bounds\n");
203		return (EINVAL);
204	}
205
206	cfg->log_resolution = resolution;
207
208	return ice_reconfig_fw_log(sc, cfg);
209}
210
211/**
212 * ice_sysctl_fwlog_register - Sysctl for (de)registering firmware logs
213 * @oidp: sysctl oid structure
214 * @arg1: private softc structure
215 * @arg2: __unused__
216 * @req: sysctl request pointer
217 *
218 * On read: displays whether firmware logging is registered
219 * On write: (de)registers firmware logging.
220 */
221static int
222ice_sysctl_fwlog_register(SYSCTL_HANDLER_ARGS)
223{
224	struct ice_softc *sc = (struct ice_softc *)arg1;
225	struct ice_fwlog_cfg *cfg = &sc->hw.fwlog_cfg;
226	enum ice_status status;
227	int error;
228	u8 enabled;
229
230	UNREFERENCED_PARAMETER(arg2);
231
232	if (ice_test_state(&sc->state, ICE_STATE_ATTACHING)) {
233		device_printf(sc->dev, "Registering FW Logging via kenv is supported with the on_load option\n");
234		return (EIO);
235	}
236
237	if (cfg->options & ICE_FWLOG_OPTION_IS_REGISTERED)
238		enabled = true;
239	else
240		enabled = false;
241
242	error = sysctl_handle_bool(oidp, &enabled, 0, req);
243	if ((error) || (req->newptr == NULL))
244		return (error);
245
246	if (!ice_check_sq_alive(&sc->hw, &sc->hw.adminq))
247		return (0);
248
249	if (enabled) {
250		status = ice_fwlog_register(&sc->hw);
251		if (!status)
252			ice_set_bit(ICE_FEATURE_FW_LOGGING, sc->feat_en);
253	} else {
254		status = ice_fwlog_unregister(&sc->hw);
255		if (!status)
256			ice_clear_bit(ICE_FEATURE_FW_LOGGING, sc->feat_en);
257	}
258
259	if (status)
260		return (EIO);
261
262	return (0);
263}
264
265/**
266 * ice_sysctl_fwlog_module_log_severity - Add tunables for a FW logging module
267 * @oidp: sysctl oid structure
268 * @arg1: private softc structure
269 * @arg2: index to logging module
270 * @req: sysctl request pointer
271 */
272static int
273ice_sysctl_fwlog_module_log_severity(SYSCTL_HANDLER_ARGS)
274{
275	struct ice_softc *sc = (struct ice_softc *)arg1;
276	struct ice_fwlog_cfg *cfg = &sc->hw.fwlog_cfg;
277	struct sbuf *sbuf;
278	char *sev_str_end;
279	enum ice_aqc_fw_logging_mod module = (enum ice_aqc_fw_logging_mod)arg2;
280	int error, ll_num;
281	u8 log_level;
282	char sev_str[16];
283	bool sev_set = false;
284
285	log_level = cfg->module_entries[module].log_level;
286	sbuf = sbuf_new(NULL, sev_str, sizeof(sev_str), SBUF_FIXEDLEN);
287	sbuf_printf(sbuf, "%d<%s>", log_level, ice_log_sev_str(log_level));
288	sbuf_finish(sbuf);
289	sbuf_delete(sbuf);
290
291	error = sysctl_handle_string(oidp, sev_str, sizeof(sev_str), req);
292	if ((error) || (req->newptr == NULL))
293		return (error);
294
295	if (strcasecmp(ice_log_sev_str(ICE_FWLOG_LEVEL_VERBOSE), sev_str) == 0) {
296		log_level = ICE_FWLOG_LEVEL_VERBOSE;
297		sev_set = true;
298	} else if (strcasecmp(ice_log_sev_str(ICE_FWLOG_LEVEL_NORMAL), sev_str) == 0) {
299		log_level = ICE_FWLOG_LEVEL_NORMAL;
300		sev_set = true;
301	} else if (strcasecmp(ice_log_sev_str(ICE_FWLOG_LEVEL_WARNING), sev_str) == 0) {
302		log_level = ICE_FWLOG_LEVEL_WARNING;
303		sev_set = true;
304	} else if (strcasecmp(ice_log_sev_str(ICE_FWLOG_LEVEL_ERROR), sev_str) == 0) {
305		log_level = ICE_FWLOG_LEVEL_ERROR;
306		sev_set = true;
307	} else if (strcasecmp(ice_log_sev_str(ICE_FWLOG_LEVEL_NONE), sev_str) == 0) {
308		log_level = ICE_FWLOG_LEVEL_NONE;
309		sev_set = true;
310	}
311
312	if (!sev_set) {
313		ll_num = strtol(sev_str, &sev_str_end, 0);
314		if (sev_str_end == sev_str)
315			ll_num = -1;
316		if ((ll_num >= ICE_FWLOG_LEVEL_NONE) &&
317		    (ll_num < ICE_FWLOG_LEVEL_INVALID))
318			log_level = ll_num;
319		else {
320			device_printf(sc->dev,
321			    "%s: \"%s\" is not a valid log level\n",
322			    __func__, sev_str);
323			return (EINVAL);
324		}
325	}
326
327	cfg->module_entries[module].log_level = log_level;
328
329	return ice_reconfig_fw_log(sc, cfg);
330}
331
332/**
333 * ice_add_fw_logging_tunables - Add tunables to configure FW logging events
334 * @sc: private softc structure
335 * @parent: parent node to add the tunables under
336 *
337 * Add tunables for configuring the firmware logging support. This includes
338 * a control to enable the logging, and controls for each module to configure
339 * which events to receive.
340 */
341void
342ice_add_fw_logging_tunables(struct ice_softc *sc, struct sysctl_oid *parent)
343{
344	struct sysctl_oid_list *parent_list, *fwlog_list, *module_list;
345	struct sysctl_oid *fwlog_node, *module_node;
346	struct sysctl_ctx_list *ctx;
347	struct ice_hw *hw = &sc->hw;
348	struct ice_fwlog_cfg *cfg;
349	device_t dev = sc->dev;
350	enum ice_aqc_fw_logging_mod module;
351	u16 i;
352
353	cfg = &hw->fwlog_cfg;
354	ctx = device_get_sysctl_ctx(dev);
355	parent_list = SYSCTL_CHILDREN(parent);
356
357	fwlog_node = SYSCTL_ADD_NODE(ctx, parent_list, OID_AUTO, "fw_log",
358				     ICE_CTLFLAG_DEBUG | CTLFLAG_RD, NULL,
359				     "Firmware Logging");
360	fwlog_list = SYSCTL_CHILDREN(fwlog_node);
361
362	cfg->log_resolution = 10;
363	SYSCTL_ADD_PROC(ctx, fwlog_list, OID_AUTO, "log_resolution",
364	    ICE_CTLFLAG_DEBUG | CTLTYPE_U8 | CTLFLAG_RWTUN, sc,
365	    0, ice_sysctl_fwlog_log_resolution,
366	    "CU", ICE_SYSCTL_HELP_FWLOG_LOG_RESOLUTION);
367
368	cfg->options |= ICE_FWLOG_OPTION_ARQ_ENA;
369	SYSCTL_ADD_PROC(ctx, fwlog_list, OID_AUTO, "arq_en",
370	    ICE_CTLFLAG_DEBUG | CTLTYPE_U8 | CTLFLAG_RWTUN, sc,
371	    ICE_FWLOG_OPTION_ARQ_ENA, ice_sysctl_fwlog_set_cfg_options,
372	    "CU", ICE_SYSCTL_HELP_FWLOG_ARQ_ENA);
373
374	SYSCTL_ADD_PROC(ctx, fwlog_list, OID_AUTO, "uart_en",
375	    ICE_CTLFLAG_DEBUG | CTLTYPE_U8 | CTLFLAG_RWTUN, sc,
376	    ICE_FWLOG_OPTION_UART_ENA, ice_sysctl_fwlog_set_cfg_options,
377	    "CU", ICE_SYSCTL_HELP_FWLOG_UART_ENA);
378
379	SYSCTL_ADD_PROC(ctx, fwlog_list, OID_AUTO, "on_load",
380	    ICE_CTLFLAG_DEBUG | CTLTYPE_U8 | CTLFLAG_RWTUN, sc,
381	    ICE_FWLOG_OPTION_REGISTER_ON_INIT, ice_sysctl_fwlog_set_cfg_options,
382	    "CU", ICE_SYSCTL_HELP_FWLOG_ENABLE_ON_LOAD);
383
384	SYSCTL_ADD_PROC(ctx, fwlog_list, OID_AUTO, "register",
385	    ICE_CTLFLAG_DEBUG | CTLTYPE_U8 | CTLFLAG_RWTUN, sc,
386	    0, ice_sysctl_fwlog_register,
387	    "CU", ICE_SYSCTL_HELP_FWLOG_REGISTER);
388
389	module_node = SYSCTL_ADD_NODE(ctx, fwlog_list, OID_AUTO, "severity",
390				      ICE_CTLFLAG_DEBUG | CTLFLAG_RD, NULL,
391				      "Level of log output");
392
393	module_list = SYSCTL_CHILDREN(module_node);
394
395	for (i = 0; i < ICE_AQC_FW_LOG_ID_MAX; i++) {
396		/* Setup some defaults */
397		cfg->module_entries[i].module_id = i;
398		cfg->module_entries[i].log_level = ICE_FWLOG_LEVEL_NONE;
399		module = (enum ice_aqc_fw_logging_mod)i;
400
401		SYSCTL_ADD_PROC(ctx, module_list,
402		    OID_AUTO, ice_fw_module_str(module),
403		    ICE_CTLFLAG_DEBUG | CTLTYPE_STRING | CTLFLAG_RWTUN, sc,
404		    module, ice_sysctl_fwlog_module_log_severity,
405		    "A", ICE_SYSCTL_HELP_FWLOG_MODULE_SEVERITY);
406	}
407}
408
409/**
410 * ice_handle_fw_log_event - Handle a firmware logging event from the AdminQ
411 * @sc: pointer to private softc structure
412 * @desc: the AdminQ descriptor for this firmware event
413 * @buf: pointer to the buffer accompanying the AQ message
414 */
415void
416ice_handle_fw_log_event(struct ice_softc *sc, struct ice_aq_desc *desc,
417			void *buf)
418{
419	/* Trigger a DTrace probe event for this firmware message */
420	SDT_PROBE2(ice_fwlog, , , message, (const u8 *)buf, desc->datalen);
421
422	/* Possibly dump the firmware message to the console, if enabled */
423	ice_fwlog_event_dump(&sc->hw, desc, buf);
424}
425