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