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 close_module_list(cookie); 468 return interface; 469 } 470 471 module->uninit_device(device); 472 } 473 put_module(moduleName); 474 } 475 } 476 477 close_module_list(cookie); 478 479 return NULL; 480 } 481 482 483 /*! Feeds the device monitors of the \a interface with the specified \a buffer. 484 You might want to check interface::monitor_count before calling this 485 function for optimization. 486 */ 487 void 488 device_interface_monitor_receive(net_device_interface* interface, 489 net_buffer* buffer) 490 { 491 RecursiveLocker locker(interface->monitor_lock); 492 493 DeviceMonitorList::Iterator iterator 494 = interface->monitor_funcs.GetIterator(); 495 while (iterator.HasNext()) { 496 net_device_monitor* monitor = iterator.Next(); 497 monitor->receive(monitor, buffer); 498 } 499 } 500 501 502 status_t 503 up_device_interface(net_device_interface* interface) 504 { 505 net_device* device = interface->device; 506 507 RecursiveLocker locker(interface->receive_lock); 508 509 if (interface->up_count != 0) { 510 interface->up_count++; 511 return B_OK; 512 } 513 514 status_t status = device->module->up(device); 515 if (status != B_OK) 516 return status; 517 518 if (device->module->receive_data != NULL) { 519 // give the thread a nice name 520 char name[B_OS_NAME_LENGTH]; 521 snprintf(name, sizeof(name), "%s reader", device->name); 522 523 interface->reader_thread = spawn_kernel_thread(device_reader_thread, 524 name, B_REAL_TIME_DISPLAY_PRIORITY - 10, interface); 525 if (interface->reader_thread < B_OK) 526 return interface->reader_thread; 527 } 528 529 device->flags |= IFF_UP; 530 531 if (device->module->receive_data != NULL) 532 resume_thread(interface->reader_thread); 533 534 interface->up_count = 1; 535 return B_OK; 536 } 537 538 539 void 540 down_device_interface(net_device_interface* interface) 541 { 542 // Receive lock must be held when calling down_device_interface. 543 // Known callers are `interface_protocol_down' which gets 544 // here via one of the following paths: 545 // 546 // - domain_interface_control() [rx lock held, domain lock held] 547 // interface_set_down() 548 // interface_protocol_down() 549 // 550 // - domain_interface_control() [rx lock held, domain lock held] 551 // remove_interface_from_domain() 552 // delete_interface() 553 // interface_set_down() 554 555 net_device* device = interface->device; 556 557 device->flags &= ~IFF_UP; 558 device->module->down(device); 559 560 notify_device_monitors(interface, B_DEVICE_GOING_DOWN); 561 562 if (device->module->receive_data != NULL) { 563 thread_id readerThread = interface->reader_thread; 564 565 // make sure the reader thread is gone before shutting down the interface 566 status_t status; 567 wait_for_thread(readerThread, &status); 568 } 569 } 570 571 572 // #pragma mark - devices stack API 573 574 575 /*! Unregisters a previously registered deframer function. */ 576 status_t 577 unregister_device_deframer(net_device* device) 578 { 579 MutexLocker locker(sLock); 580 581 // find device interface for this device 582 net_device_interface* interface = find_device_interface(device->name); 583 if (interface == NULL) 584 return B_DEVICE_NOT_FOUND; 585 586 RecursiveLocker _(interface->receive_lock); 587 588 if (--interface->deframe_ref_count == 0) 589 interface->deframe_func = NULL; 590 591 return B_OK; 592 } 593 594 595 /*! Registers the deframer function for the specified \a device. 596 Note, however, that right now, you can only register one single 597 deframer function per device. 598 599 If the need arises, we might want to lift that limitation at a 600 later time (which would require a slight API change, though). 601 */ 602 status_t 603 register_device_deframer(net_device* device, net_deframe_func deframeFunc) 604 { 605 MutexLocker locker(sLock); 606 607 // find device interface for this device 608 net_device_interface* interface = find_device_interface(device->name); 609 if (interface == NULL) 610 return B_DEVICE_NOT_FOUND; 611 612 RecursiveLocker _(interface->receive_lock); 613 614 if (interface->deframe_func != NULL 615 && interface->deframe_func != deframeFunc) 616 return B_ERROR; 617 618 interface->deframe_func = deframeFunc; 619 interface->deframe_ref_count++; 620 return B_OK; 621 } 622 623 624 /*! Registers a domain to receive net_buffers from the specified \a device. */ 625 status_t 626 register_domain_device_handler(struct net_device* device, int32 type, 627 struct net_domain* _domain) 628 { 629 net_domain_private* domain = (net_domain_private*)_domain; 630 if (domain->module == NULL || domain->module->receive_data == NULL) 631 return B_BAD_VALUE; 632 633 return register_device_handler(device, type, &domain_receive_adapter, 634 domain); 635 } 636 637 638 /*! Registers a receiving function callback for the specified \a device. */ 639 status_t 640 register_device_handler(struct net_device* device, int32 type, 641 net_receive_func receiveFunc, void* cookie) 642 { 643 MutexLocker locker(sLock); 644 645 // find device interface for this device 646 net_device_interface* interface = find_device_interface(device->name); 647 if (interface == NULL) 648 return B_DEVICE_NOT_FOUND; 649 650 RecursiveLocker _(interface->receive_lock); 651 652 // see if such a handler already for this device 653 654 DeviceHandlerList::Iterator iterator 655 = interface->receive_funcs.GetIterator(); 656 while (net_device_handler* handler = iterator.Next()) { 657 if (handler->type == type) 658 return B_ERROR; 659 } 660 661 // Add new handler 662 663 net_device_handler* handler = new(std::nothrow) net_device_handler; 664 if (handler == NULL) 665 return B_NO_MEMORY; 666 667 handler->func = receiveFunc; 668 handler->type = type; 669 handler->cookie = cookie; 670 interface->receive_funcs.Add(handler); 671 return B_OK; 672 } 673 674 675 /*! Unregisters a previously registered device handler. */ 676 status_t 677 unregister_device_handler(struct net_device* device, int32 type) 678 { 679 MutexLocker locker(sLock); 680 681 // find device interface for this device 682 net_device_interface* interface = find_device_interface(device->name); 683 if (interface == NULL) 684 return B_DEVICE_NOT_FOUND; 685 686 RecursiveLocker _(interface->receive_lock); 687 688 // search for the handler 689 690 DeviceHandlerList::Iterator iterator 691 = interface->receive_funcs.GetIterator(); 692 while (net_device_handler* handler = iterator.Next()) { 693 if (handler->type == type) { 694 // found it 695 iterator.Remove(); 696 delete handler; 697 return B_OK; 698 } 699 } 700 701 return B_BAD_VALUE; 702 } 703 704 705 /*! Registers a device monitor for the specified device. */ 706 status_t 707 register_device_monitor(net_device* device, net_device_monitor* monitor) 708 { 709 if (monitor->receive == NULL || monitor->event == NULL) 710 return B_BAD_VALUE; 711 712 MutexLocker locker(sLock); 713 714 // find device interface for this device 715 net_device_interface* interface = find_device_interface(device->name); 716 if (interface == NULL) 717 return B_DEVICE_NOT_FOUND; 718 719 RecursiveLocker monitorLocker(interface->monitor_lock); 720 interface->monitor_funcs.Add(monitor); 721 atomic_add(&interface->monitor_count, 1); 722 723 return B_OK; 724 } 725 726 727 /*! Unregisters a previously registered device monitor. */ 728 status_t 729 unregister_device_monitor(net_device* device, net_device_monitor* monitor) 730 { 731 MutexLocker locker(sLock); 732 733 // find device interface for this device 734 net_device_interface* interface = find_device_interface(device->name); 735 if (interface == NULL) 736 return B_DEVICE_NOT_FOUND; 737 738 RecursiveLocker monitorLocker(interface->monitor_lock); 739 740 // search for the monitor 741 742 DeviceMonitorList::Iterator iterator 743 = interface->monitor_funcs.GetIterator(); 744 while (iterator.HasNext()) { 745 if (iterator.Next() == monitor) { 746 iterator.Remove(); 747 atomic_add(&interface->monitor_count, -1); 748 return B_OK; 749 } 750 } 751 752 return B_BAD_VALUE; 753 } 754 755 756 /*! This function is called by device modules in case their link 757 state changed, ie. if an ethernet cable was plugged in or 758 removed. 759 */ 760 status_t 761 device_link_changed(net_device* device) 762 { 763 notify_link_changed(device); 764 return B_OK; 765 } 766 767 768 /*! This function is called by device modules once their device got 769 physically removed, ie. a USB networking card is unplugged. 770 */ 771 status_t 772 device_removed(net_device* device) 773 { 774 MutexLocker locker(sLock); 775 776 net_device_interface* interface = find_device_interface(device->name); 777 if (interface == NULL) 778 return B_DEVICE_NOT_FOUND; 779 if (interface->busy) 780 return B_BUSY; 781 782 // Acquire a reference to the device interface being removed 783 // so our put_() will (eventually) do the final cleanup 784 atomic_add(&interface->ref_count, 1); 785 interface->busy = true; 786 locker.Unlock(); 787 788 // Propagate the loss of the device throughout the stack. 789 790 interface_removed_device_interface(interface); 791 notify_device_monitors(interface, B_DEVICE_BEING_REMOVED); 792 793 // By now all of the monitors must have removed themselves. If they 794 // didn't, they'll probably wait forever to be callback'ed again. 795 RecursiveLocker monitorLocker(interface->monitor_lock); 796 interface->monitor_funcs.RemoveAll(); 797 monitorLocker.Unlock(); 798 799 // All of the readers should be gone as well since we are out of 800 // interfaces and put_domain_datalink_protocols() is called for 801 // each delete_interface(). 802 803 put_device_interface(interface); 804 805 return B_OK; 806 } 807 808 809 status_t 810 device_enqueue_buffer(net_device* device, net_buffer* buffer) 811 { 812 net_device_interface* interface = get_device_interface(device->index); 813 if (interface == NULL) 814 return B_DEVICE_NOT_FOUND; 815 816 status_t status = fifo_enqueue_buffer(&interface->receive_queue, buffer); 817 818 put_device_interface(interface); 819 return status; 820 } 821 822 823 // #pragma mark - 824 825 826 status_t 827 init_device_interfaces() 828 { 829 mutex_init(&sLock, "net device interfaces"); 830 831 new (&sInterfaces) DeviceInterfaceList; 832 // static C++ objects are not initialized in the module startup 833 834 #if ENABLE_DEBUGGER_COMMANDS 835 add_debugger_command("net_device_interface", &dump_device_interface, 836 "Dump the given network device interface"); 837 add_debugger_command("net_device_interfaces", &dump_device_interfaces, 838 "Dump network device interfaces"); 839 #endif 840 return B_OK; 841 } 842 843 844 status_t 845 uninit_device_interfaces() 846 { 847 #if ENABLE_DEBUGGER_COMMANDS 848 remove_debugger_command("net_device_interface", &dump_device_interface); 849 remove_debugger_command("net_device_interfaces", &dump_device_interfaces); 850 #endif 851 852 mutex_destroy(&sLock); 853 return B_OK; 854 } 855 856