1 /* 2 * Copyright 2009, Colin Günther, coling@gmx.de. 3 * All rights reserved. Distributed under the terms of the MIT License. 4 */ 5 6 7 /*- 8 * Copyright (c) 2003-2009 Sam Leffler, Errno Consulting 9 * All rights reserved. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions 13 * are met: 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in the 18 * documentation and/or other materials provided with the distribution. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 21 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 22 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 23 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 24 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 25 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 29 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 33 /* 34 * IEEE 802.11 support (Haiku-specific code) 35 */ 36 37 38 #include "ieee80211_haiku.h" 39 40 extern "C" { 41 # include <sys/kernel.h> 42 # include <sys/mbuf.h> 43 # include <sys/bus.h> 44 # include <sys/sockio.h> 45 46 # include <net/if.h> 47 # include <net/if_media.h> 48 # include <net/if_types.h> 49 # include <net/if_var.h> 50 51 # include "ieee80211_var.h" 52 }; 53 54 #include <SupportDefs.h> 55 56 #include <util/KMessage.h> 57 58 #include <ether_driver.h> 59 #include <bosii_driver.h> 60 #include <net_notifications.h> 61 62 #include <shared.h> 63 64 65 #define TRACE_WLAN 66 #ifdef TRACE_WLAN 67 # define TRACE(x...) dprintf(x); 68 #else 69 # define TRACE(x...) ; 70 #endif 71 72 73 #define MC_ALIGN(m, len) \ 74 do { \ 75 (m)->m_data += (MCLBYTES - (len)) &~ (sizeof(long) - 1);\ 76 } while (/* CONSTCOND */ 0) 77 78 79 static net_notifications_module_info* sNotificationModule; 80 81 82 static struct ifnet* 83 get_ifnet(device_t device, int& i) 84 { 85 int unit = device_get_unit(device); 86 87 for (i = 0; i < MAX_DEVICES; i++) { 88 if (gDevices[i] != NULL && gDevices[i]->if_dunit == unit) 89 return gDevices[i]; 90 } 91 92 return NULL; 93 } 94 95 96 status_t 97 init_wlan_stack(void) 98 { 99 ieee80211_phy_init(); 100 ieee80211_auth_setup(); 101 ieee80211_ht_init(); 102 103 get_module(NET_NOTIFICATIONS_MODULE_NAME, 104 (module_info**)&sNotificationModule); 105 106 return B_OK; 107 } 108 109 110 void 111 uninit_wlan_stack(void) 112 { 113 if (sNotificationModule != NULL) 114 put_module(NET_NOTIFICATIONS_MODULE_NAME); 115 } 116 117 118 status_t 119 start_wlan(device_t device) 120 { 121 int i; 122 struct ifnet* ifp = get_ifnet(device, i); 123 if (ifp == NULL) 124 return B_BAD_VALUE; 125 126 // TODO: review this and find a cleaner solution! 127 // This ensures that the cloned device gets 128 // the same index assigned as the base device 129 // Resulting in the same device name 130 // e.g.: /dev/net/atheros/0 instead of 131 // /dev/net/atheros/1 132 gDevices[i] = NULL; 133 134 struct ieee80211com* ic = (ieee80211com*)ifp->if_l2com; 135 136 struct ieee80211vap* vap = ic->ic_vap_create(ic, "wlan", 137 device_get_unit(device), 138 IEEE80211_M_STA, // mode 139 0, // flags 140 NULL, // BSSID 141 IF_LLADDR(ifp)); // MAC address 142 143 if (vap == NULL) { 144 gDevices[i] = ifp; 145 return B_ERROR; 146 } 147 148 // ic_vap_create() established that gDevices[i] links to vap->iv_ifp now 149 KASSERT(gDevices[i] == vap->iv_ifp, 150 ("start_wlan: gDevices[i] != vap->iv_ifp")); 151 152 vap->iv_ifp->scan_done_sem = create_sem(0, "wlan scan done"); 153 154 // We aren't connected to a WLAN, yet. 155 if_link_state_change(vap->iv_ifp, LINK_STATE_DOWN); 156 157 dprintf("%s: wlan started.\n", __func__); 158 159 return B_OK; 160 } 161 162 163 status_t 164 stop_wlan(device_t device) 165 { 166 int i; 167 struct ifnet* ifp = get_ifnet(device, i); 168 if (ifp == NULL) 169 return B_BAD_VALUE; 170 171 if (ifp->if_type == IFT_IEEE80211) { 172 // This happens when there was an error in starting the wlan before, 173 // resulting in never creating a clone device 174 return B_OK; 175 } 176 177 delete_sem(ifp->scan_done_sem); 178 179 struct ieee80211vap* vap = (ieee80211vap*)ifp->if_softc; 180 struct ieee80211com* ic = vap->iv_ic; 181 182 ic->ic_vap_delete(vap); 183 184 // ic_vap_delete freed gDevices[i] 185 KASSERT(gDevices[i] == NULL, ("stop_wlan: gDevices[i] != NULL")); 186 187 // assign the base device ifp again 188 gDevices[i] = ic->ic_ifp; 189 190 return B_OK; 191 } 192 193 194 status_t 195 wlan_control(void* cookie, uint32 op, void* arg, size_t length) 196 { 197 struct ifnet* ifp = (struct ifnet*)cookie; 198 199 switch (op) { 200 case BOSII_DEVICE: 201 return B_OK; 202 203 case BOSII_DETECT_NETWORKS: 204 { 205 struct ieee80211req request; 206 struct ieee80211_scan_req scanRequest; 207 208 if_printf(ifp, "%s: BOSII_DETECT_NETWORKS\n", __func__); 209 memset(&scanRequest, 0, sizeof(scanRequest)); 210 scanRequest.sr_flags = IEEE80211_IOC_SCAN_ACTIVE 211 | IEEE80211_IOC_SCAN_NOPICK 212 | IEEE80211_IOC_SCAN_ONCE; 213 scanRequest.sr_duration = 10000; // 10 s 214 scanRequest.sr_nssid = 0; 215 216 memset(&request, 0, sizeof(request)); 217 request.i_type = IEEE80211_IOC_SCAN_REQ; 218 request.i_data = &scanRequest; 219 request.i_len = sizeof(scanRequest); 220 221 ifp->if_ioctl(ifp, SIOCS80211, (caddr_t)&request); 222 223 acquire_sem_etc(ifp->scan_done_sem, 1, B_RELATIVE_TIMEOUT, 224 10000000); // 10 s 225 226 return B_OK; 227 } 228 229 case BOSII_GET_DETECTED_NETWORKS: 230 { 231 struct ieee80211req request; 232 struct ifreq ifRequest; 233 struct route_entry* networkRequest = &ifRequest.ifr_route; 234 235 if_printf(ifp, "%s: BOSII_GET_DETECTED_NETWORKS\n", __func__); 236 237 if (length < sizeof(struct ieee80211req_scan_result)) 238 return B_BAD_VALUE; 239 240 if (user_memcpy(&ifRequest, arg, sizeof(ifRequest)) < B_OK) 241 return B_BAD_ADDRESS; 242 243 memset(&request, 0, sizeof(request)); 244 request.i_type = IEEE80211_IOC_SCAN_RESULTS; 245 request.i_len = length; 246 request.i_data = networkRequest->destination; 247 248 // After return value of request.i_data is copied into user 249 // space, already. 250 if (ifp->if_ioctl(ifp, SIOCG80211, (caddr_t)&request) < B_OK) 251 return B_BAD_ADDRESS; 252 253 // Tell the user space how much data was copied 254 networkRequest->mtu = request.i_len; 255 if (user_memcpy(&((struct ifreq*)arg)->ifr_route.mtu, 256 &networkRequest->mtu, sizeof(networkRequest->mtu)) < B_OK) 257 return B_BAD_ADDRESS; 258 259 return B_OK; 260 } 261 262 case BOSII_JOIN_NETWORK: 263 { 264 struct ieee80211req request; 265 struct ifreq ifRequest; 266 struct route_entry* networkRequest = &ifRequest.ifr_route; 267 struct ieee80211req_scan_result network; 268 269 if_printf(ifp, "%s: BOSII_JOIN_NETWORK\n", __func__); 270 271 if (length < sizeof(struct ifreq)) 272 return B_BAD_VALUE; 273 274 if (user_memcpy(&ifRequest, arg, sizeof(ifRequest)) != B_OK 275 || user_memcpy(&network, networkRequest->source, 276 sizeof(ieee80211req_scan_result)) != B_OK) 277 return B_BAD_ADDRESS; 278 279 memset(&request, 0, sizeof(ieee80211req)); 280 281 request.i_type = IEEE80211_IOC_SSID; 282 request.i_val = 0; 283 request.i_len = network.isr_ssid_len; 284 request.i_data = (uint8*)networkRequest->source 285 + network.isr_ie_off; 286 if (ifp->if_ioctl(ifp, SIOCS80211, (caddr_t)&request) < B_OK) 287 return B_ERROR; 288 289 // wait for network join 290 291 return B_OK; 292 } 293 294 case BOSII_GET_ASSOCIATED_NETWORK: 295 { 296 struct ieee80211req request; 297 struct ifreq ifRequest; 298 struct route_entry* networkRequest = &ifRequest.ifr_route; 299 300 if_printf(ifp, "%s: BOSII_GET_ASSOCIATED_NETWORK\n", __func__); 301 302 if (length < sizeof(struct ieee80211req_sta_req)) 303 return B_BAD_VALUE; 304 305 if (user_memcpy(&ifRequest, arg, sizeof(ifRequest)) < B_OK) 306 return B_BAD_ADDRESS; 307 308 // Only want station information about associated network. 309 memset(&request, 0, sizeof(request)); 310 request.i_type = IEEE80211_IOC_BSSID; 311 request.i_len = IEEE80211_ADDR_LEN; 312 request.i_data = ((struct ieee80211req_sta_req*)networkRequest-> 313 destination)->is_u.macaddr; 314 if (ifp->if_ioctl(ifp, SIOCG80211, (caddr_t)&request) < B_OK) 315 return B_BAD_ADDRESS; 316 317 request.i_type = IEEE80211_IOC_STA_INFO; 318 request.i_len = length; 319 request.i_data = networkRequest->destination; 320 321 // After return value of request.i_data is copied into user 322 // space, already. 323 if (ifp->if_ioctl(ifp, SIOCG80211, (caddr_t)&request) < B_OK) 324 return B_BAD_ADDRESS; 325 326 // Tell the user space how much data was copied 327 networkRequest->mtu = request.i_len; 328 if (user_memcpy(&((struct ifreq*)arg)->ifr_route.mtu, 329 &networkRequest->mtu, sizeof(networkRequest->mtu)) != B_OK) 330 return B_BAD_ADDRESS; 331 332 return B_OK; 333 } 334 335 case SIOCG80211: 336 case SIOCS80211: 337 { 338 // Allowing FreeBSD based WLAN ioctls to pass, as those will become 339 // the future Haiku WLAN ioctls anyway. 340 341 // FreeBSD drivers assume that the request structure has already 342 // been copied into kernel space 343 struct ieee80211req request; 344 if (user_memcpy(&request, arg, sizeof(struct ieee80211req)) != B_OK) 345 return B_BAD_ADDRESS; 346 347 TRACE("wlan_control: %ld, %d\n", op, request.i_type); 348 status_t status = ifp->if_ioctl(ifp, op, (caddr_t)&request); 349 if (status != B_OK) 350 return status; 351 352 if (op == SIOCG80211 && user_memcpy(arg, &request, 353 sizeof(struct ieee80211req)) != B_OK) 354 return B_BAD_ADDRESS; 355 return B_OK; 356 } 357 358 case SIOCSIFFLAGS: 359 case SIOCSIFMEDIA: 360 case SIOCGIFMEDIA: 361 case SIOCSIFMTU: 362 // Requests that make it here always come from the kernel 363 return ifp->if_ioctl(ifp, op, (caddr_t)arg); 364 } 365 366 return B_BAD_VALUE; 367 } 368 369 370 status_t 371 wlan_close(void* cookie) 372 { 373 struct ifnet* ifp = (struct ifnet*)cookie; 374 375 ifp->if_flags &= ~IFF_UP; 376 ifp->if_ioctl(ifp, SIOCSIFFLAGS, NULL); 377 378 return release_sem_etc(ifp->scan_done_sem, 1, B_RELEASE_ALL); 379 } 380 381 382 status_t 383 wlan_if_l2com_alloc(void* data) 384 { 385 struct ifnet* ifp = (struct ifnet*)data; 386 387 ifp->if_l2com = _kernel_malloc(sizeof(struct ieee80211com), M_ZERO); 388 if (ifp->if_l2com == NULL) 389 return B_NO_MEMORY; 390 ((struct ieee80211com*)(ifp->if_l2com))->ic_ifp = ifp; 391 return B_OK; 392 } 393 394 395 void 396 get_random_bytes(void* p, size_t n) 397 { 398 uint8_t* dp = (uint8_t*)p; 399 400 while (n > 0) { 401 uint32_t v = arc4random(); 402 size_t nb = n > sizeof(uint32_t) ? sizeof(uint32_t) : n; 403 bcopy(&v, dp, n > sizeof(uint32_t) ? sizeof(uint32_t) : n); 404 dp += sizeof(uint32_t), n -= nb; 405 } 406 } 407 408 409 struct mbuf* 410 ieee80211_getmgtframe(uint8_t** frm, int headroom, int pktlen) 411 { 412 struct mbuf* m; 413 u_int len; 414 415 len = roundup2(headroom + pktlen, 4); 416 KASSERT(len <= MCLBYTES, ("802.11 mgt frame too large: %u", len)); 417 if (len < MINCLSIZE) { 418 m = m_gethdr(M_NOWAIT, MT_DATA); 419 if (m != NULL) 420 MH_ALIGN(m, len); 421 } else { 422 m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); 423 if (m != NULL) 424 MC_ALIGN(m, len); 425 } 426 if (m != NULL) { 427 m->m_data += headroom; 428 *frm = (uint8_t*)m->m_data; 429 } 430 return m; 431 } 432 433 434 /* 435 * Decrements the reference-counter and 436 * tests whether it became zero. 437 * 438 * @return 1 reference-counter became zero 439 * @return 0 reference-counter didn't became zero 440 */ 441 int 442 ieee80211_node_dectestref(struct ieee80211_node* ni) 443 { 444 // atomic_add returns old value 445 return atomic_add((vint32*)&ni->ni_refcnt, -1) == 1; 446 } 447 448 449 void 450 ieee80211_drain_ifq(struct ifqueue* ifq) 451 { 452 struct ieee80211_node* ni; 453 struct mbuf* m; 454 455 for (;;) { 456 IF_DEQUEUE(ifq, m); 457 if (m == NULL) 458 break; 459 460 ni = (struct ieee80211_node*)m->m_pkthdr.rcvif; 461 KASSERT(ni != NULL, ("frame w/o node")); 462 ieee80211_free_node(ni); 463 m->m_pkthdr.rcvif = NULL; 464 465 m_freem(m); 466 } 467 } 468 469 470 void 471 ieee80211_flush_ifq(struct ifqueue* ifq, struct ieee80211vap* vap) 472 { 473 struct ieee80211_node* ni; 474 struct mbuf* m; 475 struct mbuf** mprev; 476 477 IF_LOCK(ifq); 478 mprev = &ifq->ifq_head; 479 while ((m = *mprev) != NULL) { 480 ni = (struct ieee80211_node*)m->m_pkthdr.rcvif; 481 if (ni != NULL && ni->ni_vap == vap) { 482 *mprev = m->m_nextpkt; 483 // remove from list 484 ifq->ifq_len--; 485 486 m_freem(m); 487 ieee80211_free_node(ni); 488 // reclaim ref 489 } else 490 mprev = &m->m_nextpkt; 491 } 492 // recalculate tail ptr 493 m = ifq->ifq_head; 494 for (; m != NULL && m->m_nextpkt != NULL; m = m->m_nextpkt); 495 ifq->ifq_tail = m; 496 IF_UNLOCK(ifq); 497 } 498 499 500 int 501 ieee80211_add_callback(struct mbuf* m, 502 void (*func)(struct ieee80211_node*, void*, int), void* arg) 503 { 504 struct m_tag* mtag; 505 struct ieee80211_cb* cb; 506 507 mtag = m_tag_alloc(MTAG_ABI_NET80211, NET80211_TAG_CALLBACK, 508 sizeof(struct ieee80211_cb), M_NOWAIT); 509 if (mtag == NULL) 510 return 0; 511 512 cb = (struct ieee80211_cb*)(mtag+1); 513 cb->func = func; 514 cb->arg = arg; 515 m_tag_prepend(m, mtag); 516 m->m_flags |= M_TXCB; 517 return 1; 518 } 519 520 521 void 522 ieee80211_process_callback(struct ieee80211_node* ni, struct mbuf* m, 523 int status) 524 { 525 struct m_tag* mtag; 526 527 mtag = m_tag_locate(m, MTAG_ABI_NET80211, NET80211_TAG_CALLBACK, NULL); 528 if (mtag != NULL) { 529 struct ieee80211_cb* cb = (struct ieee80211_cb*)(mtag+1); 530 cb->func(ni, cb->arg, status); 531 } 532 } 533 534 535 void 536 ieee80211_sysctl_vattach(struct ieee80211vap* vap) 537 { 538 vap->iv_debug = IEEE80211_MSG_XRATE 539 | IEEE80211_MSG_NODE 540 | IEEE80211_MSG_ASSOC 541 | IEEE80211_MSG_AUTH 542 | IEEE80211_MSG_STATE 543 | IEEE80211_MSG_POWER 544 | IEEE80211_MSG_WME 545 | IEEE80211_MSG_DOTH 546 | IEEE80211_MSG_INACT 547 | IEEE80211_MSG_ROAM 548 | IEEE80211_MSG_RATECTL; 549 } 550 551 552 void 553 ieee80211_sysctl_vdetach(struct ieee80211vap* vap) 554 { 555 dprintf("%s not implemented, yet.\n", __func__); 556 } 557 558 559 void 560 ieee80211_vap_destroy(struct ieee80211vap* vap) 561 { 562 struct ieee80211com* ic = vap->iv_ic; 563 564 ic->ic_vap_delete(vap); 565 dprintf("%s: done.\n", __func__); 566 } 567 568 569 void 570 ieee80211_load_module(const char* modname) 571 { 572 dprintf("%s not implemented, yet: modname %s\n", __func__, modname); 573 } 574 575 576 void 577 ieee80211_notify_node_join(struct ieee80211_node* ni, int newassoc) 578 { 579 struct ieee80211vap* vap = ni->ni_vap; 580 struct ifnet* ifp = vap->iv_ifp; 581 582 if (ni == vap->iv_bss) 583 if_link_state_change(ifp, LINK_STATE_UP); 584 585 TRACE("%s\n", __FUNCTION__); 586 587 if (sNotificationModule != NULL) { 588 char messageBuffer[512]; 589 KMessage message; 590 message.SetTo(messageBuffer, sizeof(messageBuffer), B_NETWORK_MONITOR); 591 message.AddInt32("opcode", B_NETWORK_WLAN_JOINED); 592 message.AddString("interface", ifp->device_name); 593 // TODO: add data about the node 594 595 sNotificationModule->send_notification(&message); 596 } 597 } 598 599 600 void 601 ieee80211_notify_node_leave(struct ieee80211_node* ni) 602 { 603 struct ieee80211vap* vap = ni->ni_vap; 604 struct ifnet* ifp = vap->iv_ifp; 605 606 if (ni == vap->iv_bss) 607 if_link_state_change(ifp, LINK_STATE_DOWN); 608 609 TRACE("%s\n", __FUNCTION__); 610 611 if (sNotificationModule != NULL) { 612 char messageBuffer[512]; 613 KMessage message; 614 message.SetTo(messageBuffer, sizeof(messageBuffer), B_NETWORK_MONITOR); 615 message.AddInt32("opcode", B_NETWORK_WLAN_LEFT); 616 message.AddString("interface", ifp->device_name); 617 // TODO: add data about the node 618 619 sNotificationModule->send_notification(&message); 620 } 621 } 622 623 624 void 625 ieee80211_notify_scan_done(struct ieee80211vap* vap) 626 { 627 release_sem_etc(vap->iv_ifp->scan_done_sem, 1, 628 B_DO_NOT_RESCHEDULE | B_RELEASE_ALL); 629 630 TRACE("%s\n", __FUNCTION__); 631 632 if (sNotificationModule != NULL) { 633 char messageBuffer[512]; 634 KMessage message; 635 message.SetTo(messageBuffer, sizeof(messageBuffer), B_NETWORK_MONITOR); 636 message.AddInt32("opcode", B_NETWORK_WLAN_SCANNED); 637 message.AddString("interface", vap->iv_ifp->device_name); 638 639 sNotificationModule->send_notification(&message); 640 } 641 } 642 643 644 void 645 ieee80211_notify_replay_failure(struct ieee80211vap* vap, 646 const struct ieee80211_frame* wh, const struct ieee80211_key* k, 647 u_int64_t rsc, int tid) 648 { 649 dprintf("%s not implemented, yet.\n", __func__); 650 } 651 652 653 void 654 ieee80211_notify_michael_failure(struct ieee80211vap* vap, 655 const struct ieee80211_frame* wh, u_int keyix) 656 { 657 dprintf("%s not implemented, yet.\n", __func__); 658 } 659 660 661 void 662 ieee80211_notify_wds_discover(struct ieee80211_node* ni) 663 { 664 dprintf("%s not implemented, yet.\n", __func__); 665 } 666 667 668 void 669 ieee80211_notify_csa(struct ieee80211com* ic, 670 const struct ieee80211_channel* c, int mode, int count) 671 { 672 dprintf("%s not implemented, yet.\n", __func__); 673 } 674 675 676 void 677 ieee80211_notify_radar(struct ieee80211com* ic, 678 const struct ieee80211_channel* c) 679 { 680 dprintf("%s not implemented, yet.\n", __func__); 681 } 682 683 684 void 685 ieee80211_notify_cac(struct ieee80211com* ic, 686 const struct ieee80211_channel* c, enum ieee80211_notify_cac_event type) 687 { 688 dprintf("%s not implemented, yet.\n", __func__); 689 } 690 691 692 void 693 ieee80211_notify_node_deauth(struct ieee80211_node* ni) 694 { 695 dprintf("%s not implemented, yet.\n", __func__); 696 } 697 698 699 void 700 ieee80211_notify_node_auth(struct ieee80211_node* ni) 701 { 702 dprintf("%s not implemented, yet.\n", __func__); 703 } 704 705 706 void 707 ieee80211_notify_country(struct ieee80211vap* vap, 708 const uint8_t bssid[IEEE80211_ADDR_LEN], const uint8_t cc[2]) 709 { 710 dprintf("%s not implemented, yet.\n", __func__); 711 } 712 713 714 void 715 ieee80211_notify_radio(struct ieee80211com* ic, int state) 716 { 717 dprintf("%s not implemented, yet.\n", __func__); 718 } 719 720 721 void 722 ieee80211_sysctl_attach(struct ieee80211com* ic) 723 { 724 dprintf("%s not implemented, yet.\n", __func__); 725 } 726 727 728 void 729 ieee80211_sysctl_detach(struct ieee80211com* ic) 730 { 731 dprintf("%s not implemented, yet.\n", __func__); 732 } 733