/* arch/arm/mach-msm/smd_rpcrouter_device.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. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "smd_rpcrouter.h" #define SAFETY_MEM_SIZE 65536 /* Next minor # available for a remote server */ static int next_minor = 1; struct class *msm_rpcrouter_class; dev_t msm_rpcrouter_devno; static struct cdev rpcrouter_cdev; static struct device *rpcrouter_device; static int rpcrouter_open(struct inode *inode, struct file *filp) { int rc; struct msm_rpc_endpoint *ept; rc = nonseekable_open(inode, filp); if (rc < 0) return rc; ept = msm_rpcrouter_create_local_endpoint(inode->i_rdev); if (!ept) return -ENOMEM; filp->private_data = ept; return 0; } static int rpcrouter_release(struct inode *inode, struct file *filp) { struct msm_rpc_endpoint *ept; ept = (struct msm_rpc_endpoint *) filp->private_data; return msm_rpcrouter_destroy_local_endpoint(ept); } static ssize_t rpcrouter_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) { struct msm_rpc_endpoint *ept; struct rr_fragment *frag, *next; int rc; ept = (struct msm_rpc_endpoint *) filp->private_data; rc = __msm_rpc_read(ept, &frag, count, -1); if (rc < 0) return rc; count = rc; while (frag != NULL) { if (copy_to_user(buf, frag->data, frag->length)) { printk(KERN_ERR "rpcrouter: could not copy all read data to user!\n"); rc = -EFAULT; } buf += frag->length; next = frag->next; kfree(frag); frag = next; } return rc; } static ssize_t rpcrouter_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) { struct msm_rpc_endpoint *ept; int rc = 0; void *k_buffer; ept = (struct msm_rpc_endpoint *) filp->private_data; /* A check for safety, this seems non-standard */ if (count > SAFETY_MEM_SIZE) return -EINVAL; k_buffer = kmalloc(count, GFP_KERNEL); if (!k_buffer) return -ENOMEM; if (copy_from_user(k_buffer, buf, count)) { rc = -EFAULT; goto write_out_free; } rc = msm_rpc_write(ept, k_buffer, count); if (rc < 0) goto write_out_free; rc = count; write_out_free: kfree(k_buffer); return rc; } static unsigned int rpcrouter_poll(struct file *filp, struct poll_table_struct *wait) { struct msm_rpc_endpoint *ept; unsigned mask = 0; ept = (struct msm_rpc_endpoint *) filp->private_data; /* If there's data already in the read queue, return POLLIN. * Else, wait for the requested amount of time, and check again. */ if (!list_empty(&ept->read_q)) mask |= POLLIN; if (!mask) { poll_wait(filp, &ept->wait_q, wait); if (!list_empty(&ept->read_q)) mask |= POLLIN; } return mask; } static long rpcrouter_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct msm_rpc_endpoint *ept; struct rpcrouter_ioctl_server_args server_args; int rc = 0; uint32_t n; ept = (struct msm_rpc_endpoint *) filp->private_data; switch (cmd) { case RPC_ROUTER_IOCTL_GET_VERSION: n = RPC_ROUTER_VERSION_V1; rc = put_user(n, (unsigned int *) arg); break; case RPC_ROUTER_IOCTL_GET_MTU: /* the pacmark word reduces the actual payload * possible per message */ n = RPCROUTER_MSGSIZE_MAX - sizeof(uint32_t); rc = put_user(n, (unsigned int *) arg); break; case RPC_ROUTER_IOCTL_REGISTER_SERVER: rc = copy_from_user(&server_args, (void *) arg, sizeof(server_args)); if (rc < 0) break; msm_rpc_register_server(ept, server_args.prog, server_args.vers); break; case RPC_ROUTER_IOCTL_UNREGISTER_SERVER: rc = copy_from_user(&server_args, (void *) arg, sizeof(server_args)); if (rc < 0) break; msm_rpc_unregister_server(ept, server_args.prog, server_args.vers); break; case RPC_ROUTER_IOCTL_GET_MINOR_VERSION: n = MSM_RPC_GET_MINOR(msm_rpc_get_vers(ept)); rc = put_user(n, (unsigned int *)arg); break; default: rc = -EINVAL; break; } return rc; } static struct file_operations rpcrouter_server_fops = { .owner = THIS_MODULE, .open = rpcrouter_open, .release = rpcrouter_release, .read = rpcrouter_read, .write = rpcrouter_write, .poll = rpcrouter_poll, .unlocked_ioctl = rpcrouter_ioctl, }; static struct file_operations rpcrouter_router_fops = { .owner = THIS_MODULE, .open = rpcrouter_open, .release = rpcrouter_release, .read = rpcrouter_read, .write = rpcrouter_write, .poll = rpcrouter_poll, .unlocked_ioctl = rpcrouter_ioctl, }; int msm_rpcrouter_create_server_cdev(struct rr_server *server) { int rc; uint32_t dev_vers; if (next_minor == RPCROUTER_MAX_REMOTE_SERVERS) { printk(KERN_ERR "rpcrouter: Minor numbers exhausted - Increase " "RPCROUTER_MAX_REMOTE_SERVERS\n"); return -ENOBUFS; } #if CONFIG_MSM_AMSS_VERSION >= 6350 || defined(CONFIG_ARCH_QSD8X50) || defined(CONFIG_ARCH_MSM7X30) /* Servers with bit 31 set are remote msm servers with hashkey version. * Servers with bit 31 not set are remote msm servers with * backwards compatible version type in which case the minor number * (lower 16 bits) is set to zero. * */ if ((server->vers & RPC_VERSION_MODE_MASK)) dev_vers = server->vers; else dev_vers = server->vers & RPC_VERSION_MAJOR_MASK; #else dev_vers = server->vers; #endif server->device_number = MKDEV(MAJOR(msm_rpcrouter_devno), next_minor++); server->device = device_create(msm_rpcrouter_class, rpcrouter_device, server->device_number, NULL, "%.8x:%.8x", server->prog, dev_vers); if (IS_ERR(server->device)) { printk(KERN_ERR "rpcrouter: Unable to create device (%ld)\n", PTR_ERR(server->device)); return PTR_ERR(server->device);; } cdev_init(&server->cdev, &rpcrouter_server_fops); server->cdev.owner = THIS_MODULE; rc = cdev_add(&server->cdev, server->device_number, 1); if (rc < 0) { printk(KERN_ERR "rpcrouter: Unable to add chrdev (%d)\n", rc); device_destroy(msm_rpcrouter_class, server->device_number); return rc; } return 0; } /* for backward compatible version type (31st bit cleared) * clearing minor number (lower 16 bits) in device name * is neccessary for driver binding */ int msm_rpcrouter_create_server_pdev(struct rr_server *server) { sprintf(server->pdev_name, "rs%.8x:%.8x", server->prog, #if CONFIG_MSM_AMSS_VERSION >= 6350 || defined(CONFIG_ARCH_QSD8X50) || defined(CONFIG_ARCH_MSM7X30) (server->vers & RPC_VERSION_MODE_MASK) ? server->vers : (server->vers & RPC_VERSION_MAJOR_MASK)); #else server->vers); #endif server->p_device.base.id = -1; server->p_device.base.name = server->pdev_name; server->p_device.prog = server->prog; server->p_device.vers = server->vers; platform_device_register(&server->p_device.base); return 0; } int msm_rpcrouter_init_devices(void) { int rc; int major; /* Create the device nodes */ msm_rpcrouter_class = class_create(THIS_MODULE, "oncrpc"); if (IS_ERR(msm_rpcrouter_class)) { rc = -ENOMEM; printk(KERN_ERR "rpcrouter: failed to create oncrpc class\n"); goto fail; } rc = alloc_chrdev_region(&msm_rpcrouter_devno, 0, RPCROUTER_MAX_REMOTE_SERVERS + 1, "oncrpc"); if (rc < 0) { printk(KERN_ERR "rpcrouter: Failed to alloc chardev region (%d)\n", rc); goto fail_destroy_class; } major = MAJOR(msm_rpcrouter_devno); rpcrouter_device = device_create(msm_rpcrouter_class, NULL, msm_rpcrouter_devno, NULL, "%.8x:%d", 0, 0); if (IS_ERR(rpcrouter_device)) { rc = -ENOMEM; goto fail_unregister_cdev_region; } cdev_init(&rpcrouter_cdev, &rpcrouter_router_fops); rpcrouter_cdev.owner = THIS_MODULE; rc = cdev_add(&rpcrouter_cdev, msm_rpcrouter_devno, 1); if (rc < 0) goto fail_destroy_device; return 0; fail_destroy_device: device_destroy(msm_rpcrouter_class, msm_rpcrouter_devno); fail_unregister_cdev_region: unregister_chrdev_region(msm_rpcrouter_devno, RPCROUTER_MAX_REMOTE_SERVERS + 1); fail_destroy_class: class_destroy(msm_rpcrouter_class); fail: return rc; } void msm_rpcrouter_exit_devices(void) { cdev_del(&rpcrouter_cdev); device_destroy(msm_rpcrouter_class, msm_rpcrouter_devno); unregister_chrdev_region(msm_rpcrouter_devno, RPCROUTER_MAX_REMOTE_SERVERS + 1); class_destroy(msm_rpcrouter_class); }