1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 27 /* 28 * Sysevent Driver for GPEC 29 */ 30 31 #include <sys/types.h> 32 #include <sys/param.h> 33 #include <sys/cred.h> 34 #include <sys/file.h> 35 #include <sys/stat.h> 36 #include <sys/conf.h> 37 #include <sys/ddi.h> 38 #include <sys/sunddi.h> 39 #include <sys/modctl.h> 40 #include <sys/open.h> /* OTYP_CHR definition */ 41 #include <sys/sysmacros.h> /* L_BITSMINOR definition */ 42 #include <sys/bitmap.h> 43 #include <sys/sysevent.h> 44 #include <sys/sysevent_impl.h> 45 46 static dev_info_t *sysevent_devi; 47 48 /* Definitions for binding handle array */ 49 static ulong_t sysevent_bitmap_initial = 1; /* index 0 indicates error */ 50 static ulong_t *sysevent_minor_bitmap = &sysevent_bitmap_initial; 51 static size_t sysevent_minor_bits = BT_NBIPUL; 52 static kmutex_t sysevent_minor_mutex; 53 54 /* 55 * evchan_ctl acts as a container for the binding handle 56 */ 57 typedef struct evchan_ctl { 58 evchan_t *chp; 59 } evchan_ctl_t; 60 61 static void *evchan_ctlp; 62 63 /* 64 * Check if it's a null terminated array - to avoid DoS attack 65 * It is supposed that string points to an array with 66 * a minimum length of len. len must be strlen + 1. 67 * Checks for printable characters are already done in library. 68 */ 69 static int 70 sysevent_isstrend(char *string, size_t len) 71 { 72 /* Return 0 if string has length of zero */ 73 if (len > 0) { 74 return (string[len - 1] == '\0' ? 1 : 0); 75 } else { 76 return (0); 77 } 78 } 79 80 /* 81 * Following sysevent_minor_* routines map 82 * a binding handle (evchan_t *) to a minor number 83 * Has to be called w/ locks held. 84 */ 85 static ulong_t * 86 sysevent_minor_alloc(void) 87 { 88 ulong_t *bhst = sysevent_minor_bitmap; 89 90 /* Increase bitmap by one BT_NBIPUL */ 91 if (sysevent_minor_bits + BT_NBIPUL > SYSEVENT_MINOR_MAX) { 92 return ((ulong_t *)NULL); 93 } 94 sysevent_minor_bitmap = kmem_zalloc( 95 BT_SIZEOFMAP(sysevent_minor_bits + BT_NBIPUL), KM_SLEEP); 96 bcopy(bhst, sysevent_minor_bitmap, BT_SIZEOFMAP(sysevent_minor_bits)); 97 if (bhst != &sysevent_bitmap_initial) 98 kmem_free(bhst, BT_SIZEOFMAP(sysevent_minor_bits)); 99 sysevent_minor_bits += BT_NBIPUL; 100 101 return (sysevent_minor_bitmap); 102 } 103 104 static void 105 sysevent_minor_free(ulong_t *bitmap) 106 { 107 if (bitmap != &sysevent_bitmap_initial) 108 kmem_free(bitmap, BT_SIZEOFMAP(sysevent_minor_bits)); 109 } 110 111 static index_t 112 sysevent_minor_get(void) 113 { 114 index_t idx; 115 ulong_t *bhst; 116 117 /* Search for an available index */ 118 mutex_enter(&sysevent_minor_mutex); 119 if ((idx = bt_availbit(sysevent_minor_bitmap, 120 sysevent_minor_bits)) == -1) { 121 /* All busy - allocate additional binding handle bitmap space */ 122 if ((bhst = sysevent_minor_alloc()) == NULL) { 123 /* Reached our maximum of id's == SHRT_MAX */ 124 mutex_exit(&sysevent_minor_mutex); 125 return (0); 126 } else { 127 sysevent_minor_bitmap = bhst; 128 } 129 idx = bt_availbit(sysevent_minor_bitmap, sysevent_minor_bits); 130 } 131 BT_SET(sysevent_minor_bitmap, idx); 132 mutex_exit(&sysevent_minor_mutex); 133 return (idx); 134 } 135 136 static void 137 sysevent_minor_rele(index_t idx) 138 { 139 mutex_enter(&sysevent_minor_mutex); 140 ASSERT(BT_TEST(sysevent_minor_bitmap, idx) == 1); 141 BT_CLEAR(sysevent_minor_bitmap, idx); 142 mutex_exit(&sysevent_minor_mutex); 143 } 144 145 static void 146 sysevent_minor_init(void) 147 { 148 mutex_init(&sysevent_minor_mutex, NULL, MUTEX_DEFAULT, NULL); 149 } 150 151 /* ARGSUSED */ 152 static int 153 sysevent_publish(dev_t dev, int *rvalp, void *arg, int flag, cred_t *cr) 154 { 155 int km_flags; 156 sev_publish_args_t uargs; 157 sysevent_impl_t *ev; 158 evchan_ctl_t *ctl; 159 160 ctl = ddi_get_soft_state(evchan_ctlp, getminor(dev)); 161 if (ctl == NULL || ctl->chp == NULL) 162 return (ENXIO); 163 164 if (copyin(arg, &uargs, sizeof (sev_publish_args_t)) != 0) 165 return (EFAULT); 166 167 /* 168 * This limits the size of an event 169 */ 170 if (uargs.ev.len > MAX_EV_SIZE_LEN) 171 return (EOVERFLOW); 172 173 /* 174 * Check for valid uargs.flags 175 */ 176 if (uargs.flags & ~(EVCH_NOSLEEP | EVCH_SLEEP | EVCH_QWAIT)) 177 return (EINVAL); 178 179 /* 180 * Check that at least one of EVCH_NOSLEEP or EVCH_SLEEP is 181 * specified 182 */ 183 km_flags = uargs.flags & (EVCH_NOSLEEP | EVCH_SLEEP); 184 if (km_flags != EVCH_NOSLEEP && km_flags != EVCH_SLEEP) 185 return (EINVAL); 186 187 ev = evch_usrallocev(uargs.ev.len, uargs.flags); 188 189 if (copyin((void *)(uintptr_t)uargs.ev.name, ev, uargs.ev.len) != 0) { 190 evch_usrfreeev(ev); 191 return (EFAULT); 192 } 193 194 return (evch_usrpostevent(ctl->chp, ev, uargs.flags)); 195 196 /* Event will be freed internally */ 197 } 198 199 /* 200 * sysevent_chan_open - used to open a channel in the GPEC channel layer 201 */ 202 203 /* ARGSUSED */ 204 static int 205 sysevent_chan_open(dev_t dev, int *rvalp, void *arg, int flag, cred_t *cr) 206 { 207 sev_bind_args_t uargs; 208 evchan_ctl_t *ctl; 209 char *chan_name; 210 int ec; 211 212 ctl = ddi_get_soft_state(evchan_ctlp, getminor(dev)); 213 if (ctl == NULL) { 214 return (ENXIO); 215 } 216 217 if (copyin(arg, &uargs, sizeof (sev_bind_args_t)) != 0) 218 return (EFAULT); 219 220 if (uargs.chan_name.len > MAX_CHNAME_LEN) 221 return (EINVAL); 222 223 chan_name = kmem_alloc(uargs.chan_name.len, KM_SLEEP); 224 225 if (copyin((void *)(uintptr_t)uargs.chan_name.name, chan_name, 226 uargs.chan_name.len) != 0) { 227 kmem_free(chan_name, uargs.chan_name.len); 228 return (EFAULT); 229 } 230 231 if (!sysevent_isstrend(chan_name, uargs.chan_name.len)) { 232 kmem_free(chan_name, uargs.chan_name.len); 233 return (EINVAL); 234 } 235 236 /* 237 * Check of uargs.flags and uargs.perms just to avoid DoS attacks. 238 * libsysevent does this carefully 239 */ 240 ctl->chp = evch_usrchanopen((const char *)chan_name, 241 uargs.flags & EVCH_B_FLAGS, &ec); 242 243 kmem_free(chan_name, uargs.chan_name.len); 244 245 if (ec != 0) { 246 return (ec); 247 } 248 249 return (0); 250 } 251 252 /* ARGSUSED */ 253 static int 254 sysevent_chan_control(dev_t dev, int *rvalp, void *arg, int flag, cred_t *cr) 255 { 256 sev_control_args_t uargs; 257 evchan_ctl_t *ctl; 258 int rc; 259 260 ctl = ddi_get_soft_state(evchan_ctlp, getminor(dev)); 261 if (ctl == NULL || ctl->chp == NULL) 262 return (ENXIO); 263 264 if (copyin(arg, &uargs, sizeof (sev_control_args_t)) != 0) 265 return (EFAULT); 266 267 switch (uargs.cmd) { 268 case EVCH_GET_CHAN_LEN: 269 case EVCH_GET_CHAN_LEN_MAX: 270 rc = evch_usrcontrol_get(ctl->chp, uargs.cmd, &uargs.value); 271 if (rc == 0) { 272 if (copyout((void *)&uargs, arg, 273 sizeof (sev_control_args_t)) != 0) { 274 rc = EFAULT; 275 } 276 } 277 break; 278 case EVCH_SET_CHAN_LEN: 279 rc = evch_usrcontrol_set(ctl->chp, uargs.cmd, uargs.value); 280 break; 281 default: 282 rc = EINVAL; 283 } 284 return (rc); 285 } 286 287 /* ARGSUSED */ 288 static int 289 sysevent_subscribe(dev_t dev, int *rvalp, void *arg, int flag, cred_t *cr) 290 { 291 sev_subscribe_args_t uargs; 292 char *sid; 293 char *class_info = NULL; 294 evchan_ctl_t *ctl; 295 int rc; 296 297 ctl = ddi_get_soft_state(evchan_ctlp, getminor(dev)); 298 if (ctl == NULL || ctl->chp == NULL) 299 return (ENXIO); 300 301 if (copyin(arg, &uargs, sizeof (sev_subscribe_args_t)) != 0) 302 return (EFAULT); 303 304 if (uargs.sid.len > MAX_SUBID_LEN || 305 uargs.class_info.len > MAX_CLASS_LEN) 306 return (EINVAL); 307 308 sid = kmem_alloc(uargs.sid.len, KM_SLEEP); 309 if (copyin((void *)(uintptr_t)uargs.sid.name, 310 sid, uargs.sid.len) != 0) { 311 kmem_free(sid, uargs.sid.len); 312 return (EFAULT); 313 } 314 if (!sysevent_isstrend(sid, uargs.sid.len)) { 315 kmem_free(sid, uargs.sid.len); 316 return (EINVAL); 317 } 318 319 /* If class string empty then class EC_ALL is assumed */ 320 if (uargs.class_info.len != 0) { 321 class_info = kmem_alloc(uargs.class_info.len, KM_SLEEP); 322 if (copyin((void *)(uintptr_t)uargs.class_info.name, class_info, 323 uargs.class_info.len) != 0) { 324 kmem_free(class_info, uargs.class_info.len); 325 kmem_free(sid, uargs.sid.len); 326 return (EFAULT); 327 } 328 if (!sysevent_isstrend(class_info, uargs.class_info.len)) { 329 kmem_free(class_info, uargs.class_info.len); 330 kmem_free(sid, uargs.sid.len); 331 return (EINVAL); 332 } 333 } 334 335 /* 336 * Check of uargs.flags just to avoid DoS attacks 337 * libsysevent does this carefully. 338 */ 339 rc = evch_usrsubscribe(ctl->chp, sid, class_info, 340 (int)uargs.door_desc, uargs.flags); 341 342 kmem_free(class_info, uargs.class_info.len); 343 kmem_free(sid, uargs.sid.len); 344 345 return (rc); 346 } 347 348 /* ARGSUSED */ 349 static int 350 sysevent_unsubscribe(dev_t dev, int *rvalp, void *arg, int flag, cred_t *cr) 351 { 352 sev_unsubscribe_args_t uargs; 353 char *sid; 354 evchan_ctl_t *ctl; 355 356 if (copyin(arg, &uargs, sizeof (sev_unsubscribe_args_t)) != 0) 357 return (EFAULT); 358 359 ctl = ddi_get_soft_state(evchan_ctlp, getminor(dev)); 360 if (ctl == NULL || ctl->chp == NULL) 361 return (ENXIO); 362 363 if (uargs.sid.len > MAX_SUBID_LEN) 364 return (EINVAL); 365 366 /* Unsubscribe for all */ 367 if (uargs.sid.len == 0) { 368 evch_usrunsubscribe(ctl->chp, NULL, 0); 369 return (0); 370 } 371 372 sid = kmem_alloc(uargs.sid.len, KM_SLEEP); 373 374 if (copyin((void *)(uintptr_t)uargs.sid.name, 375 sid, uargs.sid.len) != 0) { 376 kmem_free(sid, uargs.sid.len); 377 return (EFAULT); 378 } 379 380 evch_usrunsubscribe(ctl->chp, sid, 0); 381 382 kmem_free(sid, uargs.sid.len); 383 384 return (0); 385 } 386 387 /* ARGSUSED */ 388 static int 389 sysevent_channames(dev_t dev, int *rvalp, void *arg, int flag, cred_t *cr) 390 { 391 sev_chandata_args_t uargs; 392 char *buf; 393 int len; 394 int rc = 0; 395 396 if (copyin(arg, &uargs, sizeof (sev_chandata_args_t)) != 0) 397 return (EFAULT); 398 399 if (uargs.out_data.len == 0 || uargs.out_data.len > EVCH_MAX_DATA_SIZE) 400 return (EINVAL); 401 402 buf = kmem_alloc(uargs.out_data.len, KM_SLEEP); 403 404 if ((len = evch_usrgetchnames(buf, uargs.out_data.len)) == -1) { 405 rc = EOVERFLOW; 406 } 407 408 if (rc == 0) { 409 ASSERT(len <= uargs.out_data.len); 410 if (copyout(buf, 411 (void *)(uintptr_t)uargs.out_data.name, len) != 0) { 412 rc = EFAULT; 413 } 414 } 415 416 kmem_free(buf, uargs.out_data.len); 417 418 return (rc); 419 } 420 421 /* ARGSUSED */ 422 static int 423 sysevent_chandata(dev_t dev, int *rvalp, void *arg, int flag, cred_t *cr) 424 { 425 sev_chandata_args_t uargs; 426 char *channel; 427 char *buf; 428 int len; 429 int rc = 0; 430 431 if (copyin(arg, &uargs, sizeof (sev_chandata_args_t)) != 0) 432 return (EFAULT); 433 434 if (uargs.in_data.len > MAX_CHNAME_LEN || 435 uargs.out_data.len > EVCH_MAX_DATA_SIZE) 436 return (EINVAL); 437 438 channel = kmem_alloc(uargs.in_data.len, KM_SLEEP); 439 440 if (copyin((void *)(uintptr_t)uargs.in_data.name, channel, 441 uargs.in_data.len) != 0) { 442 kmem_free(channel, uargs.in_data.len); 443 return (EFAULT); 444 } 445 446 if (!sysevent_isstrend(channel, uargs.in_data.len)) { 447 kmem_free(channel, uargs.in_data.len); 448 return (EINVAL); 449 } 450 451 buf = kmem_alloc(uargs.out_data.len, KM_SLEEP); 452 453 len = evch_usrgetchdata(channel, buf, uargs.out_data.len); 454 if (len == 0) { 455 rc = EOVERFLOW; 456 } else if (len == -1) { 457 rc = ENOENT; 458 } 459 460 if (rc == 0) { 461 ASSERT(len <= uargs.out_data.len); 462 if (copyout(buf, 463 (void *)(uintptr_t)uargs.out_data.name, len) != 0) { 464 rc = EFAULT; 465 } 466 } 467 468 kmem_free(buf, uargs.out_data.len); 469 kmem_free(channel, uargs.in_data.len); 470 471 return (rc); 472 } 473 474 /*ARGSUSED*/ 475 static int 476 sysevent_ioctl(dev_t dev, int cmd, intptr_t arg, 477 int flag, cred_t *cr, int *rvalp) 478 { 479 int rc; 480 481 switch (cmd) { 482 case SEV_PUBLISH: 483 rc = sysevent_publish(dev, rvalp, (void *)arg, flag, cr); 484 break; 485 case SEV_CHAN_OPEN: 486 rc = sysevent_chan_open(dev, rvalp, (void *)arg, flag, cr); 487 break; 488 case SEV_CHAN_CONTROL: 489 rc = sysevent_chan_control(dev, rvalp, (void *)arg, flag, cr); 490 break; 491 case SEV_SUBSCRIBE: 492 rc = sysevent_subscribe(dev, rvalp, (void *)arg, flag, cr); 493 break; 494 case SEV_UNSUBSCRIBE: 495 rc = sysevent_unsubscribe(dev, rvalp, (void *)arg, flag, cr); 496 break; 497 case SEV_CHANNAMES: 498 rc = sysevent_channames(dev, rvalp, (void *)arg, flag, cr); 499 break; 500 case SEV_CHANDATA: 501 rc = sysevent_chandata(dev, rvalp, (void *)arg, flag, cr); 502 break; 503 default: 504 rc = EINVAL; 505 } 506 507 return (rc); 508 } 509 510 /*ARGSUSED*/ 511 static int 512 sysevent_open(dev_t *devp, int flag, int otyp, cred_t *cr) 513 { 514 int minor; 515 516 if (otyp != OTYP_CHR) 517 return (EINVAL); 518 519 if (getminor(*devp) != 0) 520 return (ENXIO); 521 522 minor = sysevent_minor_get(); 523 if (minor == 0) 524 /* All minors are busy */ 525 return (EBUSY); 526 527 if (ddi_soft_state_zalloc(evchan_ctlp, minor) 528 != DDI_SUCCESS) { 529 sysevent_minor_rele(minor); 530 return (ENOMEM); 531 } 532 533 *devp = makedevice(getmajor(*devp), minor); 534 535 return (0); 536 } 537 538 /*ARGSUSED*/ 539 static int 540 sysevent_close(dev_t dev, int flag, int otyp, cred_t *cr) 541 { 542 int minor = (int)getminor(dev); 543 evchan_ctl_t *ctl; 544 545 if (otyp != OTYP_CHR) 546 return (EINVAL); 547 548 ctl = ddi_get_soft_state(evchan_ctlp, minor); 549 if (ctl == NULL) { 550 return (ENXIO); 551 } 552 553 if (ctl->chp) { 554 /* Release all non-persistant subscriptions */ 555 evch_usrunsubscribe(ctl->chp, NULL, EVCH_SUB_KEEP); 556 evch_usrchanclose(ctl->chp); 557 } 558 559 ddi_soft_state_free(evchan_ctlp, minor); 560 sysevent_minor_rele(minor); 561 562 return (0); 563 } 564 565 /* ARGSUSED */ 566 static int 567 sysevent_info(dev_info_t *dip, ddi_info_cmd_t infocmd, 568 void *arg, void **result) 569 { 570 switch (infocmd) { 571 case DDI_INFO_DEVT2DEVINFO: 572 *result = sysevent_devi; 573 return (DDI_SUCCESS); 574 case DDI_INFO_DEVT2INSTANCE: 575 *result = 0; 576 return (DDI_SUCCESS); 577 } 578 return (DDI_FAILURE); 579 } 580 581 /* ARGSUSED */ 582 static int 583 sysevent_attach(dev_info_t *devi, ddi_attach_cmd_t cmd) 584 { 585 586 if (cmd != DDI_ATTACH) { 587 return (DDI_FAILURE); 588 } 589 590 if (ddi_create_minor_node(devi, "sysevent", S_IFCHR, 591 0, DDI_PSEUDO, NULL) == DDI_FAILURE) { 592 ddi_remove_minor_node(devi, NULL); 593 return (DDI_FAILURE); 594 } 595 sysevent_devi = devi; 596 597 sysevent_minor_init(); 598 599 return (DDI_SUCCESS); 600 } 601 602 static int 603 sysevent_detach(dev_info_t *devi, ddi_detach_cmd_t cmd) 604 { 605 if (cmd != DDI_DETACH) { 606 return (DDI_FAILURE); 607 } 608 609 sysevent_minor_free(sysevent_minor_bitmap); 610 ddi_remove_minor_node(devi, NULL); 611 return (DDI_SUCCESS); 612 } 613 614 static struct cb_ops sysevent_cb_ops = { 615 sysevent_open, /* open */ 616 sysevent_close, /* close */ 617 nodev, /* strategy */ 618 nodev, /* print */ 619 nodev, /* dump */ 620 nodev, /* read */ 621 nodev, /* write */ 622 sysevent_ioctl, /* ioctl */ 623 nodev, /* devmap */ 624 nodev, /* mmap */ 625 nodev, /* segmap */ 626 nochpoll, /* poll */ 627 ddi_prop_op, /* prop_op */ 628 0, /* streamtab */ 629 D_NEW|D_MP, /* flag */ 630 NULL, /* aread */ 631 NULL /* awrite */ 632 }; 633 634 static struct dev_ops sysevent_ops = { 635 DEVO_REV, /* devo_rev */ 636 0, /* refcnt */ 637 sysevent_info, /* info */ 638 nulldev, /* identify */ 639 nulldev, /* probe */ 640 sysevent_attach, /* attach */ 641 sysevent_detach, /* detach */ 642 nodev, /* reset */ 643 &sysevent_cb_ops, /* driver operations */ 644 (struct bus_ops *)0, /* no bus operations */ 645 nulldev, /* power */ 646 ddi_quiesce_not_needed, /* quiesce */ 647 }; 648 649 static struct modldrv modldrv = { 650 &mod_driverops, "sysevent driver", &sysevent_ops 651 }; 652 653 static struct modlinkage modlinkage = { 654 MODREV_1, &modldrv, NULL 655 }; 656 657 int 658 _init(void) 659 { 660 int s; 661 662 s = ddi_soft_state_init(&evchan_ctlp, sizeof (evchan_ctl_t), 1); 663 if (s != 0) 664 return (s); 665 666 if ((s = mod_install(&modlinkage)) != 0) 667 ddi_soft_state_fini(&evchan_ctlp); 668 return (s); 669 } 670 671 int 672 _fini(void) 673 { 674 int s; 675 676 if ((s = mod_remove(&modlinkage)) != 0) 677 return (s); 678 679 ddi_soft_state_fini(&evchan_ctlp); 680 return (s); 681 } 682 683 int 684 _info(struct modinfo *modinfop) 685 { 686 return (mod_info(&modlinkage, modinfop)); 687 } 688