2020-03-12 15:08:40 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright ( C ) 2020 The Linux Foundation . All rights reserved .
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/string.h>
# include <linux/workqueue.h>
# include "pdr_internal.h"
struct pdr_service {
char service_name [ SERVREG_NAME_LENGTH + 1 ] ;
char service_path [ SERVREG_NAME_LENGTH + 1 ] ;
struct sockaddr_qrtr addr ;
unsigned int instance ;
unsigned int service ;
u8 service_data_valid ;
u32 service_data ;
int state ;
bool need_notifier_register ;
bool need_notifier_remove ;
bool need_locator_lookup ;
bool service_connected ;
struct list_head node ;
} ;
struct pdr_handle {
struct qmi_handle locator_hdl ;
struct qmi_handle notifier_hdl ;
struct sockaddr_qrtr locator_addr ;
struct list_head lookups ;
struct list_head indack_list ;
/* control access to pdr lookup/indack lists */
struct mutex list_lock ;
/* serialize pd status invocation */
struct mutex status_lock ;
/* control access to the locator state */
struct mutex lock ;
bool locator_init_complete ;
struct work_struct locator_work ;
struct work_struct notifier_work ;
struct work_struct indack_work ;
struct workqueue_struct * notifier_wq ;
struct workqueue_struct * indack_wq ;
void ( * status ) ( int state , char * service_path , void * priv ) ;
void * priv ;
} ;
struct pdr_list_node {
enum servreg_service_state curr_state ;
u16 transaction_id ;
struct pdr_service * pds ;
struct list_head node ;
} ;
static int pdr_locator_new_server ( struct qmi_handle * qmi ,
struct qmi_service * svc )
{
struct pdr_handle * pdr = container_of ( qmi , struct pdr_handle ,
locator_hdl ) ;
struct pdr_service * pds ;
/* Create a local client port for QMI communication */
pdr - > locator_addr . sq_family = AF_QIPCRTR ;
pdr - > locator_addr . sq_node = svc - > node ;
pdr - > locator_addr . sq_port = svc - > port ;
mutex_lock ( & pdr - > lock ) ;
pdr - > locator_init_complete = true ;
mutex_unlock ( & pdr - > lock ) ;
/* Service pending lookup requests */
mutex_lock ( & pdr - > list_lock ) ;
list_for_each_entry ( pds , & pdr - > lookups , node ) {
if ( pds - > need_locator_lookup )
schedule_work ( & pdr - > locator_work ) ;
}
mutex_unlock ( & pdr - > list_lock ) ;
return 0 ;
}
static void pdr_locator_del_server ( struct qmi_handle * qmi ,
struct qmi_service * svc )
{
struct pdr_handle * pdr = container_of ( qmi , struct pdr_handle ,
locator_hdl ) ;
mutex_lock ( & pdr - > lock ) ;
pdr - > locator_init_complete = false ;
mutex_unlock ( & pdr - > lock ) ;
pdr - > locator_addr . sq_node = 0 ;
pdr - > locator_addr . sq_port = 0 ;
}
static struct qmi_ops pdr_locator_ops = {
. new_server = pdr_locator_new_server ,
. del_server = pdr_locator_del_server ,
} ;
static int pdr_register_listener ( struct pdr_handle * pdr ,
struct pdr_service * pds ,
bool enable )
{
struct servreg_register_listener_resp resp ;
struct servreg_register_listener_req req ;
struct qmi_txn txn ;
int ret ;
ret = qmi_txn_init ( & pdr - > notifier_hdl , & txn ,
servreg_register_listener_resp_ei ,
& resp ) ;
if ( ret < 0 )
return ret ;
req . enable = enable ;
strcpy ( req . service_path , pds - > service_path ) ;
ret = qmi_send_request ( & pdr - > notifier_hdl , & pds - > addr ,
& txn , SERVREG_REGISTER_LISTENER_REQ ,
SERVREG_REGISTER_LISTENER_REQ_LEN ,
servreg_register_listener_req_ei ,
& req ) ;
if ( ret < 0 ) {
qmi_txn_cancel ( & txn ) ;
return ret ;
}
ret = qmi_txn_wait ( & txn , 5 * HZ ) ;
if ( ret < 0 ) {
pr_err ( " PDR: %s register listener txn wait failed: %d \n " ,
pds - > service_path , ret ) ;
return ret ;
}
if ( resp . resp . result ! = QMI_RESULT_SUCCESS_V01 ) {
pr_err ( " PDR: %s register listener failed: 0x%x \n " ,
pds - > service_path , resp . resp . error ) ;
return ret ;
}
if ( ( int ) resp . curr_state < INT_MIN | | ( int ) resp . curr_state > INT_MAX )
pr_err ( " PDR: %s notification state invalid: 0x%x \n " ,
pds - > service_path , resp . curr_state ) ;
pds - > state = resp . curr_state ;
return 0 ;
}
static void pdr_notifier_work ( struct work_struct * work )
{
struct pdr_handle * pdr = container_of ( work , struct pdr_handle ,
notifier_work ) ;
struct pdr_service * pds ;
int ret ;
mutex_lock ( & pdr - > list_lock ) ;
list_for_each_entry ( pds , & pdr - > lookups , node ) {
if ( pds - > service_connected ) {
if ( ! pds - > need_notifier_register )
continue ;
pds - > need_notifier_register = false ;
ret = pdr_register_listener ( pdr , pds , true ) ;
if ( ret < 0 )
pds - > state = SERVREG_SERVICE_STATE_DOWN ;
} else {
if ( ! pds - > need_notifier_remove )
continue ;
pds - > need_notifier_remove = false ;
pds - > state = SERVREG_SERVICE_STATE_DOWN ;
}
mutex_lock ( & pdr - > status_lock ) ;
pdr - > status ( pds - > state , pds - > service_path , pdr - > priv ) ;
mutex_unlock ( & pdr - > status_lock ) ;
}
mutex_unlock ( & pdr - > list_lock ) ;
}
static int pdr_notifier_new_server ( struct qmi_handle * qmi ,
struct qmi_service * svc )
{
struct pdr_handle * pdr = container_of ( qmi , struct pdr_handle ,
notifier_hdl ) ;
struct pdr_service * pds ;
mutex_lock ( & pdr - > list_lock ) ;
list_for_each_entry ( pds , & pdr - > lookups , node ) {
if ( pds - > service = = svc - > service & &
pds - > instance = = svc - > instance ) {
pds - > service_connected = true ;
pds - > need_notifier_register = true ;
pds - > addr . sq_family = AF_QIPCRTR ;
pds - > addr . sq_node = svc - > node ;
pds - > addr . sq_port = svc - > port ;
queue_work ( pdr - > notifier_wq , & pdr - > notifier_work ) ;
}
}
mutex_unlock ( & pdr - > list_lock ) ;
return 0 ;
}
static void pdr_notifier_del_server ( struct qmi_handle * qmi ,
struct qmi_service * svc )
{
struct pdr_handle * pdr = container_of ( qmi , struct pdr_handle ,
notifier_hdl ) ;
struct pdr_service * pds ;
mutex_lock ( & pdr - > list_lock ) ;
list_for_each_entry ( pds , & pdr - > lookups , node ) {
if ( pds - > service = = svc - > service & &
pds - > instance = = svc - > instance ) {
pds - > service_connected = false ;
pds - > need_notifier_remove = true ;
pds - > addr . sq_node = 0 ;
pds - > addr . sq_port = 0 ;
queue_work ( pdr - > notifier_wq , & pdr - > notifier_work ) ;
}
}
mutex_unlock ( & pdr - > list_lock ) ;
}
static struct qmi_ops pdr_notifier_ops = {
. new_server = pdr_notifier_new_server ,
. del_server = pdr_notifier_del_server ,
} ;
static int pdr_send_indack_msg ( struct pdr_handle * pdr , struct pdr_service * pds ,
u16 tid )
{
struct servreg_set_ack_resp resp ;
struct servreg_set_ack_req req ;
struct qmi_txn txn ;
int ret ;
ret = qmi_txn_init ( & pdr - > notifier_hdl , & txn , servreg_set_ack_resp_ei ,
& resp ) ;
if ( ret < 0 )
return ret ;
req . transaction_id = tid ;
strcpy ( req . service_path , pds - > service_path ) ;
ret = qmi_send_request ( & pdr - > notifier_hdl , & pds - > addr ,
& txn , SERVREG_SET_ACK_REQ ,
SERVREG_SET_ACK_REQ_LEN ,
servreg_set_ack_req_ei ,
& req ) ;
/* Skip waiting for response */
qmi_txn_cancel ( & txn ) ;
return ret ;
}
static void pdr_indack_work ( struct work_struct * work )
{
struct pdr_handle * pdr = container_of ( work , struct pdr_handle ,
indack_work ) ;
struct pdr_list_node * ind , * tmp ;
struct pdr_service * pds ;
list_for_each_entry_safe ( ind , tmp , & pdr - > indack_list , node ) {
pds = ind - > pds ;
pdr_send_indack_msg ( pdr , pds , ind - > transaction_id ) ;
mutex_lock ( & pdr - > status_lock ) ;
pds - > state = ind - > curr_state ;
pdr - > status ( pds - > state , pds - > service_path , pdr - > priv ) ;
mutex_unlock ( & pdr - > status_lock ) ;
mutex_lock ( & pdr - > list_lock ) ;
list_del ( & ind - > node ) ;
mutex_unlock ( & pdr - > list_lock ) ;
kfree ( ind ) ;
}
}
static void pdr_indication_cb ( struct qmi_handle * qmi ,
struct sockaddr_qrtr * sq ,
struct qmi_txn * txn , const void * data )
{
struct pdr_handle * pdr = container_of ( qmi , struct pdr_handle ,
notifier_hdl ) ;
const struct servreg_state_updated_ind * ind_msg = data ;
struct pdr_list_node * ind ;
struct pdr_service * pds ;
2020-03-16 23:48:55 +03:00
bool found = false ;
2020-03-12 15:08:40 +03:00
if ( ! ind_msg | | ! ind_msg - > service_path [ 0 ] | |
strlen ( ind_msg - > service_path ) > SERVREG_NAME_LENGTH )
return ;
mutex_lock ( & pdr - > list_lock ) ;
list_for_each_entry ( pds , & pdr - > lookups , node ) {
if ( strcmp ( pds - > service_path , ind_msg - > service_path ) )
continue ;
found = true ;
break ;
}
mutex_unlock ( & pdr - > list_lock ) ;
if ( ! found )
return ;
pr_info ( " PDR: Indication received from %s, state: 0x%x, trans-id: %d \n " ,
ind_msg - > service_path , ind_msg - > curr_state ,
ind_msg - > transaction_id ) ;
ind = kzalloc ( sizeof ( * ind ) , GFP_KERNEL ) ;
if ( ! ind )
return ;
ind - > transaction_id = ind_msg - > transaction_id ;
ind - > curr_state = ind_msg - > curr_state ;
ind - > pds = pds ;
mutex_lock ( & pdr - > list_lock ) ;
list_add_tail ( & ind - > node , & pdr - > indack_list ) ;
mutex_unlock ( & pdr - > list_lock ) ;
queue_work ( pdr - > indack_wq , & pdr - > indack_work ) ;
}
static struct qmi_msg_handler qmi_indication_handler [ ] = {
{
. type = QMI_INDICATION ,
. msg_id = SERVREG_STATE_UPDATED_IND_ID ,
. ei = servreg_state_updated_ind_ei ,
. decoded_size = sizeof ( struct servreg_state_updated_ind ) ,
. fn = pdr_indication_cb ,
} ,
{ }
} ;
static int pdr_get_domain_list ( struct servreg_get_domain_list_req * req ,
struct servreg_get_domain_list_resp * resp ,
struct pdr_handle * pdr )
{
struct qmi_txn txn ;
int ret ;
ret = qmi_txn_init ( & pdr - > locator_hdl , & txn ,
servreg_get_domain_list_resp_ei , resp ) ;
if ( ret < 0 )
return ret ;
ret = qmi_send_request ( & pdr - > locator_hdl ,
& pdr - > locator_addr ,
& txn , SERVREG_GET_DOMAIN_LIST_REQ ,
SERVREG_GET_DOMAIN_LIST_REQ_MAX_LEN ,
servreg_get_domain_list_req_ei ,
req ) ;
if ( ret < 0 ) {
qmi_txn_cancel ( & txn ) ;
return ret ;
}
ret = qmi_txn_wait ( & txn , 5 * HZ ) ;
if ( ret < 0 ) {
pr_err ( " PDR: %s get domain list txn wait failed: %d \n " ,
req - > service_name , ret ) ;
return ret ;
}
if ( resp - > resp . result ! = QMI_RESULT_SUCCESS_V01 ) {
pr_err ( " PDR: %s get domain list failed: 0x%x \n " ,
req - > service_name , resp - > resp . error ) ;
return - EREMOTEIO ;
}
return 0 ;
}
static int pdr_locate_service ( struct pdr_handle * pdr , struct pdr_service * pds )
{
struct servreg_get_domain_list_resp * resp ;
struct servreg_get_domain_list_req req ;
struct servreg_location_entry * entry ;
int domains_read = 0 ;
int ret , i ;
resp = kzalloc ( sizeof ( * resp ) , GFP_KERNEL ) ;
if ( ! resp )
return - ENOMEM ;
/* Prepare req message */
strcpy ( req . service_name , pds - > service_name ) ;
req . domain_offset_valid = true ;
req . domain_offset = 0 ;
do {
req . domain_offset = domains_read ;
ret = pdr_get_domain_list ( & req , resp , pdr ) ;
if ( ret < 0 )
goto out ;
for ( i = domains_read ; i < resp - > domain_list_len ; i + + ) {
entry = & resp - > domain_list [ i ] ;
if ( strnlen ( entry - > name , sizeof ( entry - > name ) ) = = sizeof ( entry - > name ) )
continue ;
if ( ! strcmp ( entry - > name , pds - > service_path ) ) {
pds - > service_data_valid = entry - > service_data_valid ;
pds - > service_data = entry - > service_data ;
pds - > instance = entry - > instance ;
goto out ;
}
}
/* Update ret to indicate that the service is not yet found */
ret = - ENXIO ;
/* Always read total_domains from the response msg */
if ( resp - > domain_list_len > resp - > total_domains )
resp - > domain_list_len = resp - > total_domains ;
domains_read + = resp - > domain_list_len ;
} while ( domains_read < resp - > total_domains ) ;
out :
kfree ( resp ) ;
return ret ;
}
static void pdr_notify_lookup_failure ( struct pdr_handle * pdr ,
struct pdr_service * pds ,
int err )
{
pr_err ( " PDR: service lookup for %s failed: %d \n " ,
pds - > service_name , err ) ;
if ( err = = - ENXIO )
return ;
list_del ( & pds - > node ) ;
pds - > state = SERVREG_LOCATOR_ERR ;
mutex_lock ( & pdr - > status_lock ) ;
pdr - > status ( pds - > state , pds - > service_path , pdr - > priv ) ;
mutex_unlock ( & pdr - > status_lock ) ;
kfree ( pds ) ;
}
static void pdr_locator_work ( struct work_struct * work )
{
struct pdr_handle * pdr = container_of ( work , struct pdr_handle ,
locator_work ) ;
struct pdr_service * pds , * tmp ;
int ret = 0 ;
/* Bail out early if the SERVREG LOCATOR QMI service is not up */
mutex_lock ( & pdr - > lock ) ;
if ( ! pdr - > locator_init_complete ) {
mutex_unlock ( & pdr - > lock ) ;
pr_debug ( " PDR: SERVICE LOCATOR service not available \n " ) ;
return ;
}
mutex_unlock ( & pdr - > lock ) ;
mutex_lock ( & pdr - > list_lock ) ;
list_for_each_entry_safe ( pds , tmp , & pdr - > lookups , node ) {
if ( ! pds - > need_locator_lookup )
continue ;
ret = pdr_locate_service ( pdr , pds ) ;
if ( ret < 0 ) {
pdr_notify_lookup_failure ( pdr , pds , ret ) ;
continue ;
}
ret = qmi_add_lookup ( & pdr - > notifier_hdl , pds - > service , 1 ,
pds - > instance ) ;
if ( ret < 0 ) {
pdr_notify_lookup_failure ( pdr , pds , ret ) ;
continue ;
}
pds - > need_locator_lookup = false ;
}
mutex_unlock ( & pdr - > list_lock ) ;
}
/**
* pdr_add_lookup ( ) - register a tracking request for a PD
* @ pdr : PDR client handle
* @ service_name : service name of the tracking request
* @ service_path : service path of the tracking request
*
* Registering a pdr lookup allows for tracking the life cycle of the PD .
*
* Return : pdr_service object on success , ERR_PTR on failure . - EALREADY is
* returned if a lookup is already in progress for the given service path .
*/
struct pdr_service * pdr_add_lookup ( struct pdr_handle * pdr ,
const char * service_name ,
const char * service_path )
{
struct pdr_service * pds , * tmp ;
int ret ;
if ( IS_ERR_OR_NULL ( pdr ) )
return ERR_PTR ( - EINVAL ) ;
if ( ! service_name | | strlen ( service_name ) > SERVREG_NAME_LENGTH | |
! service_path | | strlen ( service_path ) > SERVREG_NAME_LENGTH )
return ERR_PTR ( - EINVAL ) ;
pds = kzalloc ( sizeof ( * pds ) , GFP_KERNEL ) ;
if ( ! pds )
return ERR_PTR ( - ENOMEM ) ;
pds - > service = SERVREG_NOTIFIER_SERVICE ;
strcpy ( pds - > service_name , service_name ) ;
strcpy ( pds - > service_path , service_path ) ;
pds - > need_locator_lookup = true ;
mutex_lock ( & pdr - > list_lock ) ;
list_for_each_entry ( tmp , & pdr - > lookups , node ) {
if ( strcmp ( tmp - > service_path , service_path ) )
continue ;
mutex_unlock ( & pdr - > list_lock ) ;
ret = - EALREADY ;
goto err ;
}
list_add ( & pds - > node , & pdr - > lookups ) ;
mutex_unlock ( & pdr - > list_lock ) ;
schedule_work ( & pdr - > locator_work ) ;
return pds ;
err :
kfree ( pds ) ;
return ERR_PTR ( ret ) ;
}
EXPORT_SYMBOL ( pdr_add_lookup ) ;
/**
* pdr_restart_pd ( ) - restart PD
* @ pdr : PDR client handle
* @ pds : PD service handle
*
* Restarts the PD tracked by the PDR client handle for a given service path .
*
* Return : 0 on success , negative errno on failure .
*/
int pdr_restart_pd ( struct pdr_handle * pdr , struct pdr_service * pds )
{
struct servreg_restart_pd_resp resp ;
struct servreg_restart_pd_req req ;
struct sockaddr_qrtr addr ;
struct pdr_service * tmp ;
struct qmi_txn txn ;
int ret ;
if ( IS_ERR_OR_NULL ( pdr ) | | IS_ERR_OR_NULL ( pds ) )
return - EINVAL ;
mutex_lock ( & pdr - > list_lock ) ;
list_for_each_entry ( tmp , & pdr - > lookups , node ) {
if ( tmp ! = pds )
continue ;
if ( ! pds - > service_connected )
break ;
/* Prepare req message */
strcpy ( req . service_path , pds - > service_path ) ;
addr = pds - > addr ;
break ;
}
mutex_unlock ( & pdr - > list_lock ) ;
if ( ! req . service_path [ 0 ] )
return - EINVAL ;
ret = qmi_txn_init ( & pdr - > notifier_hdl , & txn ,
servreg_restart_pd_resp_ei ,
& resp ) ;
if ( ret < 0 )
return ret ;
ret = qmi_send_request ( & pdr - > notifier_hdl , & addr ,
& txn , SERVREG_RESTART_PD_REQ ,
SERVREG_RESTART_PD_REQ_MAX_LEN ,
servreg_restart_pd_req_ei , & req ) ;
if ( ret < 0 ) {
qmi_txn_cancel ( & txn ) ;
return ret ;
}
ret = qmi_txn_wait ( & txn , 5 * HZ ) ;
if ( ret < 0 ) {
pr_err ( " PDR: %s PD restart txn wait failed: %d \n " ,
req . service_path , ret ) ;
return ret ;
}
/* Check response if PDR is disabled */
if ( resp . resp . result = = QMI_RESULT_FAILURE_V01 & &
resp . resp . error = = QMI_ERR_DISABLED_V01 ) {
pr_err ( " PDR: %s PD restart is disabled: 0x%x \n " ,
req . service_path , resp . resp . error ) ;
return - EOPNOTSUPP ;
}
/* Check the response for other error case*/
if ( resp . resp . result ! = QMI_RESULT_SUCCESS_V01 ) {
pr_err ( " PDR: %s request for PD restart failed: 0x%x \n " ,
req . service_path , resp . resp . error ) ;
return - EREMOTEIO ;
}
return 0 ;
}
EXPORT_SYMBOL ( pdr_restart_pd ) ;
/**
* pdr_handle_alloc ( ) - initialize the PDR client handle
* @ status : function to be called on PD state change
* @ priv : handle for client ' s use
*
* Initializes the PDR client handle to allow for tracking / restart of PDs .
*
* Return : pdr_handle object on success , ERR_PTR on failure .
*/
struct pdr_handle * pdr_handle_alloc ( void ( * status ) ( int state ,
char * service_path ,
void * priv ) , void * priv )
{
struct pdr_handle * pdr ;
int ret ;
if ( ! status )
return ERR_PTR ( - EINVAL ) ;
pdr = kzalloc ( sizeof ( * pdr ) , GFP_KERNEL ) ;
if ( ! pdr )
return ERR_PTR ( - ENOMEM ) ;
pdr - > status = status ;
pdr - > priv = priv ;
mutex_init ( & pdr - > status_lock ) ;
mutex_init ( & pdr - > list_lock ) ;
mutex_init ( & pdr - > lock ) ;
INIT_LIST_HEAD ( & pdr - > lookups ) ;
INIT_LIST_HEAD ( & pdr - > indack_list ) ;
INIT_WORK ( & pdr - > locator_work , pdr_locator_work ) ;
INIT_WORK ( & pdr - > notifier_work , pdr_notifier_work ) ;
INIT_WORK ( & pdr - > indack_work , pdr_indack_work ) ;
pdr - > notifier_wq = create_singlethread_workqueue ( " pdr_notifier_wq " ) ;
if ( ! pdr - > notifier_wq ) {
ret = - ENOMEM ;
goto free_pdr_handle ;
}
pdr - > indack_wq = alloc_ordered_workqueue ( " pdr_indack_wq " , WQ_HIGHPRI ) ;
if ( ! pdr - > indack_wq ) {
ret = - ENOMEM ;
goto destroy_notifier ;
}
ret = qmi_handle_init ( & pdr - > locator_hdl ,
SERVREG_GET_DOMAIN_LIST_RESP_MAX_LEN ,
& pdr_locator_ops , NULL ) ;
if ( ret < 0 )
goto destroy_indack ;
ret = qmi_add_lookup ( & pdr - > locator_hdl , SERVREG_LOCATOR_SERVICE , 1 , 1 ) ;
if ( ret < 0 )
goto release_qmi_handle ;
ret = qmi_handle_init ( & pdr - > notifier_hdl ,
SERVREG_STATE_UPDATED_IND_MAX_LEN ,
& pdr_notifier_ops ,
qmi_indication_handler ) ;
if ( ret < 0 )
goto release_qmi_handle ;
return pdr ;
release_qmi_handle :
qmi_handle_release ( & pdr - > locator_hdl ) ;
destroy_indack :
destroy_workqueue ( pdr - > indack_wq ) ;
destroy_notifier :
destroy_workqueue ( pdr - > notifier_wq ) ;
free_pdr_handle :
kfree ( pdr ) ;
return ERR_PTR ( ret ) ;
}
EXPORT_SYMBOL ( pdr_handle_alloc ) ;
/**
* pdr_handle_release ( ) - release the PDR client handle
* @ pdr : PDR client handle
*
* Cleans up pending tracking requests and releases the underlying qmi handles .
*/
void pdr_handle_release ( struct pdr_handle * pdr )
{
struct pdr_service * pds , * tmp ;
if ( IS_ERR_OR_NULL ( pdr ) )
return ;
mutex_lock ( & pdr - > list_lock ) ;
list_for_each_entry_safe ( pds , tmp , & pdr - > lookups , node ) {
list_del ( & pds - > node ) ;
kfree ( pds ) ;
}
mutex_unlock ( & pdr - > list_lock ) ;
cancel_work_sync ( & pdr - > locator_work ) ;
cancel_work_sync ( & pdr - > notifier_work ) ;
cancel_work_sync ( & pdr - > indack_work ) ;
destroy_workqueue ( pdr - > notifier_wq ) ;
destroy_workqueue ( pdr - > indack_wq ) ;
qmi_handle_release ( & pdr - > locator_hdl ) ;
qmi_handle_release ( & pdr - > notifier_hdl ) ;
kfree ( pdr ) ;
}
EXPORT_SYMBOL ( pdr_handle_release ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_DESCRIPTION ( " Qualcomm Protection Domain Restart helpers " ) ;