1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* Marvell/Qlogic FastLinQ NIC driver |
3 | * |
4 | * Copyright (C) 2020 Marvell International Ltd. |
5 | */ |
6 | |
7 | #include <linux/kernel.h> |
8 | #include <linux/qed/qed_if.h> |
9 | #include <linux/vmalloc.h> |
10 | #include "qed.h" |
11 | #include "qed_devlink.h" |
12 | |
13 | enum qed_devlink_param_id { |
14 | QED_DEVLINK_PARAM_ID_BASE = DEVLINK_PARAM_GENERIC_ID_MAX, |
15 | QED_DEVLINK_PARAM_ID_IWARP_CMT, |
16 | }; |
17 | |
18 | struct qed_fw_fatal_ctx { |
19 | enum qed_hw_err_type err_type; |
20 | }; |
21 | |
22 | int qed_report_fatal_error(struct devlink *devlink, enum qed_hw_err_type err_type) |
23 | { |
24 | struct qed_devlink *qdl = devlink_priv(devlink); |
25 | struct qed_fw_fatal_ctx fw_fatal_ctx = { |
26 | .err_type = err_type, |
27 | }; |
28 | |
29 | if (qdl->fw_reporter) |
30 | devlink_health_report(reporter: qdl->fw_reporter, |
31 | msg: "Fatal error occurred" , priv_ctx: &fw_fatal_ctx); |
32 | |
33 | return 0; |
34 | } |
35 | |
36 | static int |
37 | qed_fw_fatal_reporter_dump(struct devlink_health_reporter *reporter, |
38 | struct devlink_fmsg *fmsg, void *priv_ctx, |
39 | struct netlink_ext_ack *extack) |
40 | { |
41 | struct qed_devlink *qdl = devlink_health_reporter_priv(reporter); |
42 | struct qed_fw_fatal_ctx *fw_fatal_ctx = priv_ctx; |
43 | struct qed_dev *cdev = qdl->cdev; |
44 | u32 dbg_data_buf_size; |
45 | u8 *p_dbg_data_buf; |
46 | int err; |
47 | |
48 | /* Having context means that was a dump request after fatal, |
49 | * so we enable extra debugging while gathering the dump, |
50 | * just in case |
51 | */ |
52 | cdev->print_dbg_data = fw_fatal_ctx ? true : false; |
53 | |
54 | dbg_data_buf_size = qed_dbg_all_data_size(cdev); |
55 | p_dbg_data_buf = vzalloc(size: dbg_data_buf_size); |
56 | if (!p_dbg_data_buf) { |
57 | DP_NOTICE(cdev, |
58 | "Failed to allocate memory for a debug data buffer\n" ); |
59 | return -ENOMEM; |
60 | } |
61 | |
62 | err = qed_dbg_all_data(cdev, buffer: p_dbg_data_buf); |
63 | if (err) { |
64 | DP_NOTICE(cdev, "Failed to obtain debug data\n" ); |
65 | vfree(addr: p_dbg_data_buf); |
66 | return err; |
67 | } |
68 | |
69 | devlink_fmsg_binary_pair_put(fmsg, name: "dump_data" , value: p_dbg_data_buf, |
70 | value_len: dbg_data_buf_size); |
71 | |
72 | vfree(addr: p_dbg_data_buf); |
73 | |
74 | return 0; |
75 | } |
76 | |
77 | static int |
78 | qed_fw_fatal_reporter_recover(struct devlink_health_reporter *reporter, |
79 | void *priv_ctx, |
80 | struct netlink_ext_ack *extack) |
81 | { |
82 | struct qed_devlink *qdl = devlink_health_reporter_priv(reporter); |
83 | struct qed_dev *cdev = qdl->cdev; |
84 | |
85 | qed_recovery_process(cdev); |
86 | |
87 | return 0; |
88 | } |
89 | |
90 | static const struct devlink_health_reporter_ops qed_fw_fatal_reporter_ops = { |
91 | .name = "fw_fatal" , |
92 | .recover = qed_fw_fatal_reporter_recover, |
93 | .dump = qed_fw_fatal_reporter_dump, |
94 | }; |
95 | |
96 | #define QED_REPORTER_FW_GRACEFUL_PERIOD 0 |
97 | |
98 | void qed_fw_reporters_create(struct devlink *devlink) |
99 | { |
100 | struct qed_devlink *dl = devlink_priv(devlink); |
101 | |
102 | dl->fw_reporter = devlink_health_reporter_create(devlink, ops: &qed_fw_fatal_reporter_ops, |
103 | QED_REPORTER_FW_GRACEFUL_PERIOD, priv: dl); |
104 | if (IS_ERR(ptr: dl->fw_reporter)) { |
105 | DP_NOTICE(dl->cdev, "Failed to create fw reporter, err = %ld\n" , |
106 | PTR_ERR(dl->fw_reporter)); |
107 | dl->fw_reporter = NULL; |
108 | } |
109 | } |
110 | |
111 | void qed_fw_reporters_destroy(struct devlink *devlink) |
112 | { |
113 | struct qed_devlink *dl = devlink_priv(devlink); |
114 | struct devlink_health_reporter *rep; |
115 | |
116 | rep = dl->fw_reporter; |
117 | |
118 | if (!IS_ERR_OR_NULL(ptr: rep)) |
119 | devlink_health_reporter_destroy(reporter: rep); |
120 | } |
121 | |
122 | static int qed_dl_param_get(struct devlink *dl, u32 id, |
123 | struct devlink_param_gset_ctx *ctx) |
124 | { |
125 | struct qed_devlink *qed_dl = devlink_priv(devlink: dl); |
126 | struct qed_dev *cdev; |
127 | |
128 | cdev = qed_dl->cdev; |
129 | ctx->val.vbool = cdev->iwarp_cmt; |
130 | |
131 | return 0; |
132 | } |
133 | |
134 | static int qed_dl_param_set(struct devlink *dl, u32 id, |
135 | struct devlink_param_gset_ctx *ctx) |
136 | { |
137 | struct qed_devlink *qed_dl = devlink_priv(devlink: dl); |
138 | struct qed_dev *cdev; |
139 | |
140 | cdev = qed_dl->cdev; |
141 | cdev->iwarp_cmt = ctx->val.vbool; |
142 | |
143 | return 0; |
144 | } |
145 | |
146 | static const struct devlink_param qed_devlink_params[] = { |
147 | DEVLINK_PARAM_DRIVER(QED_DEVLINK_PARAM_ID_IWARP_CMT, |
148 | "iwarp_cmt" , DEVLINK_PARAM_TYPE_BOOL, |
149 | BIT(DEVLINK_PARAM_CMODE_RUNTIME), |
150 | qed_dl_param_get, qed_dl_param_set, NULL), |
151 | }; |
152 | |
153 | static int qed_devlink_info_get(struct devlink *devlink, |
154 | struct devlink_info_req *req, |
155 | struct netlink_ext_ack *extack) |
156 | { |
157 | struct qed_devlink *qed_dl = devlink_priv(devlink); |
158 | struct qed_dev *cdev = qed_dl->cdev; |
159 | struct qed_dev_info *dev_info; |
160 | char buf[100]; |
161 | int err; |
162 | |
163 | dev_info = &cdev->common_dev_info; |
164 | |
165 | memcpy(buf, cdev->hwfns[0].hw_info.part_num, sizeof(cdev->hwfns[0].hw_info.part_num)); |
166 | buf[sizeof(cdev->hwfns[0].hw_info.part_num)] = 0; |
167 | |
168 | if (buf[0]) { |
169 | err = devlink_info_board_serial_number_put(req, bsn: buf); |
170 | if (err) |
171 | return err; |
172 | } |
173 | |
174 | snprintf(buf, size: sizeof(buf), fmt: "%d.%d.%d.%d" , |
175 | GET_MFW_FIELD(dev_info->mfw_rev, QED_MFW_VERSION_3), |
176 | GET_MFW_FIELD(dev_info->mfw_rev, QED_MFW_VERSION_2), |
177 | GET_MFW_FIELD(dev_info->mfw_rev, QED_MFW_VERSION_1), |
178 | GET_MFW_FIELD(dev_info->mfw_rev, QED_MFW_VERSION_0)); |
179 | |
180 | err = devlink_info_version_stored_put(req, |
181 | DEVLINK_INFO_VERSION_GENERIC_FW_MGMT, version_value: buf); |
182 | if (err) |
183 | return err; |
184 | |
185 | snprintf(buf, size: sizeof(buf), fmt: "%d.%d.%d.%d" , |
186 | dev_info->fw_major, |
187 | dev_info->fw_minor, |
188 | dev_info->fw_rev, |
189 | dev_info->fw_eng); |
190 | |
191 | return devlink_info_version_running_put(req, |
192 | DEVLINK_INFO_VERSION_GENERIC_FW_APP, version_value: buf); |
193 | } |
194 | |
195 | static const struct devlink_ops qed_dl_ops = { |
196 | .info_get = qed_devlink_info_get, |
197 | }; |
198 | |
199 | struct devlink *qed_devlink_register(struct qed_dev *cdev) |
200 | { |
201 | struct qed_devlink *qdevlink; |
202 | struct devlink *dl; |
203 | int rc; |
204 | |
205 | dl = devlink_alloc(ops: &qed_dl_ops, priv_size: sizeof(struct qed_devlink), |
206 | dev: &cdev->pdev->dev); |
207 | if (!dl) |
208 | return ERR_PTR(error: -ENOMEM); |
209 | |
210 | qdevlink = devlink_priv(devlink: dl); |
211 | qdevlink->cdev = cdev; |
212 | |
213 | rc = devlink_params_register(devlink: dl, params: qed_devlink_params, |
214 | ARRAY_SIZE(qed_devlink_params)); |
215 | if (rc) |
216 | goto err_unregister; |
217 | |
218 | cdev->iwarp_cmt = false; |
219 | |
220 | qed_fw_reporters_create(devlink: dl); |
221 | devlink_register(devlink: dl); |
222 | return dl; |
223 | |
224 | err_unregister: |
225 | devlink_free(devlink: dl); |
226 | |
227 | return ERR_PTR(error: rc); |
228 | } |
229 | |
230 | void qed_devlink_unregister(struct devlink *devlink) |
231 | { |
232 | if (!devlink) |
233 | return; |
234 | |
235 | devlink_unregister(devlink); |
236 | qed_fw_reporters_destroy(devlink); |
237 | |
238 | devlink_params_unregister(devlink, params: qed_devlink_params, |
239 | ARRAY_SIZE(qed_devlink_params)); |
240 | |
241 | devlink_free(devlink); |
242 | } |
243 | |