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

Last change on this file since 178 was 178, checked in by David Azarewicz, 9 years ago

Major reorganization

File size: 16.7 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-2016 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 = NULL;
97 u8 *port_mmio;
98 int rearm_ctx_hook = 0;
99 int need_reset;
100 int ccs;
101 int a;
102 int p;
103
104 DPRINTF(2,"restart_ctxhook() started\n");
105 memset(&done_queue, 0x00, sizeof(done_queue));
106
107 spin_lock(drv_lock);
108
109 for (a = 0; a < ad_info_cnt; a++)
110 {
111 ai = ad_infos + a;
112
113 if (ai->busy)
114 {
115 /* this adapter is busy; leave it alone for now */
116 rearm_ctx_hook = 1;
117 continue;
118 }
119
120 for (p = 0; p <= ai->port_max; p++)
121 {
122 if (ports_to_restart[a] & (1UL << p))
123 {
124 ports_to_restart[a] &= ~(1UL << p);
125
126 /* restart this port */
127 port_mmio = port_base(ai, p);
128 vProblemIorb = NULL;
129 need_reset = 0;
130
131 DPRINTF(2,"port %d, TF_DATA: 0x%x\n", p, readl(port_mmio + PORT_TFDATA));
132
133 /* get "current command slot"; only valid if there are no NCQ cmds */
134 ccs = (int) ((readl(port_mmio + PORT_CMD) >> 8) & 0x1f);
135 DPRINTF(3," PORT_CMD = 0x%x\n", ccs);
136
137 for (vIorb = ai->ports[p].iorb_queue.vRoot; vIorb != NULL; vIorb = vNext)
138 {
139 IORBH *pIorb = Far16ToFlat(vIorb);
140 ADD_WORKSPACE *aws = add_workspace(pIorb);
141 vNext = pIorb->pNxtIORB;
142
143 if (aws->queued_hw)
144 {
145 if (ai->ports[p].ncq_cmds & (1UL << aws->cmd_slot))
146 {
147 /* NCQ command; force non-NCQ mode and trigger port reset */
148 ai->ports[p].ncq_cmds &= ~(1UL << aws->cmd_slot);
149 aws->no_ncq = 1;
150 need_reset = 1;
151 }
152 else
153 {
154 /* regular command; clear cmd bit and identify problem IORB */
155 ai->ports[p].reg_cmds &= ~(1UL << aws->cmd_slot);
156 if (aws->cmd_slot == ccs)
157 {
158 /* this is the non-NCQ command that failed */
159 DPRINTF(0,"failing IORB: %x\n", vIorb);
160 vProblemIorb = vIorb;
161 }
162 }
163 /* we can requeue all IORBs unconditionally (see function comment) */
164 if (aws->retries++ < MAX_RETRIES)
165 {
166 iorb_requeue(pIorb);
167 }
168 else
169 {
170 /* retry count exceeded; consider IORB aborted */
171 iorb_seterr(pIorb, IOERR_CMD_ABORTED);
172 iorb_queue_del(&ai->ports[p].iorb_queue, vIorb);
173 iorb_queue_add(&done_queue, vIorb, pIorb);
174 if (vIorb == vProblemIorb)
175 {
176 /* no further analysis -- we're done with this one */
177 vProblemIorb = NULL;
178 }
179 }
180 }
181 }
182
183 /* sanity check: issued command bitmaps should be 0 now */
184 if (ai->ports[p].ncq_cmds != 0 || ai->ports[p].reg_cmds != 0)
185 {
186 DPRINTF(0,"warning: commands issued not 0 (%08lx/%08lx); resetting...\n",
187 ai->ports[p].ncq_cmds, ai->ports[p].reg_cmds);
188 need_reset = 1;
189 }
190
191 if (!need_reset)
192 {
193 if ((readl(port_mmio + PORT_TFDATA) & 0x88) != 0)
194 {
195 /* device is not in an idle state */
196 need_reset = 1;
197 }
198 }
199
200 /* restart/reset port */
201 ai->busy = 1;
202 spin_unlock(drv_lock);
203 if (need_reset)
204 {
205 ahci_reset_port(ai, p, 1);
206 }
207 else
208 {
209 ahci_stop_port(ai, p);
210 ahci_start_port(ai, p, 1);
211 }
212 spin_lock(drv_lock);
213 ai->busy = 0;
214
215 /* reset internal port status */
216 ai->ports[p].ncq_cmds = 0;
217 ai->ports[p].reg_cmds = 0;
218 ai->ports[p].cmd_slot = 0;
219
220 if (vProblemIorb != NULL)
221 {
222 IORBH *pProblemIorb = Far16ToFlat(vProblemIorb);
223 /* get details about the error that caused this IORB to fail */
224 if (need_reset)
225 {
226 /* no way to retrieve error details after a reset */
227 iorb_seterr(pProblemIorb, IOERR_DEVICE_NONSPECIFIC);
228 iorb_queue_del(&ai->ports[p].iorb_queue, vProblemIorb);
229 iorb_queue_add(&done_queue, vProblemIorb, pProblemIorb);
230
231 }
232 else
233 {
234 /* get sense information */
235 ADD_WORKSPACE *aws = add_workspace(pProblemIorb);
236 int d = iorb_unit_device(pProblemIorb);
237 int (*req_sense)(IORBH FAR16DATA *, IORBH *, int) = (ai->ports[p].devs[d].atapi) ?
238 atapi_req_sense : ata_req_sense;
239
240 aws->processing = 1;
241 aws->queued_hw = 1;
242
243 if (req_sense(vProblemIorb, pProblemIorb, 0) == 0)
244 {
245 /* execute request sense on slot #0 before anything else comes along */
246 Timer_StartTimerMS(&aws->timer, 5000, timeout_callback, CastFar16ToULONG(vProblemIorb));
247 aws->cmd_slot = 0;
248 ai->ports[p].reg_cmds = 1;
249 writel(port_mmio + PORT_CMD_ISSUE, 1);
250 readl(port_mmio); /* flush */
251
252 }
253 else
254 {
255 /* IORB is expected to contain the error code; just move to done queue */
256 iorb_queue_del(&ai->ports[p].iorb_queue, vProblemIorb);
257 iorb_queue_add(&done_queue, vProblemIorb, pProblemIorb);
258 }
259 }
260 }
261 }
262 }
263 }
264
265 spin_unlock(drv_lock);
266
267 /* call notification routine on all IORBs which have completed */
268 for (vIorb = done_queue.vRoot; vIorb != NULL; vIorb = vNext)
269 {
270 IORBH *pIorb = Far16ToFlat(vIorb);
271 vNext = pIorb->pNxtIORB;
272
273 spin_lock(drv_lock);
274 aws_free(add_workspace(pIorb));
275 spin_unlock(drv_lock);
276
277 iorb_complete(vIorb, pIorb);
278 }
279
280 /* restart engine to resume IORB processing */
281 spin_lock(drv_lock);
282 trigger_engine();
283 spin_unlock(drv_lock);
284
285 DPRINTF(2,"restart_ctxhook() completed\n");
286
287 /* Check whether we have to rearm ourselves because some adapters were busy
288 * when we wanted to restart ports on them.
289 */
290 if (rearm_ctx_hook)
291 {
292 msleep(250);
293 KernArmHook(restart_ctxhook_h, 0, 0);
294 }
295}
296
297/******************************************************************************
298 * Reset and abort context hook. This function runs at task time and takes
299 * care of port resets and their side effects. Input to this function are:
300 *
301 * ports_to_reset[] - array of port bitmaps, each bit indicating which port
302 * should be reset unconditionally. This is primarily
303 * used by the error interrupt handler.
304 *
305 * abort_queue - queue with IORBs to be arborted (timed-out, ...) If
306 * any of these commands have reached the hardware, the
307 * corresponding port is reset to interrupt command
308 * execution. This is primarily used for timeout
309 * handling and when IORBs are requested to be aborted.
310 *
311 * After resetting the requested ports, all remaining active IORBs on those
312 * ports have to be retried or aborted. Whether a retry is attempted depends
313 * on the kind of IORB -- those which are idempotent are retried, all others
314 * are aborted. This is different from the port restart hook because the
315 * restart hook can assume it is called with the port in error state, thus
316 * the controller will have stopped executing commands. The reset handler can
317 * be called at any time and we can't tell what's going on in the controller.
318 *
319 * The IORBs in the global abort_queue are expected to have their error code
320 * set (aborted, timeout, ...) but must not be marked as 'done'; otherwise,
321 * the upstream code might reuse the IORBs before we're done with them.
322 */
323void _Syscall reset_ctxhook(ULONG parm)
324{
325 IORB_QUEUE done_queue;
326 AD_INFO *ai;
327 IORBH FAR16DATA *vIorb;
328 IORBH FAR16DATA *vNext = NULL;
329 int rearm_ctx_hook = 0;
330 int a;
331 int p;
332
333 DPRINTF(2,"reset_ctxhook() started\n");
334 memset(&done_queue, 0x00, sizeof(done_queue));
335
336 spin_lock(drv_lock);
337
338 if (th_reset_watchdog != 0)
339 {
340 /* watchdog timer still active -- just reset it */
341 Timer_CancelTimer(th_reset_watchdog);
342 th_reset_watchdog = 0;
343 }
344
345 /* add ports of active IORBs from the abort queue to ports_to_reset[] */
346 for (vIorb = abort_queue.vRoot; vIorb != NULL; vIorb = vNext)
347 {
348 IORBH *pIorb = Far16ToFlat(vIorb);
349 vNext = pIorb->pNxtIORB;
350 a = iorb_unit_adapter(pIorb);
351 p = iorb_unit_port(pIorb);
352 ai = ad_infos + a;
353
354 if (ai->busy)
355 {
356 /* this adapter is busy; leave it alone for now */
357 rearm_ctx_hook = 1;
358 continue;
359 }
360
361 /* move IORB to the local 'done' queue */
362 iorb_queue_del(&abort_queue, vIorb);
363 iorb_queue_add(&done_queue, vIorb, pIorb);
364
365 /* reset port if the IORB has already been queued to hardware */
366 if (add_workspace(pIorb)->queued_hw)
367 {
368 /* prepare port reset */
369 ports_to_reset[a] |= (1UL << p);
370 }
371 }
372
373 /* reset all ports in 'ports_to_reset[]' */
374 for (a = 0; a < ad_info_cnt; a++)
375 {
376 ai = ad_infos + a;
377
378 if (ai->busy)
379 {
380 /* this adapter is busy; leave it alone for now */
381 rearm_ctx_hook = 1;
382 continue;
383 }
384
385 for (p = 0; p <= ai->port_max; p++)
386 {
387 if (ports_to_reset[a] & (1UL << p))
388 {
389 ports_to_reset[a] &= ~(1UL << p);
390
391 /* Reset this port. Since this is a rather slow operation, we'll
392 * release the spinlock while doing so. The adapter is marked as
393 * 'busy' to prevent similar routines (e.g. an ahci port scan) from
394 * interfering.
395 */
396 ai->busy = 1;
397 spin_unlock(drv_lock);
398 ahci_reset_port(ai, p, 1);
399 spin_lock(drv_lock);
400 ai->busy = 0;
401
402 /* reset port status */
403 ai->ports[p].ncq_cmds = 0;
404 ai->ports[p].reg_cmds = 0;
405 ai->ports[p].cmd_slot = 0;
406
407 /* retry or abort all remaining active commands on this port */
408 for (vIorb = ai->ports[p].iorb_queue.vRoot; vIorb != NULL; vIorb = vNext)
409 {
410 IORBH *pIorb = Far16ToFlat(vIorb);
411 ADD_WORKSPACE *aws = add_workspace(pIorb);
412 vNext = pIorb->pNxtIORB;
413
414 if (aws->queued_hw)
415 {
416 /* this IORB had already been queued to HW when we reset the port */
417 if (aws->idempotent && aws->retries++ < MAX_RETRIES)
418 {
419 /* we can retry this IORB */
420 iorb_requeue(pIorb);
421
422 }
423 else
424 {
425 /* we cannot retry this IORB; consider it aborted */
426 pIorb->ErrorCode = IOERR_CMD_ABORTED;
427 iorb_queue_del(&ai->ports[p].iorb_queue, vIorb);
428 iorb_queue_add(&done_queue, vIorb, pIorb);
429 }
430 }
431 }
432 }
433 }
434 }
435
436 spin_unlock(drv_lock);
437
438 /* complete all aborted IORBs */
439 for (vIorb = done_queue.vRoot; vIorb != NULL; vIorb = vNext)
440 {
441 IORBH *pIorb = Far16ToFlat(vIorb);
442 vNext = pIorb->pNxtIORB;
443
444 spin_lock(drv_lock);
445 aws_free(add_workspace(pIorb));
446 spin_unlock(drv_lock);
447
448 pIorb->Status |= IORB_ERROR;
449 iorb_complete(vIorb, pIorb);
450 }
451
452 /* restart engine to resume IORB processing */
453 spin_lock(drv_lock);
454 trigger_engine();
455 spin_unlock(drv_lock);
456
457 DPRINTF(2,"reset_ctxhook() completed\n");
458
459 /* Check whether we have to rearm ourselves because some adapters were busy
460 * when we wanted to reset ports on them.
461 */
462 if (rearm_ctx_hook)
463 {
464 msleep(250);
465 KernArmHook(reset_ctxhook_h, 0, 0);
466 }
467}
468
469/******************************************************************************
470 * IORB Engine context hook. This hook is executed if trigger_engine() came
471 * to the conclusion that some of the IORBs keep bouncing, most likely due to
472 * some condition on the adapter such as being busy. It could also be a very
473 * busy system. Either way, this requires some task-time help.
474 */
475void _Syscall engine_ctxhook(ULONG parm)
476{
477 int iorbs_sent;
478 int i;
479
480 DPRINTF(2,"engine_ctxhook() started\n");
481 if (resume_sleep_flag)
482 {
483 msleep(resume_sleep_flag);
484 resume_sleep_flag = 0;
485 }
486
487 spin_lock(drv_lock);
488 for (i = 0; i < 10; i++)
489 {
490 if ((iorbs_sent = trigger_engine_1()) == 0) break;
491 }
492 spin_unlock(drv_lock);
493
494 DPRINTF(2,"engine_ctxhook() completed\n");
495
496 if (iorbs_sent != 0)
497 {
498 /* need to rearm ourselves for another run */
499 msleep(250);
500 KernArmHook(engine_ctxhook_h, 0, 0);
501 }
502}
503
Note: See TracBrowser for help on using the repository browser.