ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/cebix/BasiliskII/src/AmigaOS/ether_amiga.cpp
Revision: 1.8
Committed: 2001-07-13T15:39:22Z (23 years, 4 months ago) by cebix
Branch: MAIN
Changes since 1.7: +1 -1 lines
Log Message:
- updated the TECH document
- EtherReset() clears the UDP protocol list
- audio_oss_esd.cpp: AudioExit() calls close_audio()
- ether_unix.cpp: uses map<> for protocol handlers
- updated audio_dummy.cpp and ether_dummy.cpp

File Contents

# Content
1 /*
2 * ether_amiga.cpp - Ethernet device driver, AmigaOS specific stuff
3 *
4 * Basilisk II (C) 1997-2001 Christian Bauer
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20
21 #include <exec/types.h>
22 #include <exec/memory.h>
23 #include <exec/errors.h>
24 #include <dos/dos.h>
25 #include <dos/dosextens.h>
26 #include <dos/dostags.h>
27 #include <devices/sana2.h>
28 #include <proto/exec.h>
29 #include <proto/dos.h>
30
31 #include "sysdeps.h"
32 #include "cpu_emulation.h"
33 #include "main.h"
34 #include "prefs.h"
35 #include "user_strings.h"
36 #include "macos_util.h"
37 #include "ether.h"
38 #include "ether_defs.h"
39
40 #define DEBUG 0
41 #include "debug.h"
42
43 #define MONITOR 0
44
45
46 // These messages are sent to the network process
47 const uint32 MSG_CLEANUP = 'clea'; // Remove all protocols
48 const uint32 MSG_ADD_MULTI = 'addm'; // Add multicast address
49 const uint32 MSG_DEL_MULTI = 'delm'; // Add multicast address
50 const uint32 MSG_ATTACH_PH = 'atph'; // Attach protocol handler
51 const uint32 MSG_DETACH_PH = 'deph'; // Attach protocol handler
52 const uint32 MSG_WRITE = 'writ'; // Write packet
53
54 struct NetMessage : public Message {
55 NetMessage(uint32 what_, const struct MsgPort *reply_port)
56 {
57 what = what_;
58 mn_ReplyPort = (struct MsgPort *)reply_port;
59 mn_Length = sizeof(*this);
60 }
61 uint32 what;
62 uint32 pointer;
63 uint16 type;
64 int16 result;
65 };
66
67
68 // List of attached protocols
69 static const int NUM_READ_REQUESTS = 32; // Number of read requests that are sent to device in advance
70
71 struct NetProtocol : public Node {
72 struct IOSana2Req read_io[NUM_READ_REQUESTS];
73 uint8 read_buf[NUM_READ_REQUESTS][1518]; // 14 bytes header, 1500 bytes data, 4 bytes CRC
74 uint16 type;
75 uint32 handler;
76 };
77
78 static struct List prot_list;
79
80
81 // Global variables
82 static struct Process *net_proc = NULL; // Network device handler process
83 static bool proc_error; // Flag: process didn't initialize
84 static struct MsgPort *proc_port = NULL; // Message port of process, for communication with main task
85 static struct MsgPort *reply_port = NULL; // Reply port for communication with process
86 static struct MsgPort *read_port = NULL; // Reply port for read IORequests (set up and owned by network process)
87
88 static bool write_done = false; // Flag: write request done
89
90 extern struct Task *MainTask; // Pointer to main task (from main_amiga.cpp)
91
92
93 // Prototypes
94 static void net_func(void);
95
96
97 /*
98 * Send message to network process
99 */
100
101 static int16 send_to_proc(uint32 what, uint32 pointer = 0, uint16 type = 0)
102 {
103 D(bug("sending %08lx to net_proc\n", what));
104 NetMessage msg(what, reply_port);
105 msg.pointer = pointer;
106 msg.type = type;
107 PutMsg(proc_port, &msg);
108 WaitPort(reply_port);
109 GetMsg(reply_port);
110 D(bug(" sent\n"));
111 return msg.result;
112 }
113
114
115 /*
116 * Initialization
117 */
118
119 bool ether_init(void)
120 {
121 // Do nothing if no Ethernet device specified
122 if (PrefsFindString("ether") == NULL)
123 return false;
124
125 // Initialize protocol list
126 NewList(&prot_list);
127
128 // Create message port
129 reply_port = CreateMsgPort();
130 if (reply_port == NULL)
131 goto open_error;
132 D(bug("signal mask %08lx\n", 1 << reply_port->mp_SigBit));
133
134 // Start process
135 proc_error = false;
136 SetSignal(0, SIGF_SINGLE);
137 net_proc = CreateNewProcTags(
138 NP_Entry, (ULONG)net_func,
139 NP_Name, (ULONG)"Basilisk II Ethernet Task",
140 NP_Priority, 1,
141 TAG_END
142 );
143 if (net_proc == NULL)
144 goto open_error;
145
146 // Wait for signal from process
147 Wait(SIGF_SINGLE);
148
149 // Initialization error? Then bail out
150 if (proc_error)
151 goto open_error;
152
153 // Everything OK
154 return true;
155
156 open_error:
157 net_proc = NULL;
158 if (reply_port) {
159 DeleteMsgPort(reply_port);
160 reply_port = NULL;
161 }
162 return false;
163 }
164
165
166 /*
167 * Deinitialization
168 */
169
170 void ether_exit(void)
171 {
172 // Stop process
173 if (net_proc) {
174 SetSignal(0, SIGF_SINGLE);
175 Signal(&net_proc->pr_Task, SIGBREAKF_CTRL_C);
176 Wait(SIGF_SINGLE);
177 }
178
179 // Delete reply port
180 if (reply_port) {
181 DeleteMsgPort(reply_port);
182 reply_port = NULL;
183 }
184 }
185
186
187 /*
188 * Reset
189 */
190
191 void ether_reset(void)
192 {
193 // Remove all protocols
194 if (net_proc)
195 send_to_proc(MSG_CLEANUP);
196 }
197
198
199 /*
200 * Add multicast address
201 */
202
203 int16 ether_add_multicast(uint32 pb)
204 {
205 return send_to_proc(MSG_ADD_MULTI, pb);
206 }
207
208
209 /*
210 * Delete multicast address
211 */
212
213 int16 ether_del_multicast(uint32 pb)
214 {
215 return send_to_proc(MSG_DEL_MULTI, pb);
216 }
217
218
219 /*
220 * Attach protocol handler
221 */
222
223 int16 ether_attach_ph(uint16 type, uint32 handler)
224 {
225 return send_to_proc(MSG_ATTACH_PH, handler, type);
226 }
227
228
229 /*
230 * Detach protocol handler
231 */
232
233 int16 ether_detach_ph(uint16 type)
234 {
235 return send_to_proc(MSG_DETACH_PH, type);
236 }
237
238
239 /*
240 * Transmit raw ethernet packet
241 */
242
243 int16 ether_write(uint32 wds)
244 {
245 send_to_proc(MSG_WRITE, wds);
246 return 1; // Command in progress
247 }
248
249
250 /*
251 * Remove protocol from protocol list
252 */
253
254 static void remove_protocol(NetProtocol *p)
255 {
256 // Remove from list
257 Forbid();
258 Remove(p);
259 Permit();
260
261 // Cancel read requests
262 for (int i=0; i<NUM_READ_REQUESTS; i++) {
263 AbortIO((struct IORequest *)(p->read_io + i));
264 WaitIO((struct IORequest *)(p->read_io + i));
265 }
266
267 // Free protocol struct
268 FreeMem(p, sizeof(NetProtocol));
269 }
270
271
272 /*
273 * Remove all protocols
274 */
275
276 static void remove_all_protocols(void)
277 {
278 NetProtocol *n = (NetProtocol *)prot_list.lh_Head, *next;
279 while ((next = (NetProtocol *)n->ln_Succ) != NULL) {
280 remove_protocol(n);
281 n = next;
282 }
283 }
284
285
286 /*
287 * Copy received network packet to Mac side
288 */
289
290 static __saveds __regargs LONG copy_to_buff(uint8 *to /*a0*/, uint8 *from /*a1*/, uint32 packet_len /*d0*/)
291 {
292 D(bug("CopyToBuff to %08lx, from %08lx, size %08lx\n", to, from, packet_len));
293
294 // It would be more efficient (and take up less memory) if we
295 // could invoke the packet handler from here. But we don't know
296 // in what context we run, so calling Execute68k() would not be
297 // a good idea, and even worse, we might run inside a hardware
298 // interrupt, so we can't even trigger a Basilisk interrupt from
299 // here and wait for its completion.
300 CopyMem(from, to, packet_len);
301 #if MONITOR
302 bug("Receiving Ethernet packet:\n");
303 for (int i=0; i<packet_len; i++) {
304 bug("%02lx ", from[i]);
305 }
306 bug("\n");
307 #endif
308 return 1;
309 }
310
311
312 /*
313 * Copy data from Mac WDS to outgoing network packet
314 */
315
316 static __saveds __regargs LONG copy_from_buff(uint8 *to /*a0*/, char *wds /*a1*/, uint32 packet_len /*d0*/)
317 {
318 D(bug("CopyFromBuff to %08lx, wds %08lx, size %08lx\n", to, wds, packet_len));
319 #if MONITOR
320 bug("Sending Ethernet packet:\n");
321 #endif
322 for (;;) {
323 int len = ReadMacInt16((uint32)wds);
324 if (len == 0)
325 break;
326 #if MONITOR
327 uint8 *adr = Mac2HostAddr(ReadMacInt32((uint32)wds + 2));
328 for (int i=0; i<len; i++) {
329 bug("%02lx ", adr[i]);
330 }
331 #endif
332 CopyMem(Mac2HostAddr(ReadMacInt32((uint32)wds + 2)), to, len);
333 to += len;
334 wds += 6;
335 }
336 #if MONITOR
337 bug("\n");
338 #endif
339 return 1;
340 }
341
342
343 /*
344 * Process for communication with the Ethernet device
345 */
346
347 static __saveds void net_func(void)
348 {
349 const char *str;
350 BYTE od_error;
351 struct MsgPort *write_port = NULL, *control_port = NULL;
352 struct IOSana2Req *write_io = NULL, *control_io = NULL;
353 bool opened = false;
354 ULONG read_mask = 0, write_mask = 0, proc_port_mask = 0;
355 struct Sana2DeviceQuery query_data = {sizeof(Sana2DeviceQuery)};
356 ULONG buffer_tags[] = {
357 S2_CopyToBuff, (uint32)copy_to_buff,
358 S2_CopyFromBuff, (uint32)copy_from_buff,
359 TAG_END
360 };
361
362 // Default: error occured
363 proc_error = true;
364
365 // Create message port for communication with main task
366 proc_port = CreateMsgPort();
367 if (proc_port == NULL)
368 goto quit;
369 proc_port_mask = 1 << proc_port->mp_SigBit;
370
371 // Create message ports for device I/O
372 read_port = CreateMsgPort();
373 if (read_port == NULL)
374 goto quit;
375 read_mask = 1 << read_port->mp_SigBit;
376 write_port = CreateMsgPort();
377 if (write_port == NULL)
378 goto quit;
379 write_mask = 1 << write_port->mp_SigBit;
380 control_port = CreateMsgPort();
381 if (control_port == NULL)
382 goto quit;
383
384 // Create control IORequest
385 control_io = (struct IOSana2Req *)CreateIORequest(control_port, sizeof(struct IOSana2Req));
386 if (control_io == NULL)
387 goto quit;
388 control_io->ios2_Req.io_Message.mn_Node.ln_Type = 0; // Avoid CheckIO() bug
389
390 // Parse device name
391 char dev_name[256];
392 ULONG dev_unit;
393
394 str = PrefsFindString("ether");
395 if (str)
396 {
397 const char *FirstSlash = strchr(str, '/');
398 const char *LastSlash = strrchr(str, '/');
399
400 if (FirstSlash && FirstSlash && FirstSlash != LastSlash)
401 {
402 // Device name contains path, i.e. "Networks/xyzzy.device"
403 const char *lp = str;
404 char *dp = dev_name;
405
406 while (lp != LastSlash)
407 *dp++ = *lp++;
408 *dp = '\0';
409
410 if (strlen(dev_name) < 1)
411 goto quit;
412
413 if (1 != sscanf(LastSlash, "/%ld", &dev_unit))
414 goto quit;
415
416 // printf("dev=<%s> unit=%d\n", dev_name, dev_unit);
417 }
418 else
419 {
420 if (2 != sscanf(str, "%[^/]/%ld", dev_name, &dev_unit))
421 goto quit;
422 }
423 }
424 else
425 goto quit;
426
427 // Open device
428 control_io->ios2_BufferManagement = buffer_tags;
429 od_error = OpenDevice((UBYTE *)dev_name, dev_unit, (struct IORequest *)control_io, 0);
430 if (0 != od_error || control_io->ios2_Req.io_Device == 0)
431 {
432 printf("WARNING: OpenDevice(<%s>, unit=%d) returned error %d)\n", (UBYTE *)dev_name, dev_unit, od_error);
433 goto quit;
434 }
435 opened = true;
436
437 // Is it Ethernet?
438 control_io->ios2_Req.io_Command = S2_DEVICEQUERY;
439 control_io->ios2_StatData = (void *)&query_data;
440 DoIO((struct IORequest *)control_io);
441 if (control_io->ios2_Req.io_Error)
442 goto quit;
443 if (query_data.HardwareType != S2WireType_Ethernet) {
444 WarningAlert(GetString(STR_NOT_ETHERNET_WARN));
445 goto quit;
446 }
447
448 // Yes, create IORequest for writing
449 write_io = (struct IOSana2Req *)CreateIORequest(write_port, sizeof(struct IOSana2Req));
450 if (write_io == NULL)
451 goto quit;
452 memcpy(write_io, control_io, sizeof(struct IOSana2Req));
453 write_io->ios2_Req.io_Message.mn_Node.ln_Type = 0; // Avoid CheckIO() bug
454 write_io->ios2_Req.io_Message.mn_ReplyPort = write_port;
455
456 // Configure Ethernet
457 control_io->ios2_Req.io_Command = S2_GETSTATIONADDRESS;
458 DoIO((struct IORequest *)control_io);
459 memcpy(ether_addr, control_io->ios2_DstAddr, 6);
460 memcpy(control_io->ios2_SrcAddr, control_io->ios2_DstAddr, 6);
461 control_io->ios2_Req.io_Command = S2_CONFIGINTERFACE;
462 DoIO((struct IORequest *)control_io);
463 D(bug("Ethernet address %08lx %08lx\n", *(uint32 *)ether_addr, *(uint16 *)(ether_addr + 4)));
464
465 // Initialization went well, inform main task
466 proc_error = false;
467 Signal(MainTask, SIGF_SINGLE);
468
469 // Main loop
470 for (;;) {
471
472 // Wait for I/O and messages (CTRL_C is used for quitting the task)
473 ULONG sig = Wait(proc_port_mask | read_mask | write_mask | SIGBREAKF_CTRL_C);
474
475 // Main task wants to quit us
476 if (sig & SIGBREAKF_CTRL_C)
477 break;
478
479 // Main task sent a command to us
480 if (sig & proc_port_mask) {
481 struct NetMessage *msg;
482 while (msg = (NetMessage *)GetMsg(proc_port)) {
483 D(bug("net_proc received %08lx\n", msg->what));
484 switch (msg->what) {
485 case MSG_CLEANUP:
486 remove_all_protocols();
487 break;
488
489 case MSG_ADD_MULTI:
490 control_io->ios2_Req.io_Command = S2_ADDMULTICASTADDRESS;
491 Mac2Host_memcpy(control_io->ios2_SrcAddr, msg->pointer + eMultiAddr, 6);
492 DoIO((struct IORequest *)control_io);
493 if (control_io->ios2_Req.io_Error == S2ERR_NOT_SUPPORTED) {
494 WarningAlert(GetString(STR_NO_MULTICAST_WARN));
495 msg->result = noErr;
496 } else if (control_io->ios2_Req.io_Error)
497 msg->result = eMultiErr;
498 else
499 msg->result = noErr;
500 break;
501
502 case MSG_DEL_MULTI:
503 control_io->ios2_Req.io_Command = S2_DELMULTICASTADDRESS;
504 Mac2Host_memcpy(control_io->ios2_SrcAddr, msg->pointer + eMultiAddr, 6);
505 DoIO((struct IORequest *)control_io);
506 if (control_io->ios2_Req.io_Error)
507 msg->result = eMultiErr;
508 else
509 msg->result = noErr;
510 break;
511
512 case MSG_ATTACH_PH: {
513 uint16 type = msg->type;
514 uint32 handler = msg->pointer;
515
516 // Protocol of that type already installed?
517 NetProtocol *p = (NetProtocol *)prot_list.lh_Head, *next;
518 while ((next = (NetProtocol *)p->ln_Succ) != NULL) {
519 if (p->type == type) {
520 msg->result = lapProtErr;
521 goto reply;
522 }
523 p = next;
524 }
525
526 // Allocate NetProtocol, set type and handler
527 p = (NetProtocol *)AllocMem(sizeof(NetProtocol), MEMF_PUBLIC);
528 if (p == NULL) {
529 msg->result = lapProtErr;
530 goto reply;
531 }
532 p->type = type;
533 p->handler = handler;
534
535 // Set up and submit read requests
536 for (int i=0; i<NUM_READ_REQUESTS; i++) {
537 memcpy(p->read_io + i, control_io, sizeof(struct IOSana2Req));
538 p->read_io[i].ios2_Req.io_Message.mn_Node.ln_Name = (char *)p; // Hide pointer to NetProtocol in node name
539 p->read_io[i].ios2_Req.io_Message.mn_Node.ln_Type = 0; // Avoid CheckIO() bug
540 p->read_io[i].ios2_Req.io_Message.mn_ReplyPort = read_port;
541 p->read_io[i].ios2_Req.io_Command = CMD_READ;
542 p->read_io[i].ios2_PacketType = type;
543 p->read_io[i].ios2_Data = p->read_buf[i];
544 p->read_io[i].ios2_Req.io_Flags = SANA2IOF_RAW;
545 BeginIO((struct IORequest *)(p->read_io + i));
546 }
547
548 // Add protocol to list
549 AddTail(&prot_list, p);
550
551 // Everything OK
552 msg->result = noErr;
553 break;
554 }
555
556 case MSG_DETACH_PH: {
557 uint16 type = msg->type;
558 msg->result = lapProtErr;
559 NetProtocol *p = (NetProtocol *)prot_list.lh_Head, *next;
560 while ((next = (NetProtocol *)p->ln_Succ) != NULL) {
561 if (p->type == type) {
562 remove_protocol(p);
563 msg->result = noErr;
564 break;
565 }
566 p = next;
567 }
568 break;
569 }
570
571 case MSG_WRITE: {
572 // Get pointer to Write Data Structure
573 uint32 wds = msg->pointer;
574 write_io->ios2_Data = (void *)wds;
575
576 // Calculate total packet length
577 long len = 0;
578 uint32 tmp = wds;
579 for (;;) {
580 int16 w = ReadMacInt16(tmp);
581 if (w == 0)
582 break;
583 len += w;
584 tmp += 6;
585 }
586 write_io->ios2_DataLength = len;
587
588 // Get destination address, set source address
589 uint32 hdr = ReadMacInt32(wds + 2);
590 Mac2Host_memcpy(write_io->ios2_DstAddr, hdr, 6);
591 Host2Mac_memcpy(hdr + 6, ether_addr, 6);
592
593 // Get packet type
594 uint32 type = ReadMacInt16(hdr + 12);
595 if (type <= 1500)
596 type = 0; // 802.3 packet
597 write_io->ios2_PacketType = type;
598
599 // Multicast/broadcard packet?
600 if (write_io->ios2_DstAddr[0] & 1) {
601 if (*(uint32 *)(write_io->ios2_DstAddr) == 0xffffffff && *(uint16 *)(write_io->ios2_DstAddr + 4) == 0xffff)
602 write_io->ios2_Req.io_Command = S2_BROADCAST;
603 else
604 write_io->ios2_Req.io_Command = S2_MULTICAST;
605 } else
606 write_io->ios2_Req.io_Command = CMD_WRITE;
607
608 // Send packet
609 write_done = false;
610 write_io->ios2_Req.io_Flags = SANA2IOF_RAW;
611 BeginIO((IORequest *)write_io);
612 break;
613 }
614 }
615 reply: D(bug(" net_proc replying\n"));
616 ReplyMsg(msg);
617 }
618 }
619
620 // Packet received
621 if (sig & read_mask) {
622 D(bug(" packet received, triggering Ethernet interrupt\n"));
623 SetInterruptFlag(INTFLAG_ETHER);
624 TriggerInterrupt();
625 }
626
627 // Packet write completed
628 if (sig & write_mask) {
629 GetMsg(write_port);
630 WriteMacInt32(ether_data + ed_Result, write_io->ios2_Req.io_Error ? excessCollsns : 0);
631 write_done = true;
632 D(bug(" packet write done, triggering Ethernet interrupt\n"));
633 SetInterruptFlag(INTFLAG_ETHER);
634 TriggerInterrupt();
635 }
636 }
637 quit:
638
639 // Close everything
640 remove_all_protocols();
641 if (opened) {
642 if (CheckIO((struct IORequest *)write_io) == 0) {
643 AbortIO((struct IORequest *)write_io);
644 WaitIO((struct IORequest *)write_io);
645 }
646 CloseDevice((struct IORequest *)control_io);
647 }
648 if (write_io)
649 DeleteIORequest(write_io);
650 if (control_io)
651 DeleteIORequest(control_io);
652 if (control_port)
653 DeleteMsgPort(control_port);
654 if (write_port)
655 DeleteMsgPort(write_port);
656 if (read_port)
657 DeleteMsgPort(read_port);
658
659 // Send signal to main task to confirm termination
660 Forbid();
661 Signal(MainTask, SIGF_SINGLE);
662 }
663
664
665 /*
666 * Ethernet interrupt - activate deferred tasks to call IODone or protocol handlers
667 */
668
669 void EtherInterrupt(void)
670 {
671 D(bug("EtherIRQ\n"));
672
673 // Packet write done, enqueue DT to call IODone
674 if (write_done) {
675 EnqueueMac(ether_data + ed_DeferredTask, 0xd92);
676 write_done = false;
677 }
678
679 // Call protocol handler for received packets
680 IOSana2Req *io;
681 while (io = (struct IOSana2Req *)GetMsg(read_port)) {
682
683 // Get pointer to NetProtocol (hidden in node name)
684 NetProtocol *p = (NetProtocol *)io->ios2_Req.io_Message.mn_Node.ln_Name;
685
686 // No default handler
687 if (p->handler == 0)
688 continue;
689
690 // Copy header to RHA
691 Host2Mac_memcpy(ether_data + ed_RHA, io->ios2_Data, 14);
692 D(bug(" header %08lx%04lx %08lx%04lx %04lx\n", ReadMacInt32(ether_data + ed_RHA), ReadMacInt16(ether_data + ed_RHA + 4), ReadMacInt32(ether_data + ed_RHA + 6), ReadMacInt16(ether_data + ed_RHA + 10), ReadMacInt16(ether_data + ed_RHA + 12)));
693
694 // Call protocol handler
695 M68kRegisters r;
696 r.d[0] = *(uint16 *)((uint32)io->ios2_Data + 12); // Packet type
697 r.d[1] = io->ios2_DataLength - 18; // Remaining packet length (without header, for ReadPacket) (-18 because the CRC is also included)
698 r.a[0] = (uint32)io->ios2_Data + 14; // Pointer to packet (host address, for ReadPacket)
699 r.a[3] = ether_data + ed_RHA + 14; // Pointer behind header in RHA
700 r.a[4] = ether_data + ed_ReadPacket; // Pointer to ReadPacket/ReadRest routines
701 D(bug(" calling protocol handler %08lx, type %08lx, length %08lx, data %08lx, rha %08lx, read_packet %08lx\n", p->handler, r.d[0], r.d[1], r.a[0], r.a[3], r.a[4]));
702 Execute68k(p->handler, &r);
703
704 // Resend IORequest
705 io->ios2_Req.io_Flags = SANA2IOF_RAW;
706 BeginIO((struct IORequest *)io);
707 }
708 D(bug(" EtherIRQ done\n"));
709 }