]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - 6/sys/dev/ed/if_ed_hpp.c
Clone Kip's Xen on stable/6 tree so that I can work on improving FreeBSD/amd64
[FreeBSD/FreeBSD.git] / 6 / sys / dev / ed / if_ed_hpp.c
1 /*-
2  * Copyright (c) 2005, M. Warner Losh
3  * All rights reserved.
4  * Copyright (c) 1995, David Greenman 
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice unmodified, this list of conditions, and the following
12  *    disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29
30 #include <sys/cdefs.h>
31 __FBSDID("$FreeBSD$");
32
33 #include "opt_ed.h"
34
35 #ifdef ED_HPP
36
37 #include <sys/param.h>
38 #include <sys/systm.h>
39 #include <sys/sockio.h>
40 #include <sys/mbuf.h>
41 #include <sys/kernel.h>
42 #include <sys/socket.h>
43 #include <sys/syslog.h>
44
45 #include <sys/bus.h>
46
47 #include <machine/bus.h>
48 #include <sys/rman.h>
49 #include <machine/resource.h>
50
51 #include <net/ethernet.h>
52 #include <net/if.h>
53 #include <net/if_arp.h>
54 #include <net/if_dl.h>
55 #include <net/if_mib.h>
56 #include <net/if_media.h>
57
58 #include <net/bpf.h>
59
60 #include <dev/ed/if_edreg.h>
61 #include <dev/ed/if_edvar.h>
62
63 static void     ed_hpp_writemem(struct ed_softc *, uint8_t *, uint16_t,
64                     uint16_t);
65
66 /*
67  * Interrupt conversion table for the HP PC LAN+
68  */
69 static uint16_t ed_hpp_intr_val[] = {
70         0,              /* 0 */
71         0,              /* 1 */
72         0,              /* 2 */
73         3,              /* 3 */
74         4,              /* 4 */
75         5,              /* 5 */
76         6,              /* 6 */
77         7,              /* 7 */
78         0,              /* 8 */
79         9,              /* 9 */
80         10,             /* 10 */
81         11,             /* 11 */
82         12,             /* 12 */
83         0,              /* 13 */
84         0,              /* 14 */
85         15              /* 15 */
86 };
87
88 #define ED_HPP_TEST_SIZE        16
89
90 /*
91  * Probe and vendor specific initialization for the HP PC Lan+ Cards.
92  * (HP Part nos: 27247B and 27252A).
93  *
94  * The card has an asic wrapper around a DS8390 core.  The asic handles 
95  * host accesses and offers both standard register IO and memory mapped 
96  * IO.  Memory mapped I/O allows better performance at the expense of greater
97  * chance of an incompatibility with existing ISA cards.
98  *
99  * The card has a few caveats: it isn't tolerant of byte wide accesses, only
100  * short (16 bit) or word (32 bit) accesses are allowed.  Some card revisions
101  * don't allow 32 bit accesses; these are indicated by a bit in the software
102  * ID register (see if_edreg.h).
103  * 
104  * Other caveats are: we should read the MAC address only when the card
105  * is inactive.
106  *
107  * For more information; please consult the CRYNWR packet driver.
108  *
109  * The AUI port is turned on using the "link2" option on the ifconfig 
110  * command line.
111  */
112 int
113 ed_probe_HP_pclanp(device_t dev, int port_rid, int flags)
114 {
115         struct ed_softc *sc = device_get_softc(dev);
116         int error;
117         int n;                          /* temp var */
118         int memsize;                    /* mem on board */
119         u_char checksum;                /* checksum of board address */
120         u_char irq;                     /* board configured IRQ */
121         uint8_t test_pattern[ED_HPP_TEST_SIZE]; /* read/write areas for */
122         uint8_t test_buffer[ED_HPP_TEST_SIZE];  /* probing card */
123         u_long conf_maddr, conf_msize, conf_irq, junk;
124
125         error = ed_alloc_port(dev, 0, ED_HPP_IO_PORTS);
126         if (error)
127                 return (error);
128
129         /* Fill in basic information */
130         sc->asic_offset = ED_HPP_ASIC_OFFSET;
131         sc->nic_offset  = ED_HPP_NIC_OFFSET;
132
133         sc->chip_type = ED_CHIP_TYPE_DP8390;
134         sc->isa16bit = 0;       /* the 8390 core needs to be in byte mode */
135
136         /* 
137          * Look for the HP PCLAN+ signature: "0x50,0x48,0x00,0x53" 
138          */
139         
140         if ((ed_asic_inb(sc, ED_HPP_ID) != 0x50) || 
141             (ed_asic_inb(sc, ED_HPP_ID + 1) != 0x48) ||
142             ((ed_asic_inb(sc, ED_HPP_ID + 2) & 0xF0) != 0) ||
143             (ed_asic_inb(sc, ED_HPP_ID + 3) != 0x53))
144                 return ENXIO;
145
146         /* 
147          * Read the MAC address and verify checksum on the address.
148          */
149
150         ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_MAC);
151         for (n  = 0, checksum = 0; n < ETHER_ADDR_LEN; n++)
152                 checksum += (sc->enaddr[n] = 
153                     ed_asic_inb(sc, ED_HPP_MAC_ADDR + n));
154         
155         checksum += ed_asic_inb(sc, ED_HPP_MAC_ADDR + ETHER_ADDR_LEN);
156
157         if (checksum != 0xFF)
158                 return ENXIO;
159
160         /*
161          * Verify that the software model number is 0.
162          */
163         
164         ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_ID);
165         if (((sc->hpp_id = ed_asic_inw(sc, ED_HPP_PAGE_4)) & 
166                 ED_HPP_ID_SOFT_MODEL_MASK) != 0x0000)
167                 return ENXIO;
168
169         /*
170          * Read in and save the current options configured on card.
171          */
172
173         sc->hpp_options = ed_asic_inw(sc, ED_HPP_OPTION);
174
175         sc->hpp_options |= (ED_HPP_OPTION_NIC_RESET | 
176             ED_HPP_OPTION_CHIP_RESET | ED_HPP_OPTION_ENABLE_IRQ);
177
178         /* 
179          * Reset the chip.  This requires writing to the option register
180          * so take care to preserve the other bits.
181          */
182
183         ed_asic_outw(sc, ED_HPP_OPTION, 
184             (sc->hpp_options & ~(ED_HPP_OPTION_NIC_RESET | 
185             ED_HPP_OPTION_CHIP_RESET)));
186
187         DELAY(5000);    /* wait for chip reset to complete */
188
189         ed_asic_outw(sc, ED_HPP_OPTION,
190             (sc->hpp_options | (ED_HPP_OPTION_NIC_RESET |
191             ED_HPP_OPTION_CHIP_RESET |
192             ED_HPP_OPTION_ENABLE_IRQ)));
193
194         DELAY(5000);
195
196         if (!(ed_nic_inb(sc, ED_P0_ISR) & ED_ISR_RST))
197                 return ENXIO;   /* reset did not complete */
198
199         /*
200          * Read out configuration information.
201          */
202
203         ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_HW);
204
205         irq = ed_asic_inb(sc, ED_HPP_HW_IRQ);
206
207         /*
208          * Check for impossible IRQ.
209          */
210
211         if (irq >= (sizeof(ed_hpp_intr_val) / sizeof(ed_hpp_intr_val[0])))
212                 return ENXIO;
213
214         /* 
215          * If the kernel IRQ was specified with a '?' use the cards idea
216          * of the IRQ.  If the kernel IRQ was explicitly specified, it
217          * should match that of the hardware.
218          */
219         error = bus_get_resource(dev, SYS_RES_IRQ, 0, &conf_irq, &junk);
220         if (error)
221                 bus_set_resource(dev, SYS_RES_IRQ, 0, ed_hpp_intr_val[irq], 1);
222         else {
223                 if (conf_irq != ed_hpp_intr_val[irq])
224                         return (ENXIO);
225         }
226
227         /*
228          * Fill in softconfig info.
229          */
230
231         sc->vendor = ED_VENDOR_HP;
232         sc->type = ED_TYPE_HP_PCLANPLUS;
233         sc->type_str = "HP-PCLAN+";
234
235         sc->mem_shared = 0;     /* we DON'T have dual ported RAM */
236         sc->mem_start = 0;      /* we use offsets inside the card RAM */
237
238         sc->hpp_mem_start = NULL;/* no memory mapped I/O by default */
239
240         /*
241          * The board has 32KB of memory.  Is there a way to determine
242          * this programmatically?
243          */
244         
245         memsize = 32768;
246
247         /*
248          * Check if memory mapping of the I/O registers possible.
249          */
250         if (sc->hpp_options & ED_HPP_OPTION_MEM_ENABLE) {
251                 u_long mem_addr;
252
253                 /*
254                  * determine the memory address from the board.
255                  */
256                 
257                 ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_HW);
258                 mem_addr = (ed_asic_inw(sc, ED_HPP_HW_MEM_MAP) << 8);
259
260                 /*
261                  * Check that the kernel specified start of memory and
262                  * hardware's idea of it match.
263                  */
264                 error = bus_get_resource(dev, SYS_RES_MEMORY, 0,
265                                          &conf_maddr, &conf_msize);
266                 if (error)
267                         return (error);
268                 
269                 if (mem_addr != conf_maddr)
270                         return ENXIO;
271
272                 error = ed_alloc_memory(dev, 0, memsize);
273                 if (error)
274                         return (error);
275
276                 sc->hpp_mem_start = rman_get_virtual(sc->mem_res);
277         }
278
279         /*
280          * Fill in the rest of the soft config structure.
281          */
282
283         /*
284          * The transmit page index.
285          */
286
287         sc->tx_page_start = ED_HPP_TX_PAGE_OFFSET;
288
289         if (device_get_flags(dev) & ED_FLAGS_NO_MULTI_BUFFERING)
290                 sc->txb_cnt = 1;
291         else
292                 sc->txb_cnt = 2;
293
294         /*
295          * Memory description
296          */
297
298         sc->mem_size = memsize;
299         sc->mem_ring = sc->mem_start + 
300                 (sc->txb_cnt * ED_PAGE_SIZE * ED_TXBUF_SIZE);
301         sc->mem_end = sc->mem_start + sc->mem_size;
302
303         /*
304          * Receive area starts after the transmit area and 
305          * continues till the end of memory.
306          */
307
308         sc->rec_page_start = sc->tx_page_start + 
309                                 (sc->txb_cnt * ED_TXBUF_SIZE);
310         sc->rec_page_stop = (sc->mem_size / ED_PAGE_SIZE);
311
312
313         sc->cr_proto = 0;       /* value works */
314
315         /*
316          * Set the wrap registers for string I/O reads.
317          */
318
319         ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_HW);
320         ed_asic_outw(sc, ED_HPP_HW_WRAP,
321             ((sc->rec_page_start / ED_PAGE_SIZE) |
322             (((sc->rec_page_stop / ED_PAGE_SIZE) - 1) << 8)));
323
324         /*
325          * Reset the register page to normal operation.
326          */
327
328         ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_PERF);
329
330         /*
331          * Verify that we can read/write from adapter memory.
332          * Create test pattern.
333          */
334
335         for (n = 0; n < ED_HPP_TEST_SIZE; n++)
336                 test_pattern[n] = (n*n) ^ ~n;
337
338 #undef  ED_HPP_TEST_SIZE
339
340         /*
341          * Check that the memory is accessible thru the I/O ports.
342          * Write out the contents of "test_pattern", read back
343          * into "test_buffer" and compare the two for any
344          * mismatch.
345          */
346
347         for (n = 0; n < (32768 / ED_PAGE_SIZE); n ++) {
348                 ed_hpp_writemem(sc, test_pattern, (n * ED_PAGE_SIZE), 
349                                 sizeof(test_pattern));
350                 ed_hpp_readmem(sc, (n * ED_PAGE_SIZE), 
351                         test_buffer, sizeof(test_pattern));
352
353                 if (bcmp(test_pattern, test_buffer, 
354                         sizeof(test_pattern)))
355                         return ENXIO;
356         }
357
358         return (0);
359 }
360
361 /*
362  * HP PC Lan+ : Set the physical link to use AUI or TP/TL.
363  */
364
365 void
366 ed_hpp_set_physical_link(struct ed_softc *sc)
367 {
368         struct ifnet *ifp = sc->ifp;
369         int lan_page;
370
371         ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_LAN);
372         lan_page = ed_asic_inw(sc, ED_HPP_PAGE_0);
373
374         if (ifp->if_flags & IFF_ALTPHYS) {
375                 /*
376                  * Use the AUI port.
377                  */
378
379                 lan_page |= ED_HPP_LAN_AUI;
380                 ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_LAN);
381                 ed_asic_outw(sc, ED_HPP_PAGE_0, lan_page);
382         } else {
383                 /*
384                  * Use the ThinLan interface
385                  */
386
387                 lan_page &= ~ED_HPP_LAN_AUI;
388                 ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_LAN);
389                 ed_asic_outw(sc, ED_HPP_PAGE_0, lan_page);
390         }
391
392         /*
393          * Wait for the lan card to re-initialize itself
394          */
395         DELAY(150000);  /* wait 150 ms */
396
397         /*
398          * Restore normal pages.
399          */
400         ed_asic_outw(sc, ED_HPP_PAGING, ED_HPP_PAGE_PERF);
401 }
402
403 /*
404  * Support routines to handle the HP PC Lan+ card.
405  */
406
407 /*
408  * HP PC Lan+: Read from NIC memory, using either PIO or memory mapped
409  * IO.
410  */
411
412 void
413 ed_hpp_readmem(struct ed_softc *sc, bus_size_t src, uint8_t *dst,
414     uint16_t amount)
415 {
416         int use_32bit_access = !(sc->hpp_id & ED_HPP_ID_16_BIT_ACCESS);
417
418         /* Program the source address in RAM */
419         ed_asic_outw(sc, ED_HPP_PAGE_2, src);
420
421         /*
422          * The HP PC Lan+ card supports word reads as well as
423          * a memory mapped i/o port that is aliased to every 
424          * even address on the board.
425          */
426         if (sc->hpp_mem_start) {
427                 /* Enable memory mapped access.  */
428                 ed_asic_outw(sc, ED_HPP_OPTION, sc->hpp_options & 
429                         ~(ED_HPP_OPTION_MEM_DISABLE | 
430                           ED_HPP_OPTION_BOOT_ROM_ENB));
431
432                 if (use_32bit_access && (amount > 3)) {
433                         uint32_t *dl = (uint32_t *) dst;        
434                         volatile uint32_t *const sl = 
435                                 (uint32_t *) sc->hpp_mem_start;
436                         uint32_t *const fence = dl + (amount >> 2);
437                         
438                         /*
439                          * Copy out NIC data.  We could probably write this
440                          * as a `movsl'. The currently generated code is lousy.
441                          */
442                         while (dl < fence)
443                                 *dl++ = *sl;
444                 
445                         dst += (amount & ~3);
446                         amount &= 3;
447
448                 } 
449
450                 /* Finish off any words left, as a series of short reads */
451                 if (amount > 1) {
452                         u_short *d = (u_short *) dst;   
453                         volatile u_short *const s = 
454                                 (u_short *) sc->hpp_mem_start;
455                         u_short *const fence = d + (amount >> 1);
456                         
457                         /* Copy out NIC data.  */
458                         while (d < fence)
459                                 *d++ = *s;
460         
461                         dst += (amount & ~1);
462                         amount &= 1;
463                 }
464
465                 /*
466                  * read in a byte; however we need to always read 16 bits
467                  * at a time or the hardware gets into a funny state
468                  */
469
470                 if (amount == 1) {
471                         /* need to read in a short and copy LSB */
472                         volatile u_short *const s = 
473                                 (volatile u_short *) sc->hpp_mem_start;
474                         *dst = (*s) & 0xFF;     
475                 }
476
477                 /* Restore Boot ROM access.  */
478                 ed_asic_outw(sc, ED_HPP_OPTION, sc->hpp_options);
479         } else { 
480                 /* Read in data using the I/O port */
481                 if (use_32bit_access && (amount > 3)) {
482                         ed_asic_insl(sc, ED_HPP_PAGE_4, dst, amount >> 2);
483                         dst += (amount & ~3);
484                         amount &= 3;
485                 }
486                 if (amount > 1) {
487                         ed_asic_insw(sc, ED_HPP_PAGE_4, dst, amount >> 1);
488                         dst += (amount & ~1);
489                         amount &= 1;
490                 }
491                 if (amount == 1) { /* read in a short and keep the LSB */
492                         *dst = ed_asic_inw(sc, ED_HPP_PAGE_4) & 0xFF;
493                 }
494         }
495 }
496
497 /*
498  * HP PC Lan+: Write to NIC memory, using either PIO or memory mapped
499  * IO.
500  *      Only used in the probe routine to test the memory. 'len' must
501  *      be even.
502  */
503 static void
504 ed_hpp_writemem(struct ed_softc *sc, uint8_t *src, uint16_t dst, uint16_t len)
505 {
506         /* reset remote DMA complete flag */
507         ed_nic_outb(sc, ED_P0_ISR, ED_ISR_RDC);
508
509         /* program the write address in RAM */
510         ed_asic_outw(sc, ED_HPP_PAGE_0, dst);
511
512         if (sc->hpp_mem_start) {
513                 u_short *s = (u_short *) src;
514                 volatile u_short *d = (u_short *) sc->hpp_mem_start;
515                 u_short *const fence = s + (len >> 1);
516
517                 /*
518                  * Enable memory mapped access.
519                  */
520                 ed_asic_outw(sc, ED_HPP_OPTION, sc->hpp_options & 
521                         ~(ED_HPP_OPTION_MEM_DISABLE | 
522                           ED_HPP_OPTION_BOOT_ROM_ENB));
523
524                 /*
525                  * Copy to NIC memory.
526                  */
527                 while (s < fence)
528                         *d = *s++;
529
530                 /*
531                  * Restore Boot ROM access.
532                  */
533                 ed_asic_outw(sc, ED_HPP_OPTION, sc->hpp_options);
534         } else {
535                 /* write data using I/O writes */
536                 ed_asic_outsw(sc, ED_HPP_PAGE_4, src, len / 2);
537         }
538 }
539
540 /*
541  * Write to HP PC Lan+ NIC memory.  Access to the NIC can be by using 
542  * outsw() or via the memory mapped interface to the same register.
543  * Writes have to be in word units; byte accesses won't work and may cause
544  * the NIC to behave weirdly. Long word accesses are permitted if the ASIC
545  * allows it.
546  */
547
548 u_short
549 ed_hpp_write_mbufs(struct ed_softc *sc, struct mbuf *m, int dst)
550 {
551         int len, wantbyte;
552         unsigned short total_len;
553         unsigned char savebyte[2];
554         volatile u_short * const d = 
555                 (volatile u_short *) sc->hpp_mem_start;
556         int use_32bit_accesses = !(sc->hpp_id & ED_HPP_ID_16_BIT_ACCESS);
557
558         /* select page 0 registers */
559         ed_nic_outb(sc, ED_P0_CR, sc->cr_proto | ED_CR_STA);
560
561         /* reset remote DMA complete flag */
562         ed_nic_outb(sc, ED_P0_ISR, ED_ISR_RDC);
563
564         /* program the write address in RAM */
565         ed_asic_outw(sc, ED_HPP_PAGE_0, dst);
566
567         if (sc->hpp_mem_start)  /* enable memory mapped I/O */
568                 ed_asic_outw(sc, ED_HPP_OPTION, sc->hpp_options & 
569                         ~(ED_HPP_OPTION_MEM_DISABLE |
570                         ED_HPP_OPTION_BOOT_ROM_ENB));
571
572         wantbyte = 0;
573         total_len = 0;
574
575         if (sc->hpp_mem_start) {        /* Memory mapped I/O port */
576                 while (m) {
577                         total_len += (len = m->m_len);
578                         if (len) {
579                                 caddr_t data = mtod(m, caddr_t);
580                                 /* finish the last word of the previous mbuf */
581                                 if (wantbyte) {
582                                         savebyte[1] = *data;
583                                         *d = *((u_short *) savebyte);
584                                         data++; len--; wantbyte = 0;
585                                 }
586                                 /* output contiguous words */
587                                 if ((len > 3) && (use_32bit_accesses)) {
588                                         volatile uint32_t *const dl = 
589                                                 (volatile uint32_t *) d;
590                                         uint32_t *sl = (uint32_t *) data;
591                                         uint32_t *fence = sl + (len >> 2);
592
593                                         while (sl < fence)
594                                                 *dl = *sl++;
595
596                                         data += (len & ~3);
597                                         len &= 3;
598                                 }
599                                 /* finish off remain 16 bit writes */
600                                 if (len > 1) {
601                                         u_short *s = (u_short *) data;
602                                         u_short *fence = s + (len >> 1);
603
604                                         while (s < fence)
605                                                 *d = *s++;
606
607                                         data += (len & ~1); 
608                                         len &= 1;
609                                 }
610                                 /* save last byte if needed */
611                                 if ((wantbyte = (len == 1)) != 0)
612                                         savebyte[0] = *data;
613                         }
614                         m = m->m_next;  /* to next mbuf */
615                 }
616                 if (wantbyte) /* write last byte */
617                         *d = *((u_short *) savebyte);
618         } else {
619                 /* use programmed I/O */
620                 while (m) {
621                         total_len += (len = m->m_len);
622                         if (len) {
623                                 caddr_t data = mtod(m, caddr_t);
624                                 /* finish the last word of the previous mbuf */
625                                 if (wantbyte) {
626                                         savebyte[1] = *data;
627                                         ed_asic_outw(sc, ED_HPP_PAGE_4,
628                                                      *((u_short *)savebyte));
629                                         data++; 
630                                         len--; 
631                                         wantbyte = 0;
632                                 }
633                                 /* output contiguous words */
634                                 if ((len > 3) && use_32bit_accesses) {
635                                         ed_asic_outsl(sc, ED_HPP_PAGE_4,
636                                                       data, len >> 2);
637                                         data += (len & ~3);
638                                         len &= 3;
639                                 }
640                                 /* finish off remaining 16 bit accesses */
641                                 if (len > 1) {
642                                         ed_asic_outsw(sc, ED_HPP_PAGE_4,
643                                                       data, len >> 1);
644                                         data += (len & ~1);
645                                         len &= 1;
646                                 }
647                                 if ((wantbyte = (len == 1)) != 0)
648                                         savebyte[0] = *data;
649
650                         } /* if len != 0 */
651                         m = m->m_next;
652                 }
653                 if (wantbyte) /* spit last byte */
654                         ed_asic_outw(sc, ED_HPP_PAGE_4, *(u_short *)savebyte);
655
656         }
657
658         if (sc->hpp_mem_start)  /* turn off memory mapped i/o */
659                 ed_asic_outw(sc, ED_HPP_OPTION, sc->hpp_options);
660
661         return (total_len);
662 }
663
664 #endif /* ED_HPP */