1#ifdef HAVE_CONFIG_H 2#include "config.h" 3#endif /* HAVE_CONFIG_H */ 4#include <sys/types.h> 5#include <sys/kmem.h> 6#include <sys/conf.h> 7#include <sys/stream.h> 8#include <sys/devops.h> 9#include <sys/modctl.h> 10#include <sys/ddi.h> 11#include <sys/stat.h> 12#include <sys/sockio.h> 13#include <sys/socket.h> 14#include <sys/tihdr.h> 15#include <sys/tiuser.h> 16#include <sys/timod.h> 17#include <sys/sunddi.h> 18#include <sys/ethernet.h> 19#include <net/if.h> 20#include <net/route.h> 21#include <errno.h> 22 23#include <netatalk/endian.h> 24#include <netatalk/at.h> 25#include <netatalk/ddp.h> 26 27#include "ioc.h" 28#include "if.h" 29#include "sock.h" 30#include "rt.h" 31 32 static int 33tpi_getinfo( dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp ) 34{ 35 *resultp = NULL; 36 return( DDI_FAILURE ); 37} 38 39/* Solaris 10 removed DDI_IDENTIFIED and replaced "identify" with "nulldev" */ 40#ifdef DDI_IDENTIFIED 41 static int 42tpi_identify( dev_info_t *dip ) 43{ 44 char *tmp; 45 46 /* don't use strcmp under Solaris 9, problem loading kernel module */ 47 tmp = ddi_get_name( dip ); 48 if ((tmp[0]== 'd') && (tmp[1]=='d') && (tmp[2]=='p') && tmp[3]==0) { 49 return( DDI_IDENTIFIED ); 50 } else { 51 return( DDI_NOT_IDENTIFIED ); 52 } 53} 54#endif /* DDI_IDENTIFIED */ 55 56 static int 57tpi_attach( dev_info_t *dip, ddi_attach_cmd_t cmd ) 58{ 59 int rc; 60 61 if ( cmd != DDI_ATTACH ) { 62 return( DDI_FAILURE ); 63 } 64 65 if (( rc = ddi_create_minor_node( dip, "ddp", S_IFCHR, 0, DDI_PSEUDO, 66 CLONE_DEV )) != DDI_SUCCESS ) { 67 /* undo anything */ 68 } 69 return( rc ); 70} 71 72 static int 73tpi_detach( dev_info_t *dip, ddi_detach_cmd_t cmd ) 74{ 75 if ( cmd != DDI_DETACH ) { 76 return( DDI_FAILURE ); 77 } 78 79 ddi_remove_minor_node( dip, "ddp" ); 80 81 return( DDI_SUCCESS ); 82} 83 84 static int 85tpi_open( queue_t *q, dev_t *dev, int oflag, int sflag, cred_t *cred ) 86{ 87 static minor_t minor = 1; 88 89 if ( sflag != CLONEOPEN ) { 90 return( EINVAL ); 91 } 92 if (( q->q_ptr = (void *)sock_alloc( q )) == NULL ) { 93 return( ENOMEM ); 94 } 95 96 *dev = makedevice( getmajor( *dev ), minor++ ); 97 qprocson( q ); 98 return( 0 ); 99} 100 101 static int 102tpi_close( queue_t *q, int oflag, cred_t *cred ) 103{ 104 struct sock_data *sd = (struct sock_data *)q->q_ptr; 105 106 qprocsoff( q ); 107 sock_free( sd ); 108 return( 0 ); 109} 110 111 static int 112tpi_rput( queue_t *q, mblk_t *m ) 113{ 114 cmn_err( CE_NOTE, "tpi_rput dp_type = 0x%X\n", m->b_datap->db_type ); 115 freemsg( m ); 116 return( 0 ); 117} 118 119 void 120t_bind_ack( queue_t *q, struct sockaddr_at *sat ) 121{ 122 mblk_t *m; 123 struct T_bind_ack *t; 124 125 if (( m = allocb( sizeof( struct T_bind_ack ) + 126 sizeof( struct sockaddr_at ), BPRI_HI )) == NULL ) { 127 return; 128 } 129 m->b_wptr = m->b_rptr + sizeof( struct T_bind_ack ); 130 m->b_datap->db_type = M_PCPROTO; 131 132 t = (struct T_bind_ack *)m->b_rptr; 133 t->PRIM_type = T_BIND_ACK; 134 t->ADDR_length = sizeof( struct sockaddr_at ); 135 t->ADDR_offset = m->b_wptr - m->b_rptr; 136 t->CONIND_number = 0; 137 138 bcopy( (caddr_t)sat, m->b_wptr, sizeof( struct sockaddr_at )); 139 m->b_wptr += sizeof( struct sockaddr_at ); 140 141 qreply( q, m ); 142 return; 143} 144 145 void 146t_ok_ack( queue_t *q, long prim ) 147{ 148 mblk_t *m; 149 struct T_ok_ack *t; 150 151 152 if (( m = allocb( sizeof( struct T_ok_ack ), BPRI_HI )) == NULL ) { 153 return; 154 } 155 m->b_wptr = m->b_rptr + sizeof( struct T_ok_ack ); 156 m->b_datap->db_type = M_PCPROTO; 157 158 t = (struct T_ok_ack *)m->b_rptr; 159 t->PRIM_type = T_OK_ACK; 160 t->CORRECT_prim = prim; 161 qreply( q, m ); 162 return; 163} 164 165 void 166t_error_ack( queue_t *q, long prim, long terror, long uerror ) 167{ 168 mblk_t *m; 169 struct T_error_ack *t; 170 171 172 if (( m = allocb( sizeof( struct T_error_ack ), BPRI_HI )) == NULL ) { 173 return; 174 } 175 m->b_wptr = m->b_rptr + sizeof( struct T_error_ack ); 176 m->b_datap->db_type = M_PCPROTO; 177 178 t = (struct T_error_ack *)m->b_rptr; 179 t->PRIM_type = T_ERROR_ACK; 180 t->ERROR_prim = prim; 181 t->TLI_error = terror; 182 t->UNIX_error = uerror; 183 qreply( q, m ); 184 return; 185} 186 187 void 188t_info_ack( queue_t *q, long state ) 189{ 190 mblk_t *m; 191 struct T_info_ack *t; 192 193 194 if (( m = allocb( sizeof( struct T_info_ack ), BPRI_HI )) == NULL ) { 195 return; 196 } 197 m->b_wptr = m->b_rptr + sizeof( struct T_info_ack ); 198 m->b_datap->db_type = M_PCPROTO; 199 200 t = (struct T_info_ack *)m->b_rptr; 201 t->PRIM_type = T_INFO_ACK; 202 t->TSDU_size = 586; 203 t->ETSDU_size = -2; 204 t->CDATA_size = -2; 205 t->DDATA_size = -2; 206 t->ADDR_size = sizeof( struct sockaddr_at ); 207 t->OPT_size = 64; 208 t->TIDU_size = 1024; 209 t->SERV_type = T_CLTS; 210 t->CURRENT_state = state; 211 t->PROVIDER_flag = 0; 212 qreply( q, m ); 213 return; 214} 215 216 void 217t_unitdata_ind( queue_t *q, mblk_t *m0, struct sockaddr_at *sat ) 218{ 219 mblk_t *m; 220 struct T_unitdata_ind *t; 221 222 if (( m = allocb( sizeof( struct T_unitdata_ind ) + 223 sizeof( struct sockaddr_at ), BPRI_HI )) == NULL ) { 224 return; 225 } 226 m->b_wptr = m->b_rptr + sizeof( struct T_unitdata_ind ); 227 m->b_datap->db_type = M_PROTO; 228 229 t = (struct T_unitdata_ind *)m->b_rptr; 230 t->PRIM_type = T_UNITDATA_IND; 231 t->SRC_length = sizeof( struct sockaddr_at ); 232 t->SRC_offset = m->b_wptr - m->b_rptr; 233 bcopy( (caddr_t)sat, m->b_wptr, sizeof( struct sockaddr_at )); 234 m->b_wptr += sizeof( struct sockaddr_at ); 235 t->OPT_length = 0; 236 t->OPT_offset = 0; 237 linkb( m, m0 ); 238 239 qreply( q, m ); 240 return; 241} 242 243struct ioc_state { 244 int is_state; 245 int is_count; 246 caddr_t is_addr; 247}; 248 249 static int 250tpi_wput( queue_t *q, mblk_t *m ) 251{ 252 struct sock_data *sd = (struct sock_data *)RD(q)->q_ptr; 253 union T_primitives *tl; 254 struct iocblk *ioc; 255 struct copyresp *cp; 256 struct ioc_state *is; 257 struct ddpehdr *deh; 258 mblk_t *m0; 259 struct sockaddr_at sat; 260 struct netbuf nb; 261 struct rtentry rt; 262 struct ifreq ifr; 263 int err; 264 265 switch ( m->b_datap->db_type ) { 266 case M_PCPROTO : 267 case M_PROTO : 268 if ( m->b_wptr - m->b_rptr < sizeof( tl->type )) { 269 freemsg( m ); 270 break; 271 } 272 tl = (union T_primitives *)m->b_rptr; 273 switch ( tl->type ) { 274 case T_INFO_REQ : 275 t_info_ack( q, sd->sd_state ); 276 freemsg( m ); 277 break; 278 279 case T_UNBIND_REQ : 280 if ( m->b_wptr - m->b_rptr < sizeof( struct T_unbind_req )) { 281 freemsg( m ); 282 break; 283 } 284 if ( sd->sd_state != TS_IDLE ) { 285 t_error_ack( q, T_BIND_REQ, TOUTSTATE, 0 ); 286 freemsg( m ); 287 break; 288 } 289 bzero( (caddr_t)&sd->sd_sat, sizeof( struct sockaddr_at )); 290 sd->sd_state = TS_UNBND; 291 t_ok_ack( q, T_UNBIND_REQ ); 292 break; 293 294 case T_BIND_REQ : 295 if ( m->b_wptr - m->b_rptr < sizeof( struct T_bind_req )) { 296 freemsg( m ); 297 break; 298 } 299 if ( sd->sd_state != TS_UNBND ) { 300 t_error_ack( q, T_BIND_REQ, TOUTSTATE, 0 ); 301 freemsg( m ); 302 break; 303 } 304 305 if ( tl->bind_req.ADDR_length == 0 ) { 306 bzero( (caddr_t)&sat, sizeof( struct sockaddr_at )); 307 sat.sat_family = AF_APPLETALK; 308 } else { 309 if ( tl->bind_req.ADDR_length != sizeof( struct sockaddr ) || 310 m->b_wptr - m->b_rptr < 311 tl->bind_req.ADDR_offset + tl->bind_req.ADDR_length ) { 312 cmn_err( CE_CONT, "tpi_wput T_BIND_REQ wierd\n" ); 313 freemsg( m ); 314 break; 315 } 316 sat = *(struct sockaddr_at *)(m->b_rptr + 317 tl->bind_req.ADDR_offset ); 318 } 319 320 if (( err = sock_bind( sd, &sat )) != 0 ) { 321 t_error_ack( q, T_BIND_REQ, TSYSERR, err ); 322 } else { 323 /* seems like we must return the requested address */ 324 t_bind_ack( q, &sat ); 325 } 326 freemsg( m ); 327 break; 328 329 case T_UNITDATA_REQ : 330 if ( m->b_wptr - m->b_rptr < sizeof( struct T_unitdata_req )) { 331 freemsg( m ); 332 break; 333 } 334 if ( sd->sd_state != TS_IDLE ) { 335 cmn_err( CE_NOTE, "tpi_wput unitdata on unbound socket\n" ); 336 t_error_ack( q, T_UNITDATA_REQ, TOUTSTATE, 0 ); 337 freemsg( m ); 338 break; 339 } 340 if ( tl->unitdata_req.DEST_length != sizeof( struct sockaddr )) { 341 cmn_err( CE_NOTE, "tpi_wput T_UNITDATA_REQ %d\n", 342 tl->unitdata_req.DEST_length ); 343 freemsg( m ); 344 break; 345 } 346 347#ifdef notdef 348 /* 349 * Sometimes, the socket layer gives us crap... Sound like a bug? 350 */ 351 if ( m->b_rptr + tl->unitdata_req.DEST_offset + 352 tl->unitdata_req.DEST_length > m->b_wptr ) { 353cmn_err( CE_CONT, "tpi_wput T_UNITDATA_REQ mblk size %X %X\n", m->b_rptr + tl->unitdata_req.DEST_offset + tl->unitdata_req.DEST_length, m->b_wptr ); 354 freemsg( m ); 355 break; 356 } 357#endif /* notdef */ 358 359 sat = *(struct sockaddr_at *)(m->b_rptr + 360 tl->unitdata_req.DEST_offset ); 361 if ( sat.sat_family != AF_APPLETALK ) { 362 cmn_err( CE_CONT, "tpi_wput non-AppleTalk\n" ); 363 freemsg( m ); 364 break; 365 } 366 367 if ( m->b_wptr - m->b_rptr < sizeof( struct ddpehdr )) { 368 cmn_err( CE_CONT, "tpi_wput m too short\n" ); 369 freemsg( m ); 370 break; 371 } 372 m->b_wptr = m->b_rptr + sizeof( struct ddpehdr ); 373 m->b_datap->db_type = M_DATA; 374 deh = (struct ddpehdr *)m->b_rptr; 375 deh->deh_pad = 0; 376 deh->deh_hops = 0; 377 deh->deh_len = msgdsize( m ); 378 379 deh->deh_dnet = sat.sat_addr.s_net; 380 deh->deh_dnode = sat.sat_addr.s_node; 381 deh->deh_dport = sat.sat_port; 382 383 deh->deh_snet = sd->sd_sat.sat_addr.s_net; 384 deh->deh_snode = sd->sd_sat.sat_addr.s_node; 385 deh->deh_sport = sd->sd_sat.sat_port; 386 387 deh->deh_sum = 0; /* XXX */ 388 deh->deh_bytes = htonl( deh->deh_bytes ); 389 return( if_route( if_withaddr( &sd->sd_sat ), m, &sat )); 390 391 default : 392 /* cmn_err( CE_NOTE, "tpi_wput M_PCPROTO 0x%X\n", tl->type ); */ 393 t_error_ack( q, tl->type, TNOTSUPPORT, 0 ); 394 freemsg( m ); 395 break; 396 } 397 break; 398 399 case M_IOCTL : 400 if ( m->b_wptr - m->b_rptr < sizeof( struct iocblk )) { 401 freemsg( m ); 402 break; 403 } 404 ioc = (struct iocblk *)m->b_rptr; 405 if ( ioc->ioc_count != TRANSPARENT ) { 406 cmn_err( CE_CONT, "tpi_wput non-TRANSPARENT %X\n", ioc->ioc_cmd ); 407 ioc_error_ack( q, m, EINVAL ); 408 break; 409 } 410 if ( m->b_cont == NULL ) { 411 cmn_err( CE_CONT, "tpi_wput M_IOCTL no arg\n" ); 412 ioc_error_ack( q, m, EINVAL ); 413 break; 414 } 415 416 /* de-allocated after M_IOCDATA processing */ 417 if (( m0 = allocb( sizeof( struct ioc_state ), BPRI_HI )) == NULL ) { 418 cmn_err( CE_CONT, "tpi_wput m0 no mem\n" ); 419 ioc_error_ack( q, m, EINVAL ); 420 break; 421 } 422 m0->b_wptr = m->b_rptr + sizeof( struct ioc_state ); 423 is = (struct ioc_state *)m0->b_rptr; 424 425 switch ( ioc->ioc_cmd ) { 426 case SIOCADDRT : 427 case SIOCDELRT : 428 if (( err = drv_priv( ioc->ioc_cr )) != 0 ) { 429 ioc_error_ack( q, m, err ); 430 break; 431 } 432 is->is_state = M_COPYIN; 433 is->is_addr = *(caddr_t *)m->b_cont->b_rptr; 434 ioc_copyin( q, m, m0, is->is_addr, sizeof( struct rtentry )); 435 break; 436 437 case SIOCADDMULTI : 438 case SIOCSIFADDR : 439 if (( err = drv_priv( ioc->ioc_cr )) != 0 ) { 440 ioc_error_ack( q, m, err ); 441 break; 442 } 443 444 case SIOCGIFADDR : 445 is->is_state = M_COPYIN; 446 is->is_addr = *(caddr_t *)m->b_cont->b_rptr; 447 ioc_copyin( q, m, m0, is->is_addr, sizeof( struct ifreq )); 448 break; 449 450 case TI_GETMYNAME : 451 is->is_state = M_COPYIN; 452 is->is_addr = *(caddr_t *)m->b_cont->b_rptr; 453 ioc_copyin( q, m, m0, is->is_addr, sizeof( struct netbuf )); 454 break; 455 456 default : 457 ioc_error_ack( q, m, EINVAL ); 458 break; 459 } 460 break; 461 462 case M_IOCDATA : 463 if ( m->b_wptr - m->b_rptr < sizeof( struct copyresp )) { 464 freemsg( m ); 465 break; 466 } 467 cp = (struct copyresp *)m->b_rptr; 468 if ( cp->cp_rval != 0 ) { 469 cmn_err( CE_CONT, "tpi_wput IOCDATA failed %s\n", cp->cp_rval ); 470 freemsg( m ); 471 break; 472 } 473 474 if (( m0 = cp->cp_private ) == NULL ) { 475 cmn_err( CE_CONT, "tpi_wput IOCDATA no state\n" ); 476 ioc_error_ack( q, m, EINVAL ); 477 break; 478 } 479 if ( m0->b_wptr - m0->b_rptr < sizeof( struct ioc_state )) { 480 cmn_err( CE_CONT, "tpi_wput IOCDATA private too short\n" ); 481 ioc_error_ack( q, m, EINVAL ); 482 break; 483 } 484 is = (struct ioc_state *)m0->b_rptr; 485 486 switch ( cp->cp_cmd ) { 487 case TI_GETMYNAME : 488 switch ( is->is_state ) { 489 case M_COPYIN : 490 if ( m->b_cont == NULL ) { 491 cmn_err( CE_CONT, "tpi_wput TI_GETMYNAME COPYIN no arg\n" ); 492 ioc_error_ack( q, m, EINVAL ); 493 break; 494 } 495 nb = *(struct netbuf *)m->b_cont->b_rptr; 496 nb.len = sizeof( struct sockaddr_at ); 497 /* copy out netbuf */ 498 is->is_state = M_COPYOUT; 499 is->is_count = 1; 500 ioc_copyout( q, m, m0, (caddr_t)&nb, is->is_addr, 501 sizeof( struct netbuf )); 502 is->is_addr = nb.buf; 503 return( 0 ); 504 505 case M_COPYOUT : 506 switch ( is->is_count ) { 507 case 1 : 508 /* copy out address to nb.buf */ 509 is->is_state = M_COPYOUT; 510 is->is_count = 2; 511 ioc_copyout( q, m, m0, (caddr_t)&sd->sd_sat, is->is_addr, 512 sizeof( struct sockaddr_at )); 513 return( 0 ); 514 515 case 2 : 516 ioc_ok_ack( q, m, 0 ); 517 break; 518 519 default : 520 cmn_err( CE_NOTE, "tpi_wput TI_GETMYNAME count %d\n", 521 is->is_count ); 522 ioc_error_ack( q, m, EINVAL ); 523 break; 524 } 525 break; 526 527 default : 528 cmn_err( CE_NOTE, "tpi_wput TI_GETMYNAME state %d\n", 529 is->is_state ); 530 ioc_error_ack( q, m, EINVAL ); 531 break; 532 } 533 break; 534 535 case SIOCADDRT : /* manipulate routing table */ 536 case SIOCDELRT : 537 if (( err = drv_priv( cp->cp_cr )) != 0 ) { 538 ioc_error_ack( q, m, err ); 539 break; 540 } 541 if ( is->is_state != M_COPYIN ) { 542 cmn_err( CE_CONT, "tpi_wput SIOC(ADD|DEL)RT bad state\n" ); 543 freemsg( m ); 544 break; 545 } 546 547 rt = *(struct rtentry *)m->b_cont->b_rptr; 548 549 if ( cp->cp_cmd == SIOCADDRT ) { 550 err = rt_add( (struct sockaddr_at *)&rt.rt_dst, 551 (struct sockaddr_at *)&rt.rt_gateway, rt.rt_flags ); 552 } else if ( cp->cp_cmd == SIOCDELRT ) { 553 err = rt_del( (struct sockaddr_at *)&rt.rt_dst, 554 (struct sockaddr_at *)&rt.rt_gateway, rt.rt_flags ); 555 } else { 556 cmn_err( CE_CONT, "tpi_wput SIOC(ADD|DEL)RT bad cmd\n" ); 557 freemsg( m ); 558 break; 559 } 560 if ( err != 0 ) { 561 ioc_error_ack( q, m, err ); 562 } else { 563 ioc_ok_ack( q, m, 0 ); 564 } 565 break; 566 567 /* 568 * These both require lower messages to be sent. 569 */ 570 case SIOCADDMULTI : 571 case SIOCSIFADDR : 572 if (( err = drv_priv( cp->cp_cr )) != 0 ) { 573 ioc_error_ack( q, m, err ); 574 break; 575 } 576 if ( is->is_state != M_COPYIN ) { 577 cmn_err( CE_CONT, "tpi_wput SIOCSIFADDR bad state\n" ); 578 freemsg( m ); 579 break; 580 } 581 582 ifr = *(struct ifreq *)m->b_cont->b_rptr; 583 584 /* initiate command, pass q and m (current context to be saved */ 585 if ( cp->cp_cmd == SIOCSIFADDR ) { 586 err = if_setaddr( q, m, ifr.ifr_name, 587 (struct sockaddr_at *)&ifr.ifr_addr ); 588 } else { 589 err = if_addmulti( q, m, ifr.ifr_name, &ifr.ifr_addr ); 590 } 591 if ( err != 0 ) { 592 ioc_error_ack( q, m, err ); 593 break; 594 } 595 break; 596 597 case SIOCGIFADDR : /* get interface address */ 598 switch ( is->is_state ) { 599 case M_COPYOUT : 600 /* ack the original ioctl */ 601 ioc_ok_ack( q, m, 0 ); 602 break; 603 604 case M_COPYIN : 605 if ( m->b_cont == NULL ) { 606 cmn_err( CE_CONT, "tpi_wput SIOCGIFADDR COPYIN no arg\n" ); 607 ioc_error_ack( q, m, EINVAL ); 608 break; 609 } 610 611 /* size??? */ 612 ifr = *(struct ifreq *)m->b_cont->b_rptr; 613 if (( err = if_getaddr( ifr.ifr_name, 614 (struct sockaddr_at *)&ifr.ifr_addr )) != 0 ) { 615 ioc_error_ack( q, m, err ); 616 } 617 is->is_state = M_COPYOUT; 618 ioc_copyout( q, m, m0, (caddr_t)&ifr, is->is_addr, 619 sizeof( struct ifreq )); 620 return( 0 ); /* avoid freemsg( m0 ) below */ 621 622 default : 623 cmn_err( CE_CONT, "tpi_wput SIOCGIFADDR bad state\n" ); 624 freemsg( m ); 625 break; 626 } 627 break; 628 629 default : 630 cmn_err( CE_NOTE, "tpi_wput M_IOCDATA 0x%X\n", cp->cp_cmd ); 631 ioc_error_ack( q, m, EINVAL ); 632 break; 633 } 634 freemsg( m0 ); 635 break; 636 637 default : 638 cmn_err( CE_NOTE, "!tpi_wput dp_type = 0x%X\n", m->b_datap->db_type ); 639 freemsg( m ); 640 break; 641 } 642 643 return( 0 ); 644} 645 646static struct module_info tpi_info = { 647 0, /* XXX */ 648 "ddp", 649 0, 650 1500, 651 3000, 652 64 653}; 654 655static struct qinit tpi_rinit = { 656 tpi_rput, /* qi_putp */ 657 NULL, /* qi_srvp */ 658 tpi_open, /* qi_qopen */ 659 tpi_close, /* qi_qclose */ 660 NULL, 661 &tpi_info, /* qi_minfo */ 662 NULL, 663}; 664 665static struct qinit tpi_winit = { 666 tpi_wput, /* qi_putp */ 667 NULL, 668 NULL, 669 NULL, 670 NULL, 671 &tpi_info, 672 NULL, 673}; 674 675static struct streamtab tpi_stream = { 676 &tpi_rinit, 677 &tpi_winit, 678 NULL, 679 NULL 680}; 681 682static struct cb_ops tpi_cbops = { 683 nulldev, /* cb_open */ 684 nulldev, /* cb_close */ 685 nodev, 686 nodev, 687 nodev, 688 nodev, 689 nodev, 690 nodev, 691 nodev, 692 nodev, 693 nodev, 694 nochpoll, 695 ddi_prop_op, 696 &tpi_stream, 697 D_NEW | D_MP | D_MTPERMOD, /* cb_flag */ 698 CB_REV, /* cb_rev */ 699 nodev, /* cb_aread */ 700 nodev, /* cb_awrite */ 701}; 702 703static struct dev_ops tpi_devops = { 704 DEVO_REV, 705 0, 706 tpi_getinfo, 707#ifdef DDI_IDENTIFIED 708 tpi_identify, 709#else 710 nulldev, 711#endif 712 nulldev, 713 tpi_attach, 714 tpi_detach, 715 nodev, 716 &tpi_cbops, 717 (struct bus_ops *)NULL, 718 NULL, 719}; 720 721/* 722 * DDP Streams device. This device is opened by socket(). 723 */ 724struct modldrv tpi_ldrv = { 725 &mod_driverops, 726 "DDP Streams device", 727 &tpi_devops, 728}; 729