1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Copyright (C) 2020 The Linux Foundation. All rights reserved. 4 */ 5 6 #include <linux/kernel.h> 7 #include <linux/module.h> 8 #include <linux/string.h> 9 #include <linux/workqueue.h> 10 11 #include "pdr_internal.h" 12 13 struct pdr_service { 14 char service_name[SERVREG_NAME_LENGTH + 1]; 15 char service_path[SERVREG_NAME_LENGTH + 1]; 16 17 struct sockaddr_qrtr addr; 18 19 unsigned int instance; 20 unsigned int service; 21 u8 service_data_valid; 22 u32 service_data; 23 int state; 24 25 bool need_notifier_register; 26 bool need_notifier_remove; 27 bool need_locator_lookup; 28 bool service_connected; 29 30 struct list_head node; 31 }; 32 33 struct pdr_handle { 34 struct qmi_handle locator_hdl; 35 struct qmi_handle notifier_hdl; 36 37 struct sockaddr_qrtr locator_addr; 38 39 struct list_head lookups; 40 struct list_head indack_list; 41 42 /* control access to pdr lookup/indack lists */ 43 struct mutex list_lock; 44 45 /* serialize pd status invocation */ 46 struct mutex status_lock; 47 48 /* control access to the locator state */ 49 struct mutex lock; 50 51 bool locator_init_complete; 52 53 struct work_struct locator_work; 54 struct work_struct notifier_work; 55 struct work_struct indack_work; 56 57 struct workqueue_struct *notifier_wq; 58 struct workqueue_struct *indack_wq; 59 60 void (*status)(int state, char *service_path, void *priv); 61 void *priv; 62 }; 63 64 struct pdr_list_node { 65 enum servreg_service_state curr_state; 66 u16 transaction_id; 67 struct pdr_service *pds; 68 struct list_head node; 69 }; 70 71 static int pdr_locator_new_server(struct qmi_handle *qmi, 72 struct qmi_service *svc) 73 { 74 struct pdr_handle *pdr = container_of(qmi, struct pdr_handle, 75 locator_hdl); 76 struct pdr_service *pds; 77 78 /* Create a local client port for QMI communication */ 79 pdr->locator_addr.sq_family = AF_QIPCRTR; 80 pdr->locator_addr.sq_node = svc->node; 81 pdr->locator_addr.sq_port = svc->port; 82 83 mutex_lock(&pdr->lock); 84 pdr->locator_init_complete = true; 85 mutex_unlock(&pdr->lock); 86 87 /* Service pending lookup requests */ 88 mutex_lock(&pdr->list_lock); 89 list_for_each_entry(pds, &pdr->lookups, node) { 90 if (pds->need_locator_lookup) 91 schedule_work(&pdr->locator_work); 92 } 93 mutex_unlock(&pdr->list_lock); 94 95 return 0; 96 } 97 98 static void pdr_locator_del_server(struct qmi_handle *qmi, 99 struct qmi_service *svc) 100 { 101 struct pdr_handle *pdr = container_of(qmi, struct pdr_handle, 102 locator_hdl); 103 104 mutex_lock(&pdr->lock); 105 pdr->locator_init_complete = false; 106 mutex_unlock(&pdr->lock); 107 108 pdr->locator_addr.sq_node = 0; 109 pdr->locator_addr.sq_port = 0; 110 } 111 112 static struct qmi_ops pdr_locator_ops = { 113 .new_server = pdr_locator_new_server, 114 .del_server = pdr_locator_del_server, 115 }; 116 117 static int pdr_register_listener(struct pdr_handle *pdr, 118 struct pdr_service *pds, 119 bool enable) 120 { 121 struct servreg_register_listener_resp resp; 122 struct servreg_register_listener_req req; 123 struct qmi_txn txn; 124 int ret; 125 126 ret = qmi_txn_init(&pdr->notifier_hdl, &txn, 127 servreg_register_listener_resp_ei, 128 &resp); 129 if (ret < 0) 130 return ret; 131 132 req.enable = enable; 133 strcpy(req.service_path, pds->service_path); 134 135 ret = qmi_send_request(&pdr->notifier_hdl, &pds->addr, 136 &txn, SERVREG_REGISTER_LISTENER_REQ, 137 SERVREG_REGISTER_LISTENER_REQ_LEN, 138 servreg_register_listener_req_ei, 139 &req); 140 if (ret < 0) { 141 qmi_txn_cancel(&txn); 142 return ret; 143 } 144 145 ret = qmi_txn_wait(&txn, 5 * HZ); 146 if (ret < 0) { 147 pr_err("PDR: %s register listener txn wait failed: %d\n", 148 pds->service_path, ret); 149 return ret; 150 } 151 152 if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { 153 pr_err("PDR: %s register listener failed: 0x%x\n", 154 pds->service_path, resp.resp.error); 155 return ret; 156 } 157 158 pds->state = resp.curr_state; 159 160 return 0; 161 } 162 163 static void pdr_notifier_work(struct work_struct *work) 164 { 165 struct pdr_handle *pdr = container_of(work, struct pdr_handle, 166 notifier_work); 167 struct pdr_service *pds; 168 int ret; 169 170 mutex_lock(&pdr->list_lock); 171 list_for_each_entry(pds, &pdr->lookups, node) { 172 if (pds->service_connected) { 173 if (!pds->need_notifier_register) 174 continue; 175 176 pds->need_notifier_register = false; 177 ret = pdr_register_listener(pdr, pds, true); 178 if (ret < 0) 179 pds->state = SERVREG_SERVICE_STATE_DOWN; 180 } else { 181 if (!pds->need_notifier_remove) 182 continue; 183 184 pds->need_notifier_remove = false; 185 pds->state = SERVREG_SERVICE_STATE_DOWN; 186 } 187 188 mutex_lock(&pdr->status_lock); 189 pdr->status(pds->state, pds->service_path, pdr->priv); 190 mutex_unlock(&pdr->status_lock); 191 } 192 mutex_unlock(&pdr->list_lock); 193 } 194 195 static int pdr_notifier_new_server(struct qmi_handle *qmi, 196 struct qmi_service *svc) 197 { 198 struct pdr_handle *pdr = container_of(qmi, struct pdr_handle, 199 notifier_hdl); 200 struct pdr_service *pds; 201 202 mutex_lock(&pdr->list_lock); 203 list_for_each_entry(pds, &pdr->lookups, node) { 204 if (pds->service == svc->service && 205 pds->instance == svc->instance) { 206 pds->service_connected = true; 207 pds->need_notifier_register = true; 208 pds->addr.sq_family = AF_QIPCRTR; 209 pds->addr.sq_node = svc->node; 210 pds->addr.sq_port = svc->port; 211 queue_work(pdr->notifier_wq, &pdr->notifier_work); 212 } 213 } 214 mutex_unlock(&pdr->list_lock); 215 216 return 0; 217 } 218 219 static void pdr_notifier_del_server(struct qmi_handle *qmi, 220 struct qmi_service *svc) 221 { 222 struct pdr_handle *pdr = container_of(qmi, struct pdr_handle, 223 notifier_hdl); 224 struct pdr_service *pds; 225 226 mutex_lock(&pdr->list_lock); 227 list_for_each_entry(pds, &pdr->lookups, node) { 228 if (pds->service == svc->service && 229 pds->instance == svc->instance) { 230 pds->service_connected = false; 231 pds->need_notifier_remove = true; 232 pds->addr.sq_node = 0; 233 pds->addr.sq_port = 0; 234 queue_work(pdr->notifier_wq, &pdr->notifier_work); 235 } 236 } 237 mutex_unlock(&pdr->list_lock); 238 } 239 240 static struct qmi_ops pdr_notifier_ops = { 241 .new_server = pdr_notifier_new_server, 242 .del_server = pdr_notifier_del_server, 243 }; 244 245 static int pdr_send_indack_msg(struct pdr_handle *pdr, struct pdr_service *pds, 246 u16 tid) 247 { 248 struct servreg_set_ack_resp resp; 249 struct servreg_set_ack_req req; 250 struct qmi_txn txn; 251 int ret; 252 253 ret = qmi_txn_init(&pdr->notifier_hdl, &txn, servreg_set_ack_resp_ei, 254 &resp); 255 if (ret < 0) 256 return ret; 257 258 req.transaction_id = tid; 259 strcpy(req.service_path, pds->service_path); 260 261 ret = qmi_send_request(&pdr->notifier_hdl, &pds->addr, 262 &txn, SERVREG_SET_ACK_REQ, 263 SERVREG_SET_ACK_REQ_LEN, 264 servreg_set_ack_req_ei, 265 &req); 266 267 /* Skip waiting for response */ 268 qmi_txn_cancel(&txn); 269 return ret; 270 } 271 272 static void pdr_indack_work(struct work_struct *work) 273 { 274 struct pdr_handle *pdr = container_of(work, struct pdr_handle, 275 indack_work); 276 struct pdr_list_node *ind, *tmp; 277 struct pdr_service *pds; 278 279 list_for_each_entry_safe(ind, tmp, &pdr->indack_list, node) { 280 pds = ind->pds; 281 pdr_send_indack_msg(pdr, pds, ind->transaction_id); 282 283 mutex_lock(&pdr->status_lock); 284 pds->state = ind->curr_state; 285 pdr->status(pds->state, pds->service_path, pdr->priv); 286 mutex_unlock(&pdr->status_lock); 287 288 mutex_lock(&pdr->list_lock); 289 list_del(&ind->node); 290 mutex_unlock(&pdr->list_lock); 291 292 kfree(ind); 293 } 294 } 295 296 static void pdr_indication_cb(struct qmi_handle *qmi, 297 struct sockaddr_qrtr *sq, 298 struct qmi_txn *txn, const void *data) 299 { 300 struct pdr_handle *pdr = container_of(qmi, struct pdr_handle, 301 notifier_hdl); 302 const struct servreg_state_updated_ind *ind_msg = data; 303 struct pdr_list_node *ind; 304 struct pdr_service *pds; 305 bool found = false; 306 307 if (!ind_msg || !ind_msg->service_path[0] || 308 strlen(ind_msg->service_path) > SERVREG_NAME_LENGTH) 309 return; 310 311 mutex_lock(&pdr->list_lock); 312 list_for_each_entry(pds, &pdr->lookups, node) { 313 if (strcmp(pds->service_path, ind_msg->service_path)) 314 continue; 315 316 found = true; 317 break; 318 } 319 mutex_unlock(&pdr->list_lock); 320 321 if (!found) 322 return; 323 324 pr_info("PDR: Indication received from %s, state: 0x%x, trans-id: %d\n", 325 ind_msg->service_path, ind_msg->curr_state, 326 ind_msg->transaction_id); 327 328 ind = kzalloc(sizeof(*ind), GFP_KERNEL); 329 if (!ind) 330 return; 331 332 ind->transaction_id = ind_msg->transaction_id; 333 ind->curr_state = ind_msg->curr_state; 334 ind->pds = pds; 335 336 mutex_lock(&pdr->list_lock); 337 list_add_tail(&ind->node, &pdr->indack_list); 338 mutex_unlock(&pdr->list_lock); 339 340 queue_work(pdr->indack_wq, &pdr->indack_work); 341 } 342 343 static struct qmi_msg_handler qmi_indication_handler[] = { 344 { 345 .type = QMI_INDICATION, 346 .msg_id = SERVREG_STATE_UPDATED_IND_ID, 347 .ei = servreg_state_updated_ind_ei, 348 .decoded_size = sizeof(struct servreg_state_updated_ind), 349 .fn = pdr_indication_cb, 350 }, 351 {} 352 }; 353 354 static int pdr_get_domain_list(struct servreg_get_domain_list_req *req, 355 struct servreg_get_domain_list_resp *resp, 356 struct pdr_handle *pdr) 357 { 358 struct qmi_txn txn; 359 int ret; 360 361 ret = qmi_txn_init(&pdr->locator_hdl, &txn, 362 servreg_get_domain_list_resp_ei, resp); 363 if (ret < 0) 364 return ret; 365 366 ret = qmi_send_request(&pdr->locator_hdl, 367 &pdr->locator_addr, 368 &txn, SERVREG_GET_DOMAIN_LIST_REQ, 369 SERVREG_GET_DOMAIN_LIST_REQ_MAX_LEN, 370 servreg_get_domain_list_req_ei, 371 req); 372 if (ret < 0) { 373 qmi_txn_cancel(&txn); 374 return ret; 375 } 376 377 ret = qmi_txn_wait(&txn, 5 * HZ); 378 if (ret < 0) { 379 pr_err("PDR: %s get domain list txn wait failed: %d\n", 380 req->service_name, ret); 381 return ret; 382 } 383 384 if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { 385 pr_err("PDR: %s get domain list failed: 0x%x\n", 386 req->service_name, resp->resp.error); 387 return -EREMOTEIO; 388 } 389 390 return 0; 391 } 392 393 static int pdr_locate_service(struct pdr_handle *pdr, struct pdr_service *pds) 394 { 395 struct servreg_get_domain_list_resp *resp; 396 struct servreg_get_domain_list_req req; 397 struct servreg_location_entry *entry; 398 int domains_read = 0; 399 int ret, i; 400 401 resp = kzalloc(sizeof(*resp), GFP_KERNEL); 402 if (!resp) 403 return -ENOMEM; 404 405 /* Prepare req message */ 406 strcpy(req.service_name, pds->service_name); 407 req.domain_offset_valid = true; 408 req.domain_offset = 0; 409 410 do { 411 req.domain_offset = domains_read; 412 ret = pdr_get_domain_list(&req, resp, pdr); 413 if (ret < 0) 414 goto out; 415 416 for (i = domains_read; i < resp->domain_list_len; i++) { 417 entry = &resp->domain_list[i]; 418 419 if (strnlen(entry->name, sizeof(entry->name)) == sizeof(entry->name)) 420 continue; 421 422 if (!strcmp(entry->name, pds->service_path)) { 423 pds->service_data_valid = entry->service_data_valid; 424 pds->service_data = entry->service_data; 425 pds->instance = entry->instance; 426 goto out; 427 } 428 } 429 430 /* Update ret to indicate that the service is not yet found */ 431 ret = -ENXIO; 432 433 /* Always read total_domains from the response msg */ 434 if (resp->domain_list_len > resp->total_domains) 435 resp->domain_list_len = resp->total_domains; 436 437 domains_read += resp->domain_list_len; 438 } while (domains_read < resp->total_domains); 439 out: 440 kfree(resp); 441 return ret; 442 } 443 444 static void pdr_notify_lookup_failure(struct pdr_handle *pdr, 445 struct pdr_service *pds, 446 int err) 447 { 448 pr_err("PDR: service lookup for %s failed: %d\n", 449 pds->service_name, err); 450 451 if (err == -ENXIO) 452 return; 453 454 list_del(&pds->node); 455 pds->state = SERVREG_LOCATOR_ERR; 456 mutex_lock(&pdr->status_lock); 457 pdr->status(pds->state, pds->service_path, pdr->priv); 458 mutex_unlock(&pdr->status_lock); 459 kfree(pds); 460 } 461 462 static void pdr_locator_work(struct work_struct *work) 463 { 464 struct pdr_handle *pdr = container_of(work, struct pdr_handle, 465 locator_work); 466 struct pdr_service *pds, *tmp; 467 int ret = 0; 468 469 /* Bail out early if the SERVREG LOCATOR QMI service is not up */ 470 mutex_lock(&pdr->lock); 471 if (!pdr->locator_init_complete) { 472 mutex_unlock(&pdr->lock); 473 pr_debug("PDR: SERVICE LOCATOR service not available\n"); 474 return; 475 } 476 mutex_unlock(&pdr->lock); 477 478 mutex_lock(&pdr->list_lock); 479 list_for_each_entry_safe(pds, tmp, &pdr->lookups, node) { 480 if (!pds->need_locator_lookup) 481 continue; 482 483 ret = pdr_locate_service(pdr, pds); 484 if (ret < 0) { 485 pdr_notify_lookup_failure(pdr, pds, ret); 486 continue; 487 } 488 489 ret = qmi_add_lookup(&pdr->notifier_hdl, pds->service, 1, 490 pds->instance); 491 if (ret < 0) { 492 pdr_notify_lookup_failure(pdr, pds, ret); 493 continue; 494 } 495 496 pds->need_locator_lookup = false; 497 } 498 mutex_unlock(&pdr->list_lock); 499 } 500 501 /** 502 * pdr_add_lookup() - register a tracking request for a PD 503 * @pdr: PDR client handle 504 * @service_name: service name of the tracking request 505 * @service_path: service path of the tracking request 506 * 507 * Registering a pdr lookup allows for tracking the life cycle of the PD. 508 * 509 * Return: pdr_service object on success, ERR_PTR on failure. -EALREADY is 510 * returned if a lookup is already in progress for the given service path. 511 */ 512 struct pdr_service *pdr_add_lookup(struct pdr_handle *pdr, 513 const char *service_name, 514 const char *service_path) 515 { 516 struct pdr_service *pds, *tmp; 517 int ret; 518 519 if (IS_ERR_OR_NULL(pdr)) 520 return ERR_PTR(-EINVAL); 521 522 if (!service_name || strlen(service_name) > SERVREG_NAME_LENGTH || 523 !service_path || strlen(service_path) > SERVREG_NAME_LENGTH) 524 return ERR_PTR(-EINVAL); 525 526 pds = kzalloc(sizeof(*pds), GFP_KERNEL); 527 if (!pds) 528 return ERR_PTR(-ENOMEM); 529 530 pds->service = SERVREG_NOTIFIER_SERVICE; 531 strcpy(pds->service_name, service_name); 532 strcpy(pds->service_path, service_path); 533 pds->need_locator_lookup = true; 534 535 mutex_lock(&pdr->list_lock); 536 list_for_each_entry(tmp, &pdr->lookups, node) { 537 if (strcmp(tmp->service_path, service_path)) 538 continue; 539 540 mutex_unlock(&pdr->list_lock); 541 ret = -EALREADY; 542 goto err; 543 } 544 545 list_add(&pds->node, &pdr->lookups); 546 mutex_unlock(&pdr->list_lock); 547 548 schedule_work(&pdr->locator_work); 549 550 return pds; 551 err: 552 kfree(pds); 553 return ERR_PTR(ret); 554 } 555 EXPORT_SYMBOL(pdr_add_lookup); 556 557 /** 558 * pdr_restart_pd() - restart PD 559 * @pdr: PDR client handle 560 * @pds: PD service handle 561 * 562 * Restarts the PD tracked by the PDR client handle for a given service path. 563 * 564 * Return: 0 on success, negative errno on failure. 565 */ 566 int pdr_restart_pd(struct pdr_handle *pdr, struct pdr_service *pds) 567 { 568 struct servreg_restart_pd_resp resp; 569 struct servreg_restart_pd_req req; 570 struct sockaddr_qrtr addr; 571 struct pdr_service *tmp; 572 struct qmi_txn txn; 573 int ret; 574 575 if (IS_ERR_OR_NULL(pdr) || IS_ERR_OR_NULL(pds)) 576 return -EINVAL; 577 578 mutex_lock(&pdr->list_lock); 579 list_for_each_entry(tmp, &pdr->lookups, node) { 580 if (tmp != pds) 581 continue; 582 583 if (!pds->service_connected) 584 break; 585 586 /* Prepare req message */ 587 strcpy(req.service_path, pds->service_path); 588 addr = pds->addr; 589 break; 590 } 591 mutex_unlock(&pdr->list_lock); 592 593 if (!req.service_path[0]) 594 return -EINVAL; 595 596 ret = qmi_txn_init(&pdr->notifier_hdl, &txn, 597 servreg_restart_pd_resp_ei, 598 &resp); 599 if (ret < 0) 600 return ret; 601 602 ret = qmi_send_request(&pdr->notifier_hdl, &addr, 603 &txn, SERVREG_RESTART_PD_REQ, 604 SERVREG_RESTART_PD_REQ_MAX_LEN, 605 servreg_restart_pd_req_ei, &req); 606 if (ret < 0) { 607 qmi_txn_cancel(&txn); 608 return ret; 609 } 610 611 ret = qmi_txn_wait(&txn, 5 * HZ); 612 if (ret < 0) { 613 pr_err("PDR: %s PD restart txn wait failed: %d\n", 614 req.service_path, ret); 615 return ret; 616 } 617 618 /* Check response if PDR is disabled */ 619 if (resp.resp.result == QMI_RESULT_FAILURE_V01 && 620 resp.resp.error == QMI_ERR_DISABLED_V01) { 621 pr_err("PDR: %s PD restart is disabled: 0x%x\n", 622 req.service_path, resp.resp.error); 623 return -EOPNOTSUPP; 624 } 625 626 /* Check the response for other error case*/ 627 if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { 628 pr_err("PDR: %s request for PD restart failed: 0x%x\n", 629 req.service_path, resp.resp.error); 630 return -EREMOTEIO; 631 } 632 633 return 0; 634 } 635 EXPORT_SYMBOL(pdr_restart_pd); 636 637 /** 638 * pdr_handle_alloc() - initialize the PDR client handle 639 * @status: function to be called on PD state change 640 * @priv: handle for client's use 641 * 642 * Initializes the PDR client handle to allow for tracking/restart of PDs. 643 * 644 * Return: pdr_handle object on success, ERR_PTR on failure. 645 */ 646 struct pdr_handle *pdr_handle_alloc(void (*status)(int state, 647 char *service_path, 648 void *priv), void *priv) 649 { 650 struct pdr_handle *pdr; 651 int ret; 652 653 if (!status) 654 return ERR_PTR(-EINVAL); 655 656 pdr = kzalloc(sizeof(*pdr), GFP_KERNEL); 657 if (!pdr) 658 return ERR_PTR(-ENOMEM); 659 660 pdr->status = status; 661 pdr->priv = priv; 662 663 mutex_init(&pdr->status_lock); 664 mutex_init(&pdr->list_lock); 665 mutex_init(&pdr->lock); 666 667 INIT_LIST_HEAD(&pdr->lookups); 668 INIT_LIST_HEAD(&pdr->indack_list); 669 670 INIT_WORK(&pdr->locator_work, pdr_locator_work); 671 INIT_WORK(&pdr->notifier_work, pdr_notifier_work); 672 INIT_WORK(&pdr->indack_work, pdr_indack_work); 673 674 pdr->notifier_wq = create_singlethread_workqueue("pdr_notifier_wq"); 675 if (!pdr->notifier_wq) { 676 ret = -ENOMEM; 677 goto free_pdr_handle; 678 } 679 680 pdr->indack_wq = alloc_ordered_workqueue("pdr_indack_wq", WQ_HIGHPRI); 681 if (!pdr->indack_wq) { 682 ret = -ENOMEM; 683 goto destroy_notifier; 684 } 685 686 ret = qmi_handle_init(&pdr->locator_hdl, 687 SERVREG_GET_DOMAIN_LIST_RESP_MAX_LEN, 688 &pdr_locator_ops, NULL); 689 if (ret < 0) 690 goto destroy_indack; 691 692 ret = qmi_add_lookup(&pdr->locator_hdl, SERVREG_LOCATOR_SERVICE, 1, 1); 693 if (ret < 0) 694 goto release_qmi_handle; 695 696 ret = qmi_handle_init(&pdr->notifier_hdl, 697 SERVREG_STATE_UPDATED_IND_MAX_LEN, 698 &pdr_notifier_ops, 699 qmi_indication_handler); 700 if (ret < 0) 701 goto release_qmi_handle; 702 703 return pdr; 704 705 release_qmi_handle: 706 qmi_handle_release(&pdr->locator_hdl); 707 destroy_indack: 708 destroy_workqueue(pdr->indack_wq); 709 destroy_notifier: 710 destroy_workqueue(pdr->notifier_wq); 711 free_pdr_handle: 712 kfree(pdr); 713 714 return ERR_PTR(ret); 715 } 716 EXPORT_SYMBOL(pdr_handle_alloc); 717 718 /** 719 * pdr_handle_release() - release the PDR client handle 720 * @pdr: PDR client handle 721 * 722 * Cleans up pending tracking requests and releases the underlying qmi handles. 723 */ 724 void pdr_handle_release(struct pdr_handle *pdr) 725 { 726 struct pdr_service *pds, *tmp; 727 728 if (IS_ERR_OR_NULL(pdr)) 729 return; 730 731 mutex_lock(&pdr->list_lock); 732 list_for_each_entry_safe(pds, tmp, &pdr->lookups, node) { 733 list_del(&pds->node); 734 kfree(pds); 735 } 736 mutex_unlock(&pdr->list_lock); 737 738 cancel_work_sync(&pdr->locator_work); 739 cancel_work_sync(&pdr->notifier_work); 740 cancel_work_sync(&pdr->indack_work); 741 742 destroy_workqueue(pdr->notifier_wq); 743 destroy_workqueue(pdr->indack_wq); 744 745 qmi_handle_release(&pdr->locator_hdl); 746 qmi_handle_release(&pdr->notifier_hdl); 747 748 kfree(pdr); 749 } 750 EXPORT_SYMBOL(pdr_handle_release); 751 752 MODULE_LICENSE("GPL v2"); 753 MODULE_DESCRIPTION("Qualcomm Protection Domain Restart helpers"); 754