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