shpchp_ctrl.c revision 0abe68ce24973a23fcc6cbce80343f68656de7b6
1/* 2 * Standard Hot Plug Controller Driver 3 * 4 * Copyright (C) 1995,2001 Compaq Computer Corporation 5 * Copyright (C) 2001 Greg Kroah-Hartman (greg@kroah.com) 6 * Copyright (C) 2001 IBM Corp. 7 * Copyright (C) 2003-2004 Intel Corporation 8 * 9 * All rights reserved. 10 * 11 * This program is free software; you can redistribute it and/or modify 12 * it under the terms of the GNU General Public License as published by 13 * the Free Software Foundation; either version 2 of the License, or (at 14 * your option) any later version. 15 * 16 * This program is distributed in the hope that it will be useful, but 17 * WITHOUT ANY WARRANTY; without even the implied warranty of 18 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or 19 * NON INFRINGEMENT. See the GNU General Public License for more 20 * details. 21 * 22 * You should have received a copy of the GNU General Public License 23 * along with this program; if not, write to the Free Software 24 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 25 * 26 * Send feedback to <greg@kroah.com>, <kristen.c.accardi@intel.com> 27 * 28 */ 29 30#include <linux/module.h> 31#include <linux/kernel.h> 32#include <linux/types.h> 33#include <linux/smp_lock.h> 34#include <linux/pci.h> 35#include <linux/workqueue.h> 36#include "../pci.h" 37#include "shpchp.h" 38 39static void interrupt_event_handler(struct work_struct *work); 40static int shpchp_enable_slot(struct slot *p_slot); 41static int shpchp_disable_slot(struct slot *p_slot); 42 43static int queue_interrupt_event(struct slot *p_slot, u32 event_type) 44{ 45 struct event_info *info; 46 47 info = kmalloc(sizeof(*info), GFP_ATOMIC); 48 if (!info) 49 return -ENOMEM; 50 51 info->event_type = event_type; 52 info->p_slot = p_slot; 53 INIT_WORK(&info->work, interrupt_event_handler); 54 55 schedule_work(&info->work); 56 57 return 0; 58} 59 60u8 shpchp_handle_attention_button(u8 hp_slot, struct controller *ctrl) 61{ 62 struct slot *p_slot; 63 u32 event_type; 64 65 /* Attention Button Change */ 66 dbg("shpchp: Attention button interrupt received.\n"); 67 68 p_slot = shpchp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); 69 p_slot->hpc_ops->get_adapter_status(p_slot, &(p_slot->presence_save)); 70 71 /* 72 * Button pressed - See if need to TAKE ACTION!!! 73 */ 74 info("Button pressed on Slot(%s)\n", p_slot->name); 75 event_type = INT_BUTTON_PRESS; 76 77 queue_interrupt_event(p_slot, event_type); 78 79 return 0; 80 81} 82 83u8 shpchp_handle_switch_change(u8 hp_slot, struct controller *ctrl) 84{ 85 struct slot *p_slot; 86 u8 getstatus; 87 u32 event_type; 88 89 /* Switch Change */ 90 dbg("shpchp: Switch interrupt received.\n"); 91 92 p_slot = shpchp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); 93 p_slot->hpc_ops->get_adapter_status(p_slot, &(p_slot->presence_save)); 94 p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); 95 dbg("%s: Card present %x Power status %x\n", __FUNCTION__, 96 p_slot->presence_save, p_slot->pwr_save); 97 98 if (getstatus) { 99 /* 100 * Switch opened 101 */ 102 info("Latch open on Slot(%s)\n", p_slot->name); 103 event_type = INT_SWITCH_OPEN; 104 if (p_slot->pwr_save && p_slot->presence_save) { 105 event_type = INT_POWER_FAULT; 106 err("Surprise Removal of card\n"); 107 } 108 } else { 109 /* 110 * Switch closed 111 */ 112 info("Latch close on Slot(%s)\n", p_slot->name); 113 event_type = INT_SWITCH_CLOSE; 114 } 115 116 queue_interrupt_event(p_slot, event_type); 117 118 return 1; 119} 120 121u8 shpchp_handle_presence_change(u8 hp_slot, struct controller *ctrl) 122{ 123 struct slot *p_slot; 124 u32 event_type; 125 126 /* Presence Change */ 127 dbg("shpchp: Presence/Notify input change.\n"); 128 129 p_slot = shpchp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); 130 131 /* 132 * Save the presence state 133 */ 134 p_slot->hpc_ops->get_adapter_status(p_slot, &(p_slot->presence_save)); 135 if (p_slot->presence_save) { 136 /* 137 * Card Present 138 */ 139 info("Card present on Slot(%s)\n", p_slot->name); 140 event_type = INT_PRESENCE_ON; 141 } else { 142 /* 143 * Not Present 144 */ 145 info("Card not present on Slot(%s)\n", p_slot->name); 146 event_type = INT_PRESENCE_OFF; 147 } 148 149 queue_interrupt_event(p_slot, event_type); 150 151 return 1; 152} 153 154u8 shpchp_handle_power_fault(u8 hp_slot, struct controller *ctrl) 155{ 156 struct slot *p_slot; 157 u32 event_type; 158 159 /* Power fault */ 160 dbg("shpchp: Power fault interrupt received.\n"); 161 162 p_slot = shpchp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); 163 164 if ( !(p_slot->hpc_ops->query_power_fault(p_slot))) { 165 /* 166 * Power fault Cleared 167 */ 168 info("Power fault cleared on Slot(%s)\n", p_slot->name); 169 p_slot->status = 0x00; 170 event_type = INT_POWER_FAULT_CLEAR; 171 } else { 172 /* 173 * Power fault 174 */ 175 info("Power fault on Slot(%s)\n", p_slot->name); 176 event_type = INT_POWER_FAULT; 177 /* set power fault status for this board */ 178 p_slot->status = 0xFF; 179 info("power fault bit %x set\n", hp_slot); 180 } 181 182 queue_interrupt_event(p_slot, event_type); 183 184 return 1; 185} 186 187/* The following routines constitute the bulk of the 188 hotplug controller logic 189 */ 190static int change_bus_speed(struct controller *ctrl, struct slot *p_slot, 191 enum pci_bus_speed speed) 192{ 193 int rc = 0; 194 195 dbg("%s: change to speed %d\n", __FUNCTION__, speed); 196 if ((rc = p_slot->hpc_ops->set_bus_speed_mode(p_slot, speed))) { 197 err("%s: Issue of set bus speed mode command failed\n", 198 __FUNCTION__); 199 return WRONG_BUS_FREQUENCY; 200 } 201 return rc; 202} 203 204static int fix_bus_speed(struct controller *ctrl, struct slot *pslot, 205 u8 flag, enum pci_bus_speed asp, enum pci_bus_speed bsp, 206 enum pci_bus_speed msp) 207{ 208 int rc = 0; 209 210 /* 211 * If other slots on the same bus are occupied, we cannot 212 * change the bus speed. 213 */ 214 if (flag) { 215 if (asp < bsp) { 216 err("%s: speed of bus %x and adapter %x mismatch\n", 217 __FUNCTION__, bsp, asp); 218 rc = WRONG_BUS_FREQUENCY; 219 } 220 return rc; 221 } 222 223 if (asp < msp) { 224 if (bsp != asp) 225 rc = change_bus_speed(ctrl, pslot, asp); 226 } else { 227 if (bsp != msp) 228 rc = change_bus_speed(ctrl, pslot, msp); 229 } 230 return rc; 231} 232 233/** 234 * board_added - Called after a board has been added to the system. 235 * 236 * Turns power on for the board 237 * Configures board 238 * 239 */ 240static int board_added(struct slot *p_slot) 241{ 242 u8 hp_slot; 243 u8 slots_not_empty = 0; 244 int rc = 0; 245 enum pci_bus_speed asp, bsp, msp; 246 struct controller *ctrl = p_slot->ctrl; 247 248 hp_slot = p_slot->device - ctrl->slot_device_offset; 249 250 dbg("%s: p_slot->device, slot_offset, hp_slot = %d, %d ,%d\n", 251 __FUNCTION__, p_slot->device, 252 ctrl->slot_device_offset, hp_slot); 253 254 /* Power on slot without connecting to bus */ 255 rc = p_slot->hpc_ops->power_on_slot(p_slot); 256 if (rc) { 257 err("%s: Failed to power on slot\n", __FUNCTION__); 258 return -1; 259 } 260 261 if ((ctrl->pci_dev->vendor == 0x8086) && (ctrl->pci_dev->device == 0x0332)) { 262 if (slots_not_empty) 263 return WRONG_BUS_FREQUENCY; 264 265 if ((rc = p_slot->hpc_ops->set_bus_speed_mode(p_slot, PCI_SPEED_33MHz))) { 266 err("%s: Issue of set bus speed mode command failed\n", __FUNCTION__); 267 return WRONG_BUS_FREQUENCY; 268 } 269 270 /* turn on board, blink green LED, turn off Amber LED */ 271 if ((rc = p_slot->hpc_ops->slot_enable(p_slot))) { 272 err("%s: Issue of Slot Enable command failed\n", __FUNCTION__); 273 return rc; 274 } 275 } 276 277 rc = p_slot->hpc_ops->get_adapter_speed(p_slot, &asp); 278 if (rc) { 279 err("%s: Can't get adapter speed or bus mode mismatch\n", 280 __FUNCTION__); 281 return WRONG_BUS_FREQUENCY; 282 } 283 284 rc = p_slot->hpc_ops->get_cur_bus_speed(p_slot, &bsp); 285 if (rc) { 286 err("%s: Can't get bus operation speed\n", __FUNCTION__); 287 return WRONG_BUS_FREQUENCY; 288 } 289 290 rc = p_slot->hpc_ops->get_max_bus_speed(p_slot, &msp); 291 if (rc) { 292 err("%s: Can't get max bus operation speed\n", __FUNCTION__); 293 msp = bsp; 294 } 295 296 /* Check if there are other slots or devices on the same bus */ 297 if (!list_empty(&ctrl->pci_dev->subordinate->devices)) 298 slots_not_empty = 1; 299 300 dbg("%s: slots_not_empty %d, adapter_speed %d, bus_speed %d, " 301 "max_bus_speed %d\n", __FUNCTION__, slots_not_empty, asp, 302 bsp, msp); 303 304 rc = fix_bus_speed(ctrl, p_slot, slots_not_empty, asp, bsp, msp); 305 if (rc) 306 return rc; 307 308 /* turn on board, blink green LED, turn off Amber LED */ 309 if ((rc = p_slot->hpc_ops->slot_enable(p_slot))) { 310 err("%s: Issue of Slot Enable command failed\n", __FUNCTION__); 311 return rc; 312 } 313 314 /* Wait for ~1 second */ 315 msleep(1000); 316 317 dbg("%s: slot status = %x\n", __FUNCTION__, p_slot->status); 318 /* Check for a power fault */ 319 if (p_slot->status == 0xFF) { 320 /* power fault occurred, but it was benign */ 321 dbg("%s: power fault\n", __FUNCTION__); 322 rc = POWER_FAILURE; 323 p_slot->status = 0; 324 goto err_exit; 325 } 326 327 if (shpchp_configure_device(p_slot)) { 328 err("Cannot add device at 0x%x:0x%x\n", p_slot->bus, 329 p_slot->device); 330 goto err_exit; 331 } 332 333 p_slot->status = 0; 334 p_slot->is_a_board = 0x01; 335 p_slot->pwr_save = 1; 336 337 p_slot->hpc_ops->green_led_on(p_slot); 338 339 return 0; 340 341err_exit: 342 /* turn off slot, turn on Amber LED, turn off Green LED */ 343 rc = p_slot->hpc_ops->slot_disable(p_slot); 344 if (rc) { 345 err("%s: Issue of Slot Disable command failed\n", __FUNCTION__); 346 return rc; 347 } 348 349 return(rc); 350} 351 352 353/** 354 * remove_board - Turns off slot and LED's 355 * 356 */ 357static int remove_board(struct slot *p_slot) 358{ 359 struct controller *ctrl = p_slot->ctrl; 360 u8 hp_slot; 361 int rc; 362 363 if (shpchp_unconfigure_device(p_slot)) 364 return(1); 365 366 hp_slot = p_slot->device - ctrl->slot_device_offset; 367 p_slot = shpchp_find_slot(ctrl, hp_slot + ctrl->slot_device_offset); 368 369 dbg("In %s, hp_slot = %d\n", __FUNCTION__, hp_slot); 370 371 /* Change status to shutdown */ 372 if (p_slot->is_a_board) 373 p_slot->status = 0x01; 374 375 /* turn off slot, turn on Amber LED, turn off Green LED */ 376 rc = p_slot->hpc_ops->slot_disable(p_slot); 377 if (rc) { 378 err("%s: Issue of Slot Disable command failed\n", __FUNCTION__); 379 return rc; 380 } 381 382 rc = p_slot->hpc_ops->set_attention_status(p_slot, 0); 383 if (rc) { 384 err("%s: Issue of Set Attention command failed\n", __FUNCTION__); 385 return rc; 386 } 387 388 p_slot->pwr_save = 0; 389 p_slot->is_a_board = 0; 390 391 return 0; 392} 393 394 395struct pushbutton_work_info { 396 struct slot *p_slot; 397 struct work_struct work; 398}; 399 400/** 401 * shpchp_pushbutton_thread 402 * 403 * Scheduled procedure to handle blocking stuff for the pushbuttons 404 * Handles all pending events and exits. 405 * 406 */ 407static void shpchp_pushbutton_thread(struct work_struct *work) 408{ 409 struct pushbutton_work_info *info = 410 container_of(work, struct pushbutton_work_info, work); 411 struct slot *p_slot = info->p_slot; 412 413 mutex_lock(&p_slot->lock); 414 switch (p_slot->state) { 415 case POWEROFF_STATE: 416 mutex_unlock(&p_slot->lock); 417 shpchp_disable_slot(p_slot); 418 mutex_lock(&p_slot->lock); 419 p_slot->state = STATIC_STATE; 420 break; 421 case POWERON_STATE: 422 mutex_unlock(&p_slot->lock); 423 if (shpchp_enable_slot(p_slot)) 424 p_slot->hpc_ops->green_led_off(p_slot); 425 mutex_lock(&p_slot->lock); 426 p_slot->state = STATIC_STATE; 427 break; 428 default: 429 break; 430 } 431 mutex_unlock(&p_slot->lock); 432 433 kfree(info); 434} 435 436void queue_pushbutton_work(struct work_struct *work) 437{ 438 struct slot *p_slot = container_of(work, struct slot, work.work); 439 struct pushbutton_work_info *info; 440 441 info = kmalloc(sizeof(*info), GFP_KERNEL); 442 if (!info) { 443 err("%s: Cannot allocate memory\n", __FUNCTION__); 444 return; 445 } 446 info->p_slot = p_slot; 447 INIT_WORK(&info->work, shpchp_pushbutton_thread); 448 449 mutex_lock(&p_slot->lock); 450 switch (p_slot->state) { 451 case BLINKINGOFF_STATE: 452 p_slot->state = POWEROFF_STATE; 453 break; 454 case BLINKINGON_STATE: 455 p_slot->state = POWERON_STATE; 456 break; 457 default: 458 goto out; 459 } 460 queue_work(shpchp_wq, &info->work); 461 out: 462 mutex_unlock(&p_slot->lock); 463} 464 465static int update_slot_info (struct slot *slot) 466{ 467 struct hotplug_slot_info *info; 468 int result; 469 470 info = kmalloc(sizeof(*info), GFP_KERNEL); 471 if (!info) 472 return -ENOMEM; 473 474 slot->hpc_ops->get_power_status(slot, &(info->power_status)); 475 slot->hpc_ops->get_attention_status(slot, &(info->attention_status)); 476 slot->hpc_ops->get_latch_status(slot, &(info->latch_status)); 477 slot->hpc_ops->get_adapter_status(slot, &(info->adapter_status)); 478 479 result = pci_hp_change_slot_info(slot->hotplug_slot, info); 480 kfree (info); 481 return result; 482} 483 484/* 485 * Note: This function must be called with slot->lock held 486 */ 487static void handle_button_press_event(struct slot *p_slot) 488{ 489 u8 getstatus; 490 491 switch (p_slot->state) { 492 case STATIC_STATE: 493 p_slot->hpc_ops->get_power_status(p_slot, &getstatus); 494 if (getstatus) { 495 p_slot->state = BLINKINGOFF_STATE; 496 info(msg_button_off, p_slot->name); 497 } else { 498 p_slot->state = BLINKINGON_STATE; 499 info(msg_button_on, p_slot->name); 500 } 501 /* blink green LED and turn off amber */ 502 p_slot->hpc_ops->green_led_blink(p_slot); 503 p_slot->hpc_ops->set_attention_status(p_slot, 0); 504 505 schedule_delayed_work(&p_slot->work, 5*HZ); 506 break; 507 case BLINKINGOFF_STATE: 508 case BLINKINGON_STATE: 509 /* 510 * Cancel if we are still blinking; this means that we 511 * press the attention again before the 5 sec. limit 512 * expires to cancel hot-add or hot-remove 513 */ 514 info("Button cancel on Slot(%s)\n", p_slot->name); 515 dbg("%s: button cancel\n", __FUNCTION__); 516 cancel_delayed_work(&p_slot->work); 517 if (p_slot->state == BLINKINGOFF_STATE) 518 p_slot->hpc_ops->green_led_on(p_slot); 519 else 520 p_slot->hpc_ops->green_led_off(p_slot); 521 p_slot->hpc_ops->set_attention_status(p_slot, 0); 522 info(msg_button_cancel, p_slot->name); 523 p_slot->state = STATIC_STATE; 524 break; 525 case POWEROFF_STATE: 526 case POWERON_STATE: 527 /* 528 * Ignore if the slot is on power-on or power-off state; 529 * this means that the previous attention button action 530 * to hot-add or hot-remove is undergoing 531 */ 532 info("Button ignore on Slot(%s)\n", p_slot->name); 533 update_slot_info(p_slot); 534 break; 535 default: 536 warn("Not a valid state\n"); 537 break; 538 } 539} 540 541static void interrupt_event_handler(struct work_struct *work) 542{ 543 struct event_info *info = container_of(work, struct event_info, work); 544 struct slot *p_slot = info->p_slot; 545 546 mutex_lock(&p_slot->lock); 547 switch (info->event_type) { 548 case INT_BUTTON_PRESS: 549 handle_button_press_event(p_slot); 550 break; 551 case INT_POWER_FAULT: 552 dbg("%s: power fault\n", __FUNCTION__); 553 p_slot->hpc_ops->set_attention_status(p_slot, 1); 554 p_slot->hpc_ops->green_led_off(p_slot); 555 break; 556 default: 557 update_slot_info(p_slot); 558 break; 559 } 560 mutex_unlock(&p_slot->lock); 561 562 kfree(info); 563} 564 565 566static int shpchp_enable_slot (struct slot *p_slot) 567{ 568 u8 getstatus = 0; 569 int rc, retval = -ENODEV; 570 571 /* Check to see if (latch closed, card present, power off) */ 572 mutex_lock(&p_slot->ctrl->crit_sect); 573 rc = p_slot->hpc_ops->get_adapter_status(p_slot, &getstatus); 574 if (rc || !getstatus) { 575 info("No adapter on slot(%s)\n", p_slot->name); 576 goto out; 577 } 578 rc = p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); 579 if (rc || getstatus) { 580 info("Latch open on slot(%s)\n", p_slot->name); 581 goto out; 582 } 583 rc = p_slot->hpc_ops->get_power_status(p_slot, &getstatus); 584 if (rc || getstatus) { 585 info("Already enabled on slot(%s)\n", p_slot->name); 586 goto out; 587 } 588 589 p_slot->is_a_board = 1; 590 591 /* We have to save the presence info for these slots */ 592 p_slot->hpc_ops->get_adapter_status(p_slot, &(p_slot->presence_save)); 593 p_slot->hpc_ops->get_power_status(p_slot, &(p_slot->pwr_save)); 594 dbg("%s: p_slot->pwr_save %x\n", __FUNCTION__, p_slot->pwr_save); 595 p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); 596 597 if(((p_slot->ctrl->pci_dev->vendor == PCI_VENDOR_ID_AMD) || 598 (p_slot->ctrl->pci_dev->device == PCI_DEVICE_ID_AMD_POGO_7458)) 599 && p_slot->ctrl->num_slots == 1) { 600 /* handle amd pogo errata; this must be done before enable */ 601 amd_pogo_errata_save_misc_reg(p_slot); 602 retval = board_added(p_slot); 603 /* handle amd pogo errata; this must be done after enable */ 604 amd_pogo_errata_restore_misc_reg(p_slot); 605 } else 606 retval = board_added(p_slot); 607 608 if (retval) { 609 p_slot->hpc_ops->get_adapter_status(p_slot, 610 &(p_slot->presence_save)); 611 p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); 612 } 613 614 update_slot_info(p_slot); 615 out: 616 mutex_unlock(&p_slot->ctrl->crit_sect); 617 return retval; 618} 619 620 621static int shpchp_disable_slot (struct slot *p_slot) 622{ 623 u8 getstatus = 0; 624 int rc, retval = -ENODEV; 625 626 if (!p_slot->ctrl) 627 return -ENODEV; 628 629 /* Check to see if (latch closed, card present, power on) */ 630 mutex_lock(&p_slot->ctrl->crit_sect); 631 632 rc = p_slot->hpc_ops->get_adapter_status(p_slot, &getstatus); 633 if (rc || !getstatus) { 634 info("No adapter on slot(%s)\n", p_slot->name); 635 goto out; 636 } 637 rc = p_slot->hpc_ops->get_latch_status(p_slot, &getstatus); 638 if (rc || getstatus) { 639 info("Latch open on slot(%s)\n", p_slot->name); 640 goto out; 641 } 642 rc = p_slot->hpc_ops->get_power_status(p_slot, &getstatus); 643 if (rc || !getstatus) { 644 info("Already disabled slot(%s)\n", p_slot->name); 645 goto out; 646 } 647 648 retval = remove_board(p_slot); 649 update_slot_info(p_slot); 650 out: 651 mutex_unlock(&p_slot->ctrl->crit_sect); 652 return retval; 653} 654 655int shpchp_sysfs_enable_slot(struct slot *p_slot) 656{ 657 int retval = -ENODEV; 658 659 mutex_lock(&p_slot->lock); 660 switch (p_slot->state) { 661 case BLINKINGON_STATE: 662 cancel_delayed_work(&p_slot->work); 663 case STATIC_STATE: 664 p_slot->state = POWERON_STATE; 665 mutex_unlock(&p_slot->lock); 666 retval = shpchp_enable_slot(p_slot); 667 mutex_lock(&p_slot->lock); 668 p_slot->state = STATIC_STATE; 669 break; 670 case POWERON_STATE: 671 info("Slot %s is already in powering on state\n", 672 p_slot->name); 673 break; 674 case BLINKINGOFF_STATE: 675 case POWEROFF_STATE: 676 info("Already enabled on slot %s\n", p_slot->name); 677 break; 678 default: 679 err("Not a valid state on slot %s\n", p_slot->name); 680 break; 681 } 682 mutex_unlock(&p_slot->lock); 683 684 return retval; 685} 686 687int shpchp_sysfs_disable_slot(struct slot *p_slot) 688{ 689 int retval = -ENODEV; 690 691 mutex_lock(&p_slot->lock); 692 switch (p_slot->state) { 693 case BLINKINGOFF_STATE: 694 cancel_delayed_work(&p_slot->work); 695 case STATIC_STATE: 696 p_slot->state = POWEROFF_STATE; 697 mutex_unlock(&p_slot->lock); 698 retval = shpchp_disable_slot(p_slot); 699 mutex_lock(&p_slot->lock); 700 p_slot->state = STATIC_STATE; 701 break; 702 case POWEROFF_STATE: 703 info("Slot %s is already in powering off state\n", 704 p_slot->name); 705 break; 706 case BLINKINGON_STATE: 707 case POWERON_STATE: 708 info("Already disabled on slot %s\n", p_slot->name); 709 break; 710 default: 711 err("Not a valid state on slot %s\n", p_slot->name); 712 break; 713 } 714 mutex_unlock(&p_slot->lock); 715 716 return retval; 717} 718