Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
at v3.9 527 lines 14 kB view raw
1/* ----------------------------------------------------------------------------- 2 * Copyright (c) 2011 Ozmo Inc 3 * Released under the GNU General Public License Version 2 (GPLv2). 4 * ----------------------------------------------------------------------------- 5 */ 6#include <linux/module.h> 7#include <linux/fs.h> 8#include <linux/cdev.h> 9#include <linux/uaccess.h> 10#include <linux/netdevice.h> 11#include <linux/etherdevice.h> 12#include <linux/poll.h> 13#include <linux/sched.h> 14#include "ozconfig.h" 15#include "ozprotocol.h" 16#include "oztrace.h" 17#include "ozappif.h" 18#include "ozeltbuf.h" 19#include "ozpd.h" 20#include "ozproto.h" 21#include "ozevent.h" 22#include "ozcdev.h" 23/*------------------------------------------------------------------------------ 24 */ 25#define OZ_RD_BUF_SZ 256 26struct oz_cdev { 27 dev_t devnum; 28 struct cdev cdev; 29 wait_queue_head_t rdq; 30 spinlock_t lock; 31 u8 active_addr[ETH_ALEN]; 32 struct oz_pd *active_pd; 33}; 34 35/* Per PD context for the serial service stored in the PD. */ 36struct oz_serial_ctx { 37 atomic_t ref_count; 38 u8 tx_seq_num; 39 u8 rx_seq_num; 40 u8 rd_buf[OZ_RD_BUF_SZ]; 41 int rd_in; 42 int rd_out; 43}; 44/*------------------------------------------------------------------------------ 45 */ 46static struct oz_cdev g_cdev; 47static struct class *g_oz_class; 48/*------------------------------------------------------------------------------ 49 * Context: process and softirq 50 */ 51static struct oz_serial_ctx *oz_cdev_claim_ctx(struct oz_pd *pd) 52{ 53 struct oz_serial_ctx *ctx; 54 spin_lock_bh(&pd->app_lock[OZ_APPID_SERIAL-1]); 55 ctx = (struct oz_serial_ctx *)pd->app_ctx[OZ_APPID_SERIAL-1]; 56 if (ctx) 57 atomic_inc(&ctx->ref_count); 58 spin_unlock_bh(&pd->app_lock[OZ_APPID_SERIAL-1]); 59 return ctx; 60} 61/*------------------------------------------------------------------------------ 62 * Context: softirq or process 63 */ 64static void oz_cdev_release_ctx(struct oz_serial_ctx *ctx) 65{ 66 if (atomic_dec_and_test(&ctx->ref_count)) { 67 oz_trace("Dealloc serial context.\n"); 68 kfree(ctx); 69 } 70} 71/*------------------------------------------------------------------------------ 72 * Context: process 73 */ 74static int oz_cdev_open(struct inode *inode, struct file *filp) 75{ 76 struct oz_cdev *dev; 77 oz_trace("oz_cdev_open()\n"); 78 oz_trace("major = %d minor = %d\n", imajor(inode), iminor(inode)); 79 dev = container_of(inode->i_cdev, struct oz_cdev, cdev); 80 filp->private_data = dev; 81 return 0; 82} 83/*------------------------------------------------------------------------------ 84 * Context: process 85 */ 86static int oz_cdev_release(struct inode *inode, struct file *filp) 87{ 88 oz_trace("oz_cdev_release()\n"); 89 return 0; 90} 91/*------------------------------------------------------------------------------ 92 * Context: process 93 */ 94static ssize_t oz_cdev_read(struct file *filp, char __user *buf, size_t count, 95 loff_t *fpos) 96{ 97 int n; 98 int ix; 99 100 struct oz_pd *pd; 101 struct oz_serial_ctx *ctx; 102 103 spin_lock_bh(&g_cdev.lock); 104 pd = g_cdev.active_pd; 105 if (pd) 106 oz_pd_get(pd); 107 spin_unlock_bh(&g_cdev.lock); 108 if (pd == NULL) 109 return -1; 110 ctx = oz_cdev_claim_ctx(pd); 111 if (ctx == NULL) 112 goto out2; 113 n = ctx->rd_in - ctx->rd_out; 114 if (n < 0) 115 n += OZ_RD_BUF_SZ; 116 if (count > n) 117 count = n; 118 ix = ctx->rd_out; 119 n = OZ_RD_BUF_SZ - ix; 120 if (n > count) 121 n = count; 122 if (copy_to_user(buf, &ctx->rd_buf[ix], n)) { 123 count = 0; 124 goto out1; 125 } 126 ix += n; 127 if (ix == OZ_RD_BUF_SZ) 128 ix = 0; 129 if (n < count) { 130 if (copy_to_user(&buf[n], ctx->rd_buf, count-n)) { 131 count = 0; 132 goto out1; 133 } 134 ix = count-n; 135 } 136 ctx->rd_out = ix; 137out1: 138 oz_cdev_release_ctx(ctx); 139out2: 140 oz_pd_put(pd); 141 return count; 142} 143/*------------------------------------------------------------------------------ 144 * Context: process 145 */ 146static ssize_t oz_cdev_write(struct file *filp, const char __user *buf, 147 size_t count, loff_t *fpos) 148{ 149 struct oz_pd *pd; 150 struct oz_elt_buf *eb; 151 struct oz_elt_info *ei; 152 struct oz_elt *elt; 153 struct oz_app_hdr *app_hdr; 154 struct oz_serial_ctx *ctx; 155 156 spin_lock_bh(&g_cdev.lock); 157 pd = g_cdev.active_pd; 158 if (pd) 159 oz_pd_get(pd); 160 spin_unlock_bh(&g_cdev.lock); 161 if (pd == NULL) 162 return -1; 163 eb = &pd->elt_buff; 164 ei = oz_elt_info_alloc(eb); 165 if (ei == NULL) { 166 count = 0; 167 goto out; 168 } 169 elt = (struct oz_elt *)ei->data; 170 app_hdr = (struct oz_app_hdr *)(elt+1); 171 elt->length = sizeof(struct oz_app_hdr) + count; 172 elt->type = OZ_ELT_APP_DATA; 173 ei->app_id = OZ_APPID_SERIAL; 174 ei->length = elt->length + sizeof(struct oz_elt); 175 app_hdr->app_id = OZ_APPID_SERIAL; 176 if (copy_from_user(app_hdr+1, buf, count)) 177 goto out; 178 spin_lock_bh(&pd->app_lock[OZ_APPID_USB-1]); 179 ctx = (struct oz_serial_ctx *)pd->app_ctx[OZ_APPID_SERIAL-1]; 180 if (ctx) { 181 app_hdr->elt_seq_num = ctx->tx_seq_num++; 182 if (ctx->tx_seq_num == 0) 183 ctx->tx_seq_num = 1; 184 spin_lock(&eb->lock); 185 if (oz_queue_elt_info(eb, 0, 0, ei) == 0) 186 ei = NULL; 187 spin_unlock(&eb->lock); 188 } 189 spin_unlock_bh(&pd->app_lock[OZ_APPID_USB-1]); 190out: 191 if (ei) { 192 count = 0; 193 spin_lock_bh(&eb->lock); 194 oz_elt_info_free(eb, ei); 195 spin_unlock_bh(&eb->lock); 196 } 197 oz_pd_put(pd); 198 return count; 199} 200/*------------------------------------------------------------------------------ 201 * Context: process 202 */ 203static int oz_set_active_pd(const u8 *addr) 204{ 205 int rc = 0; 206 struct oz_pd *pd; 207 struct oz_pd *old_pd; 208 pd = oz_pd_find(addr); 209 if (pd) { 210 spin_lock_bh(&g_cdev.lock); 211 memcpy(g_cdev.active_addr, addr, ETH_ALEN); 212 old_pd = g_cdev.active_pd; 213 g_cdev.active_pd = pd; 214 spin_unlock_bh(&g_cdev.lock); 215 if (old_pd) 216 oz_pd_put(old_pd); 217 } else { 218 if (is_zero_ether_addr(addr)) { 219 spin_lock_bh(&g_cdev.lock); 220 pd = g_cdev.active_pd; 221 g_cdev.active_pd = NULL; 222 memset(g_cdev.active_addr, 0, 223 sizeof(g_cdev.active_addr)); 224 spin_unlock_bh(&g_cdev.lock); 225 if (pd) 226 oz_pd_put(pd); 227 } else { 228 rc = -1; 229 } 230 } 231 return rc; 232} 233/*------------------------------------------------------------------------------ 234 * Context: process 235 */ 236static long oz_cdev_ioctl(struct file *filp, unsigned int cmd, 237 unsigned long arg) 238{ 239 int rc = 0; 240 if (_IOC_TYPE(cmd) != OZ_IOCTL_MAGIC) 241 return -ENOTTY; 242 if (_IOC_NR(cmd) > OZ_IOCTL_MAX) 243 return -ENOTTY; 244 if (_IOC_DIR(cmd) & _IOC_READ) 245 rc = !access_ok(VERIFY_WRITE, (void __user *)arg, 246 _IOC_SIZE(cmd)); 247 else if (_IOC_DIR(cmd) & _IOC_WRITE) 248 rc = !access_ok(VERIFY_READ, (void __user *)arg, 249 _IOC_SIZE(cmd)); 250 if (rc) 251 return -EFAULT; 252 switch (cmd) { 253 case OZ_IOCTL_GET_PD_LIST: { 254 struct oz_pd_list list; 255 oz_trace("OZ_IOCTL_GET_PD_LIST\n"); 256 list.count = oz_get_pd_list(list.addr, OZ_MAX_PDS); 257 if (copy_to_user((void __user *)arg, &list, 258 sizeof(list))) 259 return -EFAULT; 260 } 261 break; 262 case OZ_IOCTL_SET_ACTIVE_PD: { 263 u8 addr[ETH_ALEN]; 264 oz_trace("OZ_IOCTL_SET_ACTIVE_PD\n"); 265 if (copy_from_user(addr, (void __user *)arg, ETH_ALEN)) 266 return -EFAULT; 267 rc = oz_set_active_pd(addr); 268 } 269 break; 270 case OZ_IOCTL_GET_ACTIVE_PD: { 271 u8 addr[ETH_ALEN]; 272 oz_trace("OZ_IOCTL_GET_ACTIVE_PD\n"); 273 spin_lock_bh(&g_cdev.lock); 274 memcpy(addr, g_cdev.active_addr, ETH_ALEN); 275 spin_unlock_bh(&g_cdev.lock); 276 if (copy_to_user((void __user *)arg, addr, ETH_ALEN)) 277 return -EFAULT; 278 } 279 break; 280 case OZ_IOCTL_ADD_BINDING: 281 case OZ_IOCTL_REMOVE_BINDING: { 282 struct oz_binding_info b; 283 if (copy_from_user(&b, (void __user *)arg, 284 sizeof(struct oz_binding_info))) { 285 return -EFAULT; 286 } 287 /* Make sure name is null terminated. */ 288 b.name[OZ_MAX_BINDING_LEN-1] = 0; 289 if (cmd == OZ_IOCTL_ADD_BINDING) 290 oz_binding_add(b.name); 291 else 292 oz_binding_remove(b.name); 293 } 294 break; 295 } 296 return rc; 297} 298/*------------------------------------------------------------------------------ 299 * Context: process 300 */ 301static unsigned int oz_cdev_poll(struct file *filp, poll_table *wait) 302{ 303 unsigned int ret = 0; 304 struct oz_cdev *dev = filp->private_data; 305 oz_trace("Poll called wait = %p\n", wait); 306 spin_lock_bh(&dev->lock); 307 if (dev->active_pd) { 308 struct oz_serial_ctx *ctx = oz_cdev_claim_ctx(dev->active_pd); 309 if (ctx) { 310 if (ctx->rd_in != ctx->rd_out) 311 ret |= POLLIN | POLLRDNORM; 312 oz_cdev_release_ctx(ctx); 313 } 314 } 315 spin_unlock_bh(&dev->lock); 316 if (wait) 317 poll_wait(filp, &dev->rdq, wait); 318 return ret; 319} 320/*------------------------------------------------------------------------------ 321 */ 322static const struct file_operations oz_fops = { 323 .owner = THIS_MODULE, 324 .open = oz_cdev_open, 325 .release = oz_cdev_release, 326 .read = oz_cdev_read, 327 .write = oz_cdev_write, 328 .unlocked_ioctl = oz_cdev_ioctl, 329 .poll = oz_cdev_poll 330}; 331/*------------------------------------------------------------------------------ 332 * Context: process 333 */ 334int oz_cdev_register(void) 335{ 336 int err; 337 struct device *dev; 338 memset(&g_cdev, 0, sizeof(g_cdev)); 339 err = alloc_chrdev_region(&g_cdev.devnum, 0, 1, "ozwpan"); 340 if (err < 0) 341 goto out3; 342 oz_trace("Alloc dev number %d:%d\n", MAJOR(g_cdev.devnum), 343 MINOR(g_cdev.devnum)); 344 cdev_init(&g_cdev.cdev, &oz_fops); 345 g_cdev.cdev.owner = THIS_MODULE; 346 g_cdev.cdev.ops = &oz_fops; 347 spin_lock_init(&g_cdev.lock); 348 init_waitqueue_head(&g_cdev.rdq); 349 err = cdev_add(&g_cdev.cdev, g_cdev.devnum, 1); 350 if (err < 0) { 351 oz_trace("Failed to add cdev\n"); 352 goto out2; 353 } 354 g_oz_class = class_create(THIS_MODULE, "ozmo_wpan"); 355 if (IS_ERR(g_oz_class)) { 356 oz_trace("Failed to register ozmo_wpan class\n"); 357 goto out1; 358 } 359 dev = device_create(g_oz_class, NULL, g_cdev.devnum, NULL, "ozwpan"); 360 if (IS_ERR(dev)) { 361 oz_trace("Failed to create sysfs entry for cdev\n"); 362 goto out1; 363 } 364 return 0; 365out1: 366 cdev_del(&g_cdev.cdev); 367out2: 368 unregister_chrdev_region(g_cdev.devnum, 1); 369out3: 370 return err; 371} 372/*------------------------------------------------------------------------------ 373 * Context: process 374 */ 375int oz_cdev_deregister(void) 376{ 377 cdev_del(&g_cdev.cdev); 378 unregister_chrdev_region(g_cdev.devnum, 1); 379 if (g_oz_class) { 380 device_destroy(g_oz_class, g_cdev.devnum); 381 class_destroy(g_oz_class); 382 } 383 return 0; 384} 385/*------------------------------------------------------------------------------ 386 * Context: process 387 */ 388int oz_cdev_init(void) 389{ 390 oz_event_log(OZ_EVT_SERVICE, 1, OZ_APPID_SERIAL, NULL, 0); 391 oz_app_enable(OZ_APPID_SERIAL, 1); 392 return 0; 393} 394/*------------------------------------------------------------------------------ 395 * Context: process 396 */ 397void oz_cdev_term(void) 398{ 399 oz_event_log(OZ_EVT_SERVICE, 2, OZ_APPID_SERIAL, NULL, 0); 400 oz_app_enable(OZ_APPID_SERIAL, 0); 401} 402/*------------------------------------------------------------------------------ 403 * Context: softirq-serialized 404 */ 405int oz_cdev_start(struct oz_pd *pd, int resume) 406{ 407 struct oz_serial_ctx *ctx; 408 struct oz_serial_ctx *old_ctx; 409 oz_event_log(OZ_EVT_SERVICE, 3, OZ_APPID_SERIAL, NULL, resume); 410 if (resume) { 411 oz_trace("Serial service resumed.\n"); 412 return 0; 413 } 414 ctx = kzalloc(sizeof(struct oz_serial_ctx), GFP_ATOMIC); 415 if (ctx == NULL) 416 return -ENOMEM; 417 atomic_set(&ctx->ref_count, 1); 418 ctx->tx_seq_num = 1; 419 spin_lock_bh(&pd->app_lock[OZ_APPID_SERIAL-1]); 420 old_ctx = pd->app_ctx[OZ_APPID_SERIAL-1]; 421 if (old_ctx) { 422 spin_unlock_bh(&pd->app_lock[OZ_APPID_SERIAL-1]); 423 kfree(ctx); 424 } else { 425 pd->app_ctx[OZ_APPID_SERIAL-1] = ctx; 426 spin_unlock_bh(&pd->app_lock[OZ_APPID_SERIAL-1]); 427 } 428 spin_lock(&g_cdev.lock); 429 if ((g_cdev.active_pd == NULL) && 430 (memcmp(pd->mac_addr, g_cdev.active_addr, ETH_ALEN) == 0)) { 431 oz_pd_get(pd); 432 g_cdev.active_pd = pd; 433 oz_trace("Active PD arrived.\n"); 434 } 435 spin_unlock(&g_cdev.lock); 436 oz_trace("Serial service started.\n"); 437 return 0; 438} 439/*------------------------------------------------------------------------------ 440 * Context: softirq or process 441 */ 442void oz_cdev_stop(struct oz_pd *pd, int pause) 443{ 444 struct oz_serial_ctx *ctx; 445 oz_event_log(OZ_EVT_SERVICE, 4, OZ_APPID_SERIAL, NULL, pause); 446 if (pause) { 447 oz_trace("Serial service paused.\n"); 448 return; 449 } 450 spin_lock_bh(&pd->app_lock[OZ_APPID_SERIAL-1]); 451 ctx = (struct oz_serial_ctx *)pd->app_ctx[OZ_APPID_SERIAL-1]; 452 pd->app_ctx[OZ_APPID_SERIAL-1] = NULL; 453 spin_unlock_bh(&pd->app_lock[OZ_APPID_SERIAL-1]); 454 if (ctx) 455 oz_cdev_release_ctx(ctx); 456 spin_lock(&g_cdev.lock); 457 if (pd == g_cdev.active_pd) 458 g_cdev.active_pd = NULL; 459 else 460 pd = NULL; 461 spin_unlock(&g_cdev.lock); 462 if (pd) { 463 oz_pd_put(pd); 464 oz_trace("Active PD departed.\n"); 465 } 466 oz_trace("Serial service stopped.\n"); 467} 468/*------------------------------------------------------------------------------ 469 * Context: softirq-serialized 470 */ 471void oz_cdev_rx(struct oz_pd *pd, struct oz_elt *elt) 472{ 473 struct oz_serial_ctx *ctx; 474 struct oz_app_hdr *app_hdr; 475 u8 *data; 476 int len; 477 int space; 478 int copy_sz; 479 int ix; 480 481 ctx = oz_cdev_claim_ctx(pd); 482 if (ctx == NULL) { 483 oz_trace("Cannot claim serial context.\n"); 484 return; 485 } 486 487 app_hdr = (struct oz_app_hdr *)(elt+1); 488 /* If sequence number is non-zero then check it is not a duplicate. 489 */ 490 if (app_hdr->elt_seq_num != 0) { 491 if (((ctx->rx_seq_num - app_hdr->elt_seq_num) & 0x80) == 0) { 492 /* Reject duplicate element. */ 493 oz_trace("Duplicate element:%02x %02x\n", 494 app_hdr->elt_seq_num, ctx->rx_seq_num); 495 goto out; 496 } 497 } 498 ctx->rx_seq_num = app_hdr->elt_seq_num; 499 len = elt->length - sizeof(struct oz_app_hdr); 500 data = ((u8 *)(elt+1)) + sizeof(struct oz_app_hdr); 501 if (len <= 0) 502 goto out; 503 space = ctx->rd_out - ctx->rd_in - 1; 504 if (space < 0) 505 space += OZ_RD_BUF_SZ; 506 if (len > space) { 507 oz_trace("Not enough space:%d %d\n", len, space); 508 len = space; 509 } 510 ix = ctx->rd_in; 511 copy_sz = OZ_RD_BUF_SZ - ix; 512 if (copy_sz > len) 513 copy_sz = len; 514 memcpy(&ctx->rd_buf[ix], data, copy_sz); 515 len -= copy_sz; 516 ix += copy_sz; 517 if (ix == OZ_RD_BUF_SZ) 518 ix = 0; 519 if (len) { 520 memcpy(ctx->rd_buf, data+copy_sz, len); 521 ix = len; 522 } 523 ctx->rd_in = ix; 524 wake_up(&g_cdev.rdq); 525out: 526 oz_cdev_release_ctx(ctx); 527}