1 /* 2 * Copyright (c) 2004 Kontron Canada, Inc. All Rights Reserved. 3 * 4 * Base on code from 5 * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * Redistribution of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 14 * Redistribution in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * Neither the name of Sun Microsystems, Inc. or the names of 19 * contributors may be used to endorse or promote products derived 20 * from this software without specific prior written permission. 21 * 22 * This software is provided "AS IS," without a warranty of any kind. 23 * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, 24 * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A 25 * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. 26 * SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE 27 * FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING 28 * OR DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL 29 * SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, 30 * OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR 31 * PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF 32 * LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, 33 * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 34 */ 35 36 /* 37 * Tue Mar 7 14:36:12 2006 38 * <stephane.filion@ca.kontron.com> 39 * 40 * This code implements an Kontron OEM proprietary commands. 41 */ 42 #include <string.h> 43 #include <ipmitool/helper.h> 44 #include <ipmitool/log.h> 45 #include <ipmitool/ipmi.h> 46 #include <ipmitool/ipmi_intf.h> 47 #include <ipmitool/ipmi_fru.h> 48 49 extern int verbose; 50 extern int read_fru_area(struct ipmi_intf *intf, struct fru_info *fru, 51 uint8_t id, uint32_t offset, uint32_t length, 52 uint8_t *frubuf); 53 extern int write_fru_area(struct ipmi_intf * intf, struct fru_info *fru, 54 uint8_t id, uint16_t soffset, 55 uint16_t doffset, uint16_t length, 56 uint8_t *pFrubuf); 57 extern char *get_fru_area_str(uint8_t *data, uint32_t *offset); 58 59 static void ipmi_kontron_help(void); 60 static int ipmi_kontron_set_serial_number(struct ipmi_intf *intf); 61 static int ipmi_kontron_set_mfg_date (struct ipmi_intf *intf); 62 static void ipmi_kontron_nextboot_help(void); 63 static int ipmi_kontron_nextboot_set(struct ipmi_intf *intf, int argc, 64 char **argv); 65 static int ipmi_kontronoem_send_set_large_buffer(struct ipmi_intf *intf, 66 unsigned char channel, unsigned char size); 67 68 static char *bootdev[] = {"BIOS", "FDD", "HDD", "CDROM", "network", 0}; 69 70 int 71 ipmi_kontronoem_main(struct ipmi_intf *intf, int argc, char **argv) 72 { 73 int rc = 0; 74 if (argc == 0) { 75 lprintf(LOG_ERR, "Not enough parameters given."); 76 ipmi_kontron_help(); 77 return (-1); 78 } 79 if (strncmp(argv[0], "help", 4) == 0) { 80 ipmi_kontron_help(); 81 rc = 0; 82 } else if (!strncmp(argv[0], "setsn", 5)) { 83 if (argc < 1) { 84 printf("fru setsn\n"); 85 return (-1); 86 } 87 if (ipmi_kontron_set_serial_number(intf) > 0) { 88 printf("FRU serial number setted successfully\n"); 89 } else { 90 printf("FRU serial number set failed\n"); 91 rc = (-1); 92 } 93 } else if (!strncmp(argv[0], "setmfgdate", 10)) { 94 if (argc < 1) { 95 printf("fru setmfgdate\n"); 96 return (-1); 97 } 98 if (ipmi_kontron_set_mfg_date(intf) > 0) { 99 printf("FRU manufacturing date setted successfully\n"); 100 } else { 101 printf("FRU manufacturing date set failed\n"); 102 rc = (-1); 103 } 104 } else if (!strncmp(argv[0], "nextboot", 8)) { 105 if (argc < 2) { 106 lprintf(LOG_ERR, "Not enough parameters given."); 107 ipmi_kontron_nextboot_help(); 108 rc = (-1); 109 } 110 rc = ipmi_kontron_nextboot_set(intf, (argc - 1), (argv + 1)); 111 if (rc == 0) { 112 printf("Nextboot set successfully\n"); 113 } else { 114 printf("Nextboot set failed\n"); 115 rc = (-1); 116 } 117 } else { 118 lprintf(LOG_ERR, "Invalid Kontron command: %s", argv[0]); 119 ipmi_kontron_help(); 120 rc = (-1); 121 } 122 return rc; 123 } 124 125 static void 126 ipmi_kontron_help(void) 127 { 128 printf("Kontron Commands: setsn setmfgdate nextboot\n"); 129 } 130 131 int 132 ipmi_kontronoem_set_large_buffer(struct ipmi_intf *intf, unsigned char size) 133 { 134 uint8_t error_occurs = 0; 135 uint32_t prev_target_addr = intf->target_addr ; 136 if (intf->target_addr > 0 && (intf->target_addr != intf->my_addr)) { 137 intf->target_addr = intf->my_addr; 138 printf("Set local big buffer\n"); 139 if (ipmi_kontronoem_send_set_large_buffer(intf, 0x0e, size) == 0) { 140 printf("Set local big buffer:success\n"); 141 } else { 142 error_occurs = 1; 143 } 144 if (error_occurs == 0) { 145 if (ipmi_kontronoem_send_set_large_buffer(intf, 0x00, size) == 0) { 146 printf("IPMB was set\n"); 147 } else { 148 /* Revert back the previous set large buffer */ 149 error_occurs = 1; 150 ipmi_kontronoem_send_set_large_buffer( intf, 0x0e, 0 ); 151 } 152 } 153 /* Restore target address */ 154 intf->target_addr = prev_target_addr; 155 } 156 if (error_occurs == 0) { 157 if(ipmi_kontronoem_send_set_large_buffer(intf, 0x0e, size) == 0) { 158 /* printf("Set remote big buffer\n"); */ 159 } else { 160 if (intf->target_addr > 0 && (intf->target_addr != intf->my_addr)) { 161 /* Error occurs revert back the previous set large buffer */ 162 intf->target_addr = intf->my_addr; 163 /* ipmi_kontronoem_send_set_large_buffer(intf, 0x00, 0); */ 164 ipmi_kontronoem_send_set_large_buffer(intf, 0x0e, 0); 165 intf->target_addr = prev_target_addr; 166 } 167 } 168 } 169 return error_occurs; 170 } 171 172 int 173 ipmi_kontronoem_send_set_large_buffer(struct ipmi_intf *intf, 174 unsigned char channel, unsigned char size) 175 { 176 struct ipmi_rs *rsp; 177 struct ipmi_rq req; 178 uint8_t msg_data[2]; 179 int i; 180 memset(msg_data, 0, sizeof(msg_data)); 181 /* channel =~ 0x0e => Currently running interface */ 182 msg_data[0] = channel; 183 msg_data[1] = size; 184 memset(&req, 0, sizeof(req)); 185 req.msg.netfn = 0x3E; 186 /* Set Channel Buffer Length - OEM */ 187 req.msg.cmd = 0x82; 188 req.msg.data = msg_data; 189 req.msg.data_len = 2; 190 req.msg.lun = 0x00; 191 rsp = intf->sendrecv(intf, &req); 192 if (rsp == NULL) { 193 printf("Cannot send large buffer command\n"); 194 return(-1); 195 } else if (rsp->ccode > 0) { 196 printf("Invalid length for the selected interface (%s) %d\n", 197 val2str(rsp->ccode, completion_code_vals), rsp->ccode); 198 return(-1); 199 } 200 return 0; 201 } 202 203 /* ipmi_fru_set_serial_number - Set the Serial Number in FRU 204 * 205 * @intf: ipmi interface 206 * @id: fru id 207 * 208 * returns -1 on error 209 * returns 1 if successful 210 */ 211 static int 212 ipmi_kontron_set_serial_number(struct ipmi_intf *intf) 213 { 214 struct fru_header header; 215 struct fru_info fru; 216 struct ipmi_rs *rsp; 217 struct ipmi_rq req; 218 char *sn; 219 char *fru_area; 220 uint8_t checksum; 221 uint8_t *fru_data; 222 uint8_t msg_data[4]; 223 uint8_t sn_size; 224 uint32_t board_sec_len; 225 uint32_t fru_data_offset; 226 uint32_t fru_data_offset_tmp; 227 uint32_t i; 228 uint32_t prod_sec_len; 229 230 sn = NULL; 231 fru_data = NULL; 232 233 memset(msg_data, 0, 4); 234 msg_data[0] = 0xb4; 235 msg_data[1] = 0x90; 236 msg_data[2] = 0x91; 237 msg_data[3] = 0x8b; 238 239 memset(&req, 0, sizeof(req)); 240 req.msg.netfn = 0x3E; 241 req.msg.cmd = 0x0C; 242 req.msg.data = msg_data; 243 req.msg.data_len = 4; 244 /* Set Lun, necessary for this oem command */ 245 req.msg.lun = 0x03; 246 rsp = intf->sendrecv(intf, &req); 247 if (rsp == NULL) { 248 printf(" Device not present (No Response)\n"); 249 return (-1); 250 } else if (rsp->ccode > 0) { 251 printf(" This option is not implemented for this board\n"); 252 return (-1); 253 } 254 sn_size = rsp->data_len; 255 sn = malloc(sn_size + 1); 256 if (sn == NULL) { 257 lprintf(LOG_ERR, "ipmitool: malloc failure"); 258 return (-1); 259 } 260 memset(sn, 0, sn_size + 1); 261 memcpy(sn, rsp->data, sn_size); 262 if (verbose >= 1) { 263 printf("Original serial number is : [%s]\n", sn); 264 } 265 memset(msg_data, 0, 4); 266 msg_data[0] = 0; 267 memset(&req, 0, sizeof(req)); 268 req.msg.netfn = IPMI_NETFN_STORAGE; 269 req.msg.cmd = GET_FRU_INFO; 270 req.msg.data = msg_data; 271 req.msg.data_len = 1; 272 rsp = intf->sendrecv(intf, &req); 273 if (rsp == NULL) { 274 printf(" Device not present (No Response)\n"); 275 free(sn); 276 sn = NULL; 277 return (-1); 278 } else if (rsp->ccode > 0) { 279 printf(" Device not present (%s)\n", 280 val2str(rsp->ccode, completion_code_vals)); 281 free(sn); 282 sn = NULL; 283 return (-1); 284 } 285 memset(&fru, 0, sizeof(fru)); 286 fru.size = (rsp->data[1] << 8) | rsp->data[0]; 287 fru.access = rsp->data[2] & 0x1; 288 if (fru.size < 1) { 289 printf(" Invalid FRU size %d", fru.size); 290 free(sn); 291 sn = NULL; 292 return (-1); 293 } 294 /* retrieve the FRU header */ 295 msg_data[0] = 0; 296 msg_data[1] = 0; 297 msg_data[2] = 0; 298 msg_data[3] = 8; 299 300 memset(&req, 0, sizeof(req)); 301 req.msg.netfn = IPMI_NETFN_STORAGE; 302 req.msg.cmd = GET_FRU_DATA; 303 req.msg.data = msg_data; 304 req.msg.data_len = 4; 305 rsp = intf->sendrecv(intf, &req); 306 if (rsp == NULL) { 307 printf(" Device not present (No Response)\n"); 308 free(sn); 309 sn = NULL; 310 return (-1); 311 } else if (rsp->ccode > 0) { 312 printf(" Device not present (%s)\n", 313 val2str(rsp->ccode, completion_code_vals)); 314 free(sn); 315 sn = NULL; 316 return (-1); 317 } 318 if (verbose > 1) { 319 printbuf(rsp->data, rsp->data_len, "FRU DATA"); 320 } 321 memcpy(&header, rsp->data + 1, 8); 322 if (header.version != 1) { 323 printf(" Unknown FRU header version 0x%02x", 324 header.version); 325 free(sn); 326 sn = NULL; 327 return(-1); 328 } 329 /* Set the Board Section */ 330 board_sec_len = (header.offset.product * 8) - (header.offset.board * 8); 331 fru_data = malloc(fru.size); 332 if (fru_data == NULL) { 333 lprintf(LOG_ERR, "ipmitool: malloc failure"); 334 free(sn); 335 sn = NULL; 336 return (-1); 337 } 338 memset(fru_data, 0, fru.size); 339 if (read_fru_area(intf, &fru, 0, (header.offset.board * 8), 340 board_sec_len, fru_data) < 0) { 341 free(sn); 342 sn = NULL; 343 free(fru_data); 344 fru_data = NULL; 345 return (-1); 346 } 347 /* Position at Board Manufacturer */ 348 fru_data_offset = (header.offset.board * 8) + 6; 349 fru_area = get_fru_area_str(fru_data, &fru_data_offset); 350 /* Position at Board Product Name */ 351 fru_area = get_fru_area_str(fru_data, &fru_data_offset); 352 fru_data_offset_tmp = fru_data_offset; 353 /* Position at Serial Number */ 354 fru_area = get_fru_area_str(fru_data, &fru_data_offset_tmp); 355 fru_data_offset++; 356 if (strlen(fru_area) != sn_size) { 357 printf("The length of the serial number in the FRU Board Area is wrong.\n"); 358 free(sn); 359 sn = NULL; 360 free(fru_data); 361 fru_data = NULL; 362 return(-1); 363 } 364 /* Copy the new serial number in the board section saved in memory*/ 365 memcpy(fru_data + fru_data_offset, sn, sn_size); 366 checksum = 0; 367 /* Calculate Header Checksum */ 368 for(i = (header.offset.board * 8); 369 i < (((header.offset.board * 8) + board_sec_len) - 2); 370 i++) { 371 checksum += fru_data[i]; 372 } 373 checksum = (~checksum) + 1; 374 fru_data[(header.offset.board * 8) + board_sec_len - 1] = checksum; 375 /* Write the new FRU Board section */ 376 if (write_fru_area(intf, &fru, 0, (header.offset.board * 8), 377 (header.offset.board * 8), 378 board_sec_len, fru_data) < 0) { 379 free(sn); 380 sn = NULL; 381 free(fru_data); 382 fru_data = NULL; 383 return(-1); 384 } 385 /* Set the Product Section */ 386 prod_sec_len = (header.offset.multi * 8) - (header.offset.product * 8); 387 if (read_fru_area(intf, &fru, 0, (header.offset.product * 8), 388 prod_sec_len, fru_data) < 0) { 389 free(sn); 390 sn = NULL; 391 free(fru_data); 392 fru_data = NULL; 393 return(-1); 394 } 395 /* Position at Product Manufacturer */ 396 fru_data_offset = (header.offset.product * 8) + 3; 397 fru_area = get_fru_area_str(fru_data, &fru_data_offset); 398 /* Position at Product Name */ 399 fru_area = get_fru_area_str(fru_data, &fru_data_offset); 400 /* Position at Product Part */ 401 fru_area = get_fru_area_str(fru_data, &fru_data_offset); 402 /* Position at Product Version */ 403 fru_area = get_fru_area_str(fru_data, &fru_data_offset); 404 fru_data_offset_tmp = fru_data_offset; 405 /* Position at Serial Number */ 406 fru_area = get_fru_area_str(fru_data, &fru_data_offset_tmp); 407 fru_data_offset ++; 408 if (strlen(fru_area) != sn_size) { 409 free(sn); 410 sn = NULL; 411 free(fru_data); 412 fru_data = NULL; 413 printf("The length of the serial number in the FRU Product Area is wrong.\n"); 414 return(-1); 415 } 416 /* Copy the new serial number in the product section saved in memory*/ 417 memcpy(fru_data + fru_data_offset, sn, sn_size); 418 checksum = 0; 419 /* Calculate Header Checksum */ 420 for (i = (header.offset.product * 8); 421 i < (((header.offset.product * 8) + prod_sec_len) - 2); 422 i ++) { 423 checksum += fru_data[i]; 424 } 425 checksum = (~checksum) + 1; 426 fru_data[(header.offset.product * 8)+prod_sec_len - 1] = checksum; 427 /* Write the new FRU Board section */ 428 if (write_fru_area(intf, &fru, 0, (header.offset.product * 8), 429 (header.offset.product * 8), 430 prod_sec_len, fru_data) < 0) { 431 free(sn); 432 sn = NULL; 433 free(fru_data); 434 fru_data = NULL; 435 return -1; 436 } 437 free(sn); 438 sn = NULL; 439 free(fru_data); 440 fru_data = NULL; 441 return(1); 442 } 443 444 /* ipmi_fru_set_mfg_date - Set the Manufacturing Date in FRU 445 * 446 * @intf: ipmi interface 447 * @id: fru id 448 * 449 * returns -1 on error 450 * returns 1 if successful 451 */ 452 static int 453 ipmi_kontron_set_mfg_date (struct ipmi_intf *intf) 454 { 455 struct fru_header header; 456 struct fru_info fru; 457 struct ipmi_rs *rsp; 458 struct ipmi_rq req; 459 uint8_t *fru_data; 460 uint8_t checksum; 461 uint8_t msg_data[4]; 462 uint8_t mfg_date[3]; 463 uint32_t board_sec_len; 464 uint32_t i; 465 466 memset(msg_data, 0, 4); 467 msg_data[0] = 0xb4; 468 msg_data[1] = 0x90; 469 msg_data[2] = 0x91; 470 msg_data[3] = 0x8b; 471 472 memset(&req, 0, sizeof(req)); 473 req.msg.netfn = 0x3E; 474 req.msg.cmd = 0x0E; 475 req.msg.data = msg_data; 476 req.msg.data_len = 4; 477 /* Set Lun temporary, necessary for this oem command */ 478 req.msg.lun = 0x03; 479 rsp = intf->sendrecv(intf, &req); 480 if (rsp == NULL) { 481 printf("Device not present (No Response)\n"); 482 return(-1); 483 } else if (rsp->ccode > 0) { 484 printf("This option is not implemented for this board\n"); 485 return(-1); 486 } 487 if (rsp->data_len != 3) { 488 printf("Invalid response for the Manufacturing date\n"); 489 return(-1); 490 } 491 memset(mfg_date, 0, 3); 492 memcpy(mfg_date, rsp->data, 3); 493 memset(msg_data, 0, 4); 494 msg_data[0] = 0; 495 496 memset(&req, 0, sizeof(req)); 497 req.msg.netfn = IPMI_NETFN_STORAGE; 498 req.msg.cmd = GET_FRU_INFO; 499 req.msg.data = msg_data; 500 req.msg.data_len = 1; 501 rsp = intf->sendrecv(intf, &req); 502 if (rsp == NULL) { 503 printf(" Device not present (No Response)\n"); 504 return(-1); 505 } else if (rsp->ccode > 0) { 506 printf(" Device not present (%s)\n", 507 val2str(rsp->ccode, completion_code_vals)); 508 return(-1); 509 } 510 511 memset(&fru, 0, sizeof(fru)); 512 fru.size = (rsp->data[1] << 8) | rsp->data[0]; 513 fru.access = rsp->data[2] & 0x1; 514 if (fru.size < 1) { 515 printf(" Invalid FRU size %d", fru.size); 516 return(-1); 517 } 518 /* retrieve the FRU header */ 519 msg_data[0] = 0; 520 msg_data[1] = 0; 521 msg_data[2] = 0; 522 msg_data[3] = 8; 523 524 memset(&req, 0, sizeof(req)); 525 req.msg.netfn = IPMI_NETFN_STORAGE; 526 req.msg.cmd = GET_FRU_DATA; 527 req.msg.data = msg_data; 528 req.msg.data_len = 4; 529 rsp = intf->sendrecv(intf, &req); 530 if (rsp == NULL) { 531 printf(" Device not present (No Response)\n"); 532 return (-1); 533 } else if (rsp->ccode > 0) { 534 printf(" Device not present (%s)\n", 535 val2str(rsp->ccode, completion_code_vals)); 536 return (-1); 537 } 538 if (verbose > 1) { 539 printbuf(rsp->data, rsp->data_len, "FRU DATA"); 540 } 541 memcpy(&header, rsp->data + 1, 8); 542 if (header.version != 1) { 543 printf(" Unknown FRU header version 0x%02x", 544 header.version); 545 return(-1); 546 } 547 board_sec_len = (header.offset.product * 8) - (header.offset.board * 8); 548 fru_data = malloc(fru.size); 549 if(fru_data == NULL) { 550 lprintf(LOG_ERR, "ipmitool: malloc failure"); 551 return(-1); 552 } 553 memset(fru_data, 0, fru.size); 554 if (read_fru_area(intf ,&fru ,0 ,(header.offset.board * 8), 555 board_sec_len ,fru_data) < 0) { 556 free(fru_data); 557 fru_data = NULL; 558 return(-1); 559 } 560 /* Copy the new manufacturing date in the board section saved in memory*/ 561 memcpy(fru_data + (header.offset.board * 8) + 3, mfg_date, 3); 562 checksum = 0; 563 /* Calculate Header Checksum */ 564 for (i = (header.offset.board * 8); 565 i < (((header.offset.board * 8) + board_sec_len) - 2); 566 i ++ ) { 567 checksum += fru_data[i]; 568 } 569 checksum = (~checksum) + 1; 570 fru_data[(header.offset.board * 8)+board_sec_len - 1] = checksum; 571 /* Write the new FRU Board section */ 572 if (write_fru_area(intf, &fru, 0, (header.offset.board * 8), 573 (header.offset.board * 8), 574 board_sec_len, fru_data) < 0) { 575 free(fru_data); 576 fru_data = NULL; 577 return (-1); 578 } 579 free(fru_data); 580 fru_data = NULL; 581 return (1); 582 } 583 584 static void 585 ipmi_kontron_nextboot_help(void) 586 { 587 int i; 588 printf("nextboot <device>\n" 589 "Supported devices:\n"); 590 for (i = 0; bootdev[i] != 0; i++) { 591 printf("- %s\n", bootdev[i]); 592 } 593 } 594 595 /* ipmi_kontron_next_boot_set - Select the next boot order on CP6012 596 * 597 * @intf: ipmi interface 598 * @id: fru id 599 * 600 * returns -1 on error 601 * returns 1 if successful 602 */ 603 static int 604 ipmi_kontron_nextboot_set(struct ipmi_intf *intf, int argc, char **argv) 605 { 606 struct ipmi_rs *rsp; 607 struct ipmi_rq req; 608 uint8_t msg_data[8]; 609 int i; 610 611 memset(msg_data, 0, sizeof(msg_data)); 612 msg_data[0] = 0xb4; 613 msg_data[1] = 0x90; 614 msg_data[2] = 0x91; 615 msg_data[3] = 0x8b; 616 msg_data[4] = 0x9d; 617 msg_data[5] = 0xFF; 618 msg_data[6] = 0xFF; /* any */ 619 for (i = 0; bootdev[i] != 0; i++) { 620 if (strcmp(argv[0], bootdev[i]) == 0) { 621 msg_data[5] = i; 622 break; 623 } 624 } 625 /* Invalid device selected? */ 626 if (msg_data[5] == 0xFF) { 627 printf("Unknown boot device: %s\n", argv[0]); 628 return (-1); 629 } 630 memset(&req, 0, sizeof(req)); 631 req.msg.netfn = 0x3E; 632 req.msg.cmd = 0x02; 633 req.msg.data = msg_data; 634 req.msg.data_len = 7; 635 /* Set Lun temporary, necessary for this oem command */ 636 req.msg.lun = 0x03; 637 rsp = intf->sendrecv(intf, &req); 638 if (rsp == NULL) { 639 printf("Device not present (No Response)\n"); 640 return(-1); 641 } else if (rsp->ccode > 0) { 642 printf("Device not present (%s)\n", 643 val2str(rsp->ccode, completion_code_vals)); 644 return (-1); 645 } 646 return 0; 647 } 648