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