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(sizeof(*ind), GFP_KERNEL); 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(sizeof(*resp), 400 GFP_KERNEL); 401 if (!resp) 402 return -ENOMEM; 403 404 /* Prepare req message */ 405 strscpy(req.service_name, pds->service_name, sizeof(req.service_name)); 406 req.domain_offset_valid = true; 407 req.domain_offset = 0; 408 409 do { 410 req.domain_offset = domains_read; 411 ret = pdr_get_domain_list(&req, resp, pdr); 412 if (ret < 0) 413 return ret; 414 415 for (i = 0; i < resp->domain_list_len; i++) { 416 entry = &resp->domain_list[i]; 417 418 if (strnlen(entry->name, sizeof(entry->name)) == sizeof(entry->name)) 419 continue; 420 421 if (!strcmp(entry->name, pds->service_path)) { 422 pds->service_data_valid = entry->service_data_valid; 423 pds->service_data = entry->service_data; 424 pds->instance = entry->instance; 425 return 0; 426 } 427 } 428 429 /* Update ret to indicate that the service is not yet found */ 430 ret = -ENXIO; 431 432 /* Always read total_domains from the response msg */ 433 if (resp->domain_list_len > resp->total_domains) 434 resp->domain_list_len = resp->total_domains; 435 436 domains_read += resp->domain_list_len; 437 } while (domains_read < resp->total_domains); 438 439 return ret; 440 } 441 442 static void pdr_notify_lookup_failure(struct pdr_handle *pdr, 443 struct pdr_service *pds, 444 int err) 445 { 446 pr_err("PDR: service lookup for %s failed: %d\n", 447 pds->service_name, err); 448 449 if (err == -ENXIO) 450 return; 451 452 list_del(&pds->node); 453 pds->state = SERVREG_LOCATOR_ERR; 454 mutex_lock(&pdr->status_lock); 455 pdr->status(pds->state, pds->service_path, pdr->priv); 456 mutex_unlock(&pdr->status_lock); 457 kfree(pds); 458 } 459 460 static void pdr_locator_work(struct work_struct *work) 461 { 462 struct pdr_handle *pdr = container_of(work, struct pdr_handle, 463 locator_work); 464 struct pdr_service *pds, *tmp; 465 int ret = 0; 466 467 /* Bail out early if the SERVREG LOCATOR QMI service is not up */ 468 mutex_lock(&pdr->lock); 469 if (!pdr->locator_init_complete) { 470 mutex_unlock(&pdr->lock); 471 pr_debug("PDR: SERVICE LOCATOR service not available\n"); 472 return; 473 } 474 mutex_unlock(&pdr->lock); 475 476 mutex_lock(&pdr->list_lock); 477 list_for_each_entry_safe(pds, tmp, &pdr->lookups, node) { 478 if (!pds->need_locator_lookup) 479 continue; 480 481 ret = pdr_locate_service(pdr, pds); 482 if (ret < 0) { 483 pdr_notify_lookup_failure(pdr, pds, ret); 484 continue; 485 } 486 487 ret = qmi_add_lookup(&pdr->notifier_hdl, pds->service, 1, 488 pds->instance); 489 if (ret < 0) { 490 pdr_notify_lookup_failure(pdr, pds, ret); 491 continue; 492 } 493 494 pds->need_locator_lookup = false; 495 } 496 mutex_unlock(&pdr->list_lock); 497 } 498 499 /** 500 * pdr_add_lookup() - register a tracking request for a PD 501 * @pdr: PDR client handle 502 * @service_name: service name of the tracking request 503 * @service_path: service path of the tracking request 504 * 505 * Registering a pdr lookup allows for tracking the life cycle of the PD. 506 * 507 * Return: pdr_service object on success, ERR_PTR on failure. -EALREADY is 508 * returned if a lookup is already in progress for the given service path. 509 */ 510 struct pdr_service *pdr_add_lookup(struct pdr_handle *pdr, 511 const char *service_name, 512 const char *service_path) 513 { 514 struct pdr_service *tmp; 515 516 if (IS_ERR_OR_NULL(pdr)) 517 return ERR_PTR(-EINVAL); 518 519 if (!service_name || strlen(service_name) > SERVREG_NAME_LENGTH || 520 !service_path || strlen(service_path) > SERVREG_NAME_LENGTH) 521 return ERR_PTR(-EINVAL); 522 523 struct pdr_service *pds __free(kfree) = kzalloc(sizeof(*pds), GFP_KERNEL); 524 if (!pds) 525 return ERR_PTR(-ENOMEM); 526 527 pds->service = SERVREG_NOTIFIER_SERVICE; 528 strscpy(pds->service_name, service_name, sizeof(pds->service_name)); 529 strscpy(pds->service_path, service_path, sizeof(pds->service_path)); 530 pds->need_locator_lookup = true; 531 532 mutex_lock(&pdr->list_lock); 533 list_for_each_entry(tmp, &pdr->lookups, node) { 534 if (strcmp(tmp->service_path, service_path)) 535 continue; 536 537 mutex_unlock(&pdr->list_lock); 538 return ERR_PTR(-EALREADY); 539 } 540 541 list_add(&pds->node, &pdr->lookups); 542 mutex_unlock(&pdr->list_lock); 543 544 schedule_work(&pdr->locator_work); 545 546 return_ptr(pds); 547 } 548 EXPORT_SYMBOL_GPL(pdr_add_lookup); 549 550 /** 551 * pdr_restart_pd() - restart PD 552 * @pdr: PDR client handle 553 * @pds: PD service handle 554 * 555 * Restarts the PD tracked by the PDR client handle for a given service path. 556 * 557 * Return: 0 on success, negative errno on failure. 558 */ 559 int pdr_restart_pd(struct pdr_handle *pdr, struct pdr_service *pds) 560 { 561 struct servreg_restart_pd_resp resp; 562 struct servreg_restart_pd_req req = { 0 }; 563 struct sockaddr_qrtr addr; 564 struct pdr_service *tmp; 565 struct qmi_txn txn; 566 int ret; 567 568 if (IS_ERR_OR_NULL(pdr) || IS_ERR_OR_NULL(pds)) 569 return -EINVAL; 570 571 mutex_lock(&pdr->list_lock); 572 list_for_each_entry(tmp, &pdr->lookups, node) { 573 if (tmp != pds) 574 continue; 575 576 if (!pds->service_connected) 577 break; 578 579 /* Prepare req message */ 580 strscpy(req.service_path, pds->service_path, sizeof(req.service_path)); 581 addr = pds->addr; 582 break; 583 } 584 mutex_unlock(&pdr->list_lock); 585 586 if (!req.service_path[0]) 587 return -EINVAL; 588 589 ret = qmi_txn_init(&pdr->notifier_hdl, &txn, 590 servreg_restart_pd_resp_ei, 591 &resp); 592 if (ret < 0) 593 return ret; 594 595 ret = qmi_send_request(&pdr->notifier_hdl, &addr, 596 &txn, SERVREG_RESTART_PD_REQ, 597 SERVREG_RESTART_PD_REQ_MAX_LEN, 598 servreg_restart_pd_req_ei, &req); 599 if (ret < 0) { 600 qmi_txn_cancel(&txn); 601 return ret; 602 } 603 604 ret = qmi_txn_wait(&txn, 5 * HZ); 605 if (ret < 0) { 606 pr_err("PDR: %s PD restart txn wait failed: %d\n", 607 req.service_path, ret); 608 return ret; 609 } 610 611 /* Check response if PDR is disabled */ 612 if (resp.resp.result == QMI_RESULT_FAILURE_V01 && 613 resp.resp.error == QMI_ERR_DISABLED_V01) { 614 pr_err("PDR: %s PD restart is disabled: 0x%x\n", 615 req.service_path, resp.resp.error); 616 return -EOPNOTSUPP; 617 } 618 619 /* Check the response for other error case*/ 620 if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { 621 pr_err("PDR: %s request for PD restart failed: 0x%x\n", 622 req.service_path, resp.resp.error); 623 return -EREMOTEIO; 624 } 625 626 return 0; 627 } 628 EXPORT_SYMBOL_GPL(pdr_restart_pd); 629 630 /** 631 * pdr_handle_alloc() - initialize the PDR client handle 632 * @status: function to be called on PD state change 633 * @priv: handle for client's use 634 * 635 * Initializes the PDR client handle to allow for tracking/restart of PDs. 636 * 637 * Return: pdr_handle object on success, ERR_PTR on failure. 638 */ 639 struct pdr_handle *pdr_handle_alloc(void (*status)(int state, 640 char *service_path, 641 void *priv), void *priv) 642 { 643 int ret; 644 645 if (!status) 646 return ERR_PTR(-EINVAL); 647 648 struct pdr_handle *pdr __free(kfree) = kzalloc(sizeof(*pdr), GFP_KERNEL); 649 if (!pdr) 650 return ERR_PTR(-ENOMEM); 651 652 pdr->status = status; 653 pdr->priv = priv; 654 655 mutex_init(&pdr->status_lock); 656 mutex_init(&pdr->list_lock); 657 mutex_init(&pdr->lock); 658 659 INIT_LIST_HEAD(&pdr->lookups); 660 INIT_LIST_HEAD(&pdr->indack_list); 661 662 INIT_WORK(&pdr->locator_work, pdr_locator_work); 663 INIT_WORK(&pdr->notifier_work, pdr_notifier_work); 664 INIT_WORK(&pdr->indack_work, pdr_indack_work); 665 666 pdr->notifier_wq = create_singlethread_workqueue("pdr_notifier_wq"); 667 if (!pdr->notifier_wq) 668 return ERR_PTR(-ENOMEM); 669 670 pdr->indack_wq = alloc_ordered_workqueue("pdr_indack_wq", WQ_HIGHPRI); 671 if (!pdr->indack_wq) { 672 ret = -ENOMEM; 673 goto destroy_notifier; 674 } 675 676 ret = qmi_handle_init(&pdr->locator_hdl, 677 SERVREG_GET_DOMAIN_LIST_RESP_MAX_LEN, 678 &pdr_locator_ops, NULL); 679 if (ret < 0) 680 goto destroy_indack; 681 682 ret = qmi_add_lookup(&pdr->locator_hdl, SERVREG_LOCATOR_SERVICE, 1, 1); 683 if (ret < 0) 684 goto release_qmi_handle; 685 686 ret = qmi_handle_init(&pdr->notifier_hdl, 687 SERVREG_STATE_UPDATED_IND_MAX_LEN, 688 &pdr_notifier_ops, 689 qmi_indication_handler); 690 if (ret < 0) 691 goto release_qmi_handle; 692 693 return_ptr(pdr); 694 695 release_qmi_handle: 696 qmi_handle_release(&pdr->locator_hdl); 697 destroy_indack: 698 destroy_workqueue(pdr->indack_wq); 699 destroy_notifier: 700 destroy_workqueue(pdr->notifier_wq); 701 702 return ERR_PTR(ret); 703 } 704 EXPORT_SYMBOL_GPL(pdr_handle_alloc); 705 706 /** 707 * pdr_handle_release() - release the PDR client handle 708 * @pdr: PDR client handle 709 * 710 * Cleans up pending tracking requests and releases the underlying qmi handles. 711 */ 712 void pdr_handle_release(struct pdr_handle *pdr) 713 { 714 struct pdr_service *pds, *tmp; 715 716 if (IS_ERR_OR_NULL(pdr)) 717 return; 718 719 mutex_lock(&pdr->list_lock); 720 list_for_each_entry_safe(pds, tmp, &pdr->lookups, node) { 721 list_del(&pds->node); 722 kfree(pds); 723 } 724 mutex_unlock(&pdr->list_lock); 725 726 cancel_work_sync(&pdr->locator_work); 727 cancel_work_sync(&pdr->notifier_work); 728 cancel_work_sync(&pdr->indack_work); 729 730 destroy_workqueue(pdr->notifier_wq); 731 destroy_workqueue(pdr->indack_wq); 732 733 qmi_handle_release(&pdr->locator_hdl); 734 qmi_handle_release(&pdr->notifier_hdl); 735 736 kfree(pdr); 737 } 738 EXPORT_SYMBOL_GPL(pdr_handle_release); 739 740 MODULE_LICENSE("GPL v2"); 741 MODULE_DESCRIPTION("Qualcomm Protection Domain Restart helpers"); 742