File Index
Files and directories
Sign in
FILE

pdr_interface.c

File preview with syntax highlighting.

linux-6.19.14/drivers/soc/qcom/pdr_interface.c C
  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
 15struct 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
 35struct 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
 66struct 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
 73static 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
 94static 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
108static const struct qmi_ops pdr_locator_ops = {
109	.new_server = pdr_locator_new_server,
110	.del_server = pdr_locator_del_server,
111};
112
113static 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
159static 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
191static 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
215static 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
236static const struct qmi_ops pdr_notifier_ops = {
237	.new_server = pdr_notifier_new_server,
238	.del_server = pdr_notifier_del_server,
239};
240
241static 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
268static 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
294static 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
340static 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
351static 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
392static 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
442static 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
460static 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 */
510struct 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}
548EXPORT_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 */
559int 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}
628EXPORT_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 */
639struct 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
695release_qmi_handle:
696	qmi_handle_release(&pdr->locator_hdl);
697destroy_indack:
698	destroy_workqueue(pdr->indack_wq);
699destroy_notifier:
700	destroy_workqueue(pdr->notifier_wq);
701
702	return ERR_PTR(ret);
703}
704EXPORT_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 */
712void 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}
738EXPORT_SYMBOL_GPL(pdr_handle_release);
739
740MODULE_LICENSE("GPL v2");
741MODULE_DESCRIPTION("Qualcomm Protection Domain Restart helpers");
~/ Read-only file preview.