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