Commit | Line | Data |
---|---|---|
ecfa166d | 1 | /* |
bb08cf86 | 2 | * @(#)if_dmv.c 7.3 (Berkeley) %G% |
ecfa166d MK |
3 | * DMV-11 Driver |
4 | * | |
5 | * Qbus Sync DDCMP interface - DMV operated in full duplex, point to point mode | |
6 | * | |
7 | * Derived from 4.3 release if_dmv.c rev. 6.12 dated 4/23/86 | |
8 | * (which wasn't the 4.3 release!) | |
9 | * | |
10 | * Bob Kridle | |
11 | * mt Xinu | |
12 | */ | |
13 | ||
14 | #include "dmv.h" | |
15 | #if NDMV > 0 | |
16 | ||
ecfa166d MK |
17 | #include "param.h" |
18 | #include "systm.h" | |
19 | #include "mbuf.h" | |
20 | #include "buf.h" | |
21 | #include "ioctl.h" /* must precede tty.h */ | |
22 | #include "tty.h" | |
23 | #include "protosw.h" | |
24 | #include "socket.h" | |
25 | #include "syslog.h" | |
26 | #include "vmmac.h" | |
27 | #include "errno.h" | |
bb08cf86 MK |
28 | #include "time.h" |
29 | #include "kernel.h" | |
ecfa166d MK |
30 | |
31 | #include "../net/if.h" | |
32 | #include "../net/netisr.h" | |
33 | #include "../net/route.h" | |
34 | ||
35 | #ifdef INET | |
36 | #include "../netinet/in.h" | |
37 | #include "../netinet/in_systm.h" | |
38 | #include "../netinet/in_var.h" | |
39 | #include "../netinet/ip.h" | |
40 | #endif | |
41 | ||
42 | #include "../vax/cpu.h" | |
43 | #include "../vax/mtpr.h" | |
bb08cf86 | 44 | #include "../vax/pte.h" |
ecfa166d MK |
45 | #include "../vaxuba/ubareg.h" |
46 | #include "../vaxuba/ubavar.h" | |
bb08cf86 MK |
47 | #include "if_uba.h" |
48 | #include "if_dmv.h" | |
ecfa166d | 49 | |
ecfa166d | 50 | int dmv_timeout = 8; /* timeout value */ |
ecfa166d MK |
51 | |
52 | /* | |
53 | * Driver information for auto-configuration stuff. | |
54 | */ | |
55 | int dmvprobe(), dmvattach(), dmvinit(), dmvioctl(); | |
bb08cf86 | 56 | int dmvoutput(), dmvreset(), dmvtimeout(); |
ecfa166d MK |
57 | struct uba_device *dmvinfo[NDMV]; |
58 | u_short dmvstd[] = { 0 }; | |
59 | struct uba_driver dmvdriver = | |
60 | { dmvprobe, 0, dmvattach, 0, dmvstd, "dmv", dmvinfo }; | |
61 | ||
62 | /* | |
63 | * Don't really know how many buffers/commands can be queued to a DMV-11. | |
64 | * Manual doesn't say... Perhaps we can look at a DEC driver some day. | |
bb08cf86 | 65 | * These numbers ame from DMC/DMR driver. |
ecfa166d MK |
66 | */ |
67 | #define NRCV 5 | |
68 | #define NXMT 3 | |
69 | #define NCMDS (NRCV+NXMT+4) /* size of command queue */ | |
70 | ||
bb08cf86 MK |
71 | #ifdef DEBUG |
72 | #define printd(f) if (sc->sc_if.if_flags & IFF_DEBUG) \ | |
73 | printf("DMVDEBUG: dmv%d: ", unit), printf(f) | |
74 | #else | |
75 | #define printd(f) /* nil */ | |
76 | #endif | |
ecfa166d MK |
77 | |
78 | /* error reporting intervals */ | |
79 | ||
80 | #define DMV_RPRTE 1 | |
81 | #define DMV_RPTTE 1 | |
82 | #define DMV_RPSTE 1 | |
83 | #define DMV_RPNXM 1 | |
84 | #define DMV_RPMODD 1 | |
85 | #define DMV_RPQOVF 1 | |
86 | #define DMV_RPCXRL 1 | |
d9bcdfcc MK |
87 | |
88 | /* number of errors to accept before trying a reset */ | |
89 | #define DMV_RPUNKNOWN 10 | |
ecfa166d MK |
90 | |
91 | struct dmv_command { | |
92 | u_char qp_mask; /* Which registers to set up */ | |
93 | #define QP_TRIB 0x01 | |
94 | #define QP_SEL4 0x02 | |
95 | #define QP_SEL6 0x04 | |
96 | #define QP_SEL10 0x08 | |
97 | u_char qp_cmd; | |
98 | u_char qp_tributary; | |
99 | u_short qp_sel4; | |
100 | u_short qp_sel6; | |
101 | u_short qp_sel10; | |
102 | struct dmv_command *qp_next; /* next command on queue */ | |
103 | }; | |
104 | ||
105 | #define qp_lowbufaddr qp_ | |
106 | ||
107 | struct dmvbufs { | |
108 | int ubinfo; /* from uballoc */ | |
109 | short cc; /* buffer size */ | |
110 | short flags; /* access control */ | |
111 | }; | |
112 | ||
113 | #define DBUF_OURS 0 /* buffer is available */ | |
114 | #define DBUF_DMVS 1 /* buffer claimed by somebody */ | |
115 | #define DBUF_XMIT 4 /* transmit buffer */ | |
116 | #define DBUF_RCV 8 /* receive buffer */ | |
117 | ||
118 | ||
119 | /* | |
120 | * DMV software status per interface. | |
121 | * | |
122 | * Each interface is referenced by a network interface structure, | |
123 | * sc_if, which the routing code uses to locate the interface. | |
124 | * This structure contains the output queue for the interface, its address, ... | |
125 | * We also have, for each interface, a set of 7 UBA interface structures | |
126 | * for each, which | |
127 | * contain information about the UNIBUS resources held by the interface: | |
128 | * map registers, buffered data paths, etc. Information is cached in this | |
129 | * structure for use by the if_uba.c routines in running the interface | |
130 | * efficiently. | |
131 | */ | |
132 | struct dmv_softc { | |
133 | struct ifnet sc_if; /* network-visible interface */ | |
ecfa166d MK |
134 | short sc_oused; /* output buffers currently in use */ |
135 | short sc_iused; /* input buffers given to DMV */ | |
136 | short sc_flag; /* flags */ | |
ecfa166d MK |
137 | int sc_ubinfo; /* UBA mapping info for base table */ |
138 | int sc_errors[8]; /* error counters */ | |
139 | #define sc_rte sc_errors[0] /* receive threshhold error */ | |
140 | #define sc_xte sc_errors[1] /* xmit threshhold error */ | |
141 | #define sc_ste sc_errors[2] /* select threshhold error */ | |
142 | #define sc_nxm sc_errors[3] /* non-existant memory */ | |
143 | #define sc_modd sc_errors[4] /* modem disconnect */ | |
144 | #define sc_qovf sc_errors[5] /* command/response queue overflow */ | |
145 | #define sc_cxrl sc_errors[6] /* carrier loss */ | |
146 | #define sc_unknown sc_errors[7] /* other errors - look in DMV manual */ | |
bb08cf86 MK |
147 | struct dmvbufs sc_rbufs[NRCV]; /* receive buffer info */ |
148 | struct dmvbufs sc_xbufs[NXMT]; /* transmit buffer info */ | |
149 | struct ifubinfo sc_ifuba; /* UNIBUS resources */ | |
150 | struct ifrw sc_ifr[NRCV]; /* UNIBUS receive buffer maps */ | |
151 | struct ifxmt sc_ifw[NXMT]; /* UNIBUS receive buffer maps */ | |
ecfa166d MK |
152 | /* command queue stuff */ |
153 | struct dmv_command sc_cmdbuf[NCMDS]; | |
154 | struct dmv_command *sc_qhead; /* head of command queue */ | |
155 | struct dmv_command *sc_qtail; /* tail of command queue */ | |
156 | struct dmv_command *sc_qactive; /* command in progress */ | |
157 | struct dmv_command *sc_qfreeh; /* head of list of free cmd buffers */ | |
158 | struct dmv_command *sc_qfreet; /* tail of list of free cmd buffers */ | |
159 | /* end command queue stuff */ | |
160 | } dmv_softc[NDMV]; | |
161 | ||
162 | /* flags */ | |
bb08cf86 MK |
163 | #define DMV_RESTART 0x01 /* software restart in progress */ |
164 | #define DMV_ONLINE 0x02 /* device managed to transmit */ | |
165 | #define DMV_RUNNING 0x04 /* device initialized */ | |
ecfa166d MK |
166 | |
167 | ||
168 | /* queue manipulation macros */ | |
169 | #define QUEUE_AT_HEAD(qp, head, tail) \ | |
170 | (qp)->qp_next = (head); \ | |
171 | (head) = (qp); \ | |
172 | if ((tail) == (struct dmv_command *) 0) \ | |
173 | (tail) = (head) | |
174 | ||
175 | #define QUEUE_AT_TAIL(qp, head, tail) \ | |
176 | if ((tail)) \ | |
177 | (tail)->qp_next = (qp); \ | |
178 | else \ | |
179 | (head) = (qp); \ | |
180 | (qp)->qp_next = (struct dmv_command *) 0; \ | |
181 | (tail) = (qp) | |
182 | ||
183 | #define DEQUEUE(head, tail) \ | |
184 | (head) = (head)->qp_next;\ | |
185 | if ((head) == (struct dmv_command *) 0)\ | |
186 | (tail) = (head) | |
187 | ||
188 | dmvprobe(reg) | |
189 | caddr_t reg; | |
190 | { | |
191 | register int br, cvec; | |
192 | register struct dmvdevice *addr = (struct dmvdevice *)reg; | |
193 | register int i; | |
194 | ||
195 | #ifdef lint | |
196 | br = 0; cvec = br; br = cvec; | |
197 | dmvrint(0); dmvxint(0); | |
198 | #endif | |
199 | addr->bsel1 = DMV_MCLR; | |
200 | for (i = 100000; i && (addr->bsel1 & DMV_RUN) == 0; i--) | |
201 | ; | |
202 | if ((addr->bsel1 & DMV_RUN) == 0) { | |
203 | printf("dmvprobe: can't start device\n" ); | |
204 | return (0); | |
205 | } | |
206 | if ((addr->bsel4 != 033) || (addr->bsel6 != 0305)) | |
207 | { | |
208 | printf("dmvprobe: device init failed, bsel4=%o, bsel6=%o\n", | |
209 | addr->bsel4, addr->bsel6); | |
210 | return (0); | |
211 | } | |
212 | addr->bsel0 = DMV_RQI|DMV_IEI|DMV_IEO; | |
213 | DELAY(1000000); | |
214 | addr->bsel1 = DMV_MCLR; | |
215 | for (i = 100000; i && (addr->bsel1 & DMV_RUN) == 0; i--) | |
216 | ; | |
bb08cf86 | 217 | br = 0x15; /* screwy interrupt structure */ |
ecfa166d MK |
218 | return (1); |
219 | } | |
220 | ||
221 | /* | |
222 | * Interface exists: make available by filling in network interface | |
223 | * record. System will initialize the interface when it is ready | |
224 | * to accept packets. | |
225 | */ | |
226 | dmvattach(ui) | |
227 | register struct uba_device *ui; | |
228 | { | |
229 | register struct dmv_softc *sc = &dmv_softc[ui->ui_unit]; | |
230 | ||
231 | sc->sc_if.if_unit = ui->ui_unit; | |
232 | sc->sc_if.if_name = "dmv"; | |
233 | sc->sc_if.if_mtu = DMVMTU; | |
234 | sc->sc_if.if_init = dmvinit; | |
235 | sc->sc_if.if_output = dmvoutput; | |
236 | sc->sc_if.if_ioctl = dmvioctl; | |
237 | sc->sc_if.if_reset = dmvreset; | |
bb08cf86 | 238 | sc->sc_if.if_watchdog = dmvtimeout; |
ecfa166d MK |
239 | sc->sc_if.if_flags = IFF_POINTOPOINT; |
240 | sc->sc_ifuba.iff_flags = UBA_CANTWAIT; | |
241 | ||
ecfa166d MK |
242 | if_attach(&sc->sc_if); |
243 | } | |
244 | ||
245 | /* | |
246 | * Reset of interface after UNIBUS reset. | |
247 | * If interface is on specified UBA, reset its state. | |
248 | */ | |
249 | dmvreset(unit, uban) | |
250 | int unit, uban; | |
251 | { | |
252 | register struct uba_device *ui; | |
253 | register struct dmv_softc *sc = &dmv_softc[unit]; | |
254 | ||
255 | if (unit >= NDMV || (ui = dmvinfo[unit]) == 0 || ui->ui_alive == 0 || | |
256 | ui->ui_ubanum != uban) | |
257 | return; | |
258 | printf(" dmv%d", unit); | |
259 | sc->sc_flag = 0; | |
260 | sc->sc_if.if_flags &= ~IFF_RUNNING; | |
261 | dmvinit(unit); | |
262 | } | |
263 | ||
264 | /* | |
265 | * Initialization of interface; reinitialize UNIBUS usage. | |
266 | */ | |
267 | dmvinit(unit) | |
268 | int unit; | |
269 | { | |
270 | register struct dmv_softc *sc = &dmv_softc[unit]; | |
271 | register struct uba_device *ui = dmvinfo[unit]; | |
272 | register struct dmvdevice *addr; | |
273 | register struct ifnet *ifp = &sc->sc_if; | |
274 | register struct ifrw *ifrw; | |
275 | register struct ifxmt *ifxp; | |
276 | register struct dmvbufs *rp; | |
277 | register struct dmv_command *qp; | |
278 | struct ifaddr *ifa; | |
279 | int base; | |
280 | int s; | |
281 | ||
282 | addr = (struct dmvdevice *)ui->ui_addr; | |
283 | ||
284 | /* | |
285 | * Check to see that an address has been set | |
286 | * (both local and destination for an address family). | |
287 | */ | |
288 | for (ifa = ifp->if_addrlist; ifa; ifa = ifa->ifa_next) | |
289 | if (ifa->ifa_addr.sa_family && ifa->ifa_dstaddr.sa_family) | |
290 | break; | |
291 | if (ifa == (struct ifaddr *) 0) | |
292 | return; | |
293 | ||
294 | if ((addr->bsel1&DMV_RUN) == 0) { | |
295 | log(LOG_CRIT, "dmvinit: dmv%d not running\n", unit); | |
296 | ifp->if_flags &= ~IFF_UP; | |
297 | return; | |
298 | } | |
bb08cf86 | 299 | printd(("dmvinit\n")); |
ecfa166d MK |
300 | /* initialize UNIBUS resources */ |
301 | sc->sc_iused = sc->sc_oused = 0; | |
302 | if ((ifp->if_flags & IFF_RUNNING) == 0) { | |
303 | if (if_ubaminit( | |
304 | &sc->sc_ifuba, | |
305 | ui->ui_ubanum, | |
306 | sizeof(struct dmv_header), | |
307 | (int)btoc(DMVMTU), | |
308 | sc->sc_ifr, | |
309 | NRCV, | |
310 | sc->sc_ifw, | |
311 | NXMT | |
312 | ) == 0) { | |
313 | log(LOG_CRIT, "dmvinit: dmv%d can't allocate uba resources\n", unit); | |
314 | ifp->if_flags &= ~IFF_UP; | |
315 | return; | |
316 | } | |
317 | ifp->if_flags |= IFF_RUNNING; | |
318 | } | |
bb08cf86 MK |
319 | /* |
320 | * Limit packets enqueued until we see if we're on the air. | |
321 | */ | |
322 | ifp->if_snd.ifq_maxlen = 3; | |
323 | ||
ecfa166d MK |
324 | |
325 | /* initialize buffer pool */ | |
326 | /* receives */ | |
327 | ifrw = &sc->sc_ifr[0]; | |
328 | for (rp = &sc->sc_rbufs[0]; rp < &sc->sc_rbufs[NRCV]; rp++) { | |
329 | rp->ubinfo = ifrw->ifrw_info & 0x3ffff; | |
330 | rp->cc = DMVMTU + sizeof (struct dmv_header); | |
331 | rp->flags = DBUF_OURS|DBUF_RCV; | |
332 | ifrw++; | |
333 | } | |
334 | /* transmits */ | |
335 | ifxp = &sc->sc_ifw[0]; | |
336 | for (rp = &sc->sc_xbufs[0]; rp < &sc->sc_xbufs[NXMT]; rp++) { | |
337 | rp->ubinfo = ifxp->ifw_info & 0x3ffff; | |
338 | rp->cc = 0; | |
339 | rp->flags = DBUF_OURS|DBUF_XMIT; | |
340 | ifxp++; | |
341 | } | |
342 | ||
343 | /* set up command queues */ | |
344 | sc->sc_qfreeh = sc->sc_qfreet | |
345 | = sc->sc_qhead = sc->sc_qtail = sc->sc_qactive = | |
346 | (struct dmv_command *)0; | |
347 | /* set up free command buffer list */ | |
348 | for (qp = &sc->sc_cmdbuf[0]; qp < &sc->sc_cmdbuf[NCMDS]; qp++) { | |
349 | QUEUE_AT_HEAD(qp, sc->sc_qfreeh, sc->sc_qfreet); | |
350 | } | |
351 | if(sc->sc_flag & DMV_RUNNING) | |
352 | dmvload( sc, DMV_CNTRLI, (QP_TRIB|QP_SEL6), 1, 0, DMV_REQHS,0); | |
353 | else | |
354 | dmvload( sc, DMV_CNTRLI, (QP_TRIB|QP_SEL6), 1, 0, DMV_ESTTRIB,0); | |
355 | dmvload( sc, DMV_CNTRLI, (QP_TRIB|QP_SEL6), 1, 0, DMV_REQSUS,0); | |
356 | sc->sc_flag |= (DMV_RESTART|DMV_RUNNING); | |
bb08cf86 | 357 | sc->sc_flag &= ~DMV_ONLINE; |
ecfa166d MK |
358 | addr->bsel0 |= DMV_IEO; |
359 | } | |
360 | ||
361 | /* | |
362 | * Start output on interface. Get another datagram | |
363 | * to send from the interface queue and map it to | |
364 | * the interface before starting output. | |
365 | * | |
366 | * Must be called at spl 5 | |
367 | */ | |
368 | dmvstart(dev) | |
369 | dev_t dev; | |
370 | { | |
371 | int unit = minor(dev); | |
372 | register struct dmv_softc *sc = &dmv_softc[unit]; | |
373 | struct mbuf *m; | |
374 | register struct dmvbufs *rp; | |
375 | register int n; | |
376 | ||
377 | /* | |
378 | * Dequeue up to NXMT requests and map them to the UNIBUS. | |
379 | * If no more requests, or no dmv buffers available, just return. | |
380 | */ | |
bb08cf86 | 381 | printd(("dmvstart\n")); |
ecfa166d MK |
382 | n = 0; |
383 | for (rp = &sc->sc_xbufs[0]; rp < &sc->sc_xbufs[NXMT]; rp++ ) { | |
384 | /* find an available buffer */ | |
385 | if ((rp->flags & DBUF_DMVS) == 0) { | |
386 | IF_DEQUEUE(&sc->sc_if.if_snd, m); | |
387 | if (m == 0) | |
388 | return; | |
389 | /* mark it dmvs */ | |
390 | rp->flags |= (DBUF_DMVS); | |
391 | /* | |
392 | * Have request mapped to UNIBUS for transmission | |
393 | * and start the output. | |
394 | */ | |
395 | rp->cc = if_ubaput(&sc->sc_ifuba, &sc->sc_ifw[n], m); | |
bb08cf86 MK |
396 | if (++sc->sc_oused == 1) |
397 | sc->sc_if.if_timer = dmv_timeout; | |
ecfa166d MK |
398 | dmvload( |
399 | sc, | |
400 | DMV_BACCX, | |
401 | QP_TRIB|QP_SEL4|QP_SEL6|QP_SEL10, | |
402 | 1, | |
403 | rp->ubinfo, | |
404 | (rp->ubinfo>>16)&0x3f, | |
405 | rp->cc | |
406 | ); | |
407 | } | |
408 | n++; | |
409 | } | |
410 | } | |
411 | ||
412 | /* | |
413 | * Utility routine to load the DMV device registers. | |
414 | */ | |
415 | dmvload(sc, cmd, mask, tributary, sel4, sel6, sel10) | |
416 | register struct dmv_softc *sc; | |
417 | u_char cmd, tributary, mask; | |
418 | u_short sel4, sel6, sel10; | |
419 | { | |
420 | register struct dmvdevice *addr; | |
421 | register int unit, sps; | |
422 | register struct dmv_command *qp; | |
423 | ||
424 | unit = sc - dmv_softc; | |
bb08cf86 | 425 | printd(("dmvload: cmd=%x mask=%x trib=%x sel4=%x sel6=%x sel10=%x\n", |
ecfa166d MK |
426 | (unsigned) cmd, |
427 | (unsigned) mask, | |
428 | (unsigned) tributary, | |
429 | (unsigned) sel4, | |
430 | (unsigned) sel6, | |
431 | (unsigned) sel10 | |
bb08cf86 | 432 | )); |
ecfa166d MK |
433 | addr = (struct dmvdevice *)dmvinfo[unit]->ui_addr; |
434 | sps = spl5(); | |
435 | ||
436 | /* grab a command buffer from the free list */ | |
437 | if ((qp = sc->sc_qfreeh) == (struct dmv_command *)0) | |
438 | panic("dmv command queue overflow"); | |
439 | DEQUEUE(sc->sc_qfreeh, sc->sc_qfreet); | |
440 | ||
441 | /* fill in requested info */ | |
442 | qp->qp_cmd = cmd; | |
443 | qp->qp_mask = mask; | |
444 | qp->qp_tributary = tributary; | |
445 | qp->qp_sel4 = sel4; | |
446 | qp->qp_sel6 = sel6; | |
447 | qp->qp_sel10 = sel10; | |
448 | ||
449 | if (sc->sc_qactive) { /* command in progress */ | |
450 | if (cmd == DMV_BACCR) { /* supply read buffers first */ | |
451 | QUEUE_AT_HEAD(qp, sc->sc_qhead, sc->sc_qtail); | |
452 | } else { | |
453 | QUEUE_AT_TAIL(qp, sc->sc_qhead, sc->sc_qtail); | |
454 | } | |
455 | } else { /* command port free */ | |
456 | sc->sc_qactive = qp; | |
457 | addr->bsel0 = (DMV_RQI|DMV_IEI|DMV_IEO); | |
458 | } | |
459 | splx(sps); | |
460 | } | |
461 | /* | |
462 | * DMV interface input interrupt. | |
463 | * Ready to accept another command, | |
464 | * pull one off the command queue. | |
465 | */ | |
466 | dmvrint(unit) | |
467 | int unit; | |
468 | { | |
469 | register struct dmv_softc *sc; | |
470 | register struct dmvdevice *addr; | |
471 | register struct dmv_command *qp; | |
472 | register int n; | |
473 | ||
474 | addr = (struct dmvdevice *)dmvinfo[unit]->ui_addr; | |
475 | sc = &dmv_softc[unit]; | |
bb08cf86 | 476 | printd(("dmvrint\n")); |
ecfa166d MK |
477 | if ((qp = sc->sc_qactive) == (struct dmv_command *) 0) { |
478 | log(LOG_WARNING, "dmvrint: dmv%d no command\n", unit); | |
479 | return; | |
480 | } | |
481 | while (addr->bsel2&DMV_RDI) { | |
482 | if(qp->qp_mask&QP_SEL4) | |
483 | addr->wsel4 = qp->qp_sel4; | |
484 | if(qp->qp_mask&QP_SEL6) | |
485 | addr->wsel6 = qp->qp_sel6; | |
486 | if(qp->qp_mask&QP_SEL10) { | |
487 | addr->wsel10 = qp->qp_sel10; | |
488 | qp->qp_cmd |= DMV_22BIT; | |
489 | } | |
490 | if(qp->qp_mask&QP_TRIB) | |
491 | addr->wsel2 = qp->qp_cmd|(qp->qp_tributary << 8); | |
492 | else | |
493 | addr->bsel2 = qp->qp_cmd; | |
494 | QUEUE_AT_HEAD(qp, sc->sc_qfreeh, sc->sc_qfreet); | |
495 | if ((sc->sc_qactive = sc->sc_qhead) == (struct dmv_command *)0) | |
496 | break; | |
497 | qp = sc->sc_qactive; | |
498 | DEQUEUE(sc->sc_qhead, sc->sc_qtail); | |
499 | if (addr->bsel2&DMV_RDO) | |
500 | break; | |
501 | } | |
502 | if (!sc->sc_qactive) { | |
503 | if(addr->bsel2&DMV_RDI) { | |
504 | /* clear RQI prior to last command per DMV manual */ | |
505 | addr->bsel0 &= ~DMV_RQI; | |
506 | addr->wsel6 = DMV_NOP; | |
507 | addr->bsel2 = DMV_CNTRLI; | |
508 | } | |
509 | addr->bsel0 = DMV_IEO; | |
510 | } | |
511 | else /* RDO set or DMV still holding CSR */ | |
512 | addr->bsel0 = (DMV_RQI|DMV_IEI|DMV_IEO); | |
513 | ||
514 | } | |
515 | ||
516 | /* | |
517 | * DMV interface output interrupt. | |
518 | * A transfer may have completed, check for errors. | |
519 | * If it was a read, notify appropriate protocol. | |
520 | * If it was a write, pull the next one off the queue. | |
521 | */ | |
522 | dmvxint(unit) | |
523 | int unit; | |
524 | { | |
525 | register struct dmv_softc *sc; | |
526 | register struct ifnet *ifp; | |
527 | struct uba_device *ui = dmvinfo[unit]; | |
528 | struct dmvdevice *addr; | |
529 | struct mbuf *m; | |
530 | struct ifqueue *inq; | |
531 | int sel2, sel3, sel4, sel6, sel10, pkaddr, len, s; | |
532 | register struct ifrw *ifrw; | |
533 | register struct dmvbufs *rp; | |
534 | register struct ifxmt *ifxp; | |
535 | struct dmv_header *dh; | |
bb08cf86 | 536 | int off, resid; |
ecfa166d MK |
537 | |
538 | addr = (struct dmvdevice *)ui->ui_addr; | |
539 | sc = &dmv_softc[unit]; | |
540 | ifp = &sc->sc_if; | |
541 | ||
542 | while (addr->bsel2 & DMV_RDO) { | |
543 | ||
544 | sel2 = addr->bsel2; | |
545 | sel3 = addr->bsel3; | |
546 | sel4 = addr->wsel4; /* release port */ | |
547 | sel6 = addr->wsel6; | |
548 | if(sel2 & DMV_22BIT) | |
549 | sel10 = addr->wsel10; | |
550 | addr->bsel2 &= ~DMV_RDO; | |
551 | pkaddr = sel4 | ((sel6 & 0x3f) << 16); | |
bb08cf86 | 552 | printd(("dmvxint: sel2=%x sel4=%x sel6=%x sel10=%x pkaddr=%x\n", |
ecfa166d MK |
553 | (unsigned) sel2, |
554 | (unsigned) sel4, | |
555 | (unsigned) sel6, | |
556 | (unsigned) sel10, | |
557 | (unsigned) pkaddr | |
bb08cf86 | 558 | )); |
ecfa166d MK |
559 | if((sc->sc_flag & DMV_RUNNING)==0) { |
560 | log(LOG_WARNING, "dmvxint: dmv%d xint while down\n", unit); | |
561 | return; | |
562 | } | |
563 | switch (sel2 & 07) { | |
564 | case DMV_BDRUS: | |
565 | /* | |
566 | * A read has completed. | |
567 | * Pass packet to type specific | |
568 | * higher-level input routine. | |
569 | */ | |
570 | ifp->if_ipackets++; | |
571 | /* find location in dmvuba struct */ | |
572 | ifrw= &sc->sc_ifr[0]; | |
573 | for (rp = &sc->sc_rbufs[0]; rp < &sc->sc_rbufs[NRCV]; rp++) { | |
574 | if(rp->ubinfo == pkaddr) | |
575 | break; | |
576 | ifrw++; | |
577 | } | |
578 | if (rp >= &sc->sc_rbufs[NRCV]) | |
579 | panic("dmv rcv"); | |
580 | if ((rp->flags & DBUF_DMVS) == 0) | |
581 | log(LOG_WARNING, "dmvxint: dmv%d done unalloc rbuf\n", unit); | |
582 | ||
583 | len = (sel10&0x3fff) - sizeof (struct dmv_header); | |
584 | if (len < 0 || len > DMVMTU) { | |
585 | ifp->if_ierrors++; | |
586 | log(LOG_ERR, "dmvxint: dmv%d bad rcv pkt addr 0x%x len 0x%x\n", | |
587 | unit, pkaddr, len); | |
588 | goto setup; | |
589 | } | |
590 | /* | |
591 | * Deal with trailer protocol: if type is trailer | |
592 | * get true type from first 16-bit word past data. | |
593 | * Remember that type was trailer by setting off. | |
594 | */ | |
595 | dh = (struct dmv_header *)ifrw->ifrw_addr; | |
596 | dh->dmv_type = ntohs((u_short)dh->dmv_type); | |
597 | #define dmvdataaddr(dh, off, type) ((type)(((caddr_t)((dh)+1)+(off)))) | |
598 | if (dh->dmv_type >= DMV_TRAILER && | |
599 | dh->dmv_type < DMV_TRAILER+DMV_NTRAILER) { | |
600 | off = (dh->dmv_type - DMV_TRAILER) * 512; | |
601 | if (off >= DMVMTU) | |
602 | goto setup; /* sanity */ | |
603 | dh->dmv_type = ntohs(*dmvdataaddr(dh, off, u_short *)); | |
604 | resid = ntohs(*(dmvdataaddr(dh, off+2, u_short *))); | |
605 | if (off + resid > len) | |
606 | goto setup; /* sanity */ | |
607 | len = off + resid; | |
608 | } else | |
609 | off = 0; | |
610 | if (len == 0) | |
611 | goto setup; | |
612 | ||
613 | /* | |
614 | * Pull packet off interface. Off is nonzero if | |
615 | * packet has trailing header; dmv_get will then | |
616 | * force this header information to be at the front, | |
617 | * but we still have to drop the type and length | |
618 | * which are at the front of any trailer data. | |
619 | */ | |
620 | m = if_ubaget(&sc->sc_ifuba, ifrw, len, off, ifp); | |
621 | if (m == 0) | |
622 | goto setup; | |
623 | if (off) { | |
624 | ifp = *(mtod(m, struct ifnet **)); | |
625 | m->m_off += 2 * sizeof (u_short); | |
626 | m->m_len -= 2 * sizeof (u_short); | |
627 | *(mtod(m, struct ifnet **)) = ifp; | |
628 | } | |
629 | switch (dh->dmv_type) { | |
630 | #ifdef INET | |
631 | case DMV_IPTYPE: | |
632 | schednetisr(NETISR_IP); | |
633 | inq = &ipintrq; | |
634 | break; | |
635 | #endif | |
636 | default: | |
637 | m_freem(m); | |
638 | goto setup; | |
639 | } | |
640 | ||
641 | s = splimp(); | |
642 | if (IF_QFULL(inq)) { | |
643 | IF_DROP(inq); | |
644 | m_freem(m); | |
645 | } else | |
646 | IF_ENQUEUE(inq, m); | |
647 | splx(s); | |
648 | setup: | |
649 | /* is this needed? */ | |
650 | rp->ubinfo = ifrw->ifrw_info & 0x3ffff; | |
651 | dmvload( | |
652 | sc, | |
653 | DMV_BACCR, | |
654 | QP_SEL4|QP_SEL6|QP_SEL10, | |
655 | 0, | |
656 | rp->ubinfo, | |
657 | (rp->ubinfo>>16)&0x3f, | |
658 | rp->cc | |
659 | ); | |
660 | break; | |
661 | case DMV_BDXSA: | |
662 | /* | |
663 | * A write has completed, start another | |
664 | * transfer if there is more data to send. | |
665 | */ | |
666 | ifp->if_opackets++; | |
667 | /* find associated dmvbuf structure */ | |
668 | ifxp = &sc->sc_ifw[0]; | |
669 | for (rp = &sc->sc_xbufs[0]; rp < &sc->sc_xbufs[NXMT]; rp++) { | |
670 | if(rp->ubinfo == pkaddr) | |
671 | break; | |
672 | ifxp++; | |
673 | } | |
674 | if (rp >= &sc->sc_xbufs[NXMT]) { | |
675 | log(LOG_ERR, "dmv%d: bad packet address 0x%x\n", | |
676 | unit, pkaddr); | |
677 | break; | |
678 | } | |
679 | if ((rp->flags & DBUF_DMVS) == 0) | |
680 | log(LOG_ERR, "dmvxint: dmv%d unallocated packet 0x%x\n", | |
681 | unit, pkaddr); | |
682 | /* mark buffer free */ | |
683 | if (ifxp->ifw_xtofree) { | |
684 | (void)m_freem(ifxp->ifw_xtofree); | |
685 | ifxp->ifw_xtofree = 0; | |
686 | } | |
687 | rp->flags &= ~DBUF_DMVS; | |
bb08cf86 MK |
688 | if (--sc->sc_oused == 0) |
689 | sc->sc_if.if_timer = 0; | |
690 | else | |
691 | sc->sc_if.if_timer = dmv_timeout; | |
692 | if ((sc->sc_flag & DMV_ONLINE) == 0) { | |
693 | extern int ifqmaxlen; | |
694 | ||
695 | /* | |
696 | * We're on the air. | |
697 | * Open the queue to the usual value. | |
698 | */ | |
699 | sc->sc_flag |= DMV_ONLINE; | |
700 | ifp->if_snd.ifq_maxlen = ifqmaxlen; | |
701 | } | |
ecfa166d MK |
702 | break; |
703 | ||
704 | case DMV_CNTRLO: | |
705 | /* ACCUMULATE STATISTICS */ | |
ecfa166d MK |
706 | switch(sel6&DMV_EEC) { |
707 | case DMV_ORUN: | |
708 | if(sc->sc_flag & DMV_RESTART) { | |
709 | load_rec_bufs(sc); | |
710 | sc->sc_flag &= ~DMV_RESTART; | |
711 | log(LOG_INFO, | |
bb08cf86 | 712 | "dmv%d: far end on-line\n", unit); |
ecfa166d MK |
713 | } else { |
714 | log(LOG_WARNING, | |
bb08cf86 MK |
715 | "dmv%d: far end restart\n", unit); |
716 | goto restart; | |
ecfa166d MK |
717 | } |
718 | break; | |
719 | case DMV_RTE: | |
720 | ifp->if_ierrors++; | |
ecfa166d | 721 | if ((sc->sc_rte++ % DMV_RPRTE) == 0) |
d9bcdfcc | 722 | log(LOG_WARNING, |
bb08cf86 | 723 | "dmv%d: receive threshold error\n", |
d9bcdfcc | 724 | unit); |
ecfa166d MK |
725 | break; |
726 | case DMV_TTE: | |
727 | ifp->if_oerrors++; | |
ecfa166d | 728 | if ((sc->sc_xte++ % DMV_RPTTE) == 0) |
d9bcdfcc | 729 | log(LOG_WARNING, |
bb08cf86 | 730 | "dmv%d: transmit threshold error\n", |
d9bcdfcc | 731 | unit); |
ecfa166d MK |
732 | break; |
733 | case DMV_STE: | |
ecfa166d | 734 | if ((sc->sc_ste++ % DMV_RPSTE) == 0) |
d9bcdfcc | 735 | log(LOG_WARNING, |
bb08cf86 | 736 | "dmv%d: select threshold error\n", |
d9bcdfcc | 737 | unit); |
ecfa166d MK |
738 | break; |
739 | case DMV_NXM: | |
d9bcdfcc MK |
740 | if ((sc->sc_nxm++ % DMV_RPNXM) == 0) |
741 | log(LOG_WARNING, | |
bb08cf86 | 742 | "dmv%d: nonexistent memory error\n", |
d9bcdfcc | 743 | unit); |
ecfa166d MK |
744 | break; |
745 | case DMV_MODD: | |
bb08cf86 | 746 | if ((sc->sc_modd++ % DMV_RPMODD) == 0) { |
d9bcdfcc | 747 | log(LOG_WARNING, |
bb08cf86 | 748 | "dmv%d: modem disconnected error\n", |
d9bcdfcc | 749 | unit); |
bb08cf86 MK |
750 | goto restart; |
751 | } | |
ecfa166d MK |
752 | break; |
753 | case DMV_CXRL: | |
ecfa166d | 754 | if ((sc->sc_cxrl++ % DMV_RPCXRL) == 0) |
d9bcdfcc | 755 | log(LOG_WARNING, |
bb08cf86 | 756 | "dmv%d: carrier loss error\n", |
d9bcdfcc | 757 | unit); |
ecfa166d MK |
758 | break; |
759 | case DMV_QOVF: | |
760 | log(LOG_WARNING, | |
bb08cf86 MK |
761 | "dmv%d: response queue overflow\n", |
762 | unit); | |
ecfa166d | 763 | sc->sc_qovf++; |
bb08cf86 | 764 | goto restart; |
ecfa166d MK |
765 | |
766 | default: | |
767 | log(LOG_WARNING, | |
bb08cf86 MK |
768 | "dmv%d: unknown error %o\n", |
769 | unit, sel6&DMV_EEC); | |
ecfa166d | 770 | if ((sc->sc_unknown++ % DMV_RPUNKNOWN) == 0) |
bb08cf86 | 771 | goto restart; |
ecfa166d MK |
772 | break; |
773 | } | |
774 | break; | |
775 | ||
776 | case DMV_BDRUNUS: | |
777 | case DMV_BDXSN: | |
778 | case DMV_BDXNS: | |
779 | log(LOG_INFO, | |
bb08cf86 | 780 | "dmv%d: buffer disp for halted trib %o\n", |
ecfa166d MK |
781 | unit, sel2&0x7 |
782 | ); | |
783 | break; | |
784 | ||
785 | case DMV_MDEFO: | |
786 | if((sel6&0x1f) == 020) { | |
787 | log(LOG_INFO, | |
bb08cf86 | 788 | "dmv%d: buffer return complete sel3=%x\n", |
ecfa166d MK |
789 | unit, sel3); |
790 | } else { | |
791 | log(LOG_INFO, | |
bb08cf86 | 792 | "dmv%d: info resp sel3=%x sel4=%x sel6=%x\n", |
ecfa166d MK |
793 | unit, sel3, sel4, sel6 |
794 | ); | |
795 | } | |
796 | break; | |
797 | ||
798 | default: | |
bb08cf86 | 799 | log(LOG_WARNING, "dmv%d: bad control %o\n", |
ecfa166d MK |
800 | unit, sel2&0x7 |
801 | ); | |
802 | break; | |
803 | } | |
804 | } | |
805 | dmvstart(unit); | |
806 | return; | |
bb08cf86 | 807 | restart: |
ecfa166d MK |
808 | dmvrestart(unit); |
809 | } | |
bb08cf86 | 810 | |
ecfa166d MK |
811 | load_rec_bufs(sc) |
812 | register struct dmv_softc *sc; | |
813 | { | |
814 | register struct dmvbufs *rp; | |
815 | ||
816 | /* queue first NRCV buffers for DMV to fill */ | |
817 | for (rp = &sc->sc_rbufs[0]; rp < &sc->sc_rbufs[NRCV]; rp++) { | |
818 | rp->flags |= DBUF_DMVS; | |
819 | dmvload( | |
820 | sc, | |
821 | DMV_BACCR, | |
822 | QP_TRIB|QP_SEL4|QP_SEL6|QP_SEL10, | |
823 | 1, | |
824 | rp->ubinfo, | |
825 | (rp->ubinfo>>16)&0x3f, | |
826 | rp->cc | |
827 | ); | |
828 | sc->sc_iused++; | |
829 | } | |
830 | } | |
831 | ||
832 | /* | |
833 | * DMV output routine. | |
834 | * Encapsulate a packet of type family for the dmv. | |
835 | * Use trailer local net encapsulation if enough data in first | |
836 | * packet leaves a multiple of 512 bytes of data in remainder. | |
837 | */ | |
838 | dmvoutput(ifp, m0, dst) | |
839 | register struct ifnet *ifp; | |
840 | register struct mbuf *m0; | |
841 | struct sockaddr *dst; | |
842 | { | |
843 | int type, error, s; | |
844 | register struct mbuf *m = m0; | |
845 | register struct dmv_header *dh; | |
846 | register int off; | |
847 | ||
bb08cf86 MK |
848 | if ((ifp->if_flags & IFF_UP) == 0) { |
849 | error = ENETDOWN; | |
850 | goto bad; | |
851 | } | |
852 | ||
ecfa166d MK |
853 | switch (dst->sa_family) { |
854 | #ifdef INET | |
855 | case AF_INET: | |
856 | off = ntohs((u_short)mtod(m, struct ip *)->ip_len) - m->m_len; | |
857 | if ((ifp->if_flags & IFF_NOTRAILERS) == 0) | |
858 | if (off > 0 && (off & 0x1ff) == 0 && | |
859 | m->m_off >= MMINOFF + 2 * sizeof (u_short)) { | |
860 | type = DMV_TRAILER + (off>>9); | |
861 | m->m_off -= 2 * sizeof (u_short); | |
862 | m->m_len += 2 * sizeof (u_short); | |
863 | *mtod(m, u_short *) = htons((u_short)DMV_IPTYPE); | |
864 | *(mtod(m, u_short *) + 1) = htons((u_short)m->m_len); | |
865 | goto gottrailertype; | |
866 | } | |
867 | type = DMV_IPTYPE; | |
868 | off = 0; | |
869 | goto gottype; | |
870 | #endif | |
871 | ||
872 | case AF_UNSPEC: | |
873 | dh = (struct dmv_header *)dst->sa_data; | |
874 | type = dh->dmv_type; | |
875 | goto gottype; | |
876 | ||
877 | default: | |
bb08cf86 MK |
878 | log(LOG_ERR, "dmvoutput, dmv%d can't handle af%d\n", |
879 | ifp->if_unit, dst->sa_family); | |
ecfa166d MK |
880 | error = EAFNOSUPPORT; |
881 | goto bad; | |
882 | } | |
883 | ||
884 | gottrailertype: | |
885 | /* | |
886 | * Packet to be sent as a trailer; move first packet | |
887 | * (control information) to end of chain. | |
888 | */ | |
889 | while (m->m_next) | |
890 | m = m->m_next; | |
891 | m->m_next = m0; | |
892 | m = m0->m_next; | |
893 | m0->m_next = 0; | |
894 | m0 = m; | |
895 | ||
896 | gottype: | |
897 | /* | |
898 | * Add local network header | |
899 | * (there is space for a uba on a vax to step on) | |
900 | */ | |
901 | if (m->m_off > MMAXOFF || | |
902 | MMINOFF + sizeof(struct dmv_header) > m->m_off) { | |
903 | m = m_get(M_DONTWAIT, MT_HEADER); | |
904 | if (m == 0) { | |
905 | error = ENOBUFS; | |
906 | goto bad; | |
907 | } | |
908 | m->m_next = m0; | |
909 | m->m_off = MMINOFF; | |
910 | m->m_len = sizeof (struct dmv_header); | |
911 | } else { | |
912 | m->m_off -= sizeof (struct dmv_header); | |
913 | m->m_len += sizeof (struct dmv_header); | |
914 | } | |
915 | dh = mtod(m, struct dmv_header *); | |
916 | dh->dmv_type = htons((u_short)type); | |
917 | ||
918 | /* | |
919 | * Queue message on interface, and start output if interface | |
920 | * not yet active. | |
921 | */ | |
922 | s = splimp(); | |
923 | if (IF_QFULL(&ifp->if_snd)) { | |
924 | IF_DROP(&ifp->if_snd); | |
925 | m_freem(m); | |
926 | splx(s); | |
927 | return (ENOBUFS); | |
928 | } | |
929 | IF_ENQUEUE(&ifp->if_snd, m); | |
930 | dmvstart(ifp->if_unit); | |
931 | splx(s); | |
932 | return (0); | |
933 | ||
934 | bad: | |
935 | m_freem(m0); | |
936 | return (error); | |
937 | } | |
938 | ||
939 | ||
940 | /* | |
941 | * Process an ioctl request. | |
942 | */ | |
943 | /* ARGSUSED */ | |
944 | dmvioctl(ifp, cmd, data) | |
945 | register struct ifnet *ifp; | |
946 | int cmd; | |
947 | caddr_t data; | |
948 | { | |
949 | int s = splimp(), error = 0; | |
950 | struct mbuf *m; | |
951 | register struct dmv_softc *sc = &dmv_softc[ifp->if_unit]; | |
952 | ||
953 | switch (cmd) { | |
954 | ||
955 | case SIOCSIFADDR: | |
956 | ifp->if_flags |= IFF_UP; | |
957 | if ((ifp->if_flags & IFF_RUNNING) == 0) | |
958 | dmvinit(ifp->if_unit); | |
959 | break; | |
960 | ||
961 | case SIOCSIFDSTADDR: | |
962 | if ((ifp->if_flags & IFF_RUNNING) == 0) | |
963 | dmvinit(ifp->if_unit); | |
964 | break; | |
965 | ||
966 | case SIOCSIFFLAGS: | |
967 | if ((ifp->if_flags & IFF_UP) == 0 && | |
bb08cf86 MK |
968 | sc->sc_flag & DMV_RUNNING) |
969 | dmvdown(ifp->if_unit); | |
970 | else if (ifp->if_flags & IFF_UP && | |
ecfa166d MK |
971 | (sc->sc_flag & DMV_RUNNING) == 0) |
972 | dmvrestart(ifp->if_unit); | |
973 | break; | |
974 | ||
975 | default: | |
976 | error = EINVAL; | |
977 | } | |
978 | splx(s); | |
979 | return (error); | |
980 | } | |
981 | ||
982 | /* | |
983 | * Restart after a fatal error. | |
984 | * Clear device and reinitialize. | |
985 | */ | |
986 | dmvrestart(unit) | |
987 | int unit; | |
988 | { | |
ecfa166d | 989 | register struct dmvdevice *addr; |
ecfa166d | 990 | register int i; |
bb08cf86 MK |
991 | |
992 | dmvdown(unit); | |
993 | ||
994 | addr = (struct dmvdevice *)(dmvinfo[unit]->ui_addr); | |
ecfa166d | 995 | /* |
bb08cf86 | 996 | * Let the DMV finish the MCLR. |
ecfa166d | 997 | */ |
ecfa166d MK |
998 | for (i = 100000; i && (addr->bsel1 & DMV_RUN) == 0; i--) |
999 | ; | |
1000 | if ((addr->bsel1 & DMV_RUN) == 0) { | |
1001 | log(LOG_ERR, "dmvrestart: can't start device\n" ); | |
1002 | return (0); | |
1003 | } | |
1004 | if ((addr->bsel4 != 033) || (addr->bsel6 != 0305)) | |
1005 | { | |
bb08cf86 MK |
1006 | log(LOG_ERR, "dmv%d: device init failed, bsel4=%o, bsel6=%o\n", |
1007 | unit, addr->bsel4, addr->bsel6); | |
ecfa166d MK |
1008 | return (0); |
1009 | } | |
bb08cf86 MK |
1010 | |
1011 | /* restart DMV */ | |
1012 | dmvinit(unit); | |
1013 | dmv_softc[unit].sc_if.if_collisions++; /* why not? */ | |
1014 | } | |
1015 | ||
1016 | /* | |
1017 | * Reset a device and mark down. | |
1018 | * Flush output queue and drop queue limit. | |
1019 | */ | |
1020 | dmvdown(unit) | |
1021 | int unit; | |
1022 | { | |
1023 | struct dmv_softc *sc = &dmv_softc[unit]; | |
1024 | register struct ifxmt *ifxp; | |
1025 | ||
1026 | ((struct dmvdevice *)(dmvinfo[unit]->ui_addr))->bsel1 = DMV_MCLR; | |
1027 | sc->sc_flag &= ~(DMV_RUNNING | DMV_ONLINE); | |
1028 | ||
ecfa166d MK |
1029 | for (ifxp = sc->sc_ifw; ifxp < &sc->sc_ifw[NXMT]; ifxp++) { |
1030 | if (ifxp->ifw_xtofree) { | |
1031 | (void) m_freem(ifxp->ifw_xtofree); | |
1032 | ifxp->ifw_xtofree = 0; | |
1033 | } | |
1034 | } | |
bb08cf86 MK |
1035 | sc->sc_oused = 0; |
1036 | if_qflush(&sc->sc_if.if_snd); | |
1037 | ||
1038 | /* | |
1039 | * Limit packets enqueued until we're back on the air. | |
1040 | */ | |
1041 | sc->sc_if.if_snd.ifq_maxlen = 3; | |
ecfa166d MK |
1042 | } |
1043 | ||
1044 | /* | |
bb08cf86 MK |
1045 | * Watchdog timeout to see that transmitted packets don't |
1046 | * lose interrupts. The device has to be online. | |
ecfa166d | 1047 | */ |
bb08cf86 MK |
1048 | dmvtimeout(unit) |
1049 | int unit; | |
ecfa166d | 1050 | { |
ecfa166d MK |
1051 | register struct dmv_softc *sc; |
1052 | struct dmvdevice *addr; | |
ecfa166d | 1053 | |
bb08cf86 MK |
1054 | sc = &dmv_softc[unit]; |
1055 | if (sc->sc_flag & DMV_ONLINE) { | |
1056 | addr = (struct dmvdevice *)(dmvinfo[unit]->ui_addr); | |
1057 | log(LOG_ERR, "dmv%d: output timeout, bsel0=%b bsel2=%b\n", | |
1058 | unit, addr->bsel0 & 0xff, DMV0BITS, | |
1059 | addr->bsel2 & 0xff, DMV2BITS); | |
1060 | dmvrestart(unit); | |
ecfa166d | 1061 | } |
ecfa166d MK |
1062 | } |
1063 | #endif |