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