pdr_interface.c (18692B)
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 14struct 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 34struct 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 65struct 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 72static 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 99static 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 113static const struct qmi_ops pdr_locator_ops = { 114 .new_server = pdr_locator_new_server, 115 .del_server = pdr_locator_del_server, 116}; 117 118static 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 strscpy(req.service_path, pds->service_path, sizeof(req.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 -EREMOTEIO; 157 } 158 159 pds->state = resp.curr_state; 160 161 return 0; 162} 163 164static 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 196static 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 220static 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 241static const struct qmi_ops pdr_notifier_ops = { 242 .new_server = pdr_notifier_new_server, 243 .del_server = pdr_notifier_del_server, 244}; 245 246static 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 strscpy(req.service_path, pds->service_path, sizeof(req.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 273static 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 299static 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 = NULL, *iter; 308 309 if (!ind_msg || !ind_msg->service_path[0] || 310 strlen(ind_msg->service_path) > SERVREG_NAME_LENGTH) 311 return; 312 313 mutex_lock(&pdr->list_lock); 314 list_for_each_entry(iter, &pdr->lookups, node) { 315 if (strcmp(iter->service_path, ind_msg->service_path)) 316 continue; 317 318 pds = iter; 319 break; 320 } 321 mutex_unlock(&pdr->list_lock); 322 323 if (!pds) 324 return; 325 326 pr_info("PDR: Indication received from %s, state: 0x%x, trans-id: %d\n", 327 ind_msg->service_path, ind_msg->curr_state, 328 ind_msg->transaction_id); 329 330 ind = kzalloc(sizeof(*ind), GFP_KERNEL); 331 if (!ind) 332 return; 333 334 ind->transaction_id = ind_msg->transaction_id; 335 ind->curr_state = ind_msg->curr_state; 336 ind->pds = pds; 337 338 mutex_lock(&pdr->list_lock); 339 list_add_tail(&ind->node, &pdr->indack_list); 340 mutex_unlock(&pdr->list_lock); 341 342 queue_work(pdr->indack_wq, &pdr->indack_work); 343} 344 345static const struct qmi_msg_handler qmi_indication_handler[] = { 346 { 347 .type = QMI_INDICATION, 348 .msg_id = SERVREG_STATE_UPDATED_IND_ID, 349 .ei = servreg_state_updated_ind_ei, 350 .decoded_size = sizeof(struct servreg_state_updated_ind), 351 .fn = pdr_indication_cb, 352 }, 353 {} 354}; 355 356static int pdr_get_domain_list(struct servreg_get_domain_list_req *req, 357 struct servreg_get_domain_list_resp *resp, 358 struct pdr_handle *pdr) 359{ 360 struct qmi_txn txn; 361 int ret; 362 363 ret = qmi_txn_init(&pdr->locator_hdl, &txn, 364 servreg_get_domain_list_resp_ei, resp); 365 if (ret < 0) 366 return ret; 367 368 ret = qmi_send_request(&pdr->locator_hdl, 369 &pdr->locator_addr, 370 &txn, SERVREG_GET_DOMAIN_LIST_REQ, 371 SERVREG_GET_DOMAIN_LIST_REQ_MAX_LEN, 372 servreg_get_domain_list_req_ei, 373 req); 374 if (ret < 0) { 375 qmi_txn_cancel(&txn); 376 return ret; 377 } 378 379 ret = qmi_txn_wait(&txn, 5 * HZ); 380 if (ret < 0) { 381 pr_err("PDR: %s get domain list txn wait failed: %d\n", 382 req->service_name, ret); 383 return ret; 384 } 385 386 if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { 387 pr_err("PDR: %s get domain list failed: 0x%x\n", 388 req->service_name, resp->resp.error); 389 return -EREMOTEIO; 390 } 391 392 return 0; 393} 394 395static int pdr_locate_service(struct pdr_handle *pdr, struct pdr_service *pds) 396{ 397 struct servreg_get_domain_list_resp *resp; 398 struct servreg_get_domain_list_req req; 399 struct servreg_location_entry *entry; 400 int domains_read = 0; 401 int ret, i; 402 403 resp = kzalloc(sizeof(*resp), GFP_KERNEL); 404 if (!resp) 405 return -ENOMEM; 406 407 /* Prepare req message */ 408 strscpy(req.service_name, pds->service_name, sizeof(req.service_name)); 409 req.domain_offset_valid = true; 410 req.domain_offset = 0; 411 412 do { 413 req.domain_offset = domains_read; 414 ret = pdr_get_domain_list(&req, resp, pdr); 415 if (ret < 0) 416 goto out; 417 418 for (i = domains_read; i < resp->domain_list_len; i++) { 419 entry = &resp->domain_list[i]; 420 421 if (strnlen(entry->name, sizeof(entry->name)) == sizeof(entry->name)) 422 continue; 423 424 if (!strcmp(entry->name, pds->service_path)) { 425 pds->service_data_valid = entry->service_data_valid; 426 pds->service_data = entry->service_data; 427 pds->instance = entry->instance; 428 goto out; 429 } 430 } 431 432 /* Update ret to indicate that the service is not yet found */ 433 ret = -ENXIO; 434 435 /* Always read total_domains from the response msg */ 436 if (resp->domain_list_len > resp->total_domains) 437 resp->domain_list_len = resp->total_domains; 438 439 domains_read += resp->domain_list_len; 440 } while (domains_read < resp->total_domains); 441out: 442 kfree(resp); 443 return ret; 444} 445 446static void pdr_notify_lookup_failure(struct pdr_handle *pdr, 447 struct pdr_service *pds, 448 int err) 449{ 450 pr_err("PDR: service lookup for %s failed: %d\n", 451 pds->service_name, err); 452 453 if (err == -ENXIO) 454 return; 455 456 list_del(&pds->node); 457 pds->state = SERVREG_LOCATOR_ERR; 458 mutex_lock(&pdr->status_lock); 459 pdr->status(pds->state, pds->service_path, pdr->priv); 460 mutex_unlock(&pdr->status_lock); 461 kfree(pds); 462} 463 464static void pdr_locator_work(struct work_struct *work) 465{ 466 struct pdr_handle *pdr = container_of(work, struct pdr_handle, 467 locator_work); 468 struct pdr_service *pds, *tmp; 469 int ret = 0; 470 471 /* Bail out early if the SERVREG LOCATOR QMI service is not up */ 472 mutex_lock(&pdr->lock); 473 if (!pdr->locator_init_complete) { 474 mutex_unlock(&pdr->lock); 475 pr_debug("PDR: SERVICE LOCATOR service not available\n"); 476 return; 477 } 478 mutex_unlock(&pdr->lock); 479 480 mutex_lock(&pdr->list_lock); 481 list_for_each_entry_safe(pds, tmp, &pdr->lookups, node) { 482 if (!pds->need_locator_lookup) 483 continue; 484 485 ret = pdr_locate_service(pdr, pds); 486 if (ret < 0) { 487 pdr_notify_lookup_failure(pdr, pds, ret); 488 continue; 489 } 490 491 ret = qmi_add_lookup(&pdr->notifier_hdl, pds->service, 1, 492 pds->instance); 493 if (ret < 0) { 494 pdr_notify_lookup_failure(pdr, pds, ret); 495 continue; 496 } 497 498 pds->need_locator_lookup = false; 499 } 500 mutex_unlock(&pdr->list_lock); 501} 502 503/** 504 * pdr_add_lookup() - register a tracking request for a PD 505 * @pdr: PDR client handle 506 * @service_name: service name of the tracking request 507 * @service_path: service path of the tracking request 508 * 509 * Registering a pdr lookup allows for tracking the life cycle of the PD. 510 * 511 * Return: pdr_service object on success, ERR_PTR on failure. -EALREADY is 512 * returned if a lookup is already in progress for the given service path. 513 */ 514struct pdr_service *pdr_add_lookup(struct pdr_handle *pdr, 515 const char *service_name, 516 const char *service_path) 517{ 518 struct pdr_service *pds, *tmp; 519 int ret; 520 521 if (IS_ERR_OR_NULL(pdr)) 522 return ERR_PTR(-EINVAL); 523 524 if (!service_name || strlen(service_name) > SERVREG_NAME_LENGTH || 525 !service_path || strlen(service_path) > SERVREG_NAME_LENGTH) 526 return ERR_PTR(-EINVAL); 527 528 pds = kzalloc(sizeof(*pds), GFP_KERNEL); 529 if (!pds) 530 return ERR_PTR(-ENOMEM); 531 532 pds->service = SERVREG_NOTIFIER_SERVICE; 533 strscpy(pds->service_name, service_name, sizeof(pds->service_name)); 534 strscpy(pds->service_path, service_path, sizeof(pds->service_path)); 535 pds->need_locator_lookup = true; 536 537 mutex_lock(&pdr->list_lock); 538 list_for_each_entry(tmp, &pdr->lookups, node) { 539 if (strcmp(tmp->service_path, service_path)) 540 continue; 541 542 mutex_unlock(&pdr->list_lock); 543 ret = -EALREADY; 544 goto err; 545 } 546 547 list_add(&pds->node, &pdr->lookups); 548 mutex_unlock(&pdr->list_lock); 549 550 schedule_work(&pdr->locator_work); 551 552 return pds; 553err: 554 kfree(pds); 555 return ERR_PTR(ret); 556} 557EXPORT_SYMBOL(pdr_add_lookup); 558 559/** 560 * pdr_restart_pd() - restart PD 561 * @pdr: PDR client handle 562 * @pds: PD service handle 563 * 564 * Restarts the PD tracked by the PDR client handle for a given service path. 565 * 566 * Return: 0 on success, negative errno on failure. 567 */ 568int pdr_restart_pd(struct pdr_handle *pdr, struct pdr_service *pds) 569{ 570 struct servreg_restart_pd_resp resp; 571 struct servreg_restart_pd_req req = { 0 }; 572 struct sockaddr_qrtr addr; 573 struct pdr_service *tmp; 574 struct qmi_txn txn; 575 int ret; 576 577 if (IS_ERR_OR_NULL(pdr) || IS_ERR_OR_NULL(pds)) 578 return -EINVAL; 579 580 mutex_lock(&pdr->list_lock); 581 list_for_each_entry(tmp, &pdr->lookups, node) { 582 if (tmp != pds) 583 continue; 584 585 if (!pds->service_connected) 586 break; 587 588 /* Prepare req message */ 589 strscpy(req.service_path, pds->service_path, sizeof(req.service_path)); 590 addr = pds->addr; 591 break; 592 } 593 mutex_unlock(&pdr->list_lock); 594 595 if (!req.service_path[0]) 596 return -EINVAL; 597 598 ret = qmi_txn_init(&pdr->notifier_hdl, &txn, 599 servreg_restart_pd_resp_ei, 600 &resp); 601 if (ret < 0) 602 return ret; 603 604 ret = qmi_send_request(&pdr->notifier_hdl, &addr, 605 &txn, SERVREG_RESTART_PD_REQ, 606 SERVREG_RESTART_PD_REQ_MAX_LEN, 607 servreg_restart_pd_req_ei, &req); 608 if (ret < 0) { 609 qmi_txn_cancel(&txn); 610 return ret; 611 } 612 613 ret = qmi_txn_wait(&txn, 5 * HZ); 614 if (ret < 0) { 615 pr_err("PDR: %s PD restart txn wait failed: %d\n", 616 req.service_path, ret); 617 return ret; 618 } 619 620 /* Check response if PDR is disabled */ 621 if (resp.resp.result == QMI_RESULT_FAILURE_V01 && 622 resp.resp.error == QMI_ERR_DISABLED_V01) { 623 pr_err("PDR: %s PD restart is disabled: 0x%x\n", 624 req.service_path, resp.resp.error); 625 return -EOPNOTSUPP; 626 } 627 628 /* Check the response for other error case*/ 629 if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { 630 pr_err("PDR: %s request for PD restart failed: 0x%x\n", 631 req.service_path, resp.resp.error); 632 return -EREMOTEIO; 633 } 634 635 return 0; 636} 637EXPORT_SYMBOL(pdr_restart_pd); 638 639/** 640 * pdr_handle_alloc() - initialize the PDR client handle 641 * @status: function to be called on PD state change 642 * @priv: handle for client's use 643 * 644 * Initializes the PDR client handle to allow for tracking/restart of PDs. 645 * 646 * Return: pdr_handle object on success, ERR_PTR on failure. 647 */ 648struct pdr_handle *pdr_handle_alloc(void (*status)(int state, 649 char *service_path, 650 void *priv), void *priv) 651{ 652 struct pdr_handle *pdr; 653 int ret; 654 655 if (!status) 656 return ERR_PTR(-EINVAL); 657 658 pdr = kzalloc(sizeof(*pdr), GFP_KERNEL); 659 if (!pdr) 660 return ERR_PTR(-ENOMEM); 661 662 pdr->status = status; 663 pdr->priv = priv; 664 665 mutex_init(&pdr->status_lock); 666 mutex_init(&pdr->list_lock); 667 mutex_init(&pdr->lock); 668 669 INIT_LIST_HEAD(&pdr->lookups); 670 INIT_LIST_HEAD(&pdr->indack_list); 671 672 INIT_WORK(&pdr->locator_work, pdr_locator_work); 673 INIT_WORK(&pdr->notifier_work, pdr_notifier_work); 674 INIT_WORK(&pdr->indack_work, pdr_indack_work); 675 676 pdr->notifier_wq = create_singlethread_workqueue("pdr_notifier_wq"); 677 if (!pdr->notifier_wq) { 678 ret = -ENOMEM; 679 goto free_pdr_handle; 680 } 681 682 pdr->indack_wq = alloc_ordered_workqueue("pdr_indack_wq", WQ_HIGHPRI); 683 if (!pdr->indack_wq) { 684 ret = -ENOMEM; 685 goto destroy_notifier; 686 } 687 688 ret = qmi_handle_init(&pdr->locator_hdl, 689 SERVREG_GET_DOMAIN_LIST_RESP_MAX_LEN, 690 &pdr_locator_ops, NULL); 691 if (ret < 0) 692 goto destroy_indack; 693 694 ret = qmi_add_lookup(&pdr->locator_hdl, SERVREG_LOCATOR_SERVICE, 1, 1); 695 if (ret < 0) 696 goto release_qmi_handle; 697 698 ret = qmi_handle_init(&pdr->notifier_hdl, 699 SERVREG_STATE_UPDATED_IND_MAX_LEN, 700 &pdr_notifier_ops, 701 qmi_indication_handler); 702 if (ret < 0) 703 goto release_qmi_handle; 704 705 return pdr; 706 707release_qmi_handle: 708 qmi_handle_release(&pdr->locator_hdl); 709destroy_indack: 710 destroy_workqueue(pdr->indack_wq); 711destroy_notifier: 712 destroy_workqueue(pdr->notifier_wq); 713free_pdr_handle: 714 kfree(pdr); 715 716 return ERR_PTR(ret); 717} 718EXPORT_SYMBOL(pdr_handle_alloc); 719 720/** 721 * pdr_handle_release() - release the PDR client handle 722 * @pdr: PDR client handle 723 * 724 * Cleans up pending tracking requests and releases the underlying qmi handles. 725 */ 726void pdr_handle_release(struct pdr_handle *pdr) 727{ 728 struct pdr_service *pds, *tmp; 729 730 if (IS_ERR_OR_NULL(pdr)) 731 return; 732 733 mutex_lock(&pdr->list_lock); 734 list_for_each_entry_safe(pds, tmp, &pdr->lookups, node) { 735 list_del(&pds->node); 736 kfree(pds); 737 } 738 mutex_unlock(&pdr->list_lock); 739 740 cancel_work_sync(&pdr->locator_work); 741 cancel_work_sync(&pdr->notifier_work); 742 cancel_work_sync(&pdr->indack_work); 743 744 destroy_workqueue(pdr->notifier_wq); 745 destroy_workqueue(pdr->indack_wq); 746 747 qmi_handle_release(&pdr->locator_hdl); 748 qmi_handle_release(&pdr->notifier_hdl); 749 750 kfree(pdr); 751} 752EXPORT_SYMBOL(pdr_handle_release); 753 754MODULE_LICENSE("GPL v2"); 755MODULE_DESCRIPTION("Qualcomm Protection Domain Restart helpers");