mgmtops.c revision 2db4f654cb730adffe7b55044327f68808cd1c9d
1/* 2 * 3 * BlueZ - Bluetooth protocol stack for Linux 4 * 5 * Copyright (C) 2010 Nokia Corporation 6 * Copyright (C) 2010 Marcel Holtmann <marcel@holtmann.org> 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License as published by 10 * the Free Software Foundation; either version 2 of the License, or 11 * (at your option) any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU General Public License for more details. 17 * 18 * You should have received a copy of the GNU General Public License 19 * along with this program; if not, write to the Free Software 20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 21 * 22 */ 23 24#ifdef HAVE_CONFIG_H 25#include <config.h> 26#endif 27 28#include <stdio.h> 29#include <errno.h> 30#include <unistd.h> 31#include <stdlib.h> 32#include <sys/types.h> 33#include <sys/ioctl.h> 34#include <sys/wait.h> 35 36#include <glib.h> 37 38#include <bluetooth/bluetooth.h> 39#include <bluetooth/hci.h> 40#include <bluetooth/mgmt.h> 41 42#include "plugin.h" 43#include "log.h" 44#include "manager.h" 45#include "adapter.h" 46#include "device.h" 47#include "event.h" 48 49#define MGMT_BUF_SIZE 1024 50 51static int max_index = -1; 52static struct controller_info { 53 gboolean valid; 54 gboolean notified; 55 uint8_t type; 56 bdaddr_t bdaddr; 57 uint8_t features[8]; 58 uint8_t dev_class[3]; 59 uint16_t manufacturer; 60 uint8_t hci_ver; 61 uint16_t hci_rev; 62 gboolean enabled; 63 gboolean discoverable; 64 gboolean pairable; 65 uint8_t sec_mode; 66} *controllers = NULL; 67 68static int mgmt_sock = -1; 69static guint mgmt_watch = 0; 70 71static uint8_t mgmt_version = 0; 72static uint16_t mgmt_revision = 0; 73 74static void read_version_complete(int sk, void *buf, size_t len) 75{ 76 struct mgmt_hdr hdr; 77 struct mgmt_rp_read_version *rp = buf; 78 79 if (len < sizeof(*rp)) { 80 error("Too small read version complete event"); 81 return; 82 } 83 84 mgmt_revision = btohs(bt_get_unaligned(&rp->revision)); 85 mgmt_version = rp->version; 86 87 DBG("version %u revision %u", mgmt_version, mgmt_revision); 88 89 memset(&hdr, 0, sizeof(hdr)); 90 hdr.opcode = MGMT_OP_READ_INDEX_LIST; 91 if (write(sk, &hdr, sizeof(hdr)) < 0) 92 error("Unable to read controller index list: %s (%d)", 93 strerror(errno), errno); 94} 95 96static void add_controller(uint16_t index) 97{ 98 if (index > max_index) { 99 size_t size = sizeof(struct controller_info) * (index + 1); 100 max_index = index; 101 controllers = g_realloc(controllers, size); 102 } 103 104 memset(&controllers[index], 0, sizeof(struct controller_info)); 105 106 controllers[index].valid = TRUE; 107 108 DBG("Added controller %u", index); 109} 110 111static void read_info(int sk, uint16_t index) 112{ 113 char buf[MGMT_HDR_SIZE + sizeof(struct mgmt_cp_read_info)]; 114 struct mgmt_hdr *hdr = (void *) buf; 115 struct mgmt_cp_read_info *cp = (void *) &buf[sizeof(*hdr)]; 116 117 memset(buf, 0, sizeof(buf)); 118 hdr->opcode = MGMT_OP_READ_INFO; 119 hdr->len = htobs(sizeof(*cp)); 120 121 cp->index = htobs(index); 122 123 if (write(sk, buf, sizeof(buf)) < 0) 124 error("Unable to send read_info command: %s (%d)", 125 strerror(errno), errno); 126} 127 128static void mgmt_index_added(int sk, void *buf, size_t len) 129{ 130 struct mgmt_ev_index_added *ev = buf; 131 uint16_t index; 132 133 if (len < sizeof(*ev)) { 134 error("Too small index added event"); 135 return; 136 } 137 138 index = btohs(bt_get_unaligned(&ev->index)); 139 140 add_controller(index); 141 read_info(sk, index); 142} 143 144static void remove_controller(uint16_t index) 145{ 146 if (index > max_index) 147 return; 148 149 if (!controllers[index].valid) 150 return; 151 152 btd_manager_unregister_adapter(index); 153 154 memset(&controllers[index], 0, sizeof(struct controller_info)); 155 156 DBG("Removed controller %u", index); 157} 158 159static void mgmt_index_removed(int sk, void *buf, size_t len) 160{ 161 struct mgmt_ev_index_removed *ev = buf; 162 uint16_t index; 163 164 if (len < sizeof(*ev)) { 165 error("Too small index removed event"); 166 return; 167 } 168 169 index = btohs(bt_get_unaligned(&ev->index)); 170 171 remove_controller(index); 172} 173 174static void mgmt_powered(int sk, void *buf, size_t len) 175{ 176 struct mgmt_ev_powered *ev = buf; 177 uint16_t index; 178 179 if (len < sizeof(*ev)) { 180 error("Too small powered event"); 181 return; 182 } 183 184 index = btohs(bt_get_unaligned(&ev->index)); 185 186 if (index > max_index) { 187 DBG("Ignoring powered event for unknown controller %u", index); 188 return; 189 } 190 191 controllers[index].enabled = ev->powered; 192 193 DBG("Controller %u powered %s", index, ev->powered ? "on" : "off"); 194} 195 196static void read_index_list_complete(int sk, void *buf, size_t len) 197{ 198 struct mgmt_rp_read_index_list *rp = buf; 199 uint16_t num; 200 int i; 201 202 if (len < sizeof(*rp)) { 203 error("Too small read index list complete event"); 204 return; 205 } 206 207 num = btohs(bt_get_unaligned(&rp->num_controllers)); 208 209 if (num * sizeof(uint16_t) + sizeof(*rp) != len) { 210 error("Incorrect packet size for index list event"); 211 return; 212 } 213 214 for (i = 0; i < num; i++) { 215 uint16_t index; 216 217 index = btohs(bt_get_unaligned(&rp->index[i])); 218 219 add_controller(index); 220 read_info(sk, index); 221 } 222} 223 224static int mgmt_stop(int index) 225{ 226 DBG("index %d", index); 227 return -ENOSYS; 228} 229 230static int mgmt_set_discoverable(int index, gboolean discoverable) 231{ 232 DBG("index %d discoverable %d", index, discoverable); 233 return -ENOSYS; 234} 235 236static int mgmt_set_pairable(int index, gboolean pairable) 237{ 238 DBG("index %d pairable %d", index, pairable); 239 return -ENOSYS; 240} 241 242static void read_info_complete(int sk, void *buf, size_t len) 243{ 244 struct mgmt_rp_read_info *rp = buf; 245 struct controller_info *info; 246 struct btd_adapter *adapter; 247 uint8_t mode; 248 gboolean pairable, discoverable; 249 uint16_t index; 250 char addr[18]; 251 252 if (len < sizeof(*rp)) { 253 error("Too small read info complete event"); 254 return; 255 } 256 257 index = btohs(bt_get_unaligned(&rp->index)); 258 if (index > max_index) { 259 error("Unexpected index %u in read info complete", index); 260 return; 261 } 262 263 info = &controllers[index]; 264 info->type = rp->type; 265 info->enabled = rp->powered; 266 info->discoverable = rp->discoverable; 267 info->pairable = rp->pairable; 268 info->sec_mode = rp->sec_mode; 269 bacpy(&info->bdaddr, &rp->bdaddr); 270 memcpy(info->dev_class, rp->dev_class, 3); 271 memcpy(info->features, rp->features, 8); 272 info->manufacturer = btohs(bt_get_unaligned(&rp->manufacturer)); 273 info->hci_ver = rp->hci_ver; 274 info->hci_rev = btohs(bt_get_unaligned(&rp->hci_rev)); 275 276 ba2str(&info->bdaddr, addr); 277 DBG("hci%u type %u addr %s", index, info->type, addr); 278 DBG("hci%u class 0x%02x%02x%02x", index, 279 info->dev_class[2], info->dev_class[1], info->dev_class[0]); 280 DBG("hci%u manufacturer %d HCI ver %d:%d", index, info->manufacturer, 281 info->hci_ver, info->hci_rev); 282 DBG("hci%u enabled %u discoverable %u pairable %u sec_mode %u", index, 283 info->enabled, info->discoverable, 284 info->pairable, info->sec_mode); 285 286 adapter = btd_manager_register_adapter(index); 287 if (adapter == NULL) { 288 error("mgmtops: unable to register adapter"); 289 return; 290 } 291 292 btd_adapter_get_state(adapter, &mode, NULL, &pairable); 293 if (mode == MODE_OFF) { 294 mgmt_stop(index); 295 return; 296 } 297 298 discoverable = (mode == MODE_DISCOVERABLE); 299 300 if (info->discoverable != discoverable) 301 mgmt_set_discoverable(index, discoverable); 302 303 if (info->pairable != pairable) 304 mgmt_set_pairable(index, pairable); 305 306 if (info->enabled) 307 btd_adapter_start(adapter); 308 309 btd_adapter_unref(adapter); 310} 311 312static void mgmt_cmd_complete(int sk, void *buf, size_t len) 313{ 314 struct mgmt_ev_cmd_complete *ev = buf; 315 uint16_t opcode; 316 317 DBG(""); 318 319 if (len < sizeof(*ev)) { 320 error("Too small management command complete event packet"); 321 return; 322 } 323 324 opcode = btohs(bt_get_unaligned(&ev->opcode)); 325 326 switch (opcode) { 327 case MGMT_OP_READ_VERSION: 328 read_version_complete(sk, ev->data, len - sizeof(*ev)); 329 break; 330 case MGMT_OP_READ_INDEX_LIST: 331 read_index_list_complete(sk, ev->data, len - sizeof(*ev)); 332 break; 333 case MGMT_OP_READ_INFO: 334 read_info_complete(sk, ev->data, len - sizeof(*ev)); 335 break; 336 default: 337 error("Unknown command complete for opcode %u", opcode); 338 break; 339 } 340} 341 342static void mgmt_cmd_status(int sk, void *buf, size_t len) 343{ 344 struct mgmt_ev_cmd_status *ev = buf; 345 uint16_t opcode; 346 347 if (len < sizeof(*ev)) { 348 error("Too small management command status event packet"); 349 return; 350 } 351 352 opcode = btohs(bt_get_unaligned(&ev->opcode)); 353 354 DBG("status %u opcode %u", ev->status, opcode); 355} 356 357static void mgmt_controller_error(int sk, void *buf, size_t len) 358{ 359 struct mgmt_ev_controller_error *ev = buf; 360 uint16_t index; 361 362 if (len < sizeof(*ev)) { 363 error("Too small management controller error event packet"); 364 return; 365 } 366 367 index = btohs(bt_get_unaligned(&ev->index)); 368 369 DBG("index %u error_code %u", index, ev->error_code); 370} 371 372static gboolean mgmt_event(GIOChannel *io, GIOCondition cond, gpointer user_data) 373{ 374 char buf[MGMT_BUF_SIZE]; 375 struct mgmt_hdr *hdr = (void *) buf; 376 int sk; 377 ssize_t ret; 378 uint16_t len, opcode; 379 380 DBG("cond %d", cond); 381 382 if (cond & G_IO_NVAL) 383 return FALSE; 384 385 sk = g_io_channel_unix_get_fd(io); 386 387 if (cond & (G_IO_ERR | G_IO_HUP)) { 388 error("Error on management socket"); 389 return FALSE; 390 } 391 392 ret = read(sk, buf, sizeof(buf)); 393 if (ret < 0) { 394 error("Unable to read from management socket: %s (%d)", 395 strerror(errno), errno); 396 return TRUE; 397 } 398 399 DBG("Received %zd bytes from management socket", ret); 400 401 if (ret < MGMT_HDR_SIZE) { 402 error("Too small Management packet"); 403 return TRUE; 404 } 405 406 opcode = btohs(bt_get_unaligned(&hdr->opcode)); 407 len = btohs(bt_get_unaligned(&hdr->len)); 408 409 if (ret != MGMT_HDR_SIZE + len) { 410 error("Packet length mismatch. ret %zd len %u", ret, len); 411 return TRUE; 412 } 413 414 switch (opcode) { 415 case MGMT_EV_CMD_COMPLETE: 416 mgmt_cmd_complete(sk, buf + MGMT_HDR_SIZE, len); 417 break; 418 case MGMT_EV_CMD_STATUS: 419 mgmt_cmd_status(sk, buf + MGMT_HDR_SIZE, len); 420 break; 421 case MGMT_EV_CONTROLLER_ERROR: 422 mgmt_controller_error(sk, buf + MGMT_HDR_SIZE, len); 423 break; 424 case MGMT_EV_INDEX_ADDED: 425 mgmt_index_added(sk, buf + MGMT_HDR_SIZE, len); 426 break; 427 case MGMT_EV_INDEX_REMOVED: 428 mgmt_index_removed(sk, buf + MGMT_HDR_SIZE, len); 429 break; 430 case MGMT_EV_POWERED: 431 mgmt_powered(sk, buf + MGMT_HDR_SIZE, len); 432 break; 433 default: 434 error("Unknown Management opcode %u", opcode); 435 break; 436 } 437 438 return TRUE; 439} 440 441static int mgmt_setup(void) 442{ 443 struct mgmt_hdr hdr; 444 struct sockaddr_hci addr; 445 GIOChannel *io; 446 GIOCondition condition; 447 int dd, err; 448 449 dd = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); 450 if (dd < 0) 451 return -errno; 452 453 memset(&addr, 0, sizeof(addr)); 454 addr.hci_family = AF_BLUETOOTH; 455 addr.hci_dev = HCI_DEV_NONE; 456 addr.hci_channel = HCI_CHANNEL_CONTROL; 457 458 if (bind(dd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { 459 err = -errno; 460 goto fail; 461 } 462 463 memset(&hdr, 0, sizeof(hdr)); 464 hdr.opcode = MGMT_OP_READ_VERSION; 465 if (write(dd, &hdr, sizeof(hdr)) < 0) { 466 err = -errno; 467 goto fail; 468 } 469 470 io = g_io_channel_unix_new(dd); 471 condition = G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL; 472 mgmt_watch = g_io_add_watch(io, condition, mgmt_event, NULL); 473 g_io_channel_unref(io); 474 475 mgmt_sock = dd; 476 477 info("Bluetooth Management interface initialized"); 478 479 return 0; 480 481fail: 482 close(dd); 483 return err; 484} 485 486static void mgmt_cleanup(void) 487{ 488 g_free(controllers); 489 controllers = NULL; 490 max_index = -1; 491 492 if (mgmt_sock >= 0) { 493 close(mgmt_sock); 494 mgmt_sock = -1; 495 } 496 497 if (mgmt_watch > 0) { 498 g_source_remove(mgmt_watch); 499 mgmt_watch = 0; 500 } 501} 502 503static int mgmt_start(int index) 504{ 505 DBG("index %d", index); 506 return -ENOSYS; 507} 508 509static int mgmt_set_powered(int index, gboolean powered) 510{ 511 DBG("index %d powered %d", index, powered); 512 return -ENOSYS; 513} 514 515static int mgmt_set_connectable(int index, gboolean connectable) 516{ 517 DBG("index %d connectable %d", index, connectable); 518 return -ENOSYS; 519} 520 521static int mgmt_set_dev_class(int index, uint8_t major, uint8_t minor) 522{ 523 DBG("index %d major %u minor %u", index, major, minor); 524 return -ENOSYS; 525} 526 527static int mgmt_set_limited_discoverable(int index, gboolean limited) 528{ 529 DBG("index %d limited %d", index, limited); 530 return -ENOSYS; 531} 532 533static int mgmt_start_inquiry(int index, uint8_t length, gboolean periodic) 534{ 535 DBG("index %d length %u periodic %d", index, length, periodic); 536 return -ENOSYS; 537} 538 539static int mgmt_stop_inquiry(int index) 540{ 541 DBG("index %d", index); 542 return -ENOSYS; 543} 544 545static int mgmt_start_scanning(int index) 546{ 547 DBG("index %d", index); 548 return -ENOSYS; 549} 550 551static int mgmt_stop_scanning(int index) 552{ 553 DBG("index %d", index); 554 return -ENOSYS; 555} 556 557static int mgmt_resolve_name(int index, bdaddr_t *bdaddr) 558{ 559 char addr[18]; 560 561 ba2str(bdaddr, addr); 562 DBG("index %d addr %s", index, addr); 563 564 return -ENOSYS; 565} 566 567static int mgmt_set_name(int index, const char *name) 568{ 569 DBG("index %d, name %s", index, name); 570 return -ENOSYS; 571} 572 573static int mgmt_cancel_resolve_name(int index, bdaddr_t *bdaddr) 574{ 575 char addr[18]; 576 577 ba2str(bdaddr, addr); 578 DBG("index %d addr %s", index, addr); 579 580 return -ENOSYS; 581} 582 583static int mgmt_fast_connectable(int index, gboolean enable) 584{ 585 DBG("index %d enable %d", index, enable); 586 return -ENOSYS; 587} 588 589static int mgmt_read_clock(int index, int handle, int which, int timeout, 590 uint32_t *clock, uint16_t *accuracy) 591{ 592 DBG("index %d handle %d which %d timeout %d", index, handle, 593 which, timeout); 594 return -ENOSYS; 595} 596 597static int mgmt_conn_handle(int index, const bdaddr_t *bdaddr, int *handle) 598{ 599 char addr[18]; 600 601 ba2str(bdaddr, addr); 602 DBG("index %d addr %s", index, addr); 603 604 return -ENOSYS; 605} 606 607static int mgmt_read_bdaddr(int index, bdaddr_t *bdaddr) 608{ 609 char addr[18]; 610 struct controller_info *info = &controllers[index]; 611 612 ba2str(&info->bdaddr, addr); 613 DBG("index %d addr %s", index, addr); 614 615 if (!info->valid) 616 return -ENODEV; 617 618 bacpy(bdaddr, &info->bdaddr); 619 620 return 0; 621} 622 623static int mgmt_block_device(int index, bdaddr_t *bdaddr) 624{ 625 char addr[18]; 626 627 ba2str(bdaddr, addr); 628 DBG("index %d addr %s", index, addr); 629 630 return -ENOSYS; 631} 632 633static int mgmt_unblock_device(int index, bdaddr_t *bdaddr) 634{ 635 char addr[18]; 636 637 ba2str(bdaddr, addr); 638 DBG("index %d addr %s", index, addr); 639 640 return -ENOSYS; 641} 642 643static int mgmt_get_conn_list(int index, GSList **conns) 644{ 645 DBG("index %d", index); 646 return -ENOSYS; 647} 648 649static int mgmt_read_local_version(int index, struct hci_version *ver) 650{ 651 struct controller_info *info = &controllers[index]; 652 653 DBG("index %d", index); 654 655 if (!info->valid) 656 return -ENODEV; 657 658 memset(ver, 0, sizeof(*ver)); 659 ver->manufacturer = info->manufacturer; 660 ver->hci_ver = info->hci_ver; 661 ver->hci_rev = info->hci_rev; 662 663 return 0; 664} 665 666static int mgmt_read_local_features(int index, uint8_t *features) 667{ 668 struct controller_info *info = &controllers[index]; 669 670 DBG("index %d", index); 671 672 if (!info->valid) 673 return -ENODEV; 674 675 memcpy(features, info->features, 8); 676 677 return 0; 678} 679 680static int mgmt_disconnect(int index, uint16_t handle) 681{ 682 DBG("index %d handle %u", index, handle); 683 return -ENOSYS; 684} 685 686static int mgmt_remove_bonding(int index, bdaddr_t *bdaddr) 687{ 688 char addr[18]; 689 690 ba2str(bdaddr, addr); 691 DBG("index %d addr %s", index, addr); 692 693 return -ENOSYS; 694} 695 696static int mgmt_request_authentication(int index, uint16_t handle) 697{ 698 DBG("index %d handle %u", index, handle); 699 return -ENOSYS; 700} 701 702static int mgmt_pincode_reply(int index, bdaddr_t *bdaddr, const char *pin) 703{ 704 char addr[18]; 705 706 ba2str(bdaddr, addr); 707 DBG("index %d addr %s pin %s", index, addr, pin); 708 709 return -ENOSYS; 710} 711 712static int mgmt_confirm_reply(int index, bdaddr_t *bdaddr, gboolean success) 713{ 714 char addr[18]; 715 716 ba2str(bdaddr, addr); 717 DBG("index %d addr %s success %d", index, addr, success); 718 719 return -ENOSYS; 720} 721 722static int mgmt_passkey_reply(int index, bdaddr_t *bdaddr, uint32_t passkey) 723{ 724 char addr[18]; 725 726 ba2str(bdaddr, addr); 727 DBG("index %d addr %s passkey %06u", index, addr, passkey); 728 729 return -ENOSYS; 730} 731 732static int mgmt_get_auth_info(int index, bdaddr_t *bdaddr, uint8_t *auth) 733{ 734 char addr[18]; 735 736 ba2str(bdaddr, addr); 737 DBG("index %d addr %s", index, addr); 738 739 return -ENOSYS; 740} 741 742static int mgmt_read_scan_enable(int index) 743{ 744 DBG("index %d", index); 745 return -ENOSYS; 746} 747 748static int mgmt_enable_le(int index) 749{ 750 DBG("index %d", index); 751 return -ENOSYS; 752} 753 754static int mgmt_get_remote_version(int index, uint16_t handle, 755 gboolean delayed) 756{ 757 DBG("index %d handle %u delayed %d", index, handle, delayed); 758 return -ENOSYS; 759} 760 761static int mgmt_encrypt_link(int index, bdaddr_t *dst, bt_hci_result_t cb, 762 gpointer user_data) 763{ 764 char addr[18]; 765 766 ba2str(dst, addr); 767 DBG("index %d addr %s", index, addr); 768 769 return -ENOSYS; 770} 771 772static struct btd_adapter_ops mgmt_ops = { 773 .setup = mgmt_setup, 774 .cleanup = mgmt_cleanup, 775 .start = mgmt_start, 776 .stop = mgmt_stop, 777 .set_powered = mgmt_set_powered, 778 .set_connectable = mgmt_set_connectable, 779 .set_discoverable = mgmt_set_discoverable, 780 .set_pairable = mgmt_set_pairable, 781 .set_limited_discoverable = mgmt_set_limited_discoverable, 782 .start_inquiry = mgmt_start_inquiry, 783 .stop_inquiry = mgmt_stop_inquiry, 784 .start_scanning = mgmt_start_scanning, 785 .stop_scanning = mgmt_stop_scanning, 786 .resolve_name = mgmt_resolve_name, 787 .cancel_resolve_name = mgmt_cancel_resolve_name, 788 .set_name = mgmt_set_name, 789 .set_dev_class = mgmt_set_dev_class, 790 .set_fast_connectable = mgmt_fast_connectable, 791 .read_clock = mgmt_read_clock, 792 .get_conn_handle = mgmt_conn_handle, 793 .read_bdaddr = mgmt_read_bdaddr, 794 .block_device = mgmt_block_device, 795 .unblock_device = mgmt_unblock_device, 796 .get_conn_list = mgmt_get_conn_list, 797 .read_local_version = mgmt_read_local_version, 798 .read_local_features = mgmt_read_local_features, 799 .disconnect = mgmt_disconnect, 800 .remove_bonding = mgmt_remove_bonding, 801 .request_authentication = mgmt_request_authentication, 802 .pincode_reply = mgmt_pincode_reply, 803 .confirm_reply = mgmt_confirm_reply, 804 .passkey_reply = mgmt_passkey_reply, 805 .get_auth_info = mgmt_get_auth_info, 806 .read_scan_enable = mgmt_read_scan_enable, 807 .enable_le = mgmt_enable_le, 808 .get_remote_version = mgmt_get_remote_version, 809 .encrypt_link = mgmt_encrypt_link, 810}; 811 812static int mgmt_init(void) 813{ 814 return btd_register_adapter_ops(&mgmt_ops, TRUE); 815} 816 817static void mgmt_exit(void) 818{ 819 btd_adapter_cleanup_ops(&mgmt_ops); 820} 821 822BLUETOOTH_PLUGIN_DEFINE(mgmtops, VERSION, 823 BLUETOOTH_PLUGIN_PRIORITY_LOW, mgmt_init, mgmt_exit) 824