1 /* 2 * Copyright 2006-2017, Haiku, Inc. All Rights Reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Axel Dörfler, axeld@pinc-software.de 7 */ 8 9 10 #include "device_interfaces.h" 11 #include "domains.h" 12 #include "interfaces.h" 13 #include "stack_private.h" 14 #include "utility.h" 15 16 #include <net_device.h> 17 18 #include <lock.h> 19 #include <util/AutoLock.h> 20 21 #include <KernelExport.h> 22 23 #include <net/if_dl.h> 24 #include <netinet/in.h> 25 #include <new> 26 #include <stdio.h> 27 #include <stdlib.h> 28 #include <string.h> 29 30 31 //#define TRACE_DEVICE_INTERFACES 32 #ifdef TRACE_DEVICE_INTERFACES 33 # define TRACE(x) dprintf x 34 #else 35 # define TRACE(x) ; 36 #endif 37 38 39 static mutex sLock; 40 static DeviceInterfaceList sInterfaces; 41 static uint32 sDeviceIndex; 42 43 44 /*! A service thread for each device interface. It just reads as many packets 45 as availabe, deframes them, and puts them into the receive queue of the 46 device interface. 47 */ 48 static status_t 49 device_reader_thread(void* _interface) 50 { 51 net_device_interface* interface = (net_device_interface*)_interface; 52 net_device* device = interface->device; 53 status_t status = B_OK; 54 55 while ((device->flags & IFF_UP) != 0) { 56 net_buffer* buffer; 57 status = device->module->receive_data(device, &buffer); 58 if (status == B_OK) { 59 // feed device monitors 60 if (atomic_get(&interface->monitor_count) > 0) 61 device_interface_monitor_receive(interface, buffer); 62 63 ASSERT(buffer->interface_address == NULL); 64 65 if (interface->deframe_func(interface->device, buffer) != B_OK) { 66 gNetBufferModule.free(buffer); 67 continue; 68 } 69 70 fifo_enqueue_buffer(&interface->receive_queue, buffer); 71 } else if (status == B_DEVICE_NOT_FOUND) { 72 device_removed(device); 73 } else { 74 // In case of error, give the other threads some 75 // time to run since this is a high priority time thread. 76 snooze(10000); 77 } 78 } 79 80 return status; 81 } 82 83 84 static status_t 85 device_consumer_thread(void* _interface) 86 { 87 net_device_interface* interface = (net_device_interface*)_interface; 88 net_device* device = interface->device; 89 net_buffer* buffer; 90 91 while (true) { 92 ssize_t status = fifo_dequeue_buffer(&interface->receive_queue, 0, 93 B_INFINITE_TIMEOUT, &buffer); 94 if (status != B_OK) { 95 if (status == B_INTERRUPTED) 96 continue; 97 break; 98 } 99 100 if (buffer->interface_address != NULL) { 101 // If the interface is already specified, this buffer was 102 // delivered locally. 103 if (buffer->interface_address->domain->module->receive_data(buffer) 104 == B_OK) 105 buffer = NULL; 106 } else { 107 sockaddr_dl& linkAddress = *(sockaddr_dl*)buffer->source; 108 int32 genericType = buffer->type; 109 int32 specificType = B_NET_FRAME_TYPE(linkAddress.sdl_type, 110 ntohs(linkAddress.sdl_e_type)); 111 112 buffer->index = interface->device->index; 113 114 // Find handler for this packet 115 116 RecursiveLocker locker(interface->receive_lock); 117 118 DeviceHandlerList::Iterator iterator 119 = interface->receive_funcs.GetIterator(); 120 while (buffer != NULL && iterator.HasNext()) { 121 net_device_handler* handler = iterator.Next(); 122 123 // If the handler returns B_OK, it consumed the buffer - first 124 // handler wins. 125 if ((handler->type == genericType 126 || handler->type == specificType) 127 && handler->func(handler->cookie, device, buffer) == B_OK) 128 buffer = NULL; 129 } 130 } 131 132 if (buffer != NULL) 133 gNetBufferModule.free(buffer); 134 } 135 136 return B_OK; 137 } 138 139 140 /*! The domain's device receive handler - this will inject the net_buffers into 141 the protocol layer (the domain's registered receive handler). 142 */ 143 static status_t 144 domain_receive_adapter(void* cookie, net_device* device, net_buffer* buffer) 145 { 146 net_domain_private* domain = (net_domain_private*)cookie; 147 148 return domain->module->receive_data(buffer); 149 } 150 151 152 static net_device_interface* 153 find_device_interface(const char* name) 154 { 155 ASSERT_LOCKED_MUTEX(&sLock); 156 DeviceInterfaceList::Iterator iterator = sInterfaces.GetIterator(); 157 158 while (net_device_interface* interface = iterator.Next()) { 159 if (!strcmp(interface->device->name, name)) 160 return interface; 161 } 162 163 return NULL; 164 } 165 166 167 static net_device_interface* 168 allocate_device_interface(net_device* device, net_device_module_info* module) 169 { 170 net_device_interface* interface = new(std::nothrow) net_device_interface; 171 if (interface == NULL) 172 return NULL; 173 174 recursive_lock_init(&interface->receive_lock, "device interface receive"); 175 recursive_lock_init(&interface->monitor_lock, "device interface monitors"); 176 177 char name[128]; 178 snprintf(name, sizeof(name), "%s receive queue", device->name); 179 180 if (init_fifo(&interface->receive_queue, name, 16 * 1024 * 1024) < B_OK) 181 goto error1; 182 183 interface->device = device; 184 interface->up_count = 0; 185 interface->ref_count = 1; 186 interface->busy = false; 187 interface->monitor_count = 0; 188 interface->deframe_func = NULL; 189 interface->deframe_ref_count = 0; 190 191 snprintf(name, sizeof(name), "%s consumer", device->name); 192 193 interface->reader_thread = -1; 194 interface->consumer_thread = spawn_kernel_thread(device_consumer_thread, 195 name, B_DISPLAY_PRIORITY, interface); 196 if (interface->consumer_thread < B_OK) 197 goto error2; 198 resume_thread(interface->consumer_thread); 199 200 // TODO: proper interface index allocation 201 device->index = ++sDeviceIndex; 202 device->module = module; 203 204 sInterfaces.Add(interface); 205 return interface; 206 207 error2: 208 uninit_fifo(&interface->receive_queue); 209 error1: 210 recursive_lock_destroy(&interface->receive_lock); 211 recursive_lock_destroy(&interface->monitor_lock); 212 delete interface; 213 214 return NULL; 215 } 216 217 218 static void 219 notify_device_monitors(net_device_interface* interface, int32 event) 220 { 221 RecursiveLocker locker(interface->monitor_lock); 222 223 DeviceMonitorList::Iterator iterator 224 = interface->monitor_funcs.GetIterator(); 225 while (net_device_monitor* monitor = iterator.Next()) { 226 // it's safe for the "current" item to remove itself. 227 monitor->event(monitor, event); 228 } 229 } 230 231 232 #if ENABLE_DEBUGGER_COMMANDS 233 234 235 static int 236 dump_device_interface(int argc, char** argv) 237 { 238 if (argc != 2) { 239 kprintf("usage: %s [address]\n", argv[0]); 240 return 0; 241 } 242 243 net_device_interface* interface 244 = (net_device_interface*)parse_expression(argv[1]); 245 246 kprintf("device: %p\n", interface->device); 247 kprintf("reader_thread: %" B_PRId32 "\n", interface->reader_thread); 248 kprintf("up_count: %" B_PRIu32 "\n", interface->up_count); 249 kprintf("ref_count: %" B_PRId32 "\n", interface->ref_count); 250 kprintf("deframe_func: %p\n", interface->deframe_func); 251 kprintf("deframe_ref_count: %" B_PRId32 "\n", interface->ref_count); 252 kprintf("consumer_thread: %" B_PRId32 "\n", interface->consumer_thread); 253 254 kprintf("monitor_count: %" B_PRId32 "\n", interface->monitor_count); 255 kprintf("monitor_lock: %p\n", &interface->monitor_lock); 256 kprintf("monitor_funcs:\n"); 257 DeviceMonitorList::Iterator monitorIterator 258 = interface->monitor_funcs.GetIterator(); 259 while (monitorIterator.HasNext()) 260 kprintf(" %p\n", monitorIterator.Next()); 261 262 kprintf("receive_lock: %p\n", &interface->receive_lock); 263 kprintf("receive_queue: %p\n", &interface->receive_queue); 264 kprintf("receive_funcs:\n"); 265 DeviceHandlerList::Iterator handlerIterator 266 = interface->receive_funcs.GetIterator(); 267 while (handlerIterator.HasNext()) 268 kprintf(" %p\n", handlerIterator.Next()); 269 270 return 0; 271 } 272 273 274 static int 275 dump_device_interfaces(int argc, char** argv) 276 { 277 DeviceInterfaceList::Iterator iterator = sInterfaces.GetIterator(); 278 while (net_device_interface* interface = iterator.Next()) { 279 kprintf(" %p %s\n", interface, interface->device->name); 280 } 281 282 return 0; 283 } 284 285 286 #endif // ENABLE_DEBUGGER_COMMANDS 287 288 289 // #pragma mark - device interfaces 290 291 292 net_device_interface* 293 acquire_device_interface(net_device_interface* interface) 294 { 295 if (interface == NULL || atomic_add(&interface->ref_count, 1) == 0) 296 return NULL; 297 298 return interface; 299 } 300 301 302 void 303 get_device_interface_address(net_device_interface* interface, 304 sockaddr* _address) 305 { 306 sockaddr_dl &address = *(sockaddr_dl*)_address; 307 308 address.sdl_family = AF_LINK; 309 address.sdl_index = interface->device->index; 310 address.sdl_type = interface->device->type; 311 address.sdl_nlen = strlen(interface->device->name); 312 address.sdl_slen = 0; 313 memcpy(address.sdl_data, interface->device->name, address.sdl_nlen); 314 315 address.sdl_alen = interface->device->address.length; 316 memcpy(LLADDR(&address), interface->device->address.data, address.sdl_alen); 317 318 address.sdl_len = sizeof(sockaddr_dl) - sizeof(address.sdl_data) 319 + address.sdl_nlen + address.sdl_alen; 320 } 321 322 323 uint32 324 count_device_interfaces() 325 { 326 MutexLocker locker(sLock); 327 328 DeviceInterfaceList::Iterator iterator = sInterfaces.GetIterator(); 329 uint32 count = 0; 330 331 while (iterator.HasNext()) { 332 iterator.Next(); 333 count++; 334 } 335 336 return count; 337 } 338 339 340 /*! Dumps a list of all interfaces into the supplied userland buffer. 341 If the interfaces don't fit into the buffer, an error (\c ENOBUFS) is 342 returned. 343 */ 344 status_t 345 list_device_interfaces(void* _buffer, size_t* bufferSize) 346 { 347 MutexLocker locker(sLock); 348 349 DeviceInterfaceList::Iterator iterator = sInterfaces.GetIterator(); 350 UserBuffer buffer(_buffer, *bufferSize); 351 352 while (net_device_interface* interface = iterator.Next()) { 353 buffer.Push(interface->device->name, IF_NAMESIZE); 354 355 sockaddr_storage address; 356 get_device_interface_address(interface, (sockaddr*)&address); 357 358 buffer.Push(&address, address.ss_len); 359 if (IF_NAMESIZE + address.ss_len < (int)sizeof(ifreq)) 360 buffer.Pad(sizeof(ifreq) - IF_NAMESIZE - address.ss_len); 361 362 if (buffer.Status() != B_OK) 363 return buffer.Status(); 364 } 365 366 *bufferSize = buffer.BytesConsumed(); 367 return B_OK; 368 } 369 370 371 /*! Releases the reference for the interface. When all references are 372 released, the interface is removed. 373 */ 374 void 375 put_device_interface(struct net_device_interface* interface) 376 { 377 if (interface == NULL) 378 return; 379 380 if (atomic_add(&interface->ref_count, -1) != 1) 381 return; 382 383 MutexLocker locker(sLock); 384 sInterfaces.Remove(interface); 385 locker.Unlock(); 386 387 uninit_fifo(&interface->receive_queue); 388 status_t status; 389 wait_for_thread(interface->consumer_thread, &status); 390 391 net_device* device = interface->device; 392 const char* moduleName = device->module->info.name; 393 394 device->module->uninit_device(device); 395 put_module(moduleName); 396 397 recursive_lock_destroy(&interface->monitor_lock); 398 recursive_lock_destroy(&interface->receive_lock); 399 delete interface; 400 } 401 402 403 /*! Finds an interface by the specified index and acquires a reference to it. 404 */ 405 struct net_device_interface* 406 get_device_interface(uint32 index) 407 { 408 MutexLocker locker(sLock); 409 410 // TODO: maintain an array of all device interfaces instead 411 DeviceInterfaceList::Iterator iterator = sInterfaces.GetIterator(); 412 while (net_device_interface* interface = iterator.Next()) { 413 if (interface->device->index == index) { 414 if (interface->busy) 415 break; 416 417 if (atomic_add(&interface->ref_count, 1) != 0) 418 return interface; 419 } 420 } 421 422 return NULL; 423 } 424 425 426 /*! Finds an interface by the specified name and grabs a reference to it. 427 If the interface does not yet exist, a new one is created. 428 */ 429 struct net_device_interface* 430 get_device_interface(const char* name, bool create) 431 { 432 MutexLocker locker(sLock); 433 434 net_device_interface* interface = find_device_interface(name); 435 if (interface != NULL) { 436 if (interface->busy) 437 return NULL; 438 439 if (atomic_add(&interface->ref_count, 1) != 0) 440 return interface; 441 442 // try to recreate interface - it just got removed 443 } 444 445 if (!create) 446 return NULL; 447 448 void* cookie = open_module_list("network/devices"); 449 if (cookie == NULL) 450 return NULL; 451 452 while (true) { 453 char moduleName[B_FILE_NAME_LENGTH]; 454 size_t length = sizeof(moduleName); 455 if (read_next_module_name(cookie, moduleName, &length) != B_OK) 456 break; 457 458 TRACE(("get_device_interface: ask \"%s\" for %s\n", moduleName, name)); 459 460 net_device_module_info* module; 461 if (get_module(moduleName, (module_info**)&module) == B_OK) { 462 net_device* device; 463 status_t status = module->init_device(name, &device); 464 if (status == B_OK) { 465 interface = allocate_device_interface(device, module); 466 if (interface != NULL) 467 return interface; 468 469 module->uninit_device(device); 470 } 471 put_module(moduleName); 472 } 473 } 474 475 close_module_list(cookie); 476 477 return NULL; 478 } 479 480 481 /*! Feeds the device monitors of the \a interface with the specified \a buffer. 482 You might want to check interface::monitor_count before calling this 483 function for optimization. 484 */ 485 void 486 device_interface_monitor_receive(net_device_interface* interface, 487 net_buffer* buffer) 488 { 489 RecursiveLocker locker(interface->monitor_lock); 490 491 DeviceMonitorList::Iterator iterator 492 = interface->monitor_funcs.GetIterator(); 493 while (iterator.HasNext()) { 494 net_device_monitor* monitor = iterator.Next(); 495 monitor->receive(monitor, buffer); 496 } 497 } 498 499 500 status_t 501 up_device_interface(net_device_interface* interface) 502 { 503 net_device* device = interface->device; 504 505 RecursiveLocker locker(interface->receive_lock); 506 507 if (interface->up_count != 0) { 508 interface->up_count++; 509 return B_OK; 510 } 511 512 status_t status = device->module->up(device); 513 if (status != B_OK) 514 return status; 515 516 if (device->module->receive_data != NULL) { 517 // give the thread a nice name 518 char name[B_OS_NAME_LENGTH]; 519 snprintf(name, sizeof(name), "%s reader", device->name); 520 521 interface->reader_thread = spawn_kernel_thread(device_reader_thread, 522 name, B_REAL_TIME_DISPLAY_PRIORITY - 10, interface); 523 if (interface->reader_thread < B_OK) 524 return interface->reader_thread; 525 } 526 527 device->flags |= IFF_UP; 528 529 if (device->module->receive_data != NULL) 530 resume_thread(interface->reader_thread); 531 532 interface->up_count = 1; 533 return B_OK; 534 } 535 536 537 void 538 down_device_interface(net_device_interface* interface) 539 { 540 // Receive lock must be held when calling down_device_interface. 541 // Known callers are `interface_protocol_down' which gets 542 // here via one of the following paths: 543 // 544 // - domain_interface_control() [rx lock held, domain lock held] 545 // interface_set_down() 546 // interface_protocol_down() 547 // 548 // - domain_interface_control() [rx lock held, domain lock held] 549 // remove_interface_from_domain() 550 // delete_interface() 551 // interface_set_down() 552 553 net_device* device = interface->device; 554 555 device->flags &= ~IFF_UP; 556 device->module->down(device); 557 558 notify_device_monitors(interface, B_DEVICE_GOING_DOWN); 559 560 if (device->module->receive_data != NULL) { 561 thread_id readerThread = interface->reader_thread; 562 563 // make sure the reader thread is gone before shutting down the interface 564 status_t status; 565 wait_for_thread(readerThread, &status); 566 } 567 } 568 569 570 // #pragma mark - devices stack API 571 572 573 /*! Unregisters a previously registered deframer function. */ 574 status_t 575 unregister_device_deframer(net_device* device) 576 { 577 MutexLocker locker(sLock); 578 579 // find device interface for this device 580 net_device_interface* interface = find_device_interface(device->name); 581 if (interface == NULL) 582 return B_DEVICE_NOT_FOUND; 583 584 RecursiveLocker _(interface->receive_lock); 585 586 if (--interface->deframe_ref_count == 0) 587 interface->deframe_func = NULL; 588 589 return B_OK; 590 } 591 592 593 /*! Registers the deframer function for the specified \a device. 594 Note, however, that right now, you can only register one single 595 deframer function per device. 596 597 If the need arises, we might want to lift that limitation at a 598 later time (which would require a slight API change, though). 599 */ 600 status_t 601 register_device_deframer(net_device* device, net_deframe_func deframeFunc) 602 { 603 MutexLocker locker(sLock); 604 605 // find device interface for this device 606 net_device_interface* interface = find_device_interface(device->name); 607 if (interface == NULL) 608 return B_DEVICE_NOT_FOUND; 609 610 RecursiveLocker _(interface->receive_lock); 611 612 if (interface->deframe_func != NULL 613 && interface->deframe_func != deframeFunc) 614 return B_ERROR; 615 616 interface->deframe_func = deframeFunc; 617 interface->deframe_ref_count++; 618 return B_OK; 619 } 620 621 622 /*! Registers a domain to receive net_buffers from the specified \a device. */ 623 status_t 624 register_domain_device_handler(struct net_device* device, int32 type, 625 struct net_domain* _domain) 626 { 627 net_domain_private* domain = (net_domain_private*)_domain; 628 if (domain->module == NULL || domain->module->receive_data == NULL) 629 return B_BAD_VALUE; 630 631 return register_device_handler(device, type, &domain_receive_adapter, 632 domain); 633 } 634 635 636 /*! Registers a receiving function callback for the specified \a device. */ 637 status_t 638 register_device_handler(struct net_device* device, int32 type, 639 net_receive_func receiveFunc, void* cookie) 640 { 641 MutexLocker locker(sLock); 642 643 // find device interface for this device 644 net_device_interface* interface = find_device_interface(device->name); 645 if (interface == NULL) 646 return B_DEVICE_NOT_FOUND; 647 648 RecursiveLocker _(interface->receive_lock); 649 650 // see if such a handler already for this device 651 652 DeviceHandlerList::Iterator iterator 653 = interface->receive_funcs.GetIterator(); 654 while (net_device_handler* handler = iterator.Next()) { 655 if (handler->type == type) 656 return B_ERROR; 657 } 658 659 // Add new handler 660 661 net_device_handler* handler = new(std::nothrow) net_device_handler; 662 if (handler == NULL) 663 return B_NO_MEMORY; 664 665 handler->func = receiveFunc; 666 handler->type = type; 667 handler->cookie = cookie; 668 interface->receive_funcs.Add(handler); 669 return B_OK; 670 } 671 672 673 /*! Unregisters a previously registered device handler. */ 674 status_t 675 unregister_device_handler(struct net_device* device, int32 type) 676 { 677 MutexLocker locker(sLock); 678 679 // find device interface for this device 680 net_device_interface* interface = find_device_interface(device->name); 681 if (interface == NULL) 682 return B_DEVICE_NOT_FOUND; 683 684 RecursiveLocker _(interface->receive_lock); 685 686 // search for the handler 687 688 DeviceHandlerList::Iterator iterator 689 = interface->receive_funcs.GetIterator(); 690 while (net_device_handler* handler = iterator.Next()) { 691 if (handler->type == type) { 692 // found it 693 iterator.Remove(); 694 delete handler; 695 return B_OK; 696 } 697 } 698 699 return B_BAD_VALUE; 700 } 701 702 703 /*! Registers a device monitor for the specified device. */ 704 status_t 705 register_device_monitor(net_device* device, net_device_monitor* monitor) 706 { 707 if (monitor->receive == NULL || monitor->event == NULL) 708 return B_BAD_VALUE; 709 710 MutexLocker locker(sLock); 711 712 // find device interface for this device 713 net_device_interface* interface = find_device_interface(device->name); 714 if (interface == NULL) 715 return B_DEVICE_NOT_FOUND; 716 717 RecursiveLocker monitorLocker(interface->monitor_lock); 718 interface->monitor_funcs.Add(monitor); 719 atomic_add(&interface->monitor_count, 1); 720 721 return B_OK; 722 } 723 724 725 /*! Unregisters a previously registered device monitor. */ 726 status_t 727 unregister_device_monitor(net_device* device, net_device_monitor* monitor) 728 { 729 MutexLocker locker(sLock); 730 731 // find device interface for this device 732 net_device_interface* interface = find_device_interface(device->name); 733 if (interface == NULL) 734 return B_DEVICE_NOT_FOUND; 735 736 RecursiveLocker monitorLocker(interface->monitor_lock); 737 738 // search for the monitor 739 740 DeviceMonitorList::Iterator iterator 741 = interface->monitor_funcs.GetIterator(); 742 while (iterator.HasNext()) { 743 if (iterator.Next() == monitor) { 744 iterator.Remove(); 745 atomic_add(&interface->monitor_count, -1); 746 return B_OK; 747 } 748 } 749 750 return B_BAD_VALUE; 751 } 752 753 754 /*! This function is called by device modules in case their link 755 state changed, ie. if an ethernet cable was plugged in or 756 removed. 757 */ 758 status_t 759 device_link_changed(net_device* device) 760 { 761 notify_link_changed(device); 762 return B_OK; 763 } 764 765 766 /*! This function is called by device modules once their device got 767 physically removed, ie. a USB networking card is unplugged. 768 */ 769 status_t 770 device_removed(net_device* device) 771 { 772 MutexLocker locker(sLock); 773 774 net_device_interface* interface = find_device_interface(device->name); 775 if (interface == NULL) 776 return B_DEVICE_NOT_FOUND; 777 if (interface->busy) 778 return B_BUSY; 779 780 // Acquire a reference to the device interface being removed 781 // so our put_() will (eventually) do the final cleanup 782 atomic_add(&interface->ref_count, 1); 783 interface->busy = true; 784 locker.Unlock(); 785 786 // Propagate the loss of the device throughout the stack. 787 788 interface_removed_device_interface(interface); 789 notify_device_monitors(interface, B_DEVICE_BEING_REMOVED); 790 791 // By now all of the monitors must have removed themselves. If they 792 // didn't, they'll probably wait forever to be callback'ed again. 793 RecursiveLocker monitorLocker(interface->monitor_lock); 794 interface->monitor_funcs.RemoveAll(); 795 monitorLocker.Unlock(); 796 797 // All of the readers should be gone as well since we are out of 798 // interfaces and put_domain_datalink_protocols() is called for 799 // each delete_interface(). 800 801 put_device_interface(interface); 802 803 return B_OK; 804 } 805 806 807 status_t 808 device_enqueue_buffer(net_device* device, net_buffer* buffer) 809 { 810 net_device_interface* interface = get_device_interface(device->index); 811 if (interface == NULL) 812 return B_DEVICE_NOT_FOUND; 813 814 status_t status = fifo_enqueue_buffer(&interface->receive_queue, buffer); 815 816 put_device_interface(interface); 817 return status; 818 } 819 820 821 // #pragma mark - 822 823 824 status_t 825 init_device_interfaces() 826 { 827 mutex_init(&sLock, "net device interfaces"); 828 829 new (&sInterfaces) DeviceInterfaceList; 830 // static C++ objects are not initialized in the module startup 831 832 #if ENABLE_DEBUGGER_COMMANDS 833 add_debugger_command("net_device_interface", &dump_device_interface, 834 "Dump the given network device interface"); 835 add_debugger_command("net_device_interfaces", &dump_device_interfaces, 836 "Dump network device interfaces"); 837 #endif 838 return B_OK; 839 } 840 841 842 status_t 843 uninit_device_interfaces() 844 { 845 #if ENABLE_DEBUGGER_COMMANDS 846 remove_debugger_command("net_device_interface", &dump_device_interface); 847 remove_debugger_command("net_device_interfaces", &dump_device_interfaces); 848 #endif 849 850 mutex_destroy(&sLock); 851 return B_OK; 852 } 853 854