/* arch/arm/mach-msm/smd_rpcrouter.c * * Copyright (C) 2007 Google, Inc. * Copyright (c) 2007-2009 QUALCOMM Incorporated. * Author: San Mehat * * 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. * */ /* TODO: handle cases where smd_write() will tempfail due to full fifo */ /* TODO: thread priority? schedule a work to bump it? */ /* TODO: maybe make server_list_lock a mutex */ /* TODO: pool fragments to avoid kmalloc/kfree churn */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "smd_rpcrouter.h" #define TRACE_R2R_MSG 0 #define TRACE_R2R_RAW 0 #define TRACE_RPC_MSG 0 #define TRACE_NOTIFY_MSG 0 #define MSM_RPCROUTER_DEBUG 0 #define MSM_RPCROUTER_DEBUG_PKT 0 #define MSM_RPCROUTER_R2R_DEBUG 0 #define DUMP_ALL_RECEIVED_HEADERS 0 #define DIAG(x...) printk("[RR] ERROR " x) #if MSM_RPCROUTER_DEBUG #define D(x...) printk(x) #else #define D(x...) do {} while (0) #endif #if TRACE_R2R_MSG #define RR(x...) printk("[RR] "x) #else #define RR(x...) do {} while (0) #endif #if TRACE_RPC_MSG #define IO(x...) printk("[RPC] "x) #else #define IO(x...) do {} while (0) #endif #if TRACE_NOTIFY_MSG #define NTFY(x...) printk(KERN_ERR "[NOTIFY] "x) #else #define NTFY(x...) do {} while (0) #endif static LIST_HEAD(local_endpoints); static LIST_HEAD(remote_endpoints); static LIST_HEAD(server_list); static smd_channel_t *smd_channel; static int initialized; static wait_queue_head_t newserver_wait; static wait_queue_head_t smd_wait; static DEFINE_SPINLOCK(local_endpoints_lock); static DEFINE_SPINLOCK(remote_endpoints_lock); static DEFINE_SPINLOCK(server_list_lock); static DEFINE_SPINLOCK(smd_lock); static struct workqueue_struct *rpcrouter_workqueue; static int rpcrouter_need_len; static atomic_t next_xid = ATOMIC_INIT(1); static uint8_t next_pacmarkid; static void do_read_data(struct work_struct *work); static void do_create_pdevs(struct work_struct *work); static void do_create_rpcrouter_pdev(struct work_struct *work); static DECLARE_WORK(work_read_data, do_read_data); static DECLARE_WORK(work_create_pdevs, do_create_pdevs); static DECLARE_WORK(work_create_rpcrouter_pdev, do_create_rpcrouter_pdev); #define RR_STATE_IDLE 0 #define RR_STATE_HEADER 1 #define RR_STATE_BODY 2 #define RR_STATE_ERROR 3 struct rr_context { struct rr_packet *pkt; uint8_t *ptr; uint32_t state; /* current assembly state */ uint32_t count; /* bytes needed in this state */ }; static struct rr_context the_rr_context; static struct platform_device rpcrouter_pdev = { .name = "oncrpc_router", .id = -1, }; static int rpcrouter_send_control_msg(union rr_control_msg *msg) { struct rr_header hdr; unsigned long flags; int need; if (!(msg->cmd == RPCROUTER_CTRL_CMD_HELLO) && !initialized) { printk(KERN_ERR "rpcrouter_send_control_msg(): Warning, " "router not initialized\n"); return -EINVAL; } hdr.version = RPCROUTER_VERSION; hdr.type = msg->cmd; hdr.src_pid = RPCROUTER_PID_LOCAL; hdr.src_cid = RPCROUTER_ROUTER_ADDRESS; hdr.confirm_rx = 0; hdr.size = sizeof(*msg); hdr.dst_pid = 0; hdr.dst_cid = RPCROUTER_ROUTER_ADDRESS; /* TODO: what if channel is full? */ need = sizeof(hdr) + hdr.size; spin_lock_irqsave(&smd_lock, flags); while (smd_write_avail(smd_channel) < need) { spin_unlock_irqrestore(&smd_lock, flags); msleep(250); spin_lock_irqsave(&smd_lock, flags); } smd_write(smd_channel, &hdr, sizeof(hdr)); smd_write(smd_channel, msg, hdr.size); spin_unlock_irqrestore(&smd_lock, flags); return 0; } static struct rr_server *rpcrouter_create_server(uint32_t pid, uint32_t cid, uint32_t prog, uint32_t ver) { struct rr_server *server; unsigned long flags; int rc; server = kmalloc(sizeof(struct rr_server), GFP_KERNEL); if (!server) return ERR_PTR(-ENOMEM); memset(server, 0, sizeof(struct rr_server)); server->pid = pid; server->cid = cid; server->prog = prog; server->vers = ver; spin_lock_irqsave(&server_list_lock, flags); list_add_tail(&server->list, &server_list); spin_unlock_irqrestore(&server_list_lock, flags); if (pid == RPCROUTER_PID_REMOTE) { rc = msm_rpcrouter_create_server_cdev(server); if (rc < 0) goto out_fail; } return server; out_fail: spin_lock_irqsave(&server_list_lock, flags); list_del(&server->list); spin_unlock_irqrestore(&server_list_lock, flags); kfree(server); return ERR_PTR(rc); } static void rpcrouter_destroy_server(struct rr_server *server) { unsigned long flags; spin_lock_irqsave(&server_list_lock, flags); list_del(&server->list); spin_unlock_irqrestore(&server_list_lock, flags); device_destroy(msm_rpcrouter_class, server->device_number); kfree(server); } static struct rr_server *rpcrouter_lookup_server(uint32_t prog, uint32_t ver) { struct rr_server *server; unsigned long flags; spin_lock_irqsave(&server_list_lock, flags); list_for_each_entry(server, &server_list, list) { if (server->prog == prog && server->vers == ver) { spin_unlock_irqrestore(&server_list_lock, flags); return server; } } spin_unlock_irqrestore(&server_list_lock, flags); return NULL; } static struct rr_server *rpcrouter_lookup_server_by_dev(dev_t dev) { struct rr_server *server; unsigned long flags; spin_lock_irqsave(&server_list_lock, flags); list_for_each_entry(server, &server_list, list) { if (server->device_number == dev) { spin_unlock_irqrestore(&server_list_lock, flags); return server; } } spin_unlock_irqrestore(&server_list_lock, flags); return NULL; } struct msm_rpc_endpoint *msm_rpcrouter_create_local_endpoint(dev_t dev) { struct msm_rpc_endpoint *ept; unsigned long flags; ept = kmalloc(sizeof(struct msm_rpc_endpoint), GFP_KERNEL); if (!ept) return NULL; memset(ept, 0, sizeof(struct msm_rpc_endpoint)); /* mark no reply outstanding */ ept->reply_pid = 0xffffffff; ept->cid = (uint32_t) ept; ept->pid = RPCROUTER_PID_LOCAL; ept->dev = dev; if ((dev != msm_rpcrouter_devno) && (dev != MKDEV(0, 0))) { struct rr_server *srv; /* * This is a userspace client which opened * a program/ver devicenode. Bind the client * to that destination */ srv = rpcrouter_lookup_server_by_dev(dev); /* TODO: bug? really? */ BUG_ON(!srv); ept->dst_pid = srv->pid; ept->dst_cid = srv->cid; ept->dst_prog = cpu_to_be32(srv->prog); ept->dst_vers = cpu_to_be32(srv->vers); D("Creating local ept %p @ %08x:%08x\n", ept, srv->prog, srv->vers); } else { /* mark not connected */ ept->dst_pid = 0xffffffff; D("Creating a master local ept %p\n", ept); } init_waitqueue_head(&ept->wait_q); INIT_LIST_HEAD(&ept->read_q); spin_lock_init(&ept->read_q_lock); INIT_LIST_HEAD(&ept->incomplete); spin_lock_irqsave(&local_endpoints_lock, flags); list_add_tail(&ept->list, &local_endpoints); spin_unlock_irqrestore(&local_endpoints_lock, flags); return ept; } int msm_rpcrouter_destroy_local_endpoint(struct msm_rpc_endpoint *ept) { int rc; union rr_control_msg msg; msg.cmd = RPCROUTER_CTRL_CMD_REMOVE_CLIENT; msg.cli.pid = ept->pid; msg.cli.cid = ept->cid; RR("x REMOVE_CLIENT id=%d:%08x\n", ept->pid, ept->cid); rc = rpcrouter_send_control_msg(&msg); if (rc < 0) return rc; list_del(&ept->list); kfree(ept); return 0; } static int rpcrouter_create_remote_endpoint(uint32_t cid) { struct rr_remote_endpoint *new_c; unsigned long flags; new_c = kmalloc(sizeof(struct rr_remote_endpoint), GFP_KERNEL); if (!new_c) return -ENOMEM; memset(new_c, 0, sizeof(struct rr_remote_endpoint)); new_c->cid = cid; new_c->pid = RPCROUTER_PID_REMOTE; init_waitqueue_head(&new_c->quota_wait); spin_lock_init(&new_c->quota_lock); spin_lock_irqsave(&remote_endpoints_lock, flags); list_add_tail(&new_c->list, &remote_endpoints); spin_unlock_irqrestore(&remote_endpoints_lock, flags); return 0; } static struct msm_rpc_endpoint *rpcrouter_lookup_local_endpoint(uint32_t cid) { struct msm_rpc_endpoint *ept; unsigned long flags; spin_lock_irqsave(&local_endpoints_lock, flags); list_for_each_entry(ept, &local_endpoints, list) { if (ept->cid == cid) { spin_unlock_irqrestore(&local_endpoints_lock, flags); return ept; } } spin_unlock_irqrestore(&local_endpoints_lock, flags); return NULL; } static struct rr_remote_endpoint *rpcrouter_lookup_remote_endpoint(uint32_t cid) { struct rr_remote_endpoint *ept; unsigned long flags; spin_lock_irqsave(&remote_endpoints_lock, flags); list_for_each_entry(ept, &remote_endpoints, list) { if (ept->cid == cid) { spin_unlock_irqrestore(&remote_endpoints_lock, flags); return ept; } } spin_unlock_irqrestore(&remote_endpoints_lock, flags); return NULL; } static int process_control_msg(union rr_control_msg *msg, int len) { union rr_control_msg ctl; struct rr_server *server; struct rr_remote_endpoint *r_ept; int rc = 0; unsigned long flags; if (len != sizeof(*msg)) { printk(KERN_ERR "rpcrouter: r2r msg size %d != %d\n", len, sizeof(*msg)); return -EINVAL; } switch (msg->cmd) { case RPCROUTER_CTRL_CMD_HELLO: RR("o HELLO\n"); RR("x HELLO\n"); memset(&ctl, 0, sizeof(ctl)); ctl.cmd = RPCROUTER_CTRL_CMD_HELLO; rpcrouter_send_control_msg(&ctl); initialized = 1; /* Send list of servers one at a time */ ctl.cmd = RPCROUTER_CTRL_CMD_NEW_SERVER; /* TODO: long time to hold a spinlock... */ spin_lock_irqsave(&server_list_lock, flags); list_for_each_entry(server, &server_list, list) { ctl.srv.pid = server->pid; ctl.srv.cid = server->cid; ctl.srv.prog = server->prog; ctl.srv.vers = server->vers; RR("x NEW_SERVER id=%d:%08x prog=%08x:%08x\n", server->pid, server->cid, server->prog, server->vers); rpcrouter_send_control_msg(&ctl); } spin_unlock_irqrestore(&server_list_lock, flags); queue_work(rpcrouter_workqueue, &work_create_rpcrouter_pdev); break; case RPCROUTER_CTRL_CMD_RESUME_TX: RR("o RESUME_TX id=%d:%08x\n", msg->cli.pid, msg->cli.cid); r_ept = rpcrouter_lookup_remote_endpoint(msg->cli.cid); if (!r_ept) { printk(KERN_ERR "rpcrouter: Unable to resume client\n"); break; } spin_lock_irqsave(&r_ept->quota_lock, flags); r_ept->tx_quota_cntr = 0; spin_unlock_irqrestore(&r_ept->quota_lock, flags); wake_up(&r_ept->quota_wait); break; case RPCROUTER_CTRL_CMD_NEW_SERVER: RR("o NEW_SERVER id=%d:%08x prog=%08x:%08x\n", msg->srv.pid, msg->srv.cid, msg->srv.prog, msg->srv.vers); server = rpcrouter_lookup_server(msg->srv.prog, msg->srv.vers); if (!server) { server = rpcrouter_create_server( msg->srv.pid, msg->srv.cid, msg->srv.prog, msg->srv.vers); if (!server) return -ENOMEM; /* * XXX: Verify that its okay to add the * client to our remote client list * if we get a NEW_SERVER notification */ if (!rpcrouter_lookup_remote_endpoint(msg->srv.cid)) { rc = rpcrouter_create_remote_endpoint( msg->srv.cid); if (rc < 0) printk(KERN_ERR "rpcrouter:Client create" "error (%d)\n", rc); } schedule_work(&work_create_pdevs); wake_up(&newserver_wait); } else { if ((server->pid == msg->srv.pid) && (server->cid == msg->srv.cid)) { printk(KERN_ERR "rpcrouter: Duplicate svr\n"); } else { server->pid = msg->srv.pid; server->cid = msg->srv.cid; } } break; case RPCROUTER_CTRL_CMD_REMOVE_SERVER: RR("o REMOVE_SERVER prog=%08x:%d\n", msg->srv.prog, msg->srv.vers); server = rpcrouter_lookup_server(msg->srv.prog, msg->srv.vers); if (server) rpcrouter_destroy_server(server); break; case RPCROUTER_CTRL_CMD_REMOVE_CLIENT: RR("o REMOVE_CLIENT id=%d:%08x\n", msg->cli.pid, msg->cli.cid); if (msg->cli.pid != RPCROUTER_PID_REMOTE) { printk(KERN_ERR "rpcrouter: Denying remote removal of " "local client\n"); break; } r_ept = rpcrouter_lookup_remote_endpoint(msg->cli.cid); if (r_ept) { spin_lock_irqsave(&remote_endpoints_lock, flags); list_del(&r_ept->list); spin_unlock_irqrestore(&remote_endpoints_lock, flags); kfree(r_ept); } /* Notify local clients of this event */ printk(KERN_ERR "rpcrouter: LOCAL NOTIFICATION NOT IMP\n"); rc = -ENOSYS; break; default: RR("o UNKNOWN(%08x)\n", msg->cmd); rc = -ENOSYS; } return rc; } static void do_create_rpcrouter_pdev(struct work_struct *work) { platform_device_register(&rpcrouter_pdev); } static void do_create_pdevs(struct work_struct *work) { unsigned long flags; struct rr_server *server; /* TODO: race if destroyed while being registered */ spin_lock_irqsave(&server_list_lock, flags); list_for_each_entry(server, &server_list, list) { if (server->pid == RPCROUTER_PID_REMOTE) { if (server->pdev_name[0] == 0) { spin_unlock_irqrestore(&server_list_lock, flags); msm_rpcrouter_create_server_pdev(server); schedule_work(&work_create_pdevs); return; } } } spin_unlock_irqrestore(&server_list_lock, flags); } static void rpcrouter_smdnotify(void *_dev, unsigned event) { if (event != SMD_EVENT_DATA) return; wake_up(&smd_wait); } static void *rr_malloc(unsigned sz) { void *ptr = kmalloc(sz, GFP_KERNEL); if (ptr) return ptr; printk(KERN_ERR "rpcrouter: kmalloc of %d failed, retrying...\n", sz); do { ptr = kmalloc(sz, GFP_KERNEL); } while (!ptr); return ptr; } /* TODO: deal with channel teardown / restore */ static int rr_read(void *data, int len) { int rc; unsigned long flags; // printk("rr_read() %d\n", len); for(;;) { spin_lock_irqsave(&smd_lock, flags); if (smd_read_avail(smd_channel) >= len) { rc = smd_read(smd_channel, data, len); spin_unlock_irqrestore(&smd_lock, flags); if (rc == len) return 0; else return -EIO; } rpcrouter_need_len = len; spin_unlock_irqrestore(&smd_lock, flags); // printk("rr_read: waiting (%d)\n", len); wait_event(smd_wait, smd_read_avail(smd_channel) >= len); } return 0; } static uint32_t r2r_buf[RPCROUTER_MSGSIZE_MAX]; static void do_read_data(struct work_struct *work) { struct rr_header hdr; struct rr_packet *pkt; struct rr_fragment *frag; struct msm_rpc_endpoint *ept; uint32_t pm, mid; unsigned long flags; if (rr_read(&hdr, sizeof(hdr))) goto fail_io; #if TRACE_R2R_RAW RR("- ver=%d type=%d src=%d:%08x crx=%d siz=%d dst=%d:%08x\n", hdr.version, hdr.type, hdr.src_pid, hdr.src_cid, hdr.confirm_rx, hdr.size, hdr.dst_pid, hdr.dst_cid); #endif if (hdr.version != RPCROUTER_VERSION) { DIAG("version %d != %d\n", hdr.version, RPCROUTER_VERSION); goto fail_data; } if (hdr.size > RPCROUTER_MSGSIZE_MAX) { DIAG("msg size %d > max %d\n", hdr.size, RPCROUTER_MSGSIZE_MAX); goto fail_data; } if (hdr.dst_cid == RPCROUTER_ROUTER_ADDRESS) { if (rr_read(r2r_buf, hdr.size)) goto fail_io; process_control_msg((void*) r2r_buf, hdr.size); goto done; } if (hdr.size < sizeof(pm)) { DIAG("runt packet (no pacmark)\n"); goto fail_data; } if (rr_read(&pm, sizeof(pm))) goto fail_io; hdr.size -= sizeof(pm); frag = rr_malloc(hdr.size + sizeof(*frag)); frag->next = NULL; frag->length = hdr.size; if (rr_read(frag->data, hdr.size)) goto fail_io; ept = rpcrouter_lookup_local_endpoint(hdr.dst_cid); if (!ept) { DIAG("no local ept for cid %08x\n", hdr.dst_cid); kfree(frag); goto done; } /* See if there is already a partial packet that matches our mid * and if so, append this fragment to that packet. */ mid = PACMARK_MID(pm); list_for_each_entry(pkt, &ept->incomplete, list) { if (pkt->mid == mid) { pkt->last->next = frag; pkt->last = frag; pkt->length += frag->length; if (PACMARK_LAST(pm)) { list_del(&pkt->list); goto packet_complete; } goto done; } } /* This mid is new -- create a packet for it, and put it on * the incomplete list if this fragment is not a last fragment, * otherwise put it on the read queue. */ pkt = rr_malloc(sizeof(struct rr_packet)); pkt->first = frag; pkt->last = frag; memcpy(&pkt->hdr, &hdr, sizeof(hdr)); pkt->mid = mid; pkt->length = frag->length; if (!PACMARK_LAST(pm)) { list_add_tail(&pkt->list, &ept->incomplete); goto done; } packet_complete: spin_lock_irqsave(&ept->read_q_lock, flags); list_add_tail(&pkt->list, &ept->read_q); wake_up(&ept->wait_q); spin_unlock_irqrestore(&ept->read_q_lock, flags); done: if (hdr.confirm_rx) { union rr_control_msg msg; msg.cmd = RPCROUTER_CTRL_CMD_RESUME_TX; msg.cli.pid = hdr.dst_pid; msg.cli.cid = hdr.dst_cid; RR("x RESUME_TX id=%d:%08x\n", msg.cli.pid, msg.cli.cid); rpcrouter_send_control_msg(&msg); } queue_work(rpcrouter_workqueue, &work_read_data); return; fail_io: fail_data: printk(KERN_ERR "rpc_router has died\n"); } void msm_rpc_setup_req(struct rpc_request_hdr *hdr, uint32_t prog, uint32_t vers, uint32_t proc) { memset(hdr, 0, sizeof(struct rpc_request_hdr)); hdr->xid = cpu_to_be32(atomic_add_return(1, &next_xid)); hdr->rpc_vers = cpu_to_be32(2); hdr->prog = cpu_to_be32(prog); hdr->vers = cpu_to_be32(vers); hdr->procedure = cpu_to_be32(proc); } struct msm_rpc_endpoint *msm_rpc_open(void) { struct msm_rpc_endpoint *ept; ept = msm_rpcrouter_create_local_endpoint(MKDEV(0, 0)); if (ept == NULL) return ERR_PTR(-ENOMEM); return ept; } int msm_rpc_close(struct msm_rpc_endpoint *ept) { return msm_rpcrouter_destroy_local_endpoint(ept); } EXPORT_SYMBOL(msm_rpc_close); int msm_rpc_write(struct msm_rpc_endpoint *ept, void *buffer, int count) { struct rr_header hdr; uint32_t pacmark; struct rpc_request_hdr *rq = buffer; struct rr_remote_endpoint *r_ept; unsigned long flags; int needed; DEFINE_WAIT(__wait); /* TODO: fragmentation for large outbound packets */ if (count > (RPCROUTER_MSGSIZE_MAX - sizeof(uint32_t)) || !count) return -EINVAL; /* snoop the RPC packet and enforce permissions */ /* has to have at least the xid and type fields */ if (count < (sizeof(uint32_t) * 2)) { printk(KERN_ERR "rr_write: rejecting runt packet\n"); return -EINVAL; } if (rq->type == 0) { /* RPC CALL */ if (count < (sizeof(uint32_t) * 6)) { printk(KERN_ERR "rr_write: rejecting runt call packet\n"); return -EINVAL; } if (ept->dst_pid == 0xffffffff) { printk(KERN_ERR "rr_write: not connected\n"); return -ENOTCONN; } #if CONFIG_MSM_AMSS_VERSION >= 6350 if ((ept->dst_prog != rq->prog) || !msm_rpc_is_compatible_version( be32_to_cpu(ept->dst_vers), be32_to_cpu(rq->vers))) { #else if (ept->dst_prog != rq->prog || ept->dst_vers != rq->vers) { #endif printk(KERN_ERR "rr_write: cannot write to %08x:%d " "(bound to %08x:%d)\n", be32_to_cpu(rq->prog), be32_to_cpu(rq->vers), be32_to_cpu(ept->dst_prog), be32_to_cpu(ept->dst_vers)); return -EINVAL; } hdr.dst_pid = ept->dst_pid; hdr.dst_cid = ept->dst_cid; IO("CALL on ept %p to %08x:%08x @ %d:%08x (%d bytes) (xid %x proc %x)\n", ept, be32_to_cpu(rq->prog), be32_to_cpu(rq->vers), ept->dst_pid, ept->dst_cid, count, be32_to_cpu(rq->xid), be32_to_cpu(rq->procedure)); } else { /* RPC REPLY */ /* TODO: locking */ if (ept->reply_pid == 0xffffffff) { printk(KERN_ERR "rr_write: rejecting unexpected reply\n"); return -EINVAL; } if (ept->reply_xid != rq->xid) { printk(KERN_ERR "rr_write: rejecting packet w/ bad xid\n"); return -EINVAL; } hdr.dst_pid = ept->reply_pid; hdr.dst_cid = ept->reply_cid; /* consume this reply */ ept->reply_pid = 0xffffffff; IO("REPLY on ept %p to xid=%d @ %d:%08x (%d bytes)\n", ept, be32_to_cpu(rq->xid), hdr.dst_pid, hdr.dst_cid, count); } r_ept = rpcrouter_lookup_remote_endpoint(hdr.dst_cid); if (!r_ept) { printk(KERN_ERR "msm_rpc_write(): No route to ept " "[PID %x CID %x]\n", hdr.dst_pid, hdr.dst_cid); return -EHOSTUNREACH; } /* Create routing header */ hdr.type = RPCROUTER_CTRL_CMD_DATA; hdr.version = RPCROUTER_VERSION; hdr.src_pid = ept->pid; hdr.src_cid = ept->cid; hdr.confirm_rx = 0; hdr.size = count + sizeof(uint32_t); for (;;) { prepare_to_wait(&r_ept->quota_wait, &__wait, TASK_INTERRUPTIBLE); spin_lock_irqsave(&r_ept->quota_lock, flags); if (r_ept->tx_quota_cntr < RPCROUTER_DEFAULT_RX_QUOTA) break; if (signal_pending(current) && (!(ept->flags & MSM_RPC_UNINTERRUPTIBLE))) break; spin_unlock_irqrestore(&r_ept->quota_lock, flags); schedule(); } finish_wait(&r_ept->quota_wait, &__wait); if (signal_pending(current) && (!(ept->flags & MSM_RPC_UNINTERRUPTIBLE))) { spin_unlock_irqrestore(&r_ept->quota_lock, flags); return -ERESTARTSYS; } r_ept->tx_quota_cntr++; if (r_ept->tx_quota_cntr == RPCROUTER_DEFAULT_RX_QUOTA) hdr.confirm_rx = 1; /* bump pacmark while interrupts disabled to avoid race * probably should be atomic op instead */ pacmark = PACMARK(count, ++next_pacmarkid, 0, 1); spin_unlock_irqrestore(&r_ept->quota_lock, flags); spin_lock_irqsave(&smd_lock, flags); needed = sizeof(hdr) + hdr.size; while (smd_write_avail(smd_channel) < needed) { spin_unlock_irqrestore(&smd_lock, flags); msleep(250); spin_lock_irqsave(&smd_lock, flags); } /* TODO: deal with full fifo */ smd_write(smd_channel, &hdr, sizeof(hdr)); smd_write(smd_channel, &pacmark, sizeof(pacmark)); smd_write(smd_channel, buffer, count); spin_unlock_irqrestore(&smd_lock, flags); return count; } EXPORT_SYMBOL(msm_rpc_write); /* * NOTE: It is the responsibility of the caller to kfree buffer */ int msm_rpc_read(struct msm_rpc_endpoint *ept, void **buffer, unsigned user_len, long timeout) { struct rr_fragment *frag, *next; char *buf; int rc; rc = __msm_rpc_read(ept, &frag, user_len, timeout); if (rc <= 0) return rc; /* single-fragment messages conveniently can be * returned as-is (the buffer is at the front) */ if (frag->next == 0) { *buffer = (void*) frag; return rc; } /* multi-fragment messages, we have to do it the * hard way, which is rather disgusting right now */ buf = rr_malloc(rc); *buffer = buf; while (frag != NULL) { memcpy(buf, frag->data, frag->length); next = frag->next; buf += frag->length; kfree(frag); frag = next; } return rc; } int msm_rpc_call(struct msm_rpc_endpoint *ept, uint32_t proc, void *_request, int request_size, long timeout) { return msm_rpc_call_reply(ept, proc, _request, request_size, NULL, 0, timeout); } EXPORT_SYMBOL(msm_rpc_call); int msm_rpc_call_reply(struct msm_rpc_endpoint *ept, uint32_t proc, void *_request, int request_size, void *_reply, int reply_size, long timeout) { struct rpc_request_hdr *req = _request; struct rpc_reply_hdr *reply; int rc; if (request_size < sizeof(*req)) return -ETOOSMALL; if (ept->dst_pid == 0xffffffff) return -ENOTCONN; /* We can't use msm_rpc_setup_req() here, because dst_prog and * dst_vers here are already in BE. */ memset(req, 0, sizeof(*req)); req->xid = cpu_to_be32(atomic_add_return(1, &next_xid)); req->rpc_vers = cpu_to_be32(2); req->prog = ept->dst_prog; req->vers = ept->dst_vers; req->procedure = cpu_to_be32(proc); rc = msm_rpc_write(ept, req, request_size); if (rc < 0) return rc; for (;;) { rc = msm_rpc_read(ept, (void*) &reply, -1, timeout); if (rc < 0) return rc; if (rc < (3 * sizeof(uint32_t))) { rc = -EIO; break; } /* we should not get CALL packets -- ignore them */ if (reply->type == 0) { kfree(reply); continue; } /* If an earlier call timed out, we could get the (no * longer wanted) reply for it. Ignore replies that * we don't expect. */ if (reply->xid != req->xid) { kfree(reply); continue; } if (reply->reply_stat != 0) { rc = -EPERM; break; } if (reply->data.acc_hdr.accept_stat != 0) { rc = -EINVAL; break; } if (_reply == NULL) { rc = 0; break; } if (rc > reply_size) { rc = -ENOMEM; } else { memcpy(_reply, reply, rc); } break; } kfree(reply); return rc; } EXPORT_SYMBOL(msm_rpc_call_reply); static inline int ept_packet_available(struct msm_rpc_endpoint *ept) { unsigned long flags; int ret; spin_lock_irqsave(&ept->read_q_lock, flags); ret = !list_empty(&ept->read_q); spin_unlock_irqrestore(&ept->read_q_lock, flags); return ret; } int __msm_rpc_read(struct msm_rpc_endpoint *ept, struct rr_fragment **frag_ret, unsigned len, long timeout) { struct rr_packet *pkt; struct rpc_request_hdr *rq; DEFINE_WAIT(__wait); unsigned long flags; int rc; IO("READ on ept %p\n", ept); if (ept->flags & MSM_RPC_UNINTERRUPTIBLE) { if (timeout < 0) { wait_event(ept->wait_q, ept_packet_available(ept)); } else { rc = wait_event_timeout( ept->wait_q, ept_packet_available(ept), timeout); if (rc == 0) return -ETIMEDOUT; } } else { if (timeout < 0) { rc = wait_event_interruptible( ept->wait_q, ept_packet_available(ept)); if (rc < 0) return rc; } else { rc = wait_event_interruptible_timeout( ept->wait_q, ept_packet_available(ept), timeout); if (rc == 0) return -ETIMEDOUT; } } spin_lock_irqsave(&ept->read_q_lock, flags); if (list_empty(&ept->read_q)) { spin_unlock_irqrestore(&ept->read_q_lock, flags); return -EAGAIN; } pkt = list_first_entry(&ept->read_q, struct rr_packet, list); if (pkt->length > len) { spin_unlock_irqrestore(&ept->read_q_lock, flags); return -ETOOSMALL; } list_del(&pkt->list); spin_unlock_irqrestore(&ept->read_q_lock, flags); rc = pkt->length; *frag_ret = pkt->first; rq = (void*) pkt->first->data; if ((rc >= (sizeof(uint32_t) * 3)) && (rq->type == 0)) { IO("READ on ept %p is a CALL on %08x:%08x proc %d xid %d\n", ept, be32_to_cpu(rq->prog), be32_to_cpu(rq->vers), be32_to_cpu(rq->procedure), be32_to_cpu(rq->xid)); /* RPC CALL */ if (ept->reply_pid != 0xffffffff) { printk(KERN_WARNING "rr_read: lost previous reply xid...\n"); } /* TODO: locking? */ ept->reply_pid = pkt->hdr.src_pid; ept->reply_cid = pkt->hdr.src_cid; ept->reply_xid = rq->xid; } #if TRACE_RPC_MSG else if ((rc >= (sizeof(uint32_t) * 3)) && (rq->type == 1)) IO("READ on ept %p is a REPLY\n", ept); else IO("READ on ept %p (%d bytes)\n", ept, rc); #endif kfree(pkt); return rc; } #if CONFIG_MSM_AMSS_VERSION >= 6350 int msm_rpc_is_compatible_version(uint32_t server_version, uint32_t client_version) { if ((server_version & RPC_VERSION_MODE_MASK) != (client_version & RPC_VERSION_MODE_MASK)) return 0; if (server_version & RPC_VERSION_MODE_MASK) return server_version == client_version; return ((server_version & RPC_VERSION_MAJOR_MASK) == (client_version & RPC_VERSION_MAJOR_MASK)) && ((server_version & RPC_VERSION_MINOR_MASK) >= (client_version & RPC_VERSION_MINOR_MASK)); } EXPORT_SYMBOL(msm_rpc_is_compatible_version); static int msm_rpc_get_compatible_server(uint32_t prog, uint32_t ver, uint32_t *found_vers) { struct rr_server *server; unsigned long flags; if (found_vers == NULL) return 0; spin_lock_irqsave(&server_list_lock, flags); list_for_each_entry(server, &server_list, list) { if ((server->prog == prog) && msm_rpc_is_compatible_version(server->vers, ver)) { *found_vers = server->vers; spin_unlock_irqrestore(&server_list_lock, flags); return 0; } } spin_unlock_irqrestore(&server_list_lock, flags); return -1; } #endif struct msm_rpc_endpoint *msm_rpc_connect(uint32_t prog, uint32_t vers, unsigned flags) { struct msm_rpc_endpoint *ept; struct rr_server *server; #if CONFIG_MSM_AMSS_VERSION >= 6350 if (!(vers & RPC_VERSION_MODE_MASK)) { uint32_t found_vers; if (msm_rpc_get_compatible_server(prog, vers, &found_vers) < 0) return ERR_PTR(-EHOSTUNREACH); if (found_vers != vers) { D("RPC using new version %08x:{%08x --> %08x}\n", prog, vers, found_vers); vers = found_vers; } } #endif server = rpcrouter_lookup_server(prog, vers); if (!server) return ERR_PTR(-EHOSTUNREACH); ept = msm_rpc_open(); if (IS_ERR(ept)) return ept; ept->flags = flags; ept->dst_pid = server->pid; ept->dst_cid = server->cid; ept->dst_prog = cpu_to_be32(prog); ept->dst_vers = cpu_to_be32(vers); return ept; } EXPORT_SYMBOL(msm_rpc_connect); uint32_t msm_rpc_get_vers(struct msm_rpc_endpoint *ept) { return be32_to_cpu(ept->dst_vers); } EXPORT_SYMBOL(msm_rpc_get_vers); /* TODO: permission check? */ int msm_rpc_register_server(struct msm_rpc_endpoint *ept, uint32_t prog, uint32_t vers) { int rc; union rr_control_msg msg; struct rr_server *server; server = rpcrouter_create_server(ept->pid, ept->cid, prog, vers); if (!server) return -ENODEV; msg.srv.cmd = RPCROUTER_CTRL_CMD_NEW_SERVER; msg.srv.pid = ept->pid; msg.srv.cid = ept->cid; msg.srv.prog = prog; msg.srv.vers = vers; RR("x NEW_SERVER id=%d:%08x prog=%08x:%08x\n", ept->pid, ept->cid, prog, vers); rc = rpcrouter_send_control_msg(&msg); if (rc < 0) return rc; return 0; } /* TODO: permission check -- disallow unreg of somebody else's server */ int msm_rpc_unregister_server(struct msm_rpc_endpoint *ept, uint32_t prog, uint32_t vers) { struct rr_server *server; server = rpcrouter_lookup_server(prog, vers); if (!server) return -ENOENT; rpcrouter_destroy_server(server); return 0; } static int msm_rpcrouter_probe(struct platform_device *pdev) { int rc; /* Initialize what we need to start processing */ INIT_LIST_HEAD(&local_endpoints); INIT_LIST_HEAD(&remote_endpoints); init_waitqueue_head(&newserver_wait); init_waitqueue_head(&smd_wait); rpcrouter_workqueue = create_singlethread_workqueue("rpcrouter"); if (!rpcrouter_workqueue) return -ENOMEM; rc = msm_rpcrouter_init_devices(); if (rc < 0) goto fail_destroy_workqueue; /* Open up SMD channel 2 */ initialized = 0; rc = smd_open("SMD_RPCCALL", &smd_channel, NULL, rpcrouter_smdnotify); if (rc < 0) goto fail_remove_devices; queue_work(rpcrouter_workqueue, &work_read_data); return 0; fail_remove_devices: msm_rpcrouter_exit_devices(); fail_destroy_workqueue: destroy_workqueue(rpcrouter_workqueue); return rc; } static struct platform_driver msm_smd_channel2_driver = { .probe = msm_rpcrouter_probe, .driver = { .name = "SMD_RPCCALL", .owner = THIS_MODULE, }, }; static int __init rpcrouter_init(void) { return platform_driver_register(&msm_smd_channel2_driver); } module_init(rpcrouter_init); MODULE_DESCRIPTION("MSM RPC Router"); MODULE_AUTHOR("San Mehat "); MODULE_LICENSE("GPL");