Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 1 | /* arch/arm/mach-msm/smd_rpcrouter_device.c |
| 2 | * |
| 3 | * Copyright (C) 2007 Google, Inc. |
Stephen Boyd | 77db8bb | 2012-06-27 15:15:16 -0700 | [diff] [blame] | 4 | * Copyright (c) 2007-2012, Code Aurora Forum. All rights reserved. |
Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 5 | * Author: San Mehat <san@android.com> |
| 6 | * |
| 7 | * This software is licensed under the terms of the GNU General Public |
| 8 | * License version 2, as published by the Free Software Foundation, and |
| 9 | * may be copied, distributed, and modified under those terms. |
| 10 | * |
| 11 | * This program is distributed in the hope that it will be useful, |
| 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 14 | * GNU General Public License for more details. |
| 15 | * |
| 16 | */ |
| 17 | #include <linux/slab.h> |
| 18 | #include <linux/module.h> |
| 19 | #include <linux/kernel.h> |
| 20 | #include <linux/string.h> |
| 21 | #include <linux/errno.h> |
| 22 | #include <linux/cdev.h> |
| 23 | #include <linux/init.h> |
| 24 | #include <linux/device.h> |
| 25 | #include <linux/types.h> |
| 26 | #include <linux/delay.h> |
| 27 | #include <linux/fs.h> |
| 28 | #include <linux/err.h> |
| 29 | #include <linux/sched.h> |
| 30 | #include <linux/poll.h> |
| 31 | #include <linux/platform_device.h> |
| 32 | #include <linux/msm_rpcrouter.h> |
| 33 | |
| 34 | #include <asm/uaccess.h> |
| 35 | #include <asm/byteorder.h> |
| 36 | |
Stephen Boyd | 77db8bb | 2012-06-27 15:15:16 -0700 | [diff] [blame] | 37 | #include <mach/subsystem_restart.h> |
Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 38 | #include "smd_rpcrouter.h" |
| 39 | |
| 40 | /* Support 64KB of data plus some space for headers */ |
| 41 | #define SAFETY_MEM_SIZE (65536 + sizeof(struct rpc_request_hdr)) |
| 42 | |
| 43 | /* modem load timeout */ |
| 44 | #define MODEM_LOAD_TIMEOUT (10 * HZ) |
| 45 | |
| 46 | /* Next minor # available for a remote server */ |
| 47 | static int next_minor = 1; |
| 48 | static DEFINE_SPINLOCK(server_cdev_lock); |
| 49 | |
| 50 | struct class *msm_rpcrouter_class; |
| 51 | dev_t msm_rpcrouter_devno; |
| 52 | |
| 53 | static struct cdev rpcrouter_cdev; |
| 54 | static struct device *rpcrouter_device; |
| 55 | |
| 56 | struct rpcrouter_file_info { |
| 57 | struct msm_rpc_endpoint *ept; |
| 58 | void *modem_pil; |
| 59 | }; |
| 60 | |
| 61 | static void msm_rpcrouter_unload_modem(void *pil) |
| 62 | { |
| 63 | if (pil) |
Stephen Boyd | 77db8bb | 2012-06-27 15:15:16 -0700 | [diff] [blame] | 64 | subsystem_put(pil); |
Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 65 | } |
| 66 | |
| 67 | static void *msm_rpcrouter_load_modem(void) |
| 68 | { |
| 69 | void *pil; |
| 70 | int rc; |
| 71 | |
Stephen Boyd | 77db8bb | 2012-06-27 15:15:16 -0700 | [diff] [blame] | 72 | pil = subsystem_get("modem"); |
Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 73 | if (IS_ERR(pil)) |
| 74 | pr_err("%s: modem load failed\n", __func__); |
| 75 | else { |
| 76 | rc = wait_for_completion_interruptible_timeout( |
| 77 | &rpc_remote_router_up, |
| 78 | MODEM_LOAD_TIMEOUT); |
| 79 | if (!rc) |
| 80 | rc = -ETIMEDOUT; |
| 81 | if (rc < 0) { |
| 82 | pr_err("%s: wait for remote router failed %d\n", |
| 83 | __func__, rc); |
| 84 | msm_rpcrouter_unload_modem(pil); |
| 85 | pil = ERR_PTR(rc); |
| 86 | } |
| 87 | } |
| 88 | |
| 89 | return pil; |
| 90 | } |
| 91 | |
| 92 | static int rpcrouter_open(struct inode *inode, struct file *filp) |
| 93 | { |
| 94 | int rc; |
| 95 | void *pil; |
| 96 | struct msm_rpc_endpoint *ept; |
| 97 | struct rpcrouter_file_info *file_info; |
| 98 | |
| 99 | rc = nonseekable_open(inode, filp); |
| 100 | if (rc < 0) |
| 101 | return rc; |
| 102 | |
| 103 | file_info = kzalloc(sizeof(*file_info), GFP_KERNEL); |
| 104 | if (!file_info) |
| 105 | return -ENOMEM; |
| 106 | |
| 107 | ept = msm_rpcrouter_create_local_endpoint(inode->i_rdev); |
| 108 | if (!ept) { |
| 109 | kfree(file_info); |
| 110 | return -ENOMEM; |
| 111 | } |
| 112 | file_info->ept = ept; |
| 113 | |
| 114 | /* if router device, load the modem */ |
| 115 | if (inode->i_rdev == msm_rpcrouter_devno) { |
| 116 | pil = msm_rpcrouter_load_modem(); |
| 117 | if (IS_ERR(pil)) { |
| 118 | kfree(file_info); |
| 119 | msm_rpcrouter_destroy_local_endpoint(ept); |
| 120 | return PTR_ERR(pil); |
| 121 | } |
| 122 | file_info->modem_pil = pil; |
| 123 | } |
| 124 | |
| 125 | filp->private_data = file_info; |
| 126 | return 0; |
| 127 | } |
| 128 | |
| 129 | static int rpcrouter_release(struct inode *inode, struct file *filp) |
| 130 | { |
| 131 | struct rpcrouter_file_info *file_info = filp->private_data; |
| 132 | struct msm_rpc_endpoint *ept; |
| 133 | static unsigned int rpcrouter_release_cnt; |
| 134 | |
| 135 | ept = (struct msm_rpc_endpoint *) file_info->ept; |
| 136 | |
| 137 | /* A user program with many files open when ends abruptly, |
| 138 | * will cause a flood of REMOVE_CLIENT messages to the |
| 139 | * remote processor. This will cause remote processors |
| 140 | * internal queue to overflow. Inserting a sleep here |
| 141 | * regularly is the effecient option. |
| 142 | */ |
| 143 | if (rpcrouter_release_cnt++ % 2) |
| 144 | msleep(1); |
| 145 | |
| 146 | /* if router device, unload the modem */ |
| 147 | if (inode->i_rdev == msm_rpcrouter_devno) |
| 148 | msm_rpcrouter_unload_modem(file_info->modem_pil); |
| 149 | |
| 150 | kfree(file_info); |
| 151 | return msm_rpcrouter_destroy_local_endpoint(ept); |
| 152 | } |
| 153 | |
| 154 | static ssize_t rpcrouter_read(struct file *filp, char __user *buf, |
| 155 | size_t count, loff_t *ppos) |
| 156 | { |
| 157 | struct rpcrouter_file_info *file_info = filp->private_data; |
| 158 | struct msm_rpc_endpoint *ept; |
| 159 | struct rr_fragment *frag, *next; |
| 160 | int rc; |
| 161 | |
| 162 | ept = (struct msm_rpc_endpoint *) file_info->ept; |
| 163 | |
| 164 | rc = __msm_rpc_read(ept, &frag, count, -1); |
| 165 | if (rc < 0) |
| 166 | return rc; |
| 167 | |
| 168 | count = rc; |
| 169 | |
| 170 | while (frag != NULL) { |
| 171 | if (copy_to_user(buf, frag->data, frag->length)) { |
| 172 | printk(KERN_ERR |
| 173 | "rpcrouter: could not copy all read data to user!\n"); |
| 174 | rc = -EFAULT; |
| 175 | } |
| 176 | buf += frag->length; |
| 177 | next = frag->next; |
| 178 | kfree(frag); |
| 179 | frag = next; |
| 180 | } |
| 181 | |
| 182 | return rc; |
| 183 | } |
| 184 | |
| 185 | static ssize_t rpcrouter_write(struct file *filp, const char __user *buf, |
| 186 | size_t count, loff_t *ppos) |
| 187 | { |
| 188 | struct rpcrouter_file_info *file_info = filp->private_data; |
| 189 | struct msm_rpc_endpoint *ept; |
| 190 | int rc = 0; |
| 191 | void *k_buffer; |
| 192 | |
| 193 | ept = (struct msm_rpc_endpoint *) file_info->ept; |
| 194 | |
| 195 | /* A check for safety, this seems non-standard */ |
| 196 | if (count > SAFETY_MEM_SIZE) |
| 197 | return -EINVAL; |
| 198 | |
| 199 | k_buffer = kmalloc(count, GFP_KERNEL); |
| 200 | if (!k_buffer) |
| 201 | return -ENOMEM; |
| 202 | |
| 203 | if (copy_from_user(k_buffer, buf, count)) { |
| 204 | rc = -EFAULT; |
| 205 | goto write_out_free; |
| 206 | } |
| 207 | |
| 208 | rc = msm_rpc_write(ept, k_buffer, count); |
| 209 | if (rc < 0) |
| 210 | goto write_out_free; |
| 211 | |
| 212 | rc = count; |
| 213 | write_out_free: |
| 214 | kfree(k_buffer); |
| 215 | return rc; |
| 216 | } |
| 217 | |
| 218 | /* RPC VFS Poll Implementation |
| 219 | * |
| 220 | * POLLRDHUP - restart in progress |
| 221 | * POLLOUT - writes accepted (without blocking) |
| 222 | * POLLIN - data ready to read |
| 223 | * |
| 224 | * The restart state consists of several different phases including a client |
| 225 | * notification and a server restart. If the server has been restarted, then |
| 226 | * reads and writes can be performed and the POLLOUT bit will be set. If a |
| 227 | * restart is in progress, but the server hasn't been restarted, then only the |
| 228 | * POLLRDHUP is active and reads and writes will block. See the table |
| 229 | * below for a summary. POLLRDHUP is cleared once a call to msm_rpc_write_pkt |
| 230 | * or msm_rpc_read_pkt returns ENETRESET. |
| 231 | * |
| 232 | * POLLOUT POLLRDHUP |
| 233 | * 1 0 Normal operation |
| 234 | * 0 1 Restart in progress and server hasn't restarted yet |
| 235 | * 1 1 Server has been restarted, but client has |
| 236 | * not been notified of a restart by a return code |
| 237 | * of ENETRESET from msm_rpc_write_pkt or |
| 238 | * msm_rpc_read_pkt. |
| 239 | */ |
| 240 | static unsigned int rpcrouter_poll(struct file *filp, |
| 241 | struct poll_table_struct *wait) |
| 242 | { |
| 243 | struct rpcrouter_file_info *file_info = filp->private_data; |
| 244 | struct msm_rpc_endpoint *ept; |
| 245 | unsigned mask = 0; |
| 246 | |
| 247 | ept = (struct msm_rpc_endpoint *) file_info->ept; |
| 248 | |
| 249 | poll_wait(filp, &ept->wait_q, wait); |
| 250 | poll_wait(filp, &ept->restart_wait, wait); |
| 251 | |
| 252 | if (!list_empty(&ept->read_q)) |
| 253 | mask |= POLLIN; |
| 254 | if (!(ept->restart_state & RESTART_PEND_SVR)) |
| 255 | mask |= POLLOUT; |
| 256 | if (ept->restart_state != 0) |
| 257 | mask |= POLLRDHUP; |
| 258 | |
| 259 | return mask; |
| 260 | } |
| 261 | |
| 262 | static long rpcrouter_ioctl(struct file *filp, unsigned int cmd, |
| 263 | unsigned long arg) |
| 264 | { |
| 265 | struct rpcrouter_file_info *file_info = filp->private_data; |
| 266 | struct msm_rpc_endpoint *ept; |
| 267 | struct rpcrouter_ioctl_server_args server_args; |
| 268 | int rc = 0; |
| 269 | uint32_t n; |
| 270 | |
| 271 | ept = (struct msm_rpc_endpoint *) file_info->ept; |
| 272 | switch (cmd) { |
| 273 | |
| 274 | case RPC_ROUTER_IOCTL_GET_VERSION: |
| 275 | n = RPC_ROUTER_VERSION_V1; |
| 276 | rc = put_user(n, (unsigned int *) arg); |
| 277 | break; |
| 278 | |
| 279 | case RPC_ROUTER_IOCTL_GET_MTU: |
| 280 | /* the pacmark word reduces the actual payload |
| 281 | * possible per message |
| 282 | */ |
| 283 | n = RPCROUTER_MSGSIZE_MAX - sizeof(uint32_t); |
| 284 | rc = put_user(n, (unsigned int *) arg); |
| 285 | break; |
| 286 | |
| 287 | case RPC_ROUTER_IOCTL_REGISTER_SERVER: |
| 288 | rc = copy_from_user(&server_args, (void *) arg, |
| 289 | sizeof(server_args)); |
| 290 | if (rc < 0) |
| 291 | break; |
| 292 | msm_rpc_register_server(ept, |
| 293 | server_args.prog, |
| 294 | server_args.vers); |
| 295 | break; |
| 296 | |
| 297 | case RPC_ROUTER_IOCTL_UNREGISTER_SERVER: |
| 298 | rc = copy_from_user(&server_args, (void *) arg, |
| 299 | sizeof(server_args)); |
| 300 | if (rc < 0) |
| 301 | break; |
| 302 | |
| 303 | msm_rpc_unregister_server(ept, |
| 304 | server_args.prog, |
| 305 | server_args.vers); |
| 306 | break; |
| 307 | |
| 308 | case RPC_ROUTER_IOCTL_CLEAR_NETRESET: |
| 309 | msm_rpc_clear_netreset(ept); |
| 310 | break; |
| 311 | |
| 312 | case RPC_ROUTER_IOCTL_GET_CURR_PKT_SIZE: |
| 313 | rc = msm_rpc_get_curr_pkt_size(ept); |
| 314 | break; |
| 315 | |
| 316 | default: |
| 317 | rc = -EINVAL; |
| 318 | break; |
| 319 | } |
| 320 | |
| 321 | return rc; |
| 322 | } |
| 323 | |
| 324 | static struct file_operations rpcrouter_server_fops = { |
| 325 | .owner = THIS_MODULE, |
| 326 | .open = rpcrouter_open, |
| 327 | .release = rpcrouter_release, |
| 328 | .read = rpcrouter_read, |
| 329 | .write = rpcrouter_write, |
| 330 | .poll = rpcrouter_poll, |
| 331 | .unlocked_ioctl = rpcrouter_ioctl, |
| 332 | }; |
| 333 | |
| 334 | static struct file_operations rpcrouter_router_fops = { |
| 335 | .owner = THIS_MODULE, |
| 336 | .open = rpcrouter_open, |
| 337 | .release = rpcrouter_release, |
| 338 | .read = rpcrouter_read, |
| 339 | .write = rpcrouter_write, |
| 340 | .poll = rpcrouter_poll, |
| 341 | .unlocked_ioctl = rpcrouter_ioctl, |
| 342 | }; |
| 343 | |
| 344 | int msm_rpcrouter_create_server_cdev(struct rr_server *server) |
| 345 | { |
| 346 | int rc; |
| 347 | uint32_t dev_vers; |
| 348 | unsigned long flags; |
| 349 | |
| 350 | spin_lock_irqsave(&server_cdev_lock, flags); |
| 351 | if (next_minor == RPCROUTER_MAX_REMOTE_SERVERS) { |
| 352 | spin_unlock_irqrestore(&server_cdev_lock, flags); |
| 353 | printk(KERN_ERR |
| 354 | "rpcrouter: Minor numbers exhausted - Increase " |
| 355 | "RPCROUTER_MAX_REMOTE_SERVERS\n"); |
| 356 | return -ENOBUFS; |
| 357 | } |
| 358 | |
| 359 | /* Servers with bit 31 set are remote msm servers with hashkey version. |
| 360 | * Servers with bit 31 not set are remote msm servers with |
| 361 | * backwards compatible version type in which case the minor number |
| 362 | * (lower 16 bits) is set to zero. |
| 363 | * |
| 364 | */ |
| 365 | if ((server->vers & 0x80000000)) |
| 366 | dev_vers = server->vers; |
| 367 | else |
| 368 | dev_vers = server->vers & 0xffff0000; |
| 369 | |
| 370 | server->device_number = |
| 371 | MKDEV(MAJOR(msm_rpcrouter_devno), next_minor++); |
| 372 | spin_unlock_irqrestore(&server_cdev_lock, flags); |
| 373 | |
| 374 | server->device = |
| 375 | device_create(msm_rpcrouter_class, rpcrouter_device, |
| 376 | server->device_number, NULL, "%.8x:%.8x", |
| 377 | server->prog, dev_vers); |
| 378 | if (IS_ERR(server->device)) { |
| 379 | printk(KERN_ERR |
| 380 | "rpcrouter: Unable to create device (%ld)\n", |
| 381 | PTR_ERR(server->device)); |
| 382 | return PTR_ERR(server->device);; |
| 383 | } |
| 384 | |
| 385 | cdev_init(&server->cdev, &rpcrouter_server_fops); |
| 386 | server->cdev.owner = THIS_MODULE; |
| 387 | |
| 388 | rc = cdev_add(&server->cdev, server->device_number, 1); |
| 389 | if (rc < 0) { |
| 390 | printk(KERN_ERR |
| 391 | "rpcrouter: Unable to add chrdev (%d)\n", rc); |
| 392 | device_destroy(msm_rpcrouter_class, server->device_number); |
| 393 | return rc; |
| 394 | } |
| 395 | return 0; |
| 396 | } |
| 397 | |
| 398 | /* for backward compatible version type (31st bit cleared) |
| 399 | * clearing minor number (lower 16 bits) in device name |
| 400 | * is neccessary for driver binding |
| 401 | */ |
| 402 | int msm_rpcrouter_create_server_pdev(struct rr_server *server) |
| 403 | { |
| 404 | server->p_device.base.id = (server->vers & RPC_VERSION_MODE_MASK) ? |
| 405 | server->vers : |
| 406 | (server->vers & RPC_VERSION_MAJOR_MASK); |
| 407 | server->p_device.base.name = server->pdev_name; |
| 408 | |
| 409 | server->p_device.prog = server->prog; |
| 410 | server->p_device.vers = server->vers; |
| 411 | |
| 412 | platform_device_register(&server->p_device.base); |
| 413 | return 0; |
| 414 | } |
| 415 | |
| 416 | int msm_rpcrouter_init_devices(void) |
| 417 | { |
| 418 | int rc; |
| 419 | int major; |
| 420 | |
| 421 | /* Create the device nodes */ |
| 422 | msm_rpcrouter_class = class_create(THIS_MODULE, "oncrpc"); |
| 423 | if (IS_ERR(msm_rpcrouter_class)) { |
| 424 | rc = -ENOMEM; |
| 425 | printk(KERN_ERR |
| 426 | "rpcrouter: failed to create oncrpc class\n"); |
| 427 | goto fail; |
| 428 | } |
| 429 | |
| 430 | rc = alloc_chrdev_region(&msm_rpcrouter_devno, 0, |
| 431 | RPCROUTER_MAX_REMOTE_SERVERS + 1, |
| 432 | "oncrpc"); |
| 433 | if (rc < 0) { |
| 434 | printk(KERN_ERR |
| 435 | "rpcrouter: Failed to alloc chardev region (%d)\n", rc); |
| 436 | goto fail_destroy_class; |
| 437 | } |
| 438 | |
| 439 | major = MAJOR(msm_rpcrouter_devno); |
| 440 | rpcrouter_device = device_create(msm_rpcrouter_class, NULL, |
| 441 | msm_rpcrouter_devno, NULL, "%.8x:%d", |
| 442 | 0, 0); |
| 443 | if (IS_ERR(rpcrouter_device)) { |
| 444 | rc = -ENOMEM; |
| 445 | goto fail_unregister_cdev_region; |
| 446 | } |
| 447 | |
| 448 | cdev_init(&rpcrouter_cdev, &rpcrouter_router_fops); |
| 449 | rpcrouter_cdev.owner = THIS_MODULE; |
| 450 | |
| 451 | rc = cdev_add(&rpcrouter_cdev, msm_rpcrouter_devno, 1); |
| 452 | if (rc < 0) |
| 453 | goto fail_destroy_device; |
| 454 | |
| 455 | return 0; |
| 456 | |
| 457 | fail_destroy_device: |
| 458 | device_destroy(msm_rpcrouter_class, msm_rpcrouter_devno); |
| 459 | fail_unregister_cdev_region: |
| 460 | unregister_chrdev_region(msm_rpcrouter_devno, |
| 461 | RPCROUTER_MAX_REMOTE_SERVERS + 1); |
| 462 | fail_destroy_class: |
| 463 | class_destroy(msm_rpcrouter_class); |
| 464 | fail: |
| 465 | return rc; |
| 466 | } |
| 467 | |
| 468 | void msm_rpcrouter_exit_devices(void) |
| 469 | { |
| 470 | cdev_del(&rpcrouter_cdev); |
| 471 | device_destroy(msm_rpcrouter_class, msm_rpcrouter_devno); |
| 472 | unregister_chrdev_region(msm_rpcrouter_devno, |
| 473 | RPCROUTER_MAX_REMOTE_SERVERS + 1); |
| 474 | class_destroy(msm_rpcrouter_class); |
| 475 | } |
| 476 | |