Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
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
13struct 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
33struct 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
64struct 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
71static 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
98static 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
112static struct qmi_ops pdr_locator_ops = {
113 .new_server = pdr_locator_new_server,
114 .del_server = pdr_locator_del_server,
115};
116
117static 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
163static 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
195static 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
219static 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
240static struct qmi_ops pdr_notifier_ops = {
241 .new_server = pdr_notifier_new_server,
242 .del_server = pdr_notifier_del_server,
243};
244
245static 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
272static 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
296static 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
343static 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
354static 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
393static 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);
439out:
440 kfree(resp);
441 return ret;
442}
443
444static 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
462static 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 */
512struct 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;
551err:
552 kfree(pds);
553 return ERR_PTR(ret);
554}
555EXPORT_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 */
566int 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}
635EXPORT_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 */
646struct 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
705release_qmi_handle:
706 qmi_handle_release(&pdr->locator_hdl);
707destroy_indack:
708 destroy_workqueue(pdr->indack_wq);
709destroy_notifier:
710 destroy_workqueue(pdr->notifier_wq);
711free_pdr_handle:
712 kfree(pdr);
713
714 return ERR_PTR(ret);
715}
716EXPORT_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 */
724void 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}
750EXPORT_SYMBOL(pdr_handle_release);
751
752MODULE_LICENSE("GPL v2");
753MODULE_DESCRIPTION("Qualcomm Protection Domain Restart helpers");