Branch data Line data Source code
1 : : /* SPDX-License-Identifier: BSD-3-Clause
2 : : * Copyright (C) 2022 Intel Corporation.
3 : : * All rights reserved.
4 : : */
5 : :
6 : : #include "spdk/queue.h"
7 : : #include "spdk/assert.h"
8 : : #include "spdk/env.h"
9 : :
10 : : #include "ftl_mngt.h"
11 : : #include "ftl_core.h"
12 : :
13 : : struct ftl_mngt_step_status {
14 : : uint64_t start;
15 : : uint64_t stop;
16 : : int status;
17 : : int silent;
18 : : TAILQ_ENTRY(ftl_mngt_step) entry;
19 : : };
20 : :
21 : : struct ftl_mngt_step {
22 : : void *ctx;
23 : : const struct ftl_mngt_step_desc *desc;
24 : : struct ftl_mngt_step_status action;
25 : : struct ftl_mngt_step_status rollback;
26 : : };
27 : :
28 : : struct ftl_mngt_process {
29 : : struct spdk_ftl_dev *dev;
30 : : int status;
31 : : bool silent;
32 : : bool rollback;
33 : : bool continuing;
34 : : struct {
35 : : ftl_mngt_completion cb;
36 : : void *cb_ctx;
37 : : struct spdk_thread *thread;
38 : : } caller;
39 : : void *ctx;
40 : : uint64_t tsc_start;
41 : : uint64_t tsc_stop;
42 : : const struct ftl_mngt_process_desc *desc;
43 : : TAILQ_HEAD(, ftl_mngt_step) action_queue_todo;
44 : : TAILQ_HEAD(, ftl_mngt_step) action_queue_done;
45 : : TAILQ_HEAD(, ftl_mngt_step) rollback_queue_todo;
46 : : TAILQ_HEAD(, ftl_mngt_step) rollback_queue_done;
47 : : struct {
48 : : struct ftl_mngt_step step;
49 : : struct ftl_mngt_step_desc desc;
50 : : } cleanup;
51 : : struct ftl_mng_tracer *tracer;
52 : : };
53 : :
54 : : static void action_next(struct ftl_mngt_process *mngt);
55 : : static void action_msg(void *ctx);
56 : : static void action_execute(struct ftl_mngt_process *mngt);
57 : : static void action_done(struct ftl_mngt_process *mngt, int status);
58 : : static void rollback_next(struct ftl_mngt_process *mngt);
59 : : static void rollback_msg(void *ctx);
60 : : static void rollback_execute(struct ftl_mngt_process *mngt);
61 : : static void rollback_done(struct ftl_mngt_process *mngt, int status);
62 : :
63 : : static inline struct ftl_mngt_step *
64 : 2032 : get_current_step(struct ftl_mngt_process *mngt)
65 : : {
66 [ + + + + ]: 2032 : if (!mngt->rollback) {
67 : 1980 : return TAILQ_FIRST(&mngt->action_queue_todo);
68 : : } else {
69 : 52 : return TAILQ_FIRST(&mngt->rollback_queue_todo);
70 : : }
71 : : }
72 : :
73 : : static int
74 : 1645 : init_step(struct ftl_mngt_process *mngt,
75 : : const struct ftl_mngt_step_desc *desc)
76 : : {
77 : : struct ftl_mngt_step *step;
78 : :
79 : 1645 : step = calloc(1, sizeof(*step));
80 [ - + ]: 1645 : if (!step) {
81 : 0 : return -ENOMEM;
82 : : }
83 : :
84 : : /* Initialize the step's argument */
85 [ + + ]: 1645 : if (desc->ctx_size) {
86 : 28 : step->ctx = calloc(1, desc->ctx_size);
87 [ - + ]: 28 : if (!step->ctx) {
88 : 0 : free(step);
89 : 0 : return -ENOMEM;
90 : : }
91 : : }
92 : 1645 : step->desc = desc;
93 : 1645 : TAILQ_INSERT_TAIL(&mngt->action_queue_todo, step, action.entry);
94 : :
95 : 1645 : return 0;
96 : : }
97 : :
98 : : static void
99 : 253 : free_mngt(struct ftl_mngt_process *mngt)
100 : : {
101 : 253 : TAILQ_HEAD(, ftl_mngt_step) steps;
102 : :
103 [ - + ]: 253 : if (!mngt) {
104 : 0 : return;
105 : : }
106 : :
107 : 253 : TAILQ_INIT(&steps);
108 [ + + ]: 253 : TAILQ_CONCAT(&steps, &mngt->action_queue_todo, action.entry);
109 [ + + ]: 253 : TAILQ_CONCAT(&steps, &mngt->action_queue_done, action.entry);
110 : :
111 [ + + ]: 1898 : while (!TAILQ_EMPTY(&steps)) {
112 : 1645 : struct ftl_mngt_step *step = TAILQ_FIRST(&steps);
113 [ + + ]: 1645 : TAILQ_REMOVE(&steps, step, action.entry);
114 : :
115 : 1645 : free(step->ctx);
116 : 1645 : free(step);
117 : : }
118 : :
119 : 253 : free(mngt->ctx);
120 : 253 : free(mngt);
121 : : }
122 : :
123 : : static struct ftl_mngt_process *
124 : 253 : allocate_mngt(struct spdk_ftl_dev *dev, const struct ftl_mngt_process_desc *pdesc,
125 : : ftl_mngt_completion cb, void *cb_ctx, bool silent)
126 : : {
127 : : struct ftl_mngt_process *mngt;
128 : :
129 : : /* Initialize management process */
130 : 253 : mngt = calloc(1, sizeof(*mngt));
131 [ - + ]: 253 : if (!mngt) {
132 : 0 : goto error;
133 : : }
134 : 253 : mngt->dev = dev;
135 : 253 : mngt->silent = silent;
136 : 253 : mngt->caller.cb = cb;
137 : 253 : mngt->caller.cb_ctx = cb_ctx;
138 : 253 : mngt->caller.thread = spdk_get_thread();
139 : :
140 : : /* Initialize process context */
141 [ + + ]: 253 : if (pdesc->ctx_size) {
142 : 37 : mngt->ctx = calloc(1, pdesc->ctx_size);
143 [ - + ]: 37 : if (!mngt->ctx) {
144 : 0 : goto error;
145 : : }
146 : : }
147 : 253 : mngt->tsc_start = spdk_get_ticks();
148 : 253 : mngt->desc = pdesc;
149 : 253 : TAILQ_INIT(&mngt->action_queue_todo);
150 : 253 : TAILQ_INIT(&mngt->action_queue_done);
151 : 253 : TAILQ_INIT(&mngt->rollback_queue_todo);
152 : 253 : TAILQ_INIT(&mngt->rollback_queue_done);
153 : :
154 : 253 : return mngt;
155 : 0 : error:
156 : 0 : free_mngt(mngt);
157 : 0 : return NULL;
158 : : }
159 : :
160 : : static int
161 : 213 : invoke_init_handler(struct spdk_ftl_dev *dev, struct ftl_mngt_process *mngt,
162 : : const struct ftl_mngt_process_desc *pdesc, void *init_ctx)
163 : : {
164 : 213 : int rc = 0;
165 : :
166 [ + + - + ]: 213 : if (init_ctx || pdesc->init_handler) {
167 [ - + ]: 6 : ftl_bug(!init_ctx);
168 [ - + ]: 6 : ftl_bug(!pdesc->init_handler);
169 : 6 : rc = pdesc->init_handler(dev, mngt, init_ctx);
170 : : }
171 : :
172 : 213 : return rc;
173 : : }
174 : :
175 : : static int
176 : 213 : _ftl_mngt_process_execute(struct spdk_ftl_dev *dev, const struct ftl_mngt_process_desc *pdesc,
177 : : ftl_mngt_completion cb, void *cb_ctx, bool silent, void *init_ctx)
178 : : {
179 : : const struct ftl_mngt_step_desc *sdesc;
180 : : struct ftl_mngt_process *mngt;
181 : 213 : int rc = 0;
182 : :
183 : 213 : mngt = allocate_mngt(dev, pdesc, cb, cb_ctx, silent);
184 [ - + ]: 213 : if (!mngt) {
185 : 0 : rc = -ENOMEM;
186 : 0 : goto error;
187 : : }
188 : :
189 [ + + ]: 213 : if (pdesc->error_handler) {
190 : : /* Initialize a step for error handler */
191 : 22 : mngt->cleanup.step.desc = &mngt->cleanup.desc;
192 : 22 : mngt->cleanup.desc.name = "Handle ERROR";
193 : 22 : mngt->cleanup.desc.cleanup = pdesc->error_handler;
194 : :
195 : : /* Queue error handler to the rollback queue, it will be executed at the end */
196 [ - + ]: 22 : TAILQ_INSERT_HEAD(&mngt->rollback_queue_todo, &mngt->cleanup.step,
197 : : rollback.entry);
198 : : }
199 : :
200 : : /* Initialize steps */
201 : 213 : sdesc = mngt->desc->steps;
202 [ + + ]: 1533 : while (sdesc->action) {
203 : 1320 : rc = init_step(mngt, sdesc);
204 [ - + ]: 1320 : if (rc) {
205 : 0 : goto error;
206 : : }
207 : 1320 : sdesc++;
208 : : }
209 : :
210 : 213 : rc = invoke_init_handler(dev, mngt, pdesc, init_ctx);
211 [ + + ]: 213 : if (rc) {
212 : 3 : goto error;
213 : : }
214 : :
215 : 210 : action_execute(mngt);
216 : 210 : return 0;
217 : 3 : error:
218 : 3 : free_mngt(mngt);
219 : 3 : return rc;
220 : : }
221 : :
222 : : int
223 : 75 : ftl_mngt_process_execute(struct spdk_ftl_dev *dev, const struct ftl_mngt_process_desc *pdesc,
224 : : ftl_mngt_completion cb, void *cb_ctx)
225 : : {
226 : 75 : return _ftl_mngt_process_execute(dev, pdesc, cb, cb_ctx, false, NULL);
227 : : }
228 : :
229 : : int
230 : 40 : ftl_mngt_process_rollback(struct spdk_ftl_dev *dev, const struct ftl_mngt_process_desc *pdesc,
231 : : ftl_mngt_completion cb, void *cb_ctx)
232 : : {
233 : : const struct ftl_mngt_step_desc *sdesc;
234 : : struct ftl_mngt_process *mngt;
235 : 40 : int rc = 0;
236 : :
237 : 40 : mngt = allocate_mngt(dev, pdesc, cb, cb_ctx, true);
238 [ - + ]: 40 : if (!mngt) {
239 : 0 : rc = -ENOMEM;
240 : 0 : goto error;
241 : : }
242 : :
243 : : /* Initialize steps for rollback */
244 : 40 : sdesc = mngt->desc->steps;
245 [ + + ]: 544 : while (sdesc->action) {
246 [ + + ]: 504 : if (!sdesc->cleanup) {
247 : 179 : sdesc++;
248 : 179 : continue;
249 : : }
250 : 325 : rc = init_step(mngt, sdesc);
251 [ - + ]: 325 : if (rc) {
252 : 0 : goto error;
253 : : }
254 : 325 : sdesc++;
255 : : }
256 : :
257 : : /* Build rollback list */
258 : : struct ftl_mngt_step *step;
259 [ + + ]: 365 : TAILQ_FOREACH(step, &mngt->action_queue_todo, action.entry) {
260 : 325 : step->action.silent = true;
261 [ + + ]: 325 : TAILQ_INSERT_HEAD(&mngt->rollback_queue_todo, step,
262 : : rollback.entry);
263 : : }
264 : :
265 : 40 : mngt->rollback = true;
266 : 40 : rollback_execute(mngt);
267 : 40 : return 0;
268 : 0 : error:
269 : 0 : free_mngt(mngt);
270 : 0 : return rc;
271 : : }
272 : :
273 : : struct spdk_ftl_dev *
274 : 114 : ftl_mngt_get_dev(struct ftl_mngt_process *mngt)
275 : : {
276 : 114 : return mngt->dev;
277 : : }
278 : :
279 : : int
280 : 35 : ftl_mngt_alloc_step_ctx(struct ftl_mngt_process *mngt, size_t size)
281 : : {
282 : 35 : struct ftl_mngt_step *step = get_current_step(mngt);
283 : 35 : void *arg = calloc(1, size);
284 : :
285 [ - + ]: 35 : if (!arg) {
286 : 0 : return -ENOMEM;
287 : : }
288 : :
289 : 35 : free(step->ctx);
290 : 35 : step->ctx = arg;
291 : :
292 : 35 : return 0;
293 : : }
294 : :
295 : : void *
296 : 1790 : ftl_mngt_get_step_ctx(struct ftl_mngt_process *mngt)
297 : : {
298 : 1790 : return get_current_step(mngt)->ctx;
299 : : }
300 : :
301 : : void *
302 : 53 : ftl_mngt_get_process_ctx(struct ftl_mngt_process *mngt)
303 : : {
304 : 53 : return mngt->ctx;
305 : : }
306 : :
307 : : void *
308 : 28 : ftl_mngt_get_caller_ctx(struct ftl_mngt_process *mngt)
309 : : {
310 : 28 : return mngt->caller.cb_ctx;
311 : : }
312 : :
313 : : void
314 : 1636 : ftl_mngt_next_step(struct ftl_mngt_process *mngt)
315 : : {
316 [ + + + + ]: 1636 : if (false == mngt->rollback) {
317 : 1296 : action_next(mngt);
318 : : } else {
319 : 340 : rollback_next(mngt);
320 : : }
321 : 1636 : }
322 : :
323 : : void
324 : 44 : ftl_mngt_skip_step(struct ftl_mngt_process *mngt)
325 : : {
326 [ - + + + ]: 44 : if (mngt->rollback) {
327 : 22 : get_current_step(mngt)->rollback.silent = true;
328 : : } else {
329 : 22 : get_current_step(mngt)->action.silent = true;
330 : : }
331 : 44 : ftl_mngt_next_step(mngt);
332 : 44 : }
333 : :
334 : : void
335 : 157021 : ftl_mngt_continue_step(struct ftl_mngt_process *mngt)
336 : : {
337 : :
338 [ + + + - ]: 157021 : if (!mngt->continuing) {
339 [ + + + + ]: 157021 : if (false == mngt->rollback) {
340 : 157009 : action_execute(mngt);
341 : : } else {
342 : 12 : rollback_execute(mngt);
343 : : }
344 : : }
345 : :
346 : 157021 : mngt->continuing = true;
347 : 157021 : }
348 : :
349 : : static void
350 : 163 : child_cb(struct spdk_ftl_dev *dev, void *ctx, int status)
351 : : {
352 : 163 : struct ftl_mngt_process *parent = ctx;
353 : :
354 [ + + ]: 163 : if (status) {
355 : 3 : ftl_mngt_fail_step(parent);
356 : : } else {
357 : 160 : ftl_mngt_next_step(parent);
358 : : }
359 : 163 : }
360 : :
361 : : void
362 : 138 : ftl_mngt_call_process(struct ftl_mngt_process *mngt,
363 : : const struct ftl_mngt_process_desc *pdesc,
364 : : void *init_ctx)
365 : : {
366 [ + + ]: 138 : if (_ftl_mngt_process_execute(mngt->dev, pdesc, child_cb, mngt, true, init_ctx)) {
367 : 3 : ftl_mngt_fail_step(mngt);
368 : : } else {
369 [ + + + + ]: 135 : if (mngt->rollback) {
370 : 3 : get_current_step(mngt)->rollback.silent = true;
371 : : } else {
372 : 132 : get_current_step(mngt)->action.silent = true;
373 : : }
374 : : }
375 : 138 : }
376 : :
377 : : void
378 : 28 : ftl_mngt_call_process_rollback(struct ftl_mngt_process *mngt,
379 : : const struct ftl_mngt_process_desc *pdesc)
380 : : {
381 [ - + ]: 28 : if (ftl_mngt_process_rollback(mngt->dev, pdesc, child_cb, mngt)) {
382 : 0 : ftl_mngt_fail_step(mngt);
383 : : } else {
384 [ + + + + ]: 28 : if (mngt->rollback) {
385 : 3 : get_current_step(mngt)->rollback.silent = true;
386 : : } else {
387 : 25 : get_current_step(mngt)->action.silent = true;
388 : : }
389 : : }
390 : 28 : }
391 : :
392 : : void
393 : 12 : ftl_mngt_fail_step(struct ftl_mngt_process *mngt)
394 : : {
395 : 12 : mngt->status = -1;
396 : :
397 [ + + + - ]: 12 : if (false == mngt->rollback) {
398 : 12 : action_done(mngt, -1);
399 : : } else {
400 : 0 : rollback_done(mngt, -1);
401 : : }
402 : :
403 : 12 : mngt->rollback = true;
404 : 12 : rollback_execute(mngt);
405 : 12 : }
406 : :
407 : : static inline float
408 : 1516 : tsc_to_ms(uint64_t tsc)
409 : : {
410 : 1516 : float ms = tsc;
411 : 1516 : ms /= (float)spdk_get_ticks_hz();
412 : 1516 : ms *= 1000.0;
413 : 1516 : return ms;
414 : : }
415 : :
416 : : static void
417 : 1648 : trace_step(struct spdk_ftl_dev *dev, struct ftl_mngt_step *step, bool rollback)
418 : : {
419 : : uint64_t duration;
420 [ + + ]: 1648 : const char *what = rollback ? "Rollback" : "Action";
421 [ + + ]: 1648 : int silent = rollback ? step->rollback.silent : step->action.silent;
422 : :
423 [ + + ]: 1648 : if (silent) {
424 : 207 : return;
425 : : }
426 : :
427 [ + - ]: 1441 : FTL_NOTICELOG(dev, "%s\n", what);
428 [ + - ]: 1441 : FTL_NOTICELOG(dev, "\t name: %s\n", step->desc->name);
429 : 1441 : duration = step->action.stop - step->action.start;
430 [ + - ]: 1441 : FTL_NOTICELOG(dev, "\t duration: %.3f ms\n", tsc_to_ms(duration));
431 [ + - ]: 1441 : FTL_NOTICELOG(dev, "\t status: %d\n", step->action.status);
432 : : }
433 : :
434 : : static void
435 : 250 : finish_msg(void *ctx)
436 : : {
437 : 250 : struct ftl_mngt_process *mngt = ctx;
438 : 250 : char *devname = NULL;
439 : :
440 [ + + + + : 250 : if (!mngt->silent && mngt->dev->conf.name) {
+ + ]
441 : : /* the callback below can free the device so make a temp copy of the name */
442 [ - + ]: 51 : devname = strdup(mngt->dev->conf.name);
443 : : }
444 : :
445 : 250 : mngt->caller.cb(mngt->dev, mngt->caller.cb_ctx, mngt->status);
446 : :
447 [ + + ]: 250 : if (mngt->desc->deinit_handler) {
448 : 3 : mngt->desc->deinit_handler(mngt->dev, mngt);
449 : : }
450 : :
451 [ + + + + ]: 250 : if (!mngt->silent) {
452 : : /* TODO: refactor the logging macros to pass just the name instead of device */
453 : 75 : struct spdk_ftl_dev tmpdev = {
454 : : .conf = {
455 : : .name = devname
456 : : }
457 : : };
458 : :
459 : 75 : FTL_NOTICELOG(&tmpdev, "Management process finished, name '%s', duration = %.3f ms, result %d\n",
460 : : mngt->desc->name,
461 : : tsc_to_ms(mngt->tsc_stop - mngt->tsc_start),
462 : : mngt->status);
463 : : }
464 : 250 : free_mngt(mngt);
465 : 250 : free(devname);
466 : 250 : }
467 : :
468 : : void
469 : 250 : ftl_mngt_finish(struct ftl_mngt_process *mngt)
470 : : {
471 : 250 : mngt->tsc_stop = spdk_get_ticks();
472 : 250 : spdk_thread_send_msg(mngt->caller.thread, finish_msg, mngt);
473 : 250 : }
474 : :
475 : : /*
476 : : * Actions
477 : : */
478 : : static void
479 : 1296 : action_next(struct ftl_mngt_process *mngt)
480 : : {
481 [ - + ]: 1296 : if (TAILQ_EMPTY(&mngt->action_queue_todo)) {
482 : : /* Nothing to do, finish the management process */
483 : 0 : ftl_mngt_finish(mngt);
484 : 0 : return;
485 : : } else {
486 : 1296 : action_done(mngt, 0);
487 : 1296 : action_execute(mngt);
488 : : }
489 : : }
490 : :
491 : : static void
492 : 158515 : action_msg(void *ctx)
493 : : {
494 : 158515 : struct ftl_mngt_process *mngt = ctx;
495 : : struct ftl_mngt_step *step;
496 : :
497 : 158515 : mngt->continuing = false;
498 : :
499 [ + + ]: 158515 : if (TAILQ_EMPTY(&mngt->action_queue_todo)) {
500 : 198 : ftl_mngt_finish(mngt);
501 : 198 : return;
502 : : }
503 : :
504 : 158317 : step = TAILQ_FIRST(&mngt->action_queue_todo);
505 [ + + ]: 158317 : if (!step->action.start) {
506 : 1320 : step->action.start = spdk_get_ticks();
507 : : }
508 : 158317 : step->desc->action(mngt->dev, mngt);
509 : : }
510 : :
511 : : static void
512 : 158515 : action_execute(struct ftl_mngt_process *mngt)
513 : : {
514 : 158515 : spdk_thread_send_msg(mngt->dev->core_thread, action_msg, mngt);
515 : 158515 : }
516 : :
517 : : static void
518 : 1308 : action_done(struct ftl_mngt_process *mngt, int status)
519 : : {
520 : : struct ftl_mngt_step *step;
521 : :
522 [ - + ]: 1308 : assert(!TAILQ_EMPTY(&mngt->action_queue_todo));
523 : 1308 : step = TAILQ_FIRST(&mngt->action_queue_todo);
524 [ + + ]: 1308 : TAILQ_REMOVE(&mngt->action_queue_todo, step, action.entry);
525 : :
526 : 1308 : TAILQ_INSERT_TAIL(&mngt->action_queue_done, step, action.entry);
527 [ + + ]: 1308 : if (step->desc->cleanup) {
528 [ + + ]: 413 : TAILQ_INSERT_HEAD(&mngt->rollback_queue_todo, step,
529 : : rollback.entry);
530 : : }
531 : :
532 : 1308 : step->action.stop = spdk_get_ticks();
533 : 1308 : step->action.status = status;
534 : :
535 : 1308 : trace_step(mngt->dev, step, false);
536 : 1308 : }
537 : :
538 : : /*
539 : : * Rollback
540 : : */
541 : : static void
542 : 340 : rollback_next(struct ftl_mngt_process *mngt)
543 : : {
544 [ - + ]: 340 : if (TAILQ_EMPTY(&mngt->rollback_queue_todo)) {
545 : : /* Nothing to do, finish the management process */
546 : 0 : ftl_mngt_finish(mngt);
547 : 0 : return;
548 : : } else {
549 : 340 : rollback_done(mngt, 0);
550 : 340 : rollback_execute(mngt);
551 : : }
552 : : }
553 : :
554 : : static void
555 : 404 : rollback_msg(void *ctx)
556 : : {
557 : 404 : struct ftl_mngt_process *mngt = ctx;
558 : : struct ftl_mngt_step *step;
559 : :
560 : 404 : mngt->continuing = false;
561 : :
562 [ + + ]: 404 : if (TAILQ_EMPTY(&mngt->rollback_queue_todo)) {
563 : 52 : ftl_mngt_finish(mngt);
564 : 52 : return;
565 : : }
566 : :
567 : 352 : step = TAILQ_FIRST(&mngt->rollback_queue_todo);
568 [ + - ]: 352 : if (!step->rollback.start) {
569 : 352 : step->rollback.start = spdk_get_ticks();
570 : : }
571 : 352 : step->desc->cleanup(mngt->dev, mngt);
572 : : }
573 : :
574 : : static void
575 : 404 : rollback_execute(struct ftl_mngt_process *mngt)
576 : : {
577 : 404 : spdk_thread_send_msg(mngt->dev->core_thread, rollback_msg, mngt);
578 : 404 : }
579 : :
580 : : void
581 : 340 : rollback_done(struct ftl_mngt_process *mngt, int status)
582 : : {
583 : : struct ftl_mngt_step *step;
584 : :
585 [ - + ]: 340 : assert(!TAILQ_EMPTY(&mngt->rollback_queue_todo));
586 : 340 : step = TAILQ_FIRST(&mngt->rollback_queue_todo);
587 [ + + ]: 340 : TAILQ_REMOVE(&mngt->rollback_queue_todo, step, rollback.entry);
588 : 340 : TAILQ_INSERT_TAIL(&mngt->rollback_queue_done, step, rollback.entry);
589 : :
590 : 340 : step->rollback.stop = spdk_get_ticks();
591 : 340 : step->rollback.status = status;
592 : :
593 : 340 : trace_step(mngt->dev, step, true);
594 : 340 : }
|