1 /* SPDX-License-Identifier: BSD-3-Clause 2 * Copyright (c) 2022 NVIDIA Corporation & Affiliates 3 */ 4 5 #include "mlx5dr_internal.h" 6 7 const char *mlx5dr_debug_action_type_str[] = { 8 [MLX5DR_ACTION_TYP_LAST] = "LAST", 9 [MLX5DR_ACTION_TYP_REFORMAT_TNL_L2_TO_L2] = "TNL_L2_TO_L2", 10 [MLX5DR_ACTION_TYP_REFORMAT_L2_TO_TNL_L2] = "L2_TO_TNL_L2", 11 [MLX5DR_ACTION_TYP_REFORMAT_TNL_L3_TO_L2] = "TNL_L3_TO_L2", 12 [MLX5DR_ACTION_TYP_REFORMAT_L2_TO_TNL_L3] = "L2_TO_TNL_L3", 13 [MLX5DR_ACTION_TYP_DROP] = "DROP", 14 [MLX5DR_ACTION_TYP_TIR] = "TIR", 15 [MLX5DR_ACTION_TYP_TBL] = "TBL", 16 [MLX5DR_ACTION_TYP_CTR] = "CTR", 17 [MLX5DR_ACTION_TYP_TAG] = "TAG", 18 [MLX5DR_ACTION_TYP_MODIFY_HDR] = "MODIFY_HDR", 19 [MLX5DR_ACTION_TYP_VPORT] = "VPORT", 20 [MLX5DR_ACTION_TYP_MISS] = "DEFAULT_MISS", 21 [MLX5DR_ACTION_TYP_POP_VLAN] = "POP_VLAN", 22 [MLX5DR_ACTION_TYP_PUSH_VLAN] = "PUSH_VLAN", 23 [MLX5DR_ACTION_TYP_ASO_METER] = "ASO_METER", 24 [MLX5DR_ACTION_TYP_ASO_CT] = "ASO_CT", 25 [MLX5DR_ACTION_TYP_DEST_ROOT] = "DEST_ROOT", 26 [MLX5DR_ACTION_TYP_DEST_ARRAY] = "DEST_ARRAY", 27 [MLX5DR_ACTION_TYP_INSERT_HEADER] = "INSERT_HEADER", 28 [MLX5DR_ACTION_TYP_REMOVE_HEADER] = "REMOVE_HEADER", 29 [MLX5DR_ACTION_TYP_POP_IPV6_ROUTE_EXT] = "POP_IPV6_ROUTE_EXT", 30 [MLX5DR_ACTION_TYP_PUSH_IPV6_ROUTE_EXT] = "PUSH_IPV6_ROUTE_EXT", 31 [MLX5DR_ACTION_TYP_NAT64] = "NAT64", 32 }; 33 34 static_assert(ARRAY_SIZE(mlx5dr_debug_action_type_str) == MLX5DR_ACTION_TYP_MAX, 35 "Missing mlx5dr_debug_action_type_str"); 36 37 const char *mlx5dr_debug_action_type_to_str(enum mlx5dr_action_type action_type) 38 { 39 return mlx5dr_debug_action_type_str[action_type]; 40 } 41 42 static int 43 mlx5dr_debug_dump_matcher_template_definer(FILE *f, 44 void *parent_obj, 45 struct mlx5dr_definer *definer, 46 enum mlx5dr_debug_res_type type) 47 { 48 int i, ret; 49 50 if (!definer) 51 return 0; 52 53 ret = fprintf(f, "%d,0x%" PRIx64 ",0x%" PRIx64 ",%d,%d,", 54 type, 55 (uint64_t)(uintptr_t)definer, 56 (uint64_t)(uintptr_t)parent_obj, 57 definer->obj->id, 58 definer->type); 59 if (ret < 0) { 60 rte_errno = EINVAL; 61 return rte_errno; 62 } 63 64 for (i = 0; i < DW_SELECTORS; i++) { 65 ret = fprintf(f, "0x%x%s", definer->dw_selector[i], 66 (i == DW_SELECTORS - 1) ? "," : "-"); 67 if (ret < 0) { 68 rte_errno = EINVAL; 69 return rte_errno; 70 } 71 } 72 73 for (i = 0; i < BYTE_SELECTORS; i++) { 74 ret = fprintf(f, "0x%x%s", definer->byte_selector[i], 75 (i == BYTE_SELECTORS - 1) ? "," : "-"); 76 if (ret < 0) { 77 rte_errno = EINVAL; 78 return rte_errno; 79 } 80 } 81 82 for (i = 0; i < MLX5DR_JUMBO_TAG_SZ; i++) { 83 ret = fprintf(f, "%02x", definer->mask.jumbo[i]); 84 if (ret < 0) { 85 rte_errno = EINVAL; 86 return rte_errno; 87 } 88 } 89 90 ret = fprintf(f, "\n"); 91 if (ret < 0) { 92 rte_errno = EINVAL; 93 return rte_errno; 94 } 95 96 return 0; 97 } 98 99 static int 100 mlx5dr_debug_dump_matcher_match_template(FILE *f, struct mlx5dr_matcher *matcher) 101 { 102 bool is_root = matcher->tbl->level == MLX5DR_ROOT_LEVEL; 103 bool is_compare = mlx5dr_matcher_is_compare(matcher); 104 enum mlx5dr_debug_res_type type; 105 int i, ret; 106 107 for (i = 0; i < matcher->num_of_mt; i++) { 108 struct mlx5dr_match_template *mt = &matcher->mt[i]; 109 110 ret = fprintf(f, "%d,0x%" PRIx64 ",0x%" PRIx64 ",%d,%d,%d\n", 111 MLX5DR_DEBUG_RES_TYPE_MATCHER_MATCH_TEMPLATE, 112 (uint64_t)(uintptr_t)mt, 113 (uint64_t)(uintptr_t)matcher, 114 is_root ? 0 : mt->fc_sz, 115 mt->flags, 116 is_root ? 0 : mt->fcr_sz); 117 if (ret < 0) { 118 rte_errno = EINVAL; 119 return rte_errno; 120 } 121 122 type = is_compare ? MLX5DR_DEBUG_RES_TYPE_MATCHER_TEMPLATE_COMPARE_MATCH_DEFINER : 123 MLX5DR_DEBUG_RES_TYPE_MATCHER_TEMPLATE_MATCH_DEFINER; 124 ret = mlx5dr_debug_dump_matcher_template_definer(f, mt, mt->definer, type); 125 if (ret) 126 return ret; 127 128 type = MLX5DR_DEBUG_RES_TYPE_MATCHER_TEMPLATE_RANGE_DEFINER; 129 ret = mlx5dr_debug_dump_matcher_template_definer(f, mt, mt->range_definer, type); 130 if (ret) 131 return ret; 132 } 133 134 type = MLX5DR_DEBUG_RES_TYPE_MATCHER_TEMPLATE_HASH_DEFINER; 135 ret = mlx5dr_debug_dump_matcher_template_definer(f, matcher, matcher->hash_definer, type); 136 if (ret) 137 return ret; 138 139 return 0; 140 } 141 142 static int 143 mlx5dr_debug_dump_matcher_action_template(FILE *f, struct mlx5dr_matcher *matcher) 144 { 145 bool is_root = matcher->tbl->level == MLX5DR_ROOT_LEVEL; 146 enum mlx5dr_action_type action_type; 147 int i, j, ret; 148 149 for (i = 0; i < matcher->num_of_at; i++) { 150 struct mlx5dr_action_template *at = &matcher->at[i]; 151 152 ret = fprintf(f, "%d,0x%" PRIx64 ",0x%" PRIx64 ",%d,%d,%d", 153 MLX5DR_DEBUG_RES_TYPE_MATCHER_ACTION_TEMPLATE, 154 (uint64_t)(uintptr_t)at, 155 (uint64_t)(uintptr_t)matcher, 156 at->only_term ? 0 : 1, 157 is_root ? 0 : at->num_of_action_stes, 158 at->num_actions); 159 if (ret < 0) { 160 rte_errno = EINVAL; 161 return rte_errno; 162 } 163 164 for (j = 0; j < at->num_actions; j++) { 165 action_type = at->action_type_arr[j]; 166 ret = fprintf(f, ",%s", mlx5dr_debug_action_type_to_str(action_type)); 167 if (ret < 0) { 168 rte_errno = EINVAL; 169 return rte_errno; 170 } 171 } 172 173 fprintf(f, "\n"); 174 } 175 176 return 0; 177 } 178 179 static int 180 mlx5dr_debug_dump_matcher_attr(FILE *f, struct mlx5dr_matcher *matcher) 181 { 182 struct mlx5dr_matcher_attr *attr = &matcher->attr; 183 int ret; 184 185 ret = fprintf(f, "%d,0x%" PRIx64 ",%d,%d,%d,%d,%d,%d,%d,%d\n", 186 MLX5DR_DEBUG_RES_TYPE_MATCHER_ATTR, 187 (uint64_t)(uintptr_t)matcher, 188 attr->priority, 189 attr->mode, 190 attr->table.sz_row_log, 191 attr->table.sz_col_log, 192 attr->optimize_using_rule_idx, 193 attr->optimize_flow_src, 194 attr->insert_mode, 195 attr->distribute_mode); 196 if (ret < 0) { 197 rte_errno = EINVAL; 198 return rte_errno; 199 } 200 201 return 0; 202 } 203 204 static int mlx5dr_debug_dump_matcher(FILE *f, struct mlx5dr_matcher *matcher) 205 { 206 bool is_shared = mlx5dr_context_shared_gvmi_used(matcher->tbl->ctx); 207 bool is_root = matcher->tbl->level == MLX5DR_ROOT_LEVEL; 208 enum mlx5dr_table_type tbl_type = matcher->tbl->type; 209 struct mlx5dr_cmd_ft_query_attr ft_attr = {0}; 210 struct mlx5dr_devx_obj *ste_0, *ste_1 = NULL; 211 struct mlx5dr_pool_chunk *ste; 212 struct mlx5dr_pool *ste_pool; 213 uint64_t icm_addr_0 = 0; 214 uint64_t icm_addr_1 = 0; 215 int ret; 216 217 ret = fprintf(f, "%d,0x%" PRIx64 ",0x%" PRIx64 ",%d,%d,0x%" PRIx64, 218 MLX5DR_DEBUG_RES_TYPE_MATCHER, 219 (uint64_t)(uintptr_t)matcher, 220 (uint64_t)(uintptr_t)matcher->tbl, 221 matcher->num_of_mt, 222 is_root ? 0 : matcher->end_ft->id, 223 matcher->col_matcher ? (uint64_t)(uintptr_t)matcher->col_matcher : 0); 224 if (ret < 0) 225 goto out_err; 226 227 ste = &matcher->match_ste.ste; 228 ste_pool = matcher->match_ste.pool; 229 if (ste_pool) { 230 ste_0 = mlx5dr_pool_chunk_get_base_devx_obj(ste_pool, ste); 231 if (tbl_type == MLX5DR_TABLE_TYPE_FDB) 232 ste_1 = mlx5dr_pool_chunk_get_base_devx_obj_mirror(ste_pool, ste); 233 } else { 234 ste_0 = NULL; 235 ste_1 = NULL; 236 } 237 238 ret = fprintf(f, ",%d,%d,%d,%d", 239 matcher->match_ste.rtc_0 ? matcher->match_ste.rtc_0->id : 0, 240 ste_0 ? (int)ste_0->id : -1, 241 matcher->match_ste.rtc_1 ? matcher->match_ste.rtc_1->id : 0, 242 ste_1 ? (int)ste_1->id : -1); 243 if (ret < 0) 244 goto out_err; 245 246 ste = &matcher->action_ste.ste; 247 ste_pool = matcher->action_ste.pool; 248 if (ste_pool) { 249 ste_0 = mlx5dr_pool_chunk_get_base_devx_obj(ste_pool, ste); 250 if (tbl_type == MLX5DR_TABLE_TYPE_FDB) 251 ste_1 = mlx5dr_pool_chunk_get_base_devx_obj_mirror(ste_pool, ste); 252 } else { 253 ste_0 = NULL; 254 ste_1 = NULL; 255 } 256 257 if (!is_root) { 258 ft_attr.type = matcher->tbl->fw_ft_type; 259 ret = mlx5dr_cmd_flow_table_query(matcher->end_ft, 260 &ft_attr, 261 &icm_addr_0, 262 &icm_addr_1); 263 if (ret) 264 return ret; 265 } 266 267 ret = fprintf(f, ",%d,%d,%d,%d,%d,0x%" PRIx64 ",0x%" PRIx64 "\n", 268 matcher->action_ste.rtc_0 ? matcher->action_ste.rtc_0->id : 0, 269 ste_0 ? (int)ste_0->id : -1, 270 matcher->action_ste.rtc_1 ? matcher->action_ste.rtc_1->id : 0, 271 ste_1 ? (int)ste_1->id : -1, 272 is_shared && !is_root ? 273 matcher->match_ste.aliased_rtc_0->id : 0, 274 mlx5dr_debug_icm_to_idx(icm_addr_0), 275 mlx5dr_debug_icm_to_idx(icm_addr_1)); 276 if (ret < 0) 277 goto out_err; 278 279 ret = mlx5dr_debug_dump_matcher_attr(f, matcher); 280 if (ret) 281 return ret; 282 283 ret = mlx5dr_debug_dump_matcher_match_template(f, matcher); 284 if (ret) 285 return ret; 286 287 ret = mlx5dr_debug_dump_matcher_action_template(f, matcher); 288 if (ret) 289 return ret; 290 291 return 0; 292 293 out_err: 294 rte_errno = EINVAL; 295 return rte_errno; 296 } 297 298 static int mlx5dr_debug_dump_table(FILE *f, struct mlx5dr_table *tbl) 299 { 300 bool is_shared = mlx5dr_context_shared_gvmi_used(tbl->ctx); 301 bool is_root = tbl->level == MLX5DR_ROOT_LEVEL; 302 struct mlx5dr_cmd_ft_query_attr ft_attr = {0}; 303 struct mlx5dr_matcher *matcher; 304 uint64_t local_icm_addr_0 = 0; 305 uint64_t local_icm_addr_1 = 0; 306 uint64_t icm_addr_0 = 0; 307 uint64_t icm_addr_1 = 0; 308 int ret; 309 310 ret = fprintf(f, "%d,0x%" PRIx64 ",0x%" PRIx64 ",%d,%d,%d,%d,%d", 311 MLX5DR_DEBUG_RES_TYPE_TABLE, 312 (uint64_t)(uintptr_t)tbl, 313 (uint64_t)(uintptr_t)tbl->ctx, 314 is_root ? 0 : tbl->ft->id, 315 tbl->type, 316 is_root ? 0 : tbl->fw_ft_type, 317 tbl->level, 318 is_shared && !is_root ? tbl->local_ft->id : 0); 319 if (ret < 0) 320 goto out_err; 321 322 if (!is_root) { 323 ft_attr.type = tbl->fw_ft_type; 324 ret = mlx5dr_cmd_flow_table_query(tbl->ft, 325 &ft_attr, 326 &icm_addr_0, 327 &icm_addr_1); 328 if (ret) 329 return ret; 330 331 if (is_shared) { 332 ft_attr.type = tbl->fw_ft_type; 333 ret = mlx5dr_cmd_flow_table_query(tbl->local_ft, 334 &ft_attr, 335 &local_icm_addr_0, 336 &local_icm_addr_1); 337 if (ret) 338 return ret; 339 } 340 } 341 342 ret = fprintf(f, ",0x%" PRIx64 ",0x%" PRIx64 ",0x%" PRIx64 ",0x%" PRIx64 ",0x%" PRIx64 "\n", 343 mlx5dr_debug_icm_to_idx(icm_addr_0), 344 mlx5dr_debug_icm_to_idx(icm_addr_1), 345 mlx5dr_debug_icm_to_idx(local_icm_addr_0), 346 mlx5dr_debug_icm_to_idx(local_icm_addr_1), 347 (uint64_t)(uintptr_t)tbl->default_miss.miss_tbl); 348 if (ret < 0) 349 goto out_err; 350 351 LIST_FOREACH(matcher, &tbl->head, next) { 352 ret = mlx5dr_debug_dump_matcher(f, matcher); 353 if (ret) 354 return ret; 355 } 356 357 return 0; 358 359 out_err: 360 rte_errno = EINVAL; 361 return rte_errno; 362 } 363 364 static int 365 mlx5dr_debug_dump_context_send_engine(FILE *f, struct mlx5dr_context *ctx) 366 { 367 struct mlx5dr_send_engine *send_queue; 368 int ret, i, j; 369 370 for (i = 0; i < (int)ctx->queues; i++) { 371 send_queue = &ctx->send_queue[i]; 372 ret = fprintf(f, "%d,0x%" PRIx64 ",%d,%d,%d,%d,%d,%d,%d,%d,%d\n", 373 MLX5DR_DEBUG_RES_TYPE_CONTEXT_SEND_ENGINE, 374 (uint64_t)(uintptr_t)ctx, 375 i, 376 send_queue->used_entries, 377 send_queue->th_entries, 378 send_queue->rings, 379 send_queue->num_entries, 380 send_queue->err, 381 send_queue->completed.ci, 382 send_queue->completed.pi, 383 send_queue->completed.mask); 384 if (ret < 0) { 385 rte_errno = EINVAL; 386 return rte_errno; 387 } 388 389 for (j = 0; j < MLX5DR_NUM_SEND_RINGS; j++) { 390 struct mlx5dr_send_ring *send_ring = &send_queue->send_ring[j]; 391 struct mlx5dr_send_ring_cq *cq = &send_ring->send_cq; 392 struct mlx5dr_send_ring_sq *sq = &send_ring->send_sq; 393 394 ret = fprintf(f, "%d,0x%" PRIx64 ",%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n", 395 MLX5DR_DEBUG_RES_TYPE_CONTEXT_SEND_RING, 396 (uint64_t)(uintptr_t)ctx, 397 j, 398 i, 399 cq->cqn, 400 cq->cons_index, 401 cq->ncqe_mask, 402 cq->buf_sz, 403 cq->ncqe, 404 cq->cqe_log_sz, 405 cq->poll_wqe, 406 cq->cqe_sz, 407 sq->sqn, 408 sq->obj->id, 409 sq->cur_post, 410 sq->buf_mask); 411 if (ret < 0) { 412 rte_errno = EINVAL; 413 return rte_errno; 414 } 415 } 416 } 417 418 return 0; 419 } 420 421 static int mlx5dr_debug_dump_context_caps(FILE *f, struct mlx5dr_context *ctx) 422 { 423 struct mlx5dr_cmd_query_caps *caps = ctx->caps; 424 int ret; 425 426 ret = fprintf(f, "%d,0x%" PRIx64 ",%s,%d,%d,%d,%d,", 427 MLX5DR_DEBUG_RES_TYPE_CONTEXT_CAPS, 428 (uint64_t)(uintptr_t)ctx, 429 caps->fw_ver, 430 caps->wqe_based_update, 431 caps->ste_format, 432 caps->ste_alloc_log_max, 433 caps->log_header_modify_argument_max_alloc); 434 if (ret < 0) { 435 rte_errno = EINVAL; 436 return rte_errno; 437 } 438 439 ret = fprintf(f, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n", 440 caps->flex_protocols, 441 caps->rtc_reparse_mode, 442 caps->rtc_index_mode, 443 caps->ste_alloc_log_gran, 444 caps->stc_alloc_log_max, 445 caps->stc_alloc_log_gran, 446 caps->rtc_log_depth_max, 447 caps->format_select_gtpu_dw_0, 448 caps->format_select_gtpu_dw_1, 449 caps->format_select_gtpu_dw_2, 450 caps->format_select_gtpu_ext_dw_0, 451 caps->nic_ft.max_level, 452 caps->nic_ft.reparse, 453 caps->fdb_ft.max_level, 454 caps->fdb_ft.reparse, 455 caps->log_header_modify_argument_granularity); 456 if (ret < 0) { 457 rte_errno = EINVAL; 458 return rte_errno; 459 } 460 461 return 0; 462 } 463 464 static int mlx5dr_debug_dump_context_attr(FILE *f, struct mlx5dr_context *ctx) 465 { 466 int ret; 467 468 ret = fprintf(f, "%u,0x%" PRIx64 ",%d,%zu,%d,%s,%d,%d\n", 469 MLX5DR_DEBUG_RES_TYPE_CONTEXT_ATTR, 470 (uint64_t)(uintptr_t)ctx, 471 ctx->pd_num, 472 ctx->queues, 473 ctx->send_queue->num_entries, 474 mlx5dr_context_shared_gvmi_used(ctx) ? 475 mlx5_glue->get_device_name(ctx->ibv_ctx->device) : "None", 476 ctx->caps->vhca_id, 477 mlx5dr_context_shared_gvmi_used(ctx) ? 478 ctx->caps->shared_vhca_id : 0xffff); 479 if (ret < 0) { 480 rte_errno = EINVAL; 481 return rte_errno; 482 } 483 484 return 0; 485 } 486 487 static int mlx5dr_debug_dump_context_info(FILE *f, struct mlx5dr_context *ctx) 488 { 489 int ret; 490 491 ret = fprintf(f, "%d,0x%" PRIx64 ",%d,%s,%s\n", 492 MLX5DR_DEBUG_RES_TYPE_CONTEXT, 493 (uint64_t)(uintptr_t)ctx, 494 ctx->flags & MLX5DR_CONTEXT_FLAG_HWS_SUPPORT, 495 mlx5_glue->get_device_name(mlx5dr_context_get_local_ibv(ctx)->device), 496 DEBUG_VERSION); 497 if (ret < 0) { 498 rte_errno = EINVAL; 499 return rte_errno; 500 } 501 502 ret = mlx5dr_debug_dump_context_attr(f, ctx); 503 if (ret) 504 return ret; 505 506 ret = mlx5dr_debug_dump_context_caps(f, ctx); 507 if (ret) 508 return ret; 509 510 return 0; 511 } 512 513 static int 514 mlx5dr_debug_dump_context_stc_resource(FILE *f, 515 struct mlx5dr_context *ctx, 516 uint32_t tbl_type, 517 struct mlx5dr_pool_resource *resource) 518 { 519 int ret; 520 521 ret = fprintf(f, "%d,0x%" PRIx64 ",%u,%u\n", 522 MLX5DR_DEBUG_RES_TYPE_CONTEXT_STC, 523 (uint64_t)(uintptr_t)ctx, 524 tbl_type, 525 resource->base_id); 526 if (ret < 0) { 527 rte_errno = EINVAL; 528 return rte_errno; 529 } 530 531 return 0; 532 } 533 534 static int mlx5dr_debug_dump_context_stc(FILE *f, struct mlx5dr_context *ctx) 535 { 536 struct mlx5dr_pool *stc_pool; 537 int ret; 538 int i; 539 540 for (i = 0; i < MLX5DR_TABLE_TYPE_MAX; i++) { 541 stc_pool = ctx->stc_pool[i]; 542 543 if (!stc_pool) 544 continue; 545 546 if (stc_pool->resource[0] != NULL) { 547 ret = mlx5dr_debug_dump_context_stc_resource(f, ctx, i, 548 stc_pool->resource[0]); 549 if (ret) 550 return ret; 551 } 552 553 if (i == MLX5DR_TABLE_TYPE_FDB && stc_pool->mirror_resource[0] != NULL) { 554 ret = mlx5dr_debug_dump_context_stc_resource(f, ctx, i, 555 stc_pool->mirror_resource[0]); 556 if (ret) 557 return ret; 558 } 559 } 560 561 return 0; 562 } 563 564 static int mlx5dr_debug_dump_context(FILE *f, struct mlx5dr_context *ctx) 565 { 566 struct mlx5dr_table *tbl; 567 int ret; 568 569 ret = mlx5dr_debug_dump_context_info(f, ctx); 570 if (ret) 571 return ret; 572 573 ret = mlx5dr_debug_dump_context_send_engine(f, ctx); 574 if (ret) 575 return ret; 576 577 ret = mlx5dr_debug_dump_context_stc(f, ctx); 578 if (ret) 579 return ret; 580 581 LIST_FOREACH(tbl, &ctx->head, next) { 582 ret = mlx5dr_debug_dump_table(f, tbl); 583 if (ret) 584 return ret; 585 } 586 587 return 0; 588 } 589 590 int mlx5dr_debug_dump(struct mlx5dr_context *ctx, FILE *f) 591 { 592 int ret; 593 594 if (!f || !ctx) { 595 rte_errno = EINVAL; 596 return -rte_errno; 597 } 598 599 pthread_spin_lock(&ctx->ctrl_lock); 600 ret = mlx5dr_debug_dump_context(f, ctx); 601 pthread_spin_unlock(&ctx->ctrl_lock); 602 603 return -ret; 604 } 605