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