/* arch/arm/mach-msm/rpc_servers.c * * Copyright (C) 2007 Google, Inc. * Copyright (c) 2009-2010, The Linux Foundation. All rights reserved. * Author: Iliyan Malchev * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * 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. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "smd_rpcrouter.h" static struct msm_rpc_endpoint *endpoint; #define FLAG_REGISTERED 0x0001 static LIST_HEAD(rpc_server_list); static DEFINE_MUTEX(rpc_server_list_lock); static int rpc_servers_active; static struct wake_lock rpc_servers_wake_lock; static struct msm_rpc_xdr server_xdr; static uint32_t current_xid; static void rpc_server_register(struct msm_rpc_server *server) { int rc; rc = msm_rpc_register_server(endpoint, server->prog, server->vers); if (rc < 0) printk(KERN_ERR "[rpcserver] error registering %p @ %08x:%d\n", server, server->prog, server->vers); } static struct msm_rpc_server *rpc_server_find(uint32_t prog, uint32_t vers) { struct msm_rpc_server *server; mutex_lock(&rpc_server_list_lock); list_for_each_entry(server, &rpc_server_list, list) { if ((server->prog == prog) && msm_rpc_is_compatible_version(server->vers, vers)) { mutex_unlock(&rpc_server_list_lock); return server; } } mutex_unlock(&rpc_server_list_lock); return NULL; } static void rpc_server_register_all(void) { struct msm_rpc_server *server; mutex_lock(&rpc_server_list_lock); list_for_each_entry(server, &rpc_server_list, list) { if (!(server->flags & FLAG_REGISTERED)) { rpc_server_register(server); server->flags |= FLAG_REGISTERED; } } mutex_unlock(&rpc_server_list_lock); } int msm_rpc_create_server(struct msm_rpc_server *server) { void *buf; /* make sure we're in a sane state first */ server->flags = 0; INIT_LIST_HEAD(&server->list); mutex_init(&server->cb_req_lock); server->version = 1; xdr_init(&server->cb_xdr); buf = kmalloc(MSM_RPC_MSGSIZE_MAX, GFP_KERNEL); if (!buf) return -ENOMEM; xdr_init_output(&server->cb_xdr, buf, MSM_RPC_MSGSIZE_MAX); server->cb_ept = server->cb_xdr.ept = msm_rpc_open(); if (IS_ERR(server->cb_ept)) { xdr_clean_output(&server->cb_xdr); return PTR_ERR(server->cb_ept); } server->cb_ept->flags = MSM_RPC_UNINTERRUPTIBLE; server->cb_ept->dst_prog = cpu_to_be32(server->prog | 0x01000000); server->cb_ept->dst_vers = cpu_to_be32(server->vers); mutex_lock(&rpc_server_list_lock); list_add(&server->list, &rpc_server_list); if (rpc_servers_active) { rpc_server_register(server); server->flags |= FLAG_REGISTERED; } mutex_unlock(&rpc_server_list_lock); return 0; } EXPORT_SYMBOL(msm_rpc_create_server); int msm_rpc_create_server2(struct msm_rpc_server *server) { int rc; rc = msm_rpc_create_server(server); server->version = 2; return rc; } EXPORT_SYMBOL(msm_rpc_create_server2); static int rpc_send_accepted_void_reply(struct msm_rpc_endpoint *client, uint32_t xid, uint32_t accept_status) { int rc = 0; uint8_t reply_buf[sizeof(struct rpc_reply_hdr)]; struct rpc_reply_hdr *reply = (struct rpc_reply_hdr *)reply_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; rc = msm_rpc_write(client, reply_buf, sizeof(reply_buf)); if (rc == -ENETRESET) { /* Modem restarted, drop reply, clear state */ msm_rpc_clear_netreset(client); } if (rc < 0) printk(KERN_ERR "%s: could not write response: %d\n", __FUNCTION__, rc); return rc; } /* * Interface to be used to start accepted reply message for a * request. Returns the buffer pointer to attach any payload. * Should call msm_rpc_server_send_accepted_reply to complete sending * reply. Marshaling should be handled by user for the payload. * * server: pointer to server data structure * * xid: transaction id. Has to be same as the one in request. * * accept_status: acceptance status * * Return Value: * pointer to buffer to attach the payload. */ void *msm_rpc_server_start_accepted_reply(struct msm_rpc_server *server, uint32_t xid, uint32_t accept_status) { struct rpc_reply_hdr *reply; mutex_lock(&server_xdr.out_lock); reply = (struct rpc_reply_hdr *)server_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; server_xdr.out_index += sizeof(*reply); return reply + 1; } EXPORT_SYMBOL(msm_rpc_server_start_accepted_reply); /* * Interface to be used to send accepted reply for a request. * msm_rpc_server_start_accepted_reply should have been called before. * Marshaling should be handled by user for the payload. * * server: pointer to server data structure * * size: additional payload size * * Return Value: * 0 on success, otherwise returns an error code. */ int msm_rpc_server_send_accepted_reply(struct msm_rpc_server *server, uint32_t size) { int rc = 0; server_xdr.out_index += size; rc = msm_rpc_write(endpoint, server_xdr.out_buf, server_xdr.out_index); if (rc > 0) rc = 0; mutex_unlock(&server_xdr.out_lock); return rc; } EXPORT_SYMBOL(msm_rpc_server_send_accepted_reply); /* * Interface to be used to send a server callback 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. * * server: pointer to server data sturcture * * clnt_info: pointer to client information data structure. * callback will be sent to this client. * * cb_proc: callback 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_server_cb_req(struct msm_rpc_server *server, struct msm_rpc_client_info *clnt_info, uint32_t cb_proc, int (*arg_func)(struct msm_rpc_server *server, void *buf, void *data), void *arg_data, int (*ret_func)(struct msm_rpc_server *server, void *buf, void *data), void *ret_data, long timeout) { struct rpc_reply_hdr *rpc_rsp; void *buffer; int rc = 0; uint32_t req_xid; if (!clnt_info) return -EINVAL; mutex_lock(&server->cb_req_lock); msm_rpc_setup_req((struct rpc_request_hdr *)server->cb_xdr.out_buf, (server->prog | 0x01000000), be32_to_cpu(clnt_info->vers), cb_proc); server->cb_xdr.out_index = sizeof(struct rpc_request_hdr); req_xid = *(uint32_t *)server->cb_xdr.out_buf; if (arg_func) { rc = arg_func(server, (void *)((struct rpc_request_hdr *) server->cb_xdr.out_buf + 1), arg_data); if (rc < 0) goto release_locks; else server->cb_xdr.out_index += rc; } server->cb_ept->dst_pid = clnt_info->pid; server->cb_ept->dst_cid = clnt_info->cid; rc = msm_rpc_write(server->cb_ept, server->cb_xdr.out_buf, server->cb_xdr.out_index); if (rc < 0) { pr_err("%s: couldn't send RPC CB request:%d\n", __func__, rc); goto release_locks; } else rc = 0; if (timeout < 0) timeout = msecs_to_jiffies(10000); do { buffer = NULL; rc = msm_rpc_read(server->cb_ept, &buffer, -1, timeout); xdr_init_input(&server->cb_xdr, buffer, rc); if ((rc < ((int)(sizeof(uint32_t) * 2))) || (be32_to_cpu(*((uint32_t *)buffer + 1)) != 1)) { printk(KERN_ERR "%s: Invalid reply: %d\n", __func__, rc); goto free_and_release; } rpc_rsp = (struct rpc_reply_hdr *)server->cb_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)); xdr_clean_input(&server->cb_xdr); rc = timeout; /* timeout is not adjusted, but it is not critical */ } else rc = 0; } while (rc); if (be32_to_cpu(rpc_rsp->reply_stat) != RPCMSG_REPLYSTAT_ACCEPTED) { pr_err("%s: RPC cb req 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 cb req 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(server, (void *)(rpc_rsp + 1), ret_data); free_and_release: xdr_clean_input(&server->cb_xdr); server->cb_xdr.out_index = 0; release_locks: mutex_unlock(&server->cb_req_lock); return rc; } EXPORT_SYMBOL(msm_rpc_server_cb_req); /* * Interface to be used to send a server callback 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. * * server: pointer to server data sturcture * * clnt_info: pointer to client information data structure. * callback will be sent to this client. * * cb_proc: callback 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_server_cb_req2(struct msm_rpc_server *server, struct msm_rpc_client_info *clnt_info, uint32_t cb_proc, int (*arg_func)(struct msm_rpc_server *server, struct msm_rpc_xdr *xdr, void *data), void *arg_data, int (*ret_func)(struct msm_rpc_server *server, struct msm_rpc_xdr *xdr, void *data), void *ret_data, long timeout) { int size = 0; struct rpc_reply_hdr rpc_rsp; void *buffer; int rc = 0; uint32_t req_xid; if (!clnt_info) return -EINVAL; mutex_lock(&server->cb_req_lock); xdr_start_request(&server->cb_xdr, (server->prog | 0x01000000), be32_to_cpu(clnt_info->vers), cb_proc); req_xid = be32_to_cpu(*(uint32_t *)server->cb_xdr.out_buf); if (arg_func) { rc = arg_func(server, &server->cb_xdr, arg_data); if (rc < 0) goto release_locks; else size += rc; } server->cb_ept->dst_pid = clnt_info->pid; server->cb_ept->dst_cid = clnt_info->cid; rc = xdr_send_msg(&server->cb_xdr); if (rc < 0) { pr_err("%s: couldn't send RPC CB request:%d\n", __func__, rc); goto release_locks; } else rc = 0; if (timeout < 0) timeout = msecs_to_jiffies(10000); do { buffer = NULL; rc = msm_rpc_read(server->cb_ept, &buffer, -1, timeout); if (rc < 0) { server->cb_xdr.out_index = 0; goto release_locks; } xdr_init_input(&server->cb_xdr, buffer, rc); rc = xdr_recv_reply(&server->cb_xdr, &rpc_rsp); if (rc || (rpc_rsp.type != 1)) { printk(KERN_ERR "%s: Invalid reply :%d\n", __func__, rc); rc = -EINVAL; goto free_and_release; } if (req_xid != rpc_rsp.xid) { pr_info("%s: xid mismatch, req %d reply %d\n", __func__, req_xid, rpc_rsp.xid); xdr_clean_input(&server->cb_xdr); rc = timeout; /* timeout is not adjusted, but it is not critical */ } else rc = 0; } while (rc); if (rpc_rsp.reply_stat != RPCMSG_REPLYSTAT_ACCEPTED) { pr_err("%s: RPC cb req 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 cb req 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(server, &server->cb_xdr, ret_data); free_and_release: xdr_clean_input(&server->cb_xdr); server->cb_xdr.out_index = 0; release_locks: mutex_unlock(&server->cb_req_lock); return rc; } EXPORT_SYMBOL(msm_rpc_server_cb_req2); void msm_rpc_server_get_requesting_client(struct msm_rpc_client_info *clnt_info) { if (!clnt_info) return; get_requesting_client(endpoint, current_xid, clnt_info); } static int rpc_servers_thread(void *data) { void *buffer, *buf; struct rpc_request_hdr req; struct rpc_request_hdr *req1; struct msm_rpc_server *server; int rc; xdr_init(&server_xdr); server_xdr.ept = endpoint; buf = kmalloc(MSM_RPC_MSGSIZE_MAX, GFP_KERNEL); if (!buf) return -ENOMEM; xdr_init_output(&server_xdr, buf, MSM_RPC_MSGSIZE_MAX); for (;;) { wake_unlock(&rpc_servers_wake_lock); rc = wait_event_interruptible(endpoint->wait_q, !list_empty(&endpoint->read_q)); wake_lock(&rpc_servers_wake_lock); rc = msm_rpc_read(endpoint, &buffer, -1, -1); if (rc < 0) { printk(KERN_ERR "%s: could not read: %d\n", __FUNCTION__, rc); break; } req1 = (struct rpc_request_hdr *)buffer; current_xid = req1->xid; xdr_init_input(&server_xdr, buffer, rc); xdr_recv_req(&server_xdr, &req); server = rpc_server_find(req.prog, req.vers); if (req.rpc_vers != 2) goto free_buffer; if (req.type != 0) goto free_buffer; if (!server) { rpc_send_accepted_void_reply( endpoint, req.xid, RPC_ACCEPTSTAT_PROG_UNAVAIL); goto free_buffer; } if (server->version == 2) rc = server->rpc_call2(server, &req, &server_xdr); else { req1->type = be32_to_cpu(req1->type); req1->xid = be32_to_cpu(req1->xid); req1->rpc_vers = be32_to_cpu(req1->rpc_vers); req1->prog = be32_to_cpu(req1->prog); req1->vers = be32_to_cpu(req1->vers); req1->procedure = be32_to_cpu(req1->procedure); rc = server->rpc_call(server, req1, rc); } if (rc == 0) { msm_rpc_server_start_accepted_reply( server, req.xid, RPC_ACCEPTSTAT_SUCCESS); msm_rpc_server_send_accepted_reply(server, 0); } else if (rc < 0) { msm_rpc_server_start_accepted_reply( server, req.xid, RPC_ACCEPTSTAT_PROC_UNAVAIL); msm_rpc_server_send_accepted_reply(server, 0); } free_buffer: xdr_clean_input(&server_xdr); server_xdr.out_index = 0; } do_exit(0); } static int rpcservers_probe(struct platform_device *pdev) { struct task_struct *server_thread; endpoint = msm_rpc_open(); if (IS_ERR(endpoint)) return PTR_ERR(endpoint); /* we're online -- register any servers installed beforehand */ rpc_servers_active = 1; current_xid = 0; rpc_server_register_all(); /* start the kernel thread */ server_thread = kthread_run(rpc_servers_thread, NULL, "krpcserversd"); if (IS_ERR(server_thread)) return PTR_ERR(server_thread); return 0; } static struct platform_driver rpcservers_driver = { .probe = rpcservers_probe, .driver = { .name = "oncrpc_router", .owner = THIS_MODULE, }, }; static int __init rpc_servers_init(void) { wake_lock_init(&rpc_servers_wake_lock, WAKE_LOCK_SUSPEND, "rpc_server"); return platform_driver_register(&rpcservers_driver); } module_init(rpc_servers_init); MODULE_DESCRIPTION("MSM RPC Servers"); MODULE_AUTHOR("Iliyan Malchev "); MODULE_LICENSE("GPL");