source: trunk/src/os2ahci/ctxhook.c@ 204

Last change on this file since 204 was 204, checked in by David Azarewicz, 5 years ago

Variable name changes.

File size: 17.3 KB
Line 
1/******************************************************************************
2 * ctxhook.c - context hooks (kernel thread functions) for os2ahci
3 *
4 * Copyright (c) 2011 thi.guten Software Development
5 * Copyright (c) 2011 Mensys B.V.
6 * Copyright (c) 2013-2018 David Azarewicz
7 *
8 * Authors: Christian Mueller, Markus Thielen
9 *
10 * Parts copied from/inspired by the Linux AHCI driver;
11 * those parts are (c) Linux AHCI/ATA maintainers
12 *
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26 */
27
28#include "os2ahci.h"
29#include "ata.h"
30#include "atapi.h"
31
32/* -------------------------- macros and constants ------------------------- */
33
34/* ------------------------ typedefs and structures ------------------------ */
35
36/* -------------------------- function prototypes -------------------------- */
37
38/* ------------------------ global/static variables ------------------------ */
39
40/* port restart context hook and input data */
41ULONG restart_ctxhook_h;
42volatile u32 ports_to_restart[MAX_AD];
43
44/* port reset context hook and input data */
45ULONG reset_ctxhook_h;
46ULONG th_reset_watchdog;
47volatile u32 ports_to_reset[MAX_AD];
48IORB_QUEUE abort_queue;
49
50/* trigger engine context hook and input data */
51ULONG engine_ctxhook_h;
52
53/* ----------------------------- start of code ----------------------------- */
54
55/******************************************************************************
56 * Port restart context hook. This context hook is executed at task time and
57 * will handle ports which are stopped due to a device error condition.
58 *
59 * The following conditions may exist:
60 *
61 * - Only a single non-NCQ command is executed by the AHCI adapter at any
62 * given time (even if more are outstanding). This is the case for single
63 * devices or port multipliers without FIS-based command switching. Error
64 * recovery is simple because we know which command has failed and that
65 * all other commands have not yet started executing. Thus, we can requeue
66 * all of them, replacing the failing command with a "request sense"
67 * command to get error details.
68 *
69 * - Multiple non-NCQ commands are executed on different devices behind a
70 * port multiplier which supports FIS-based command switching. This is
71 * more difficult to recover from but currently not an issue because we
72 * don't yet support FIS-based command switching (the FIS receive areas
73 * would become too large for the current data model).
74 *
75 * - One or more NCQ commands were active at the time of the error, with or
76 * without FIS-based command switching. We would have to interrogate the
77 * corresponding devices to find out which command has failed but if this
78 * is combined with FIS-based command switching, even the AHCI spec
79 * recommends to reset the port. This leads to a much simpler approach:
80 * requeue all NCQ commands (they are idempotent per definition, otherwise
81 * they couldn't be reordered by the device) with the 'no_ncq' flag set
82 * in the IORB and reset the port. Then those comands will be executed as
83 * regular commands. The error, if it reoccurs, can then be handled by
84 * one of the above cases.
85 *
86 * The upstream code will guarantee that we will never have a mix of NCQ and
87 * non-NCQ commands active at the same time in order to reduce complexity
88 * in the interrupt and error handlers.
89 */
90void _Syscall restart_ctxhook(ULONG parm)
91{
92 IORB_QUEUE done_queue;
93 AD_INFO *ai;
94 IORBH FAR16DATA *vProblemIorb;
95 IORBH FAR16DATA *vIorb;
96 IORBH FAR16DATA *vNext;
97 u8 *port_mmio;
98 int rearm_ctx_hook;
99 int need_reset;
100 int ccs;
101 int a;
102 int p;
103
104 D32ThunkStackTo32();
105
106 vNext = FAR16NULL;
107 rearm_ctx_hook = 0;
108
109 DPRINTF(8,"restart_ctxhook() started\n");
110 memset(&done_queue, 0x00, sizeof(done_queue));
111
112 spin_lock(drv_lock);
113
114 for (a = 0; a < ad_info_cnt; a++)
115 {
116 ai = ad_infos + a;
117
118 if (ai->busy)
119 {
120 /* this adapter is busy; leave it alone for now */
121 rearm_ctx_hook = 1;
122 continue;
123 }
124
125 for (p = 0; p <= ai->port_max; p++)
126 {
127 if (ports_to_restart[a] & (1UL << p))
128 {
129 ports_to_restart[a] &= ~(1UL << p);
130
131 /* restart this port */
132 port_mmio = port_base(ai, p);
133 vProblemIorb = FAR16NULL;
134 need_reset = 0;
135
136 DPRINTF(8,"port %d, TF_DATA: 0x%x\n", p, readl(port_mmio + PORT_TFDATA));
137
138 /* get "current command slot"; only valid if there are no NCQ cmds */
139 ccs = (int) ((readl(port_mmio + PORT_CMD) >> 8) & 0x1f);
140 DPRINTF(8," PORT_CMD = 0x%x\n", ccs);
141
142 for (vIorb = ai->ports[p].iorb_queue.vRoot; vIorb != FAR16NULL; vIorb = vNext)
143 {
144 IORBH *pIorb = Far16ToFlat(vIorb);
145 ADD_WORKSPACE *aws = add_workspace(pIorb);
146 vNext = pIorb->f16NxtIORB;
147
148 if (aws->queued_hw)
149 {
150 if (ai->ports[p].ncq_cmds & (1UL << aws->cmd_slot))
151 {
152 /* NCQ command; force non-NCQ mode and trigger port reset */
153 ai->ports[p].ncq_cmds &= ~(1UL << aws->cmd_slot);
154 aws->no_ncq = 1;
155 need_reset = 1;
156 }
157 else
158 {
159 /* regular command; clear cmd bit and identify problem IORB */
160 ai->ports[p].reg_cmds &= ~(1UL << aws->cmd_slot);
161 if (aws->cmd_slot == ccs)
162 {
163 /* this is the non-NCQ command that failed */
164 DPRINTF(0,"failing IORB: %x\n", vIorb);
165 vProblemIorb = vIorb;
166 }
167 }
168 /* we can requeue all IORBs unconditionally (see function comment) */
169 if (aws->retries++ < MAX_RETRIES)
170 {
171 iorb_requeue(pIorb);
172 }
173 else
174 {
175 /* retry count exceeded; consider IORB aborted */
176 iorb_seterr(pIorb, IOERR_CMD_ABORTED);
177 iorb_queue_del(&ai->ports[p].iorb_queue, vIorb);
178 iorb_queue_add(&done_queue, vIorb, pIorb);
179 if (vIorb == vProblemIorb)
180 {
181 /* no further analysis -- we're done with this one */
182 vProblemIorb = FAR16NULL;
183 }
184 }
185 }
186 }
187
188 /* sanity check: issued command bitmaps should be 0 now */
189 if (ai->ports[p].ncq_cmds != 0 || ai->ports[p].reg_cmds != 0)
190 {
191 DPRINTF(0,"warning: commands issued not 0 (%08lx/%08lx); resetting...\n",
192 ai->ports[p].ncq_cmds, ai->ports[p].reg_cmds);
193 need_reset = 1;
194 }
195
196 if (!need_reset)
197 {
198 if ((readl(port_mmio + PORT_TFDATA) & 0x88) != 0)
199 {
200 /* device is not in an idle state */
201 need_reset = 1;
202 }
203 }
204
205 /* restart/reset port */
206 ai->busy = 1;
207 spin_unlock(drv_lock);
208 if (need_reset)
209 {
210 ahci_reset_port(ai, p, 1);
211 }
212 else
213 {
214 ahci_stop_port(ai, p);
215 ahci_start_port(ai, p, 1);
216 }
217 spin_lock(drv_lock);
218 ai->busy = 0;
219
220 /* reset internal port status */
221 ai->ports[p].ncq_cmds = 0;
222 ai->ports[p].reg_cmds = 0;
223 ai->ports[p].cmd_slot = 0;
224
225 if (vProblemIorb != FAR16NULL)
226 {
227 IORBH *pProblemIorb = Far16ToFlat(vProblemIorb);
228 /* get details about the error that caused this IORB to fail */
229 if (need_reset)
230 {
231 /* no way to retrieve error details after a reset */
232 iorb_seterr(pProblemIorb, IOERR_DEVICE_NONSPECIFIC);
233 iorb_queue_del(&ai->ports[p].iorb_queue, vProblemIorb);
234 iorb_queue_add(&done_queue, vProblemIorb, pProblemIorb);
235
236 }
237 else
238 {
239 /* get sense information */
240 ADD_WORKSPACE *aws = add_workspace(pProblemIorb);
241 int d = iorb_unit_device(pProblemIorb);
242 int (*req_sense)(IORBH FAR16DATA *, IORBH *, int) = (ai->ports[p].devs[d].atapi) ?
243 atapi_req_sense : ata_req_sense;
244
245 aws->processing = 1;
246 aws->queued_hw = 1;
247
248 if (req_sense(vProblemIorb, pProblemIorb, 0) == 0)
249 {
250 /* execute request sense on slot #0 before anything else comes along */
251 Timer_StartTimerMS(&aws->timer, 5000, timeout_callback, CastFar16ToULONG(vProblemIorb));
252 aws->cmd_slot = 0;
253 ai->ports[p].reg_cmds = 1;
254 writel(port_mmio + PORT_CMD_ISSUE, 1);
255 readl(port_mmio); /* flush */
256
257 }
258 else
259 {
260 /* IORB is expected to contain the error code; just move to done queue */
261 iorb_queue_del(&ai->ports[p].iorb_queue, vProblemIorb);
262 iorb_queue_add(&done_queue, vProblemIorb, pProblemIorb);
263 }
264 }
265 }
266 }
267 }
268 }
269
270 spin_unlock(drv_lock);
271
272 /* call notification routine on all IORBs which have completed */
273 for (vIorb = done_queue.vRoot; vIorb != FAR16NULL; vIorb = vNext)
274 {
275 IORBH *pIorb = Far16ToFlat(vIorb);
276 vNext = pIorb->f16NxtIORB;
277
278 spin_lock(drv_lock);
279 aws_free(add_workspace(pIorb));
280 spin_unlock(drv_lock);
281
282 iorb_complete(vIorb, pIorb);
283 }
284
285 /* restart engine to resume IORB processing */
286 spin_lock(drv_lock);
287 trigger_engine();
288 spin_unlock(drv_lock);
289
290 DPRINTF(8,"restart_ctxhook() completed\n");
291
292 /* Check whether we have to rearm ourselves because some adapters were busy
293 * when we wanted to restart ports on them.
294 */
295 if (rearm_ctx_hook)
296 {
297 msleep(250);
298 KernArmHook(restart_ctxhook_h, 0, 0);
299 }
300 KernThunkStackTo16();
301}
302
303/******************************************************************************
304 * Reset and abort context hook. This function runs at task time and takes
305 * care of port resets and their side effects. Input to this function are:
306 *
307 * ports_to_reset[] - array of port bitmaps, each bit indicating which port
308 * should be reset unconditionally. This is primarily
309 * used by the error interrupt handler.
310 *
311 * abort_queue - queue with IORBs to be arborted (timed-out, ...) If
312 * any of these commands have reached the hardware, the
313 * corresponding port is reset to interrupt command
314 * execution. This is primarily used for timeout
315 * handling and when IORBs are requested to be aborted.
316 *
317 * After resetting the requested ports, all remaining active IORBs on those
318 * ports have to be retried or aborted. Whether a retry is attempted depends
319 * on the kind of IORB -- those which are idempotent are retried, all others
320 * are aborted. This is different from the port restart hook because the
321 * restart hook can assume it is called with the port in error state, thus
322 * the controller will have stopped executing commands. The reset handler can
323 * be called at any time and we can't tell what's going on in the controller.
324 *
325 * The IORBs in the global abort_queue are expected to have their error code
326 * set (aborted, timeout, ...) but must not be marked as 'done'; otherwise,
327 * the upstream code might reuse the IORBs before we're done with them.
328 */
329void _Syscall reset_ctxhook(ULONG parm)
330{
331 IORB_QUEUE done_queue;
332 AD_INFO *ai;
333 IORBH FAR16DATA *vIorb;
334 IORBH FAR16DATA *vNext;
335 int rearm_ctx_hook;
336 int a;
337 int p;
338
339 D32ThunkStackTo32();
340
341 vNext = FAR16NULL;
342 rearm_ctx_hook = 0;
343
344 DPRINTF(8,"reset_ctxhook() started\n");
345 memset(&done_queue, 0x00, sizeof(done_queue));
346
347 spin_lock(drv_lock);
348
349 if (th_reset_watchdog != 0)
350 {
351 /* watchdog timer still active -- just reset it */
352 Timer_CancelTimer(th_reset_watchdog);
353 th_reset_watchdog = 0;
354 }
355
356 /* add ports of active IORBs from the abort queue to ports_to_reset[] */
357 for (vIorb = abort_queue.vRoot; vIorb != FAR16NULL; vIorb = vNext)
358 {
359 IORBH *pIorb = Far16ToFlat(vIorb);
360 vNext = pIorb->f16NxtIORB;
361 a = iorb_unit_adapter(pIorb);
362 p = iorb_unit_port(pIorb);
363 ai = ad_infos + a;
364
365 if (ai->busy)
366 {
367 /* this adapter is busy; leave it alone for now */
368 rearm_ctx_hook = 1;
369 continue;
370 }
371
372 /* move IORB to the local 'done' queue */
373 iorb_queue_del(&abort_queue, vIorb);
374 iorb_queue_add(&done_queue, vIorb, pIorb);
375
376 /* reset port if the IORB has already been queued to hardware */
377 if (add_workspace(pIorb)->queued_hw)
378 {
379 /* prepare port reset */
380 ports_to_reset[a] |= (1UL << p);
381 }
382 }
383
384 /* reset all ports in 'ports_to_reset[]' */
385 for (a = 0; a < ad_info_cnt; a++)
386 {
387 ai = ad_infos + a;
388
389 if (ai->busy)
390 {
391 /* this adapter is busy; leave it alone for now */
392 rearm_ctx_hook = 1;
393 continue;
394 }
395
396 for (p = 0; p <= ai->port_max; p++)
397 {
398 if (ports_to_reset[a] & (1UL << p))
399 {
400 ports_to_reset[a] &= ~(1UL << p);
401 ai->ports[p].ulResetCount++;
402
403 /* Reset this port. Since this is a rather slow operation, we'll
404 * release the spinlock while doing so. The adapter is marked as
405 * 'busy' to prevent similar routines (e.g. an ahci port scan) from
406 * interfering.
407 */
408 ai->busy = 1;
409 spin_unlock(drv_lock);
410 ahci_reset_port(ai, p, 1);
411 spin_lock(drv_lock);
412 ai->busy = 0;
413
414 /* reset port status */
415 ai->ports[p].ncq_cmds = 0;
416 ai->ports[p].reg_cmds = 0;
417 ai->ports[p].cmd_slot = 0;
418
419 /* retry or abort all remaining active commands on this port */
420 for (vIorb = ai->ports[p].iorb_queue.vRoot; vIorb != FAR16NULL; vIorb = vNext)
421 {
422 IORBH *pIorb = Far16ToFlat(vIorb);
423 ADD_WORKSPACE *aws = add_workspace(pIorb);
424 vNext = pIorb->f16NxtIORB;
425
426 if (aws->queued_hw)
427 {
428 /* this IORB had already been queued to HW when we reset the port */
429 if (aws->idempotent && aws->retries++ < MAX_RETRIES)
430 {
431 /* we can retry this IORB */
432 iorb_requeue(pIorb);
433
434 }
435 else
436 {
437 /* we cannot retry this IORB; consider it aborted */
438 pIorb->ErrorCode = IOERR_CMD_ABORTED;
439 iorb_queue_del(&ai->ports[p].iorb_queue, vIorb);
440 iorb_queue_add(&done_queue, vIorb, pIorb);
441 }
442 }
443 }
444 }
445 }
446 }
447
448 spin_unlock(drv_lock);
449
450 /* complete all aborted IORBs */
451 for (vIorb = done_queue.vRoot; vIorb != FAR16NULL; vIorb = vNext)
452 {
453 IORBH *pIorb = Far16ToFlat(vIorb);
454 vNext = pIorb->f16NxtIORB;
455
456 spin_lock(drv_lock);
457 aws_free(add_workspace(pIorb));
458 spin_unlock(drv_lock);
459
460 pIorb->Status |= IORB_ERROR;
461 iorb_complete(vIorb, pIorb);
462 }
463
464 /* restart engine to resume IORB processing */
465 spin_lock(drv_lock);
466 trigger_engine();
467 spin_unlock(drv_lock);
468
469 DPRINTF(8,"reset_ctxhook() completed\n");
470
471 /* Check whether we have to rearm ourselves because some adapters were busy
472 * when we wanted to reset ports on them.
473 */
474 if (rearm_ctx_hook)
475 {
476 /* we cannot rearm ourself because we will execute immediately leaving
477 * no time to process and clear the reason we need to rearm. Therefore
478 * we set the timer again.
479 */
480 //msleep(250);
481 //KernArmHook(reset_ctxhook_h, 0, 0);
482 Timer_StartTimerMS(&th_reset_watchdog, 250, reset_watchdog, 0);
483 }
484
485 KernThunkStackTo16();
486}
487
488/******************************************************************************
489 * IORB Engine context hook. This hook is executed if trigger_engine() came
490 * to the conclusion that some of the IORBs keep bouncing, most likely due to
491 * some condition on the adapter such as being busy. It could also be a very
492 * busy system. Either way, this requires some task-time help.
493 */
494void _Syscall engine_ctxhook(ULONG parm)
495{
496 int iorbs_sent;
497 int i;
498
499 D32ThunkStackTo32();
500
501 DPRINTF(8,"engine_ctxhook() started\n");
502 if (resume_sleep_flag)
503 {
504 msleep(resume_sleep_flag);
505 resume_sleep_flag = 0;
506 }
507
508 spin_lock(drv_lock);
509 for (i = 0; i < 10; i++)
510 {
511 if ((iorbs_sent = trigger_engine_1()) == 0) break;
512 }
513 spin_unlock(drv_lock);
514
515 DPRINTF(8,"engine_ctxhook() completed\n");
516
517 if (iorbs_sent != 0)
518 {
519 /* need to rearm ourselves for another run */
520 msleep(250);
521 KernArmHook(engine_ctxhook_h, 0, 0);
522 }
523
524 KernThunkStackTo16();
525}
526
Note: See TracBrowser for help on using the repository browser.