923 lines
23 KiB
C
923 lines
23 KiB
C
/* Copyright (c) 2009-2011, The Linux Foundation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
* only version 2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* SMD RPCROUTER CLIENTS module.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/module.h>
|
|
|
|
#include <mach/msm_rpcrouter.h>
|
|
#include "smd_rpcrouter.h"
|
|
|
|
struct msm_rpc_client_cb_item {
|
|
struct list_head list;
|
|
|
|
void *buf;
|
|
int size;
|
|
};
|
|
|
|
struct msm_rpc_cb_table_item {
|
|
struct list_head list;
|
|
|
|
uint32_t cb_id;
|
|
void *cb_func;
|
|
};
|
|
|
|
static int rpc_clients_cb_thread(void *data)
|
|
{
|
|
struct msm_rpc_client_cb_item *cb_item;
|
|
struct msm_rpc_client *client;
|
|
struct rpc_request_hdr req;
|
|
int ret;
|
|
|
|
client = data;
|
|
for (;;) {
|
|
wait_event(client->cb_wait, client->cb_avail);
|
|
if (client->exit_flag)
|
|
break;
|
|
|
|
client->cb_avail = 0;
|
|
mutex_lock(&client->cb_item_list_lock);
|
|
while (!list_empty(&client->cb_item_list)) {
|
|
cb_item = list_first_entry(
|
|
&client->cb_item_list,
|
|
struct msm_rpc_client_cb_item,
|
|
list);
|
|
list_del(&cb_item->list);
|
|
mutex_unlock(&client->cb_item_list_lock);
|
|
xdr_init_input(&client->cb_xdr, cb_item->buf,
|
|
cb_item->size);
|
|
ret = xdr_recv_req(&client->cb_xdr, &req);
|
|
if (ret)
|
|
goto bad_rpc;
|
|
|
|
if (req.type != 0)
|
|
goto bad_rpc;
|
|
if (req.rpc_vers != 2)
|
|
goto bad_rpc;
|
|
if (req.prog !=
|
|
(client->prog | 0x01000000))
|
|
goto bad_rpc;
|
|
|
|
if (client->version == 2)
|
|
client->cb_func2(client, &req, &client->cb_xdr);
|
|
else
|
|
client->cb_func(client, client->cb_xdr.in_buf,
|
|
client->cb_xdr.in_size);
|
|
bad_rpc:
|
|
xdr_clean_input(&client->cb_xdr);
|
|
kfree(cb_item);
|
|
mutex_lock(&client->cb_item_list_lock);
|
|
}
|
|
mutex_unlock(&client->cb_item_list_lock);
|
|
}
|
|
complete_and_exit(&client->cb_complete, 0);
|
|
}
|
|
|
|
static int rpc_clients_thread(void *data)
|
|
{
|
|
void *buffer;
|
|
uint32_t type;
|
|
struct msm_rpc_client *client;
|
|
int rc = 0;
|
|
struct msm_rpc_client_cb_item *cb_item;
|
|
struct rpc_request_hdr req;
|
|
|
|
client = data;
|
|
for (;;) {
|
|
buffer = NULL;
|
|
rc = msm_rpc_read(client->ept, &buffer, -1, -1);
|
|
|
|
if (client->exit_flag) {
|
|
kfree(buffer);
|
|
break;
|
|
}
|
|
|
|
if (rc < 0) {
|
|
/* wakeup any pending requests */
|
|
wake_up(&client->reply_wait);
|
|
kfree(buffer);
|
|
continue;
|
|
}
|
|
|
|
if (rc < ((int)(sizeof(uint32_t) * 2))) {
|
|
kfree(buffer);
|
|
continue;
|
|
}
|
|
|
|
type = be32_to_cpu(*((uint32_t *)buffer + 1));
|
|
if (type == 1) {
|
|
xdr_init_input(&client->xdr, buffer, rc);
|
|
wake_up(&client->reply_wait);
|
|
} else if (type == 0) {
|
|
if (client->cb_thread == NULL) {
|
|
xdr_init_input(&client->cb_xdr, buffer, rc);
|
|
xdr_recv_req(&client->cb_xdr, &req);
|
|
|
|
if ((req.rpc_vers == 2) &&
|
|
(req.prog == (client->prog | 0x01000000))) {
|
|
if (client->version == 2)
|
|
client->cb_func2(client, &req,
|
|
&client->cb_xdr);
|
|
else
|
|
client->cb_func(client,
|
|
client->cb_xdr.in_buf, rc);
|
|
}
|
|
xdr_clean_input(&client->cb_xdr);
|
|
} else {
|
|
cb_item = kmalloc(sizeof(*cb_item), GFP_KERNEL);
|
|
if (!cb_item) {
|
|
pr_err("%s: no memory for cb item\n",
|
|
__func__);
|
|
continue;
|
|
}
|
|
|
|
INIT_LIST_HEAD(&cb_item->list);
|
|
cb_item->buf = buffer;
|
|
cb_item->size = rc;
|
|
mutex_lock(&client->cb_item_list_lock);
|
|
list_add_tail(&cb_item->list,
|
|
&client->cb_item_list);
|
|
mutex_unlock(&client->cb_item_list_lock);
|
|
client->cb_avail = 1;
|
|
wake_up(&client->cb_wait);
|
|
}
|
|
}
|
|
}
|
|
complete_and_exit(&client->complete, 0);
|
|
}
|
|
|
|
static struct msm_rpc_client *msm_rpc_create_client(void)
|
|
{
|
|
struct msm_rpc_client *client;
|
|
void *buf;
|
|
|
|
client = kmalloc(sizeof(struct msm_rpc_client), GFP_KERNEL);
|
|
if (!client)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
xdr_init(&client->xdr);
|
|
xdr_init(&client->cb_xdr);
|
|
|
|
buf = kmalloc(MSM_RPC_MSGSIZE_MAX, GFP_KERNEL);
|
|
if (!buf) {
|
|
kfree(client);
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
xdr_init_output(&client->xdr, buf, MSM_RPC_MSGSIZE_MAX);
|
|
|
|
buf = kmalloc(MSM_RPC_MSGSIZE_MAX, GFP_KERNEL);
|
|
if (!buf) {
|
|
xdr_clean_output(&client->xdr);
|
|
kfree(client);
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
xdr_init_output(&client->cb_xdr, buf, MSM_RPC_MSGSIZE_MAX);
|
|
|
|
init_waitqueue_head(&client->reply_wait);
|
|
mutex_init(&client->req_lock);
|
|
client->buf = NULL;
|
|
client->cb_buf = NULL;
|
|
client->cb_size = 0;
|
|
client->exit_flag = 0;
|
|
client->cb_restart_teardown = NULL;
|
|
client->cb_restart_setup = NULL;
|
|
client->in_reset = 0;
|
|
|
|
init_completion(&client->complete);
|
|
init_completion(&client->cb_complete);
|
|
INIT_LIST_HEAD(&client->cb_item_list);
|
|
mutex_init(&client->cb_item_list_lock);
|
|
client->cb_avail = 0;
|
|
init_waitqueue_head(&client->cb_wait);
|
|
INIT_LIST_HEAD(&client->cb_list);
|
|
spin_lock_init(&client->cb_list_lock);
|
|
atomic_set(&client->next_cb_id, 1);
|
|
|
|
return client;
|
|
}
|
|
|
|
static void msm_rpc_destroy_client(struct msm_rpc_client *client)
|
|
{
|
|
xdr_clean_output(&client->xdr);
|
|
xdr_clean_output(&client->cb_xdr);
|
|
|
|
kfree(client);
|
|
}
|
|
|
|
void msm_rpc_remove_all_cb_func(struct msm_rpc_client *client)
|
|
{
|
|
struct msm_rpc_cb_table_item *cb_item, *tmp_cb_item;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&client->cb_list_lock, flags);
|
|
list_for_each_entry_safe(cb_item, tmp_cb_item,
|
|
&client->cb_list, list) {
|
|
list_del(&cb_item->list);
|
|
kfree(cb_item);
|
|
}
|
|
spin_unlock_irqrestore(&client->cb_list_lock, flags);
|
|
}
|
|
|
|
static void cb_restart_teardown(void *client_data)
|
|
{
|
|
struct msm_rpc_client *client;
|
|
|
|
client = (struct msm_rpc_client *)client_data;
|
|
if (client) {
|
|
client->in_reset = 1;
|
|
msm_rpc_remove_all_cb_func(client);
|
|
client->xdr.out_index = 0;
|
|
|
|
if (client->cb_restart_teardown)
|
|
client->cb_restart_teardown(client);
|
|
}
|
|
}
|
|
|
|
static void cb_restart_setup(void *client_data)
|
|
{
|
|
struct msm_rpc_client *client;
|
|
|
|
client = (struct msm_rpc_client *)client_data;
|
|
|
|
if (client) {
|
|
client->in_reset = 0;
|
|
if (client->cb_restart_setup)
|
|
client->cb_restart_setup(client);
|
|
}
|
|
}
|
|
|
|
/* Returns the reset state of the client.
|
|
*
|
|
* Return Value:
|
|
* 0 if client isn't in reset, >0 otherwise.
|
|
*/
|
|
int msm_rpc_client_in_reset(struct msm_rpc_client *client)
|
|
{
|
|
int ret = 1;
|
|
|
|
if (client)
|
|
ret = client->in_reset;
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(msm_rpc_client_in_reset);
|
|
|
|
/*
|
|
* Interface to be used to register the client.
|
|
*
|
|
* name: string representing the client
|
|
*
|
|
* prog: program number of the client
|
|
*
|
|
* ver: version number of the client
|
|
*
|
|
* create_cb_thread: if set calls the callback function from a seprate thread
|
|
* which helps the client requests to be processed without
|
|
* getting loaded by callback handling.
|
|
*
|
|
* cb_func: function to be called if callback request is received.
|
|
* unmarshaling should be handled by the user in callback function
|
|
*
|
|
* Return Value:
|
|
* Pointer to initialized client data sturcture
|
|
* Or, the error code if registration fails.
|
|
*
|
|
*/
|
|
struct msm_rpc_client *msm_rpc_register_client(
|
|
const char *name,
|
|
uint32_t prog, uint32_t ver,
|
|
uint32_t create_cb_thread,
|
|
int (*cb_func)(struct msm_rpc_client *, void *, int))
|
|
{
|
|
struct msm_rpc_client *client;
|
|
struct msm_rpc_endpoint *ept;
|
|
int rc;
|
|
|
|
client = msm_rpc_create_client();
|
|
if (IS_ERR(client))
|
|
return client;
|
|
|
|
ept = msm_rpc_connect_compatible(prog, ver, MSM_RPC_UNINTERRUPTIBLE);
|
|
if (IS_ERR(ept)) {
|
|
msm_rpc_destroy_client(client);
|
|
return (struct msm_rpc_client *)ept;
|
|
}
|
|
|
|
ept->client_data = client;
|
|
ept->cb_restart_teardown = cb_restart_teardown;
|
|
ept->cb_restart_setup = cb_restart_setup;
|
|
|
|
client->prog = prog;
|
|
client->ver = ver;
|
|
client->ept = client->xdr.ept = client->cb_xdr.ept = ept;
|
|
client->cb_func = cb_func;
|
|
client->version = 1;
|
|
|
|
/* start the read thread */
|
|
client->read_thread = kthread_run(rpc_clients_thread, client,
|
|
"k%sclntd", name);
|
|
if (IS_ERR(client->read_thread)) {
|
|
rc = PTR_ERR(client->read_thread);
|
|
msm_rpc_close(client->ept);
|
|
msm_rpc_destroy_client(client);
|
|
return ERR_PTR(rc);
|
|
}
|
|
|
|
if (!create_cb_thread || (cb_func == NULL)) {
|
|
client->cb_thread = NULL;
|
|
return client;
|
|
}
|
|
|
|
/* start the callback thread */
|
|
client->cb_thread = kthread_run(rpc_clients_cb_thread, client,
|
|
"k%sclntcbd", name);
|
|
if (IS_ERR(client->cb_thread)) {
|
|
rc = PTR_ERR(client->cb_thread);
|
|
client->exit_flag = 1;
|
|
msm_rpc_read_wakeup(client->ept);
|
|
wait_for_completion(&client->complete);
|
|
msm_rpc_close(client->ept);
|
|
msm_rpc_destroy_client(client);
|
|
return ERR_PTR(rc);
|
|
}
|
|
|
|
return client;
|
|
}
|
|
EXPORT_SYMBOL(msm_rpc_register_client);
|
|
|
|
/*
|
|
* Interface to be used to register the client.
|
|
*
|
|
* name: string representing the client
|
|
*
|
|
* prog: program number of the client
|
|
*
|
|
* ver: version number of the client
|
|
*
|
|
* create_cb_thread: if set calls the callback function from a seprate thread
|
|
* which helps the client requests to be processed without
|
|
* getting loaded by callback handling.
|
|
*
|
|
* cb_func: function to be called if callback request is received.
|
|
* unmarshaling should be handled by the user in callback function
|
|
*
|
|
* Return Value:
|
|
* Pointer to initialized client data sturcture
|
|
* Or, the error code if registration fails.
|
|
*
|
|
*/
|
|
struct msm_rpc_client *msm_rpc_register_client2(
|
|
const char *name,
|
|
uint32_t prog, uint32_t ver,
|
|
uint32_t create_cb_thread,
|
|
int (*cb_func)(struct msm_rpc_client *,
|
|
struct rpc_request_hdr *req, struct msm_rpc_xdr *))
|
|
{
|
|
struct msm_rpc_client *client;
|
|
struct msm_rpc_endpoint *ept;
|
|
int rc;
|
|
|
|
client = msm_rpc_create_client();
|
|
if (IS_ERR(client))
|
|
return client;
|
|
|
|
ept = msm_rpc_connect_compatible(prog, ver, MSM_RPC_UNINTERRUPTIBLE);
|
|
if (IS_ERR(ept)) {
|
|
msm_rpc_destroy_client(client);
|
|
return (struct msm_rpc_client *)ept;
|
|
}
|
|
|
|
client->prog = prog;
|
|
client->ver = ver;
|
|
client->ept = client->xdr.ept = client->cb_xdr.ept = ept;
|
|
client->cb_func2 = cb_func;
|
|
client->version = 2;
|
|
|
|
ept->client_data = client;
|
|
ept->cb_restart_teardown = cb_restart_teardown;
|
|
ept->cb_restart_setup = cb_restart_setup;
|
|
|
|
/* start the read thread */
|
|
client->read_thread = kthread_run(rpc_clients_thread, client,
|
|
"k%sclntd", name);
|
|
if (IS_ERR(client->read_thread)) {
|
|
rc = PTR_ERR(client->read_thread);
|
|
msm_rpc_close(client->ept);
|
|
msm_rpc_destroy_client(client);
|
|
return ERR_PTR(rc);
|
|
}
|
|
|
|
if (!create_cb_thread || (cb_func == NULL)) {
|
|
client->cb_thread = NULL;
|
|
return client;
|
|
}
|
|
|
|
/* start the callback thread */
|
|
client->cb_thread = kthread_run(rpc_clients_cb_thread, client,
|
|
"k%sclntcbd", name);
|
|
if (IS_ERR(client->cb_thread)) {
|
|
rc = PTR_ERR(client->cb_thread);
|
|
client->exit_flag = 1;
|
|
msm_rpc_read_wakeup(client->ept);
|
|
wait_for_completion(&client->complete);
|
|
msm_rpc_close(client->ept);
|
|
msm_rpc_destroy_client(client);
|
|
return ERR_PTR(rc);
|
|
}
|
|
|
|
return client;
|
|
}
|
|
EXPORT_SYMBOL(msm_rpc_register_client2);
|
|
|
|
/*
|
|
* Register callbacks for modem state changes.
|
|
*
|
|
* Teardown is called when the modem is going into reset.
|
|
* Setup is called after the modem has come out of reset (but may not
|
|
* be available, yet).
|
|
*
|
|
* client: pointer to client data structure.
|
|
*
|
|
* Return Value:
|
|
* 0 (success)
|
|
* 1 (client pointer invalid)
|
|
*/
|
|
int msm_rpc_register_reset_callbacks(
|
|
struct msm_rpc_client *client,
|
|
void (*teardown)(struct msm_rpc_client *client),
|
|
void (*setup)(struct msm_rpc_client *client)
|
|
)
|
|
{
|
|
int rc = 1;
|
|
|
|
if (client) {
|
|
client->cb_restart_teardown = teardown;
|
|
client->cb_restart_setup = setup;
|
|
rc = 0;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
EXPORT_SYMBOL(msm_rpc_register_reset_callbacks);
|
|
|
|
/*
|
|
* Interface to be used to unregister the client
|
|
* No client operations should be done once the unregister function
|
|
* is called.
|
|
*
|
|
* client: pointer to client data structure.
|
|
*
|
|
* Return Value:
|
|
* Always returns 0 (success).
|
|
*/
|
|
int msm_rpc_unregister_client(struct msm_rpc_client *client)
|
|
{
|
|
pr_info("%s: stopping client...\n", __func__);
|
|
client->exit_flag = 1;
|
|
if (client->cb_thread) {
|
|
client->cb_avail = 1;
|
|
wake_up(&client->cb_wait);
|
|
wait_for_completion(&client->cb_complete);
|
|
}
|
|
|
|
msm_rpc_read_wakeup(client->ept);
|
|
wait_for_completion(&client->complete);
|
|
|
|
msm_rpc_close(client->ept);
|
|
msm_rpc_remove_all_cb_func(client);
|
|
xdr_clean_output(&client->xdr);
|
|
xdr_clean_output(&client->cb_xdr);
|
|
kfree(client);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(msm_rpc_unregister_client);
|
|
|
|
/*
|
|
* Interface to be used to send a client request.
|
|
* If the request takes any arguments or expects any return, the user
|
|
* should handle it in 'arg_func' and 'ret_func' respectively.
|
|
* Marshaling and Unmarshaling should be handled by the user in argument
|
|
* and return functions.
|
|
*
|
|
* client: pointer to client data sturcture
|
|
*
|
|
* proc: procedure being requested
|
|
*
|
|
* arg_func: argument function pointer. 'buf' is where arguments needs to
|
|
* be filled. 'data' is arg_data.
|
|
*
|
|
* ret_func: return function pointer. 'buf' is where returned data should
|
|
* be read from. 'data' is ret_data.
|
|
*
|
|
* arg_data: passed as an input parameter to argument function.
|
|
*
|
|
* ret_data: passed as an input parameter to return function.
|
|
*
|
|
* timeout: timeout for reply wait in jiffies. If negative timeout is
|
|
* specified a default timeout of 10s is used.
|
|
*
|
|
* Return Value:
|
|
* 0 on success, otherwise an error code is returned.
|
|
*/
|
|
int msm_rpc_client_req(struct msm_rpc_client *client, uint32_t proc,
|
|
int (*arg_func)(struct msm_rpc_client *client,
|
|
void *buf, void *data),
|
|
void *arg_data,
|
|
int (*ret_func)(struct msm_rpc_client *client,
|
|
void *buf, void *data),
|
|
void *ret_data, long timeout)
|
|
{
|
|
struct rpc_reply_hdr *rpc_rsp;
|
|
int rc = 0;
|
|
uint32_t req_xid;
|
|
|
|
mutex_lock(&client->req_lock);
|
|
|
|
msm_rpc_setup_req((struct rpc_request_hdr *)client->xdr.out_buf,
|
|
client->prog, client->ver, proc);
|
|
client->xdr.out_index = sizeof(struct rpc_request_hdr);
|
|
req_xid = *(uint32_t *)client->xdr.out_buf;
|
|
if (arg_func) {
|
|
rc = arg_func(client,
|
|
(void *)((struct rpc_request_hdr *)
|
|
client->xdr.out_buf + 1),
|
|
arg_data);
|
|
if (rc < 0)
|
|
goto release_locks;
|
|
else
|
|
client->xdr.out_index += rc;
|
|
}
|
|
|
|
rc = msm_rpc_write(client->ept, client->xdr.out_buf,
|
|
client->xdr.out_index);
|
|
if (rc < 0) {
|
|
pr_err("%s: couldn't send RPC request:%d\n", __func__, rc);
|
|
goto release_locks;
|
|
} else
|
|
rc = 0;
|
|
|
|
if (timeout < 0)
|
|
timeout = msecs_to_jiffies(10000);
|
|
|
|
do {
|
|
rc = wait_event_timeout(client->reply_wait,
|
|
xdr_read_avail(&client->xdr) || client->in_reset,
|
|
timeout);
|
|
|
|
if (client->in_reset) {
|
|
rc = -ENETRESET;
|
|
goto release_locks;
|
|
}
|
|
|
|
if (rc == 0) {
|
|
pr_err("%s: request timeout\n", __func__);
|
|
rc = -ETIMEDOUT;
|
|
goto release_locks;
|
|
}
|
|
|
|
rpc_rsp = (struct rpc_reply_hdr *)client->xdr.in_buf;
|
|
if (req_xid != rpc_rsp->xid) {
|
|
pr_info("%s: xid mismatch, req %d reply %d\n",
|
|
__func__, be32_to_cpu(req_xid),
|
|
be32_to_cpu(rpc_rsp->xid));
|
|
timeout = rc;
|
|
xdr_clean_input(&client->xdr);
|
|
} else
|
|
rc = 0;
|
|
} while (rc);
|
|
|
|
if (be32_to_cpu(rpc_rsp->reply_stat) != RPCMSG_REPLYSTAT_ACCEPTED) {
|
|
pr_err("%s: RPC call was denied! %d\n", __func__,
|
|
be32_to_cpu(rpc_rsp->reply_stat));
|
|
rc = -EPERM;
|
|
goto free_and_release;
|
|
}
|
|
|
|
if (be32_to_cpu(rpc_rsp->data.acc_hdr.accept_stat) !=
|
|
RPC_ACCEPTSTAT_SUCCESS) {
|
|
pr_err("%s: RPC call was not successful (%d)\n", __func__,
|
|
be32_to_cpu(rpc_rsp->data.acc_hdr.accept_stat));
|
|
rc = -EINVAL;
|
|
goto free_and_release;
|
|
}
|
|
|
|
if (ret_func)
|
|
rc = ret_func(client, (void *)(rpc_rsp + 1), ret_data);
|
|
|
|
free_and_release:
|
|
xdr_clean_input(&client->xdr);
|
|
client->xdr.out_index = 0;
|
|
release_locks:
|
|
mutex_unlock(&client->req_lock);
|
|
return rc;
|
|
}
|
|
EXPORT_SYMBOL(msm_rpc_client_req);
|
|
|
|
/*
|
|
* Interface to be used to send a client request.
|
|
* If the request takes any arguments or expects any return, the user
|
|
* should handle it in 'arg_func' and 'ret_func' respectively.
|
|
* Marshaling and Unmarshaling should be handled by the user in argument
|
|
* and return functions.
|
|
*
|
|
* client: pointer to client data sturcture
|
|
*
|
|
* proc: procedure being requested
|
|
*
|
|
* arg_func: argument function pointer. 'xdr' is the xdr being used.
|
|
* 'data' is arg_data.
|
|
*
|
|
* ret_func: return function pointer. 'xdr' is the xdr being used.
|
|
* 'data' is ret_data.
|
|
*
|
|
* arg_data: passed as an input parameter to argument function.
|
|
*
|
|
* ret_data: passed as an input parameter to return function.
|
|
*
|
|
* timeout: timeout for reply wait in jiffies. If negative timeout is
|
|
* specified a default timeout of 10s is used.
|
|
*
|
|
* Return Value:
|
|
* 0 on success, otherwise an error code is returned.
|
|
*/
|
|
int msm_rpc_client_req2(struct msm_rpc_client *client, uint32_t proc,
|
|
int (*arg_func)(struct msm_rpc_client *client,
|
|
struct msm_rpc_xdr *xdr, void *data),
|
|
void *arg_data,
|
|
int (*ret_func)(struct msm_rpc_client *client,
|
|
struct msm_rpc_xdr *xdr, void *data),
|
|
void *ret_data, long timeout)
|
|
{
|
|
struct rpc_reply_hdr rpc_rsp;
|
|
int rc = 0;
|
|
uint32_t req_xid;
|
|
|
|
mutex_lock(&client->req_lock);
|
|
|
|
if (client->in_reset) {
|
|
rc = -ENETRESET;
|
|
goto release_locks;
|
|
}
|
|
|
|
xdr_start_request(&client->xdr, client->prog, client->ver, proc);
|
|
req_xid = be32_to_cpu(*(uint32_t *)client->xdr.out_buf);
|
|
if (arg_func) {
|
|
rc = arg_func(client, &client->xdr, arg_data);
|
|
if (rc < 0) {
|
|
mutex_unlock(&client->xdr.out_lock);
|
|
goto release_locks;
|
|
}
|
|
}
|
|
|
|
rc = xdr_send_msg(&client->xdr);
|
|
if (rc < 0) {
|
|
pr_err("%s: couldn't send RPC request:%d\n", __func__, rc);
|
|
goto release_locks;
|
|
} else
|
|
rc = 0;
|
|
|
|
if (timeout < 0)
|
|
timeout = msecs_to_jiffies(10000);
|
|
|
|
do {
|
|
rc = wait_event_timeout(client->reply_wait,
|
|
xdr_read_avail(&client->xdr) || client->in_reset,
|
|
timeout);
|
|
|
|
if (client->in_reset) {
|
|
rc = -ENETRESET;
|
|
goto release_locks;
|
|
}
|
|
|
|
if (rc == 0) {
|
|
pr_err("%s: request timeout\n", __func__);
|
|
rc = -ETIMEDOUT;
|
|
goto release_locks;
|
|
}
|
|
|
|
xdr_recv_reply(&client->xdr, &rpc_rsp);
|
|
/* TODO: may be this check should be a xdr function */
|
|
if (req_xid != rpc_rsp.xid) {
|
|
pr_info("%s: xid mismatch, req %d reply %d\n",
|
|
__func__, req_xid, rpc_rsp.xid);
|
|
timeout = rc;
|
|
xdr_clean_input(&client->xdr);
|
|
} else
|
|
rc = 0;
|
|
} while (rc);
|
|
|
|
if (rpc_rsp.reply_stat != RPCMSG_REPLYSTAT_ACCEPTED) {
|
|
pr_err("%s: RPC call was denied! %d\n",
|
|
__func__, rpc_rsp.reply_stat);
|
|
rc = -EPERM;
|
|
goto free_and_release;
|
|
}
|
|
|
|
if (rpc_rsp.data.acc_hdr.accept_stat != RPC_ACCEPTSTAT_SUCCESS) {
|
|
pr_err("%s: RPC call was not successful (%d)\n", __func__,
|
|
rpc_rsp.data.acc_hdr.accept_stat);
|
|
rc = -EINVAL;
|
|
goto free_and_release;
|
|
}
|
|
|
|
if (ret_func)
|
|
rc = ret_func(client, &client->xdr, ret_data);
|
|
|
|
free_and_release:
|
|
xdr_clean_input(&client->xdr);
|
|
/* TODO: put it in xdr_reset_output */
|
|
client->xdr.out_index = 0;
|
|
release_locks:
|
|
mutex_unlock(&client->req_lock);
|
|
return rc;
|
|
}
|
|
EXPORT_SYMBOL(msm_rpc_client_req2);
|
|
|
|
/*
|
|
* Interface to be used to start accepted reply message required in
|
|
* callback handling. Returns the buffer pointer to attach any
|
|
* payload. Should call msm_rpc_send_accepted_reply to complete
|
|
* sending reply. Marshaling should be handled by user for the payload.
|
|
*
|
|
* client: pointer to client data structure
|
|
*
|
|
* xid: transaction id. Has to be same as the one in callback request.
|
|
*
|
|
* accept_status: acceptance status
|
|
*
|
|
* Return Value:
|
|
* pointer to buffer to attach the payload.
|
|
*/
|
|
void *msm_rpc_start_accepted_reply(struct msm_rpc_client *client,
|
|
uint32_t xid, uint32_t accept_status)
|
|
{
|
|
struct rpc_reply_hdr *reply;
|
|
|
|
mutex_lock(&client->cb_xdr.out_lock);
|
|
|
|
reply = (struct rpc_reply_hdr *)client->cb_xdr.out_buf;
|
|
|
|
reply->xid = cpu_to_be32(xid);
|
|
reply->type = cpu_to_be32(1); /* reply */
|
|
reply->reply_stat = cpu_to_be32(RPCMSG_REPLYSTAT_ACCEPTED);
|
|
|
|
reply->data.acc_hdr.accept_stat = cpu_to_be32(accept_status);
|
|
reply->data.acc_hdr.verf_flavor = 0;
|
|
reply->data.acc_hdr.verf_length = 0;
|
|
|
|
client->cb_xdr.out_index = sizeof(*reply);
|
|
return reply + 1;
|
|
}
|
|
EXPORT_SYMBOL(msm_rpc_start_accepted_reply);
|
|
|
|
/*
|
|
* Interface to be used to send accepted reply required in callback handling.
|
|
* msm_rpc_start_accepted_reply should have been called before.
|
|
* Marshaling should be handled by user for the payload.
|
|
*
|
|
* client: pointer to client data structure
|
|
*
|
|
* size: additional payload size
|
|
*
|
|
* Return Value:
|
|
* 0 on success, otherwise returns an error code.
|
|
*/
|
|
int msm_rpc_send_accepted_reply(struct msm_rpc_client *client, uint32_t size)
|
|
{
|
|
int rc = 0;
|
|
|
|
client->cb_xdr.out_index += size;
|
|
rc = msm_rpc_write(client->ept, client->cb_xdr.out_buf,
|
|
client->cb_xdr.out_index);
|
|
if (rc > 0)
|
|
rc = 0;
|
|
|
|
mutex_unlock(&client->cb_xdr.out_lock);
|
|
return rc;
|
|
}
|
|
EXPORT_SYMBOL(msm_rpc_send_accepted_reply);
|
|
|
|
/*
|
|
* Interface to be used to add a callback function.
|
|
* If the call back function is already in client's 'cb_id - cb_func'
|
|
* table, then that cb_id is returned. otherwise, new entry
|
|
* is added to the above table and corresponding cb_id is returned.
|
|
*
|
|
* client: pointer to client data structure
|
|
*
|
|
* cb_func: callback function
|
|
*
|
|
* Return Value:
|
|
* callback ID on success, otherwise returns an error code.
|
|
* If cb_func is NULL, the callback Id returned is 0xffffffff.
|
|
* This tells the other processor that no callback is reqested.
|
|
*/
|
|
int msm_rpc_add_cb_func(struct msm_rpc_client *client, void *cb_func)
|
|
{
|
|
struct msm_rpc_cb_table_item *cb_item;
|
|
unsigned long flags;
|
|
|
|
if (cb_func == NULL)
|
|
return MSM_RPC_CLIENT_NULL_CB_ID;
|
|
|
|
spin_lock_irqsave(&client->cb_list_lock, flags);
|
|
list_for_each_entry(cb_item, &client->cb_list, list) {
|
|
if (cb_item->cb_func == cb_func) {
|
|
spin_unlock_irqrestore(&client->cb_list_lock, flags);
|
|
return cb_item->cb_id;
|
|
}
|
|
}
|
|
spin_unlock_irqrestore(&client->cb_list_lock, flags);
|
|
|
|
cb_item = kmalloc(sizeof(struct msm_rpc_cb_table_item), GFP_KERNEL);
|
|
if (!cb_item)
|
|
return -ENOMEM;
|
|
|
|
INIT_LIST_HEAD(&cb_item->list);
|
|
cb_item->cb_id = atomic_add_return(1, &client->next_cb_id);
|
|
cb_item->cb_func = cb_func;
|
|
|
|
spin_lock_irqsave(&client->cb_list_lock, flags);
|
|
list_add_tail(&cb_item->list, &client->cb_list);
|
|
spin_unlock_irqrestore(&client->cb_list_lock, flags);
|
|
|
|
return cb_item->cb_id;
|
|
}
|
|
EXPORT_SYMBOL(msm_rpc_add_cb_func);
|
|
|
|
/*
|
|
* Interface to be used to get a callback function from a callback ID.
|
|
* If no entry is found, NULL is returned.
|
|
*
|
|
* client: pointer to client data structure
|
|
*
|
|
* cb_id: callback ID
|
|
*
|
|
* Return Value:
|
|
* callback function pointer if entry with given cb_id is found,
|
|
* otherwise returns NULL.
|
|
*/
|
|
void *msm_rpc_get_cb_func(struct msm_rpc_client *client, uint32_t cb_id)
|
|
{
|
|
struct msm_rpc_cb_table_item *cb_item;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&client->cb_list_lock, flags);
|
|
list_for_each_entry(cb_item, &client->cb_list, list) {
|
|
if (cb_item->cb_id == cb_id) {
|
|
spin_unlock_irqrestore(&client->cb_list_lock, flags);
|
|
return cb_item->cb_func;
|
|
}
|
|
}
|
|
spin_unlock_irqrestore(&client->cb_list_lock, flags);
|
|
return NULL;
|
|
}
|
|
EXPORT_SYMBOL(msm_rpc_get_cb_func);
|
|
|
|
/*
|
|
* Interface to be used to remove a callback function.
|
|
*
|
|
* client: pointer to client data structure
|
|
*
|
|
* cb_func: callback function
|
|
*
|
|
*/
|
|
void msm_rpc_remove_cb_func(struct msm_rpc_client *client, void *cb_func)
|
|
{
|
|
struct msm_rpc_cb_table_item *cb_item, *tmp_cb_item;
|
|
unsigned long flags;
|
|
|
|
if (cb_func == NULL)
|
|
return;
|
|
|
|
spin_lock_irqsave(&client->cb_list_lock, flags);
|
|
list_for_each_entry_safe(cb_item, tmp_cb_item,
|
|
&client->cb_list, list) {
|
|
if (cb_item->cb_func == cb_func) {
|
|
list_del(&cb_item->list);
|
|
kfree(cb_item);
|
|
spin_unlock_irqrestore(&client->cb_list_lock, flags);
|
|
return;
|
|
}
|
|
}
|
|
spin_unlock_irqrestore(&client->cb_list_lock, flags);
|
|
}
|
|
EXPORT_SYMBOL(msm_rpc_remove_cb_func);
|