| /* 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); |