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 : 2027 : get_current_step(struct ftl_mngt_process *mngt)
65 : : {
66 [ + + + + ]: 2027 : if (!mngt->rollback) {
67 : 1965 : return TAILQ_FIRST(&mngt->action_queue_todo);
68 : : } else {
69 : 62 : return TAILQ_FIRST(&mngt->rollback_queue_todo);
70 : : }
71 : : }
72 : :
73 : : static int
74 : 1686 : init_step(struct ftl_mngt_process *mngt,
75 : : const struct ftl_mngt_step_desc *desc)
76 : : {
77 : : struct ftl_mngt_step *step;
78 : :
79 : 1686 : step = calloc(1, sizeof(*step));
80 [ - + ]: 1686 : if (!step) {
81 : 0 : return -ENOMEM;
82 : : }
83 : :
84 : : /* Initialize the step's argument */
85 [ + + ]: 1686 : if (desc->ctx_size) {
86 : 30 : step->ctx = calloc(1, desc->ctx_size);
87 [ - + ]: 30 : if (!step->ctx) {
88 : 0 : free(step);
89 : 0 : return -ENOMEM;
90 : : }
91 : : }
92 : 1686 : step->desc = desc;
93 : 1686 : TAILQ_INSERT_TAIL(&mngt->action_queue_todo, step, action.entry);
94 : :
95 : 1686 : return 0;
96 : : }
97 : :
98 : : static void
99 : 272 : free_mngt(struct ftl_mngt_process *mngt)
100 : : {
101 : 272 : TAILQ_HEAD(, ftl_mngt_step) steps;
102 : :
103 [ - + ]: 272 : if (!mngt) {
104 : 0 : return;
105 : : }
106 : :
107 : 272 : TAILQ_INIT(&steps);
108 [ + + ]: 272 : TAILQ_CONCAT(&steps, &mngt->action_queue_todo, action.entry);
109 [ + + ]: 272 : TAILQ_CONCAT(&steps, &mngt->action_queue_done, action.entry);
110 : :
111 [ + + ]: 1958 : while (!TAILQ_EMPTY(&steps)) {
112 : 1686 : struct ftl_mngt_step *step = TAILQ_FIRST(&steps);
113 [ + + ]: 1686 : TAILQ_REMOVE(&steps, step, action.entry);
114 : :
115 : 1686 : free(step->ctx);
116 : 1686 : free(step);
117 : : }
118 : :
119 : 272 : free(mngt->ctx);
120 : 272 : free(mngt);
121 : : }
122 : :
123 : : static struct ftl_mngt_process *
124 : 272 : 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 : 272 : mngt = calloc(1, sizeof(*mngt));
131 [ - + ]: 272 : if (!mngt) {
132 : 0 : goto error;
133 : : }
134 : 272 : mngt->dev = dev;
135 : 272 : mngt->silent = silent;
136 : 272 : mngt->caller.cb = cb;
137 : 272 : mngt->caller.cb_ctx = cb_ctx;
138 : 272 : mngt->caller.thread = spdk_get_thread();
139 : :
140 : : /* Initialize process context */
141 [ + + ]: 272 : if (pdesc->ctx_size) {
142 : 39 : mngt->ctx = calloc(1, pdesc->ctx_size);
143 [ - + ]: 39 : if (!mngt->ctx) {
144 : 0 : goto error;
145 : : }
146 : : }
147 : 272 : mngt->tsc_start = spdk_get_ticks();
148 : 272 : mngt->desc = pdesc;
149 : 272 : TAILQ_INIT(&mngt->action_queue_todo);
150 : 272 : TAILQ_INIT(&mngt->action_queue_done);
151 : 272 : TAILQ_INIT(&mngt->rollback_queue_todo);
152 : 272 : TAILQ_INIT(&mngt->rollback_queue_done);
153 : :
154 : 272 : return mngt;
155 : 0 : error:
156 : 0 : free_mngt(mngt);
157 : 0 : return NULL;
158 : : }
159 : :
160 : : static int
161 : 226 : 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 : 226 : int rc = 0;
165 : :
166 [ + + - + ]: 226 : if (init_ctx || pdesc->init_handler) {
167 [ - + ]: 8 : ftl_bug(!init_ctx);
168 [ - + ]: 8 : ftl_bug(!pdesc->init_handler);
169 : 8 : rc = pdesc->init_handler(dev, mngt, init_ctx);
170 : : }
171 : :
172 : 226 : return rc;
173 : : }
174 : :
175 : : static int
176 : 226 : _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 : 226 : int rc = 0;
182 : :
183 : 226 : mngt = allocate_mngt(dev, pdesc, cb, cb_ctx, silent);
184 [ - + ]: 226 : if (!mngt) {
185 : 0 : rc = -ENOMEM;
186 : 0 : goto error;
187 : : }
188 : :
189 [ + + ]: 226 : 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 : 226 : sdesc = mngt->desc->steps;
202 [ + + ]: 1574 : while (sdesc->action) {
203 : 1348 : rc = init_step(mngt, sdesc);
204 [ - + ]: 1348 : if (rc) {
205 : 0 : goto error;
206 : : }
207 : 1348 : sdesc++;
208 : : }
209 : :
210 : 226 : rc = invoke_init_handler(dev, mngt, pdesc, init_ctx);
211 [ + + ]: 226 : if (rc) {
212 : 4 : goto error;
213 : : }
214 : :
215 : 222 : action_execute(mngt);
216 : 222 : return 0;
217 : 4 : error:
218 : 4 : free_mngt(mngt);
219 : 4 : return rc;
220 : : }
221 : :
222 : : int
223 : 83 : 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 : 83 : return _ftl_mngt_process_execute(dev, pdesc, cb, cb_ctx, false, NULL);
227 : : }
228 : :
229 : : int
230 : 46 : 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 : 46 : int rc = 0;
236 : :
237 : 46 : mngt = allocate_mngt(dev, pdesc, cb, cb_ctx, true);
238 [ - + ]: 46 : if (!mngt) {
239 : 0 : rc = -ENOMEM;
240 : 0 : goto error;
241 : : }
242 : :
243 : : /* Initialize steps for rollback */
244 : 46 : sdesc = mngt->desc->steps;
245 [ + + ]: 564 : while (sdesc->action) {
246 [ + + ]: 518 : if (!sdesc->cleanup) {
247 : 180 : sdesc++;
248 : 180 : continue;
249 : : }
250 : 338 : rc = init_step(mngt, sdesc);
251 [ - + ]: 338 : if (rc) {
252 : 0 : goto error;
253 : : }
254 : 338 : sdesc++;
255 : : }
256 : :
257 : : /* Build rollback list */
258 : : struct ftl_mngt_step *step;
259 [ + + ]: 384 : TAILQ_FOREACH(step, &mngt->action_queue_todo, action.entry) {
260 : 338 : step->action.silent = true;
261 [ + + ]: 338 : TAILQ_INSERT_HEAD(&mngt->rollback_queue_todo, step,
262 : : rollback.entry);
263 : : }
264 : :
265 : 46 : mngt->rollback = true;
266 : 46 : rollback_execute(mngt);
267 : 46 : return 0;
268 : 0 : error:
269 : 0 : free_mngt(mngt);
270 : 0 : return rc;
271 : : }
272 : :
273 : : struct spdk_ftl_dev *
274 : 122 : ftl_mngt_get_dev(struct ftl_mngt_process *mngt)
275 : : {
276 : 122 : return mngt->dev;
277 : : }
278 : :
279 : : int
280 : 39 : ftl_mngt_alloc_step_ctx(struct ftl_mngt_process *mngt, size_t size)
281 : : {
282 : 39 : struct ftl_mngt_step *step = get_current_step(mngt);
283 : 39 : void *arg = calloc(1, size);
284 : :
285 [ - + ]: 39 : if (!arg) {
286 : 0 : return -ENOMEM;
287 : : }
288 : :
289 : 39 : free(step->ctx);
290 : 39 : step->ctx = arg;
291 : :
292 : 39 : return 0;
293 : : }
294 : :
295 : : void *
296 : 1775 : ftl_mngt_get_step_ctx(struct ftl_mngt_process *mngt)
297 : : {
298 : 1775 : return get_current_step(mngt)->ctx;
299 : : }
300 : :
301 : : void *
302 : 57 : ftl_mngt_get_process_ctx(struct ftl_mngt_process *mngt)
303 : : {
304 : 57 : return mngt->ctx;
305 : : }
306 : :
307 : : void *
308 : 34 : ftl_mngt_get_caller_ctx(struct ftl_mngt_process *mngt)
309 : : {
310 : 34 : return mngt->caller.cb_ctx;
311 : : }
312 : :
313 : : void
314 : 1674 : ftl_mngt_next_step(struct ftl_mngt_process *mngt)
315 : : {
316 [ + + + + ]: 1674 : if (false == mngt->rollback) {
317 : 1316 : action_next(mngt);
318 : : } else {
319 : 358 : rollback_next(mngt);
320 : : }
321 : 1674 : }
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 : 208510 : ftl_mngt_continue_step(struct ftl_mngt_process *mngt)
336 : : {
337 : :
338 [ + + + - ]: 208510 : if (!mngt->continuing) {
339 [ + + + + ]: 208510 : if (false == mngt->rollback) {
340 : 208494 : action_execute(mngt);
341 : : } else {
342 : 16 : rollback_execute(mngt);
343 : : }
344 : : }
345 : :
346 : 208510 : mngt->continuing = true;
347 : 208510 : }
348 : :
349 : : static void
350 : 169 : child_cb(struct spdk_ftl_dev *dev, void *ctx, int status)
351 : : {
352 : 169 : struct ftl_mngt_process *parent = ctx;
353 : :
354 [ + + ]: 169 : if (status) {
355 : 4 : ftl_mngt_fail_step(parent);
356 : : } else {
357 : 165 : ftl_mngt_next_step(parent);
358 : : }
359 : 169 : }
360 : :
361 : : void
362 : 143 : ftl_mngt_call_process(struct ftl_mngt_process *mngt,
363 : : const struct ftl_mngt_process_desc *pdesc,
364 : : void *init_ctx)
365 : : {
366 [ + + ]: 143 : if (_ftl_mngt_process_execute(mngt->dev, pdesc, child_cb, mngt, true, init_ctx)) {
367 : 4 : ftl_mngt_fail_step(mngt);
368 : : } else {
369 [ + + + + ]: 139 : if (mngt->rollback) {
370 : 4 : get_current_step(mngt)->rollback.silent = true;
371 : : } else {
372 : 135 : get_current_step(mngt)->action.silent = true;
373 : : }
374 : : }
375 : 143 : }
376 : :
377 : : void
378 : 30 : ftl_mngt_call_process_rollback(struct ftl_mngt_process *mngt,
379 : : const struct ftl_mngt_process_desc *pdesc)
380 : : {
381 [ - + ]: 30 : if (ftl_mngt_process_rollback(mngt->dev, pdesc, child_cb, mngt)) {
382 : 0 : ftl_mngt_fail_step(mngt);
383 : : } else {
384 [ + + + + ]: 30 : if (mngt->rollback) {
385 : 4 : get_current_step(mngt)->rollback.silent = true;
386 : : } else {
387 : 26 : get_current_step(mngt)->action.silent = true;
388 : : }
389 : : }
390 : 30 : }
391 : :
392 : : void
393 : 16 : ftl_mngt_fail_step(struct ftl_mngt_process *mngt)
394 : : {
395 : 16 : mngt->status = -1;
396 : :
397 [ + + + - ]: 16 : if (false == mngt->rollback) {
398 : 16 : action_done(mngt, -1);
399 : : } else {
400 : 0 : rollback_done(mngt, -1);
401 : : }
402 : :
403 : 16 : mngt->rollback = true;
404 : 16 : rollback_execute(mngt);
405 : 16 : }
406 : :
407 : : static inline float
408 : 1560 : tsc_to_ms(uint64_t tsc)
409 : : {
410 : 1560 : float ms = tsc;
411 : 1560 : ms /= (float)spdk_get_ticks_hz();
412 : 1560 : ms *= 1000.0;
413 : 1560 : return ms;
414 : : }
415 : :
416 : : static void
417 : 1690 : trace_step(struct spdk_ftl_dev *dev, struct ftl_mngt_step *step, bool rollback)
418 : : {
419 : : uint64_t duration;
420 [ + + ]: 1690 : const char *what = rollback ? "Rollback" : "Action";
421 [ + + ]: 1690 : int silent = rollback ? step->rollback.silent : step->action.silent;
422 : :
423 [ + + ]: 1690 : if (silent) {
424 : 213 : return;
425 : : }
426 : :
427 [ + - ]: 1477 : FTL_NOTICELOG(dev, "%s\n", what);
428 [ + - ]: 1477 : FTL_NOTICELOG(dev, "\t name: %s\n", step->desc->name);
429 : 1477 : duration = step->action.stop - step->action.start;
430 [ + - ]: 1477 : FTL_NOTICELOG(dev, "\t duration: %.3f ms\n", tsc_to_ms(duration));
431 [ + - ]: 1477 : FTL_NOTICELOG(dev, "\t status: %d\n", step->action.status);
432 : : }
433 : :
434 : : static void
435 : 268 : finish_msg(void *ctx)
436 : : {
437 : 268 : struct ftl_mngt_process *mngt = ctx;
438 : 268 : char *devname = NULL;
439 : :
440 [ + + + + : 268 : 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 : 268 : mngt->caller.cb(mngt->dev, mngt->caller.cb_ctx, mngt->status);
446 : :
447 [ + + ]: 268 : if (mngt->desc->deinit_handler) {
448 : 4 : mngt->desc->deinit_handler(mngt->dev, mngt);
449 : : }
450 : :
451 [ + + + + ]: 268 : if (!mngt->silent) {
452 : : /* TODO: refactor the logging macros to pass just the name instead of device */
453 : 83 : struct spdk_ftl_dev tmpdev = {
454 : : .conf = {
455 : : .name = devname
456 : : }
457 : : };
458 : :
459 : 83 : 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 : 268 : free_mngt(mngt);
465 : 268 : free(devname);
466 : 268 : }
467 : :
468 : : void
469 : 268 : ftl_mngt_finish(struct ftl_mngt_process *mngt)
470 : : {
471 : 268 : mngt->tsc_stop = spdk_get_ticks();
472 : 268 : spdk_thread_send_msg(mngt->caller.thread, finish_msg, mngt);
473 : 268 : }
474 : :
475 : : /*
476 : : * Actions
477 : : */
478 : : static void
479 : 1316 : action_next(struct ftl_mngt_process *mngt)
480 : : {
481 [ - + ]: 1316 : 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 : 1316 : action_done(mngt, 0);
487 : 1316 : action_execute(mngt);
488 : : }
489 : : }
490 : :
491 : : static void
492 : 210032 : action_msg(void *ctx)
493 : : {
494 : 210032 : struct ftl_mngt_process *mngt = ctx;
495 : : struct ftl_mngt_step *step;
496 : :
497 : 210032 : mngt->continuing = false;
498 : :
499 [ + + ]: 210032 : if (TAILQ_EMPTY(&mngt->action_queue_todo)) {
500 : 206 : ftl_mngt_finish(mngt);
501 : 206 : return;
502 : : }
503 : :
504 : 209826 : step = TAILQ_FIRST(&mngt->action_queue_todo);
505 [ + + ]: 209826 : if (!step->action.start) {
506 : 1348 : step->action.start = spdk_get_ticks();
507 : : }
508 : 209826 : step->desc->action(mngt->dev, mngt);
509 : : }
510 : :
511 : : static void
512 : 210032 : action_execute(struct ftl_mngt_process *mngt)
513 : : {
514 : 210032 : spdk_thread_send_msg(mngt->dev->core_thread, action_msg, mngt);
515 : 210032 : }
516 : :
517 : : static void
518 : 1332 : action_done(struct ftl_mngt_process *mngt, int status)
519 : : {
520 : : struct ftl_mngt_step *step;
521 : :
522 [ - + ]: 1332 : assert(!TAILQ_EMPTY(&mngt->action_queue_todo));
523 : 1332 : step = TAILQ_FIRST(&mngt->action_queue_todo);
524 [ + + ]: 1332 : TAILQ_REMOVE(&mngt->action_queue_todo, step, action.entry);
525 : :
526 : 1332 : TAILQ_INSERT_TAIL(&mngt->action_queue_done, step, action.entry);
527 [ + + ]: 1332 : if (step->desc->cleanup) {
528 [ + + ]: 431 : TAILQ_INSERT_HEAD(&mngt->rollback_queue_todo, step,
529 : : rollback.entry);
530 : : }
531 : :
532 : 1332 : step->action.stop = spdk_get_ticks();
533 : 1332 : step->action.status = status;
534 : :
535 : 1332 : trace_step(mngt->dev, step, false);
536 : 1332 : }
537 : :
538 : : /*
539 : : * Rollback
540 : : */
541 : : static void
542 : 358 : rollback_next(struct ftl_mngt_process *mngt)
543 : : {
544 [ - + ]: 358 : 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 : 358 : rollback_done(mngt, 0);
550 : 358 : rollback_execute(mngt);
551 : : }
552 : : }
553 : :
554 : : static void
555 : 436 : rollback_msg(void *ctx)
556 : : {
557 : 436 : struct ftl_mngt_process *mngt = ctx;
558 : : struct ftl_mngt_step *step;
559 : :
560 : 436 : mngt->continuing = false;
561 : :
562 [ + + ]: 436 : if (TAILQ_EMPTY(&mngt->rollback_queue_todo)) {
563 : 62 : ftl_mngt_finish(mngt);
564 : 62 : return;
565 : : }
566 : :
567 : 374 : step = TAILQ_FIRST(&mngt->rollback_queue_todo);
568 [ + - ]: 374 : if (!step->rollback.start) {
569 : 374 : step->rollback.start = spdk_get_ticks();
570 : : }
571 : 374 : step->desc->cleanup(mngt->dev, mngt);
572 : : }
573 : :
574 : : static void
575 : 436 : rollback_execute(struct ftl_mngt_process *mngt)
576 : : {
577 : 436 : spdk_thread_send_msg(mngt->dev->core_thread, rollback_msg, mngt);
578 : 436 : }
579 : :
580 : : void
581 : 358 : rollback_done(struct ftl_mngt_process *mngt, int status)
582 : : {
583 : : struct ftl_mngt_step *step;
584 : :
585 [ - + ]: 358 : assert(!TAILQ_EMPTY(&mngt->rollback_queue_todo));
586 : 358 : step = TAILQ_FIRST(&mngt->rollback_queue_todo);
587 [ + + ]: 358 : TAILQ_REMOVE(&mngt->rollback_queue_todo, step, rollback.entry);
588 : 358 : TAILQ_INSERT_TAIL(&mngt->rollback_queue_done, step, rollback.entry);
589 : :
590 : 358 : step->rollback.stop = spdk_get_ticks();
591 : 358 : step->rollback.status = status;
592 : :
593 : 358 : trace_step(mngt->dev, step, true);
594 : 358 : }
|