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