Commit | Line | Data |
---|---|---|
523847df SN |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* Copyright(c) 2023 Advanced Micro Devices, Inc */ | |
3 | ||
4 | #include <linux/errno.h> | |
5 | #include <linux/pci.h> | |
6 | #include <linux/utsname.h> | |
7 | ||
8 | #include "core.h" | |
9 | ||
10 | int pdsc_err_to_errno(enum pds_core_status_code code) | |
11 | { | |
12 | switch (code) { | |
13 | case PDS_RC_SUCCESS: | |
14 | return 0; | |
15 | case PDS_RC_EVERSION: | |
16 | case PDS_RC_EQTYPE: | |
17 | case PDS_RC_EQID: | |
18 | case PDS_RC_EINVAL: | |
19 | case PDS_RC_ENOSUPP: | |
20 | return -EINVAL; | |
21 | case PDS_RC_EPERM: | |
22 | return -EPERM; | |
23 | case PDS_RC_ENOENT: | |
24 | return -ENOENT; | |
25 | case PDS_RC_EAGAIN: | |
26 | return -EAGAIN; | |
27 | case PDS_RC_ENOMEM: | |
28 | return -ENOMEM; | |
29 | case PDS_RC_EFAULT: | |
30 | return -EFAULT; | |
31 | case PDS_RC_EBUSY: | |
32 | return -EBUSY; | |
33 | case PDS_RC_EEXIST: | |
34 | return -EEXIST; | |
35 | case PDS_RC_EVFID: | |
36 | return -ENODEV; | |
37 | case PDS_RC_ECLIENT: | |
38 | return -ECHILD; | |
39 | case PDS_RC_ENOSPC: | |
40 | return -ENOSPC; | |
41 | case PDS_RC_ERANGE: | |
42 | return -ERANGE; | |
43 | case PDS_RC_BAD_ADDR: | |
44 | return -EFAULT; | |
f7b5bd72 SN |
45 | case PDS_RC_BAD_PCI: |
46 | return -ENXIO; | |
523847df SN |
47 | case PDS_RC_EOPCODE: |
48 | case PDS_RC_EINTR: | |
49 | case PDS_RC_DEV_CMD: | |
50 | case PDS_RC_ERROR: | |
51 | case PDS_RC_ERDMA: | |
52 | case PDS_RC_EIO: | |
53 | default: | |
54 | return -EIO; | |
55 | } | |
56 | } | |
57 | ||
58 | bool pdsc_is_fw_running(struct pdsc *pdsc) | |
59 | { | |
e96094c1 BC |
60 | if (!pdsc->info_regs) |
61 | return false; | |
62 | ||
523847df SN |
63 | pdsc->fw_status = ioread8(&pdsc->info_regs->fw_status); |
64 | pdsc->last_fw_time = jiffies; | |
65 | pdsc->last_hb = ioread32(&pdsc->info_regs->fw_heartbeat); | |
66 | ||
67 | /* Firmware is useful only if the running bit is set and | |
68 | * fw_status != 0xff (bad PCI read) | |
69 | */ | |
f7b5bd72 | 70 | return (pdsc->fw_status != PDS_RC_BAD_PCI) && |
523847df SN |
71 | (pdsc->fw_status & PDS_CORE_FW_STS_F_RUNNING); |
72 | } | |
73 | ||
74 | bool pdsc_is_fw_good(struct pdsc *pdsc) | |
75 | { | |
4f48c303 BC |
76 | bool fw_running = pdsc_is_fw_running(pdsc); |
77 | u8 gen; | |
523847df | 78 | |
4f48c303 BC |
79 | /* Make sure to update the cached fw_status by calling |
80 | * pdsc_is_fw_running() before getting the generation | |
81 | */ | |
82 | gen = pdsc->fw_status & PDS_CORE_FW_STS_F_GENERATION; | |
83 | ||
84 | return fw_running && gen == pdsc->fw_generation; | |
523847df SN |
85 | } |
86 | ||
87 | static u8 pdsc_devcmd_status(struct pdsc *pdsc) | |
88 | { | |
89 | return ioread8(&pdsc->cmd_regs->comp.status); | |
90 | } | |
91 | ||
92 | static bool pdsc_devcmd_done(struct pdsc *pdsc) | |
93 | { | |
94 | return ioread32(&pdsc->cmd_regs->done) & PDS_CORE_DEV_CMD_DONE; | |
95 | } | |
96 | ||
97 | static void pdsc_devcmd_dbell(struct pdsc *pdsc) | |
98 | { | |
99 | iowrite32(0, &pdsc->cmd_regs->done); | |
100 | iowrite32(1, &pdsc->cmd_regs->doorbell); | |
101 | } | |
102 | ||
103 | static void pdsc_devcmd_clean(struct pdsc *pdsc) | |
104 | { | |
105 | iowrite32(0, &pdsc->cmd_regs->doorbell); | |
106 | memset_io(&pdsc->cmd_regs->cmd, 0, sizeof(pdsc->cmd_regs->cmd)); | |
107 | } | |
108 | ||
109 | static const char *pdsc_devcmd_str(int opcode) | |
110 | { | |
111 | switch (opcode) { | |
112 | case PDS_CORE_CMD_NOP: | |
113 | return "PDS_CORE_CMD_NOP"; | |
114 | case PDS_CORE_CMD_IDENTIFY: | |
115 | return "PDS_CORE_CMD_IDENTIFY"; | |
116 | case PDS_CORE_CMD_RESET: | |
117 | return "PDS_CORE_CMD_RESET"; | |
118 | case PDS_CORE_CMD_INIT: | |
119 | return "PDS_CORE_CMD_INIT"; | |
120 | case PDS_CORE_CMD_FW_DOWNLOAD: | |
121 | return "PDS_CORE_CMD_FW_DOWNLOAD"; | |
122 | case PDS_CORE_CMD_FW_CONTROL: | |
123 | return "PDS_CORE_CMD_FW_CONTROL"; | |
124 | default: | |
125 | return "PDS_CORE_CMD_UNKNOWN"; | |
126 | } | |
127 | } | |
128 | ||
0ea064e7 | 129 | static int pdsc_devcmd_wait(struct pdsc *pdsc, u8 opcode, int max_seconds) |
523847df SN |
130 | { |
131 | struct device *dev = pdsc->dev; | |
132 | unsigned long start_time; | |
133 | unsigned long max_wait; | |
134 | unsigned long duration; | |
135 | int timeout = 0; | |
f7b5bd72 | 136 | bool running; |
523847df SN |
137 | int done = 0; |
138 | int err = 0; | |
139 | int status; | |
523847df SN |
140 | |
141 | start_time = jiffies; | |
142 | max_wait = start_time + (max_seconds * HZ); | |
143 | ||
144 | while (!done && !timeout) { | |
f7b5bd72 SN |
145 | running = pdsc_is_fw_running(pdsc); |
146 | if (!running) | |
147 | break; | |
148 | ||
523847df SN |
149 | done = pdsc_devcmd_done(pdsc); |
150 | if (done) | |
151 | break; | |
152 | ||
153 | timeout = time_after(jiffies, max_wait); | |
154 | if (timeout) | |
155 | break; | |
156 | ||
157 | usleep_range(100, 200); | |
158 | } | |
159 | duration = jiffies - start_time; | |
160 | ||
161 | if (done && duration > HZ) | |
162 | dev_dbg(dev, "DEVCMD %d %s after %ld secs\n", | |
163 | opcode, pdsc_devcmd_str(opcode), duration / HZ); | |
164 | ||
f7b5bd72 | 165 | if ((!done || timeout) && running) { |
523847df SN |
166 | dev_err(dev, "DEVCMD %d %s timeout, done %d timeout %d max_seconds=%d\n", |
167 | opcode, pdsc_devcmd_str(opcode), done, timeout, | |
168 | max_seconds); | |
169 | err = -ETIMEDOUT; | |
170 | pdsc_devcmd_clean(pdsc); | |
171 | } | |
172 | ||
173 | status = pdsc_devcmd_status(pdsc); | |
174 | err = pdsc_err_to_errno(status); | |
175 | if (err && err != -EAGAIN) | |
176 | dev_err(dev, "DEVCMD %d %s failed, status=%d err %d %pe\n", | |
177 | opcode, pdsc_devcmd_str(opcode), status, err, | |
178 | ERR_PTR(err)); | |
179 | ||
180 | return err; | |
181 | } | |
182 | ||
183 | int pdsc_devcmd_locked(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, | |
184 | union pds_core_dev_comp *comp, int max_seconds) | |
185 | { | |
186 | int err; | |
187 | ||
e96094c1 BC |
188 | if (!pdsc->cmd_regs) |
189 | return -ENXIO; | |
190 | ||
523847df SN |
191 | memcpy_toio(&pdsc->cmd_regs->cmd, cmd, sizeof(*cmd)); |
192 | pdsc_devcmd_dbell(pdsc); | |
0ea064e7 | 193 | err = pdsc_devcmd_wait(pdsc, cmd->opcode, max_seconds); |
523847df | 194 | |
969cfd4c | 195 | if ((err == -ENXIO || err == -ETIMEDOUT) && pdsc->wq) |
c2dbb090 | 196 | queue_work(pdsc->wq, &pdsc->health_work); |
e96094c1 BC |
197 | else |
198 | memcpy_fromio(comp, &pdsc->cmd_regs->comp, sizeof(*comp)); | |
c2dbb090 | 199 | |
523847df SN |
200 | return err; |
201 | } | |
202 | ||
203 | int pdsc_devcmd(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, | |
204 | union pds_core_dev_comp *comp, int max_seconds) | |
205 | { | |
206 | int err; | |
207 | ||
208 | mutex_lock(&pdsc->devcmd_lock); | |
209 | err = pdsc_devcmd_locked(pdsc, cmd, comp, max_seconds); | |
210 | mutex_unlock(&pdsc->devcmd_lock); | |
211 | ||
212 | return err; | |
213 | } | |
214 | ||
215 | int pdsc_devcmd_init(struct pdsc *pdsc) | |
216 | { | |
217 | union pds_core_dev_comp comp = {}; | |
218 | union pds_core_dev_cmd cmd = { | |
219 | .opcode = PDS_CORE_CMD_INIT, | |
220 | }; | |
221 | ||
222 | return pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout); | |
223 | } | |
224 | ||
225 | int pdsc_devcmd_reset(struct pdsc *pdsc) | |
226 | { | |
227 | union pds_core_dev_comp comp = {}; | |
228 | union pds_core_dev_cmd cmd = { | |
229 | .reset.opcode = PDS_CORE_CMD_RESET, | |
230 | }; | |
231 | ||
81665adf BC |
232 | if (!pdsc_is_fw_running(pdsc)) |
233 | return 0; | |
234 | ||
523847df SN |
235 | return pdsc_devcmd(pdsc, &cmd, &comp, pdsc->devcmd_timeout); |
236 | } | |
237 | ||
238 | static int pdsc_devcmd_identify_locked(struct pdsc *pdsc) | |
239 | { | |
240 | union pds_core_dev_comp comp = {}; | |
241 | union pds_core_dev_cmd cmd = { | |
242 | .identify.opcode = PDS_CORE_CMD_IDENTIFY, | |
243 | .identify.ver = PDS_CORE_IDENTITY_VERSION_1, | |
244 | }; | |
245 | ||
246 | return pdsc_devcmd_locked(pdsc, &cmd, &comp, pdsc->devcmd_timeout); | |
247 | } | |
248 | ||
249 | static void pdsc_init_devinfo(struct pdsc *pdsc) | |
250 | { | |
251 | pdsc->dev_info.asic_type = ioread8(&pdsc->info_regs->asic_type); | |
252 | pdsc->dev_info.asic_rev = ioread8(&pdsc->info_regs->asic_rev); | |
253 | pdsc->fw_generation = PDS_CORE_FW_STS_F_GENERATION & | |
254 | ioread8(&pdsc->info_regs->fw_status); | |
255 | ||
256 | memcpy_fromio(pdsc->dev_info.fw_version, | |
257 | pdsc->info_regs->fw_version, | |
258 | PDS_CORE_DEVINFO_FWVERS_BUFLEN); | |
259 | pdsc->dev_info.fw_version[PDS_CORE_DEVINFO_FWVERS_BUFLEN] = 0; | |
260 | ||
261 | memcpy_fromio(pdsc->dev_info.serial_num, | |
262 | pdsc->info_regs->serial_num, | |
263 | PDS_CORE_DEVINFO_SERIAL_BUFLEN); | |
264 | pdsc->dev_info.serial_num[PDS_CORE_DEVINFO_SERIAL_BUFLEN] = 0; | |
265 | ||
266 | dev_dbg(pdsc->dev, "fw_version %s\n", pdsc->dev_info.fw_version); | |
267 | } | |
268 | ||
269 | static int pdsc_identify(struct pdsc *pdsc) | |
270 | { | |
271 | struct pds_core_drv_identity drv = {}; | |
272 | size_t sz; | |
273 | int err; | |
7c02f6ae | 274 | int n; |
523847df SN |
275 | |
276 | drv.drv_type = cpu_to_le32(PDS_DRIVER_LINUX); | |
7c02f6ae SN |
277 | /* Catching the return quiets a Wformat-truncation complaint */ |
278 | n = snprintf(drv.driver_ver_str, sizeof(drv.driver_ver_str), | |
279 | "%s %s", PDS_CORE_DRV_NAME, utsname()->release); | |
280 | if (n > sizeof(drv.driver_ver_str)) | |
281 | dev_dbg(pdsc->dev, "release name truncated, don't care\n"); | |
523847df SN |
282 | |
283 | /* Next let's get some info about the device | |
284 | * We use the devcmd_lock at this level in order to | |
285 | * get safe access to the cmd_regs->data before anyone | |
286 | * else can mess it up | |
287 | */ | |
288 | mutex_lock(&pdsc->devcmd_lock); | |
289 | ||
290 | sz = min_t(size_t, sizeof(drv), sizeof(pdsc->cmd_regs->data)); | |
291 | memcpy_toio(&pdsc->cmd_regs->data, &drv, sz); | |
292 | ||
293 | err = pdsc_devcmd_identify_locked(pdsc); | |
294 | if (!err) { | |
295 | sz = min_t(size_t, sizeof(pdsc->dev_ident), | |
296 | sizeof(pdsc->cmd_regs->data)); | |
297 | memcpy_fromio(&pdsc->dev_ident, &pdsc->cmd_regs->data, sz); | |
298 | } | |
299 | mutex_unlock(&pdsc->devcmd_lock); | |
300 | ||
301 | if (err) { | |
302 | dev_err(pdsc->dev, "Cannot identify device: %pe\n", | |
303 | ERR_PTR(err)); | |
304 | return err; | |
305 | } | |
306 | ||
307 | if (isprint(pdsc->dev_info.fw_version[0]) && | |
308 | isascii(pdsc->dev_info.fw_version[0])) | |
309 | dev_info(pdsc->dev, "FW: %.*s\n", | |
310 | (int)(sizeof(pdsc->dev_info.fw_version) - 1), | |
311 | pdsc->dev_info.fw_version); | |
312 | else | |
313 | dev_info(pdsc->dev, "FW: (invalid string) 0x%02x 0x%02x 0x%02x 0x%02x ...\n", | |
314 | (u8)pdsc->dev_info.fw_version[0], | |
315 | (u8)pdsc->dev_info.fw_version[1], | |
316 | (u8)pdsc->dev_info.fw_version[2], | |
317 | (u8)pdsc->dev_info.fw_version[3]); | |
318 | ||
319 | return 0; | |
320 | } | |
321 | ||
792d36cc BC |
322 | void pdsc_dev_uninit(struct pdsc *pdsc) |
323 | { | |
324 | if (pdsc->intr_info) { | |
325 | int i; | |
326 | ||
327 | for (i = 0; i < pdsc->nintrs; i++) | |
328 | pdsc_intr_free(pdsc, i); | |
329 | ||
330 | kfree(pdsc->intr_info); | |
331 | pdsc->intr_info = NULL; | |
332 | pdsc->nintrs = 0; | |
333 | } | |
334 | ||
335 | pci_free_irq_vectors(pdsc->pdev); | |
336 | } | |
337 | ||
523847df SN |
338 | int pdsc_dev_init(struct pdsc *pdsc) |
339 | { | |
340 | unsigned int nintrs; | |
341 | int err; | |
342 | ||
343 | /* Initial init and reset of device */ | |
344 | pdsc_init_devinfo(pdsc); | |
345 | pdsc->devcmd_timeout = PDS_CORE_DEVCMD_TIMEOUT; | |
346 | ||
347 | err = pdsc_devcmd_reset(pdsc); | |
348 | if (err) | |
349 | return err; | |
350 | ||
351 | err = pdsc_identify(pdsc); | |
352 | if (err) | |
353 | return err; | |
354 | ||
355 | pdsc_debugfs_add_ident(pdsc); | |
356 | ||
357 | /* Now we can reserve interrupts */ | |
358 | nintrs = le32_to_cpu(pdsc->dev_ident.nintrs); | |
359 | nintrs = min_t(unsigned int, num_online_cpus(), nintrs); | |
360 | ||
361 | /* Get intr_info struct array for tracking */ | |
362 | pdsc->intr_info = kcalloc(nintrs, sizeof(*pdsc->intr_info), GFP_KERNEL); | |
247c4ed0 BC |
363 | if (!pdsc->intr_info) |
364 | return -ENOMEM; | |
523847df SN |
365 | |
366 | err = pci_alloc_irq_vectors(pdsc->pdev, nintrs, nintrs, PCI_IRQ_MSIX); | |
367 | if (err != nintrs) { | |
368 | dev_err(pdsc->dev, "Can't get %d intrs from OS: %pe\n", | |
369 | nintrs, ERR_PTR(err)); | |
370 | err = -ENOSPC; | |
371 | goto err_out; | |
372 | } | |
373 | pdsc->nintrs = nintrs; | |
374 | ||
375 | return 0; | |
376 | ||
377 | err_out: | |
378 | kfree(pdsc->intr_info); | |
379 | pdsc->intr_info = NULL; | |
380 | ||
381 | return err; | |
382 | } |