ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/cebix/BasiliskII/src/AmigaOS/ether_amiga.cpp
Revision: 1.7
Committed: 2001-07-12T19:48:26Z (22 years, 11 months ago) by cebix
Branch: MAIN
Changes since 1.6: +6 -6 lines
Log Message:
- Implemented AppleTalk-over-UDP tunnelling, activated by setting "udptunnel"
  to "true". This uses the BSD socket API, so it's fairly portable (currently
  only imeplemented under Unix, though). This works by sending raw Ethernet
  packets as UDP packets to a fixed port number ("udpport", default is 6066),
  using IP broadcasts to simulate Ethernet broad- and multicasts. Currently
  only tested with AppleTalk.

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 EtherReset(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 }