-
Notifications
You must be signed in to change notification settings - Fork 3
/
driver.c
576 lines (504 loc) · 21 KB
/
driver.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
/*
SEthernet and SEthernet/30 Driver
Copyright (C) 2023-2024 Richard Halkyard
This program is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <AppleTalk.h>
#include <Devices.h>
#include <ENET.h>
#include <Errors.h>
#include <Events.h>
#include <Gestalt.h>
#include <MacTypes.h>
#include <OSUtils.h>
#include <Resources.h>
#include <Retro68Runtime.h>
#include <ROMDefs.h>
#include <ShutDown.h>
#include <Slots.h>
#include <Traps.h>
#include "enc624j600.h"
#include "enc624j600_registers.h"
#include "isr.h"
#include "multicast.h"
#include "protocolhandler.h"
#include "util.h"
#if defined(TARGET_SE30)
#include "sethernet30_board_defs.h"
#elif defined(TARGET_SE)
#include "sethernet_board_defs.h"
#endif
#if defined(DEBUG)
#include <Debugging.h>
#endif
#define likely(x) __builtin_expect (!!(x), 1)
#define unlikely(x) __builtin_expect (!!(x), 0)
/*
EWrite (a.k.a.) Control called with csCode=ENetWrite
Initiate transmission of an ethernet frame. This function is asynchronous and
returns as soon as the frame has been copied into the transmit buffer and
transmission has been started. Completion is signaled through a
transmit-complete or transmit-aborted interrupt.
The Device Manager handles the queueing of writes for us and won't issue another
ENetWrite until the last one has signaled completion.
The frame data is given as a Write Data Structure (WDS) - a list of
address-length pairs like an iovec, we need to read from each one in sequence,
the end of the WDS is signaled by an entry with a zero length. The ethernet
header is already prepared for us, we just have to write our hardware address
into the source field.
*/
static OSErr doEWrite(const driverGlobalsPtr theGlobals, const EParamBlkPtr pb) {
WDSElement *wds; /* a WDS is a list of address-length pairs like an iovec */
unsigned long totalLength; /* total length of frame */
Byte *dest;
/* Shouldn't ever happen unless something has gone very wrong */
if (ENC624J600_READ_REG(theGlobals->chip.base_address, ECON1) & ECON1_TXRTS) {
DBGS("\pTransmit while already transmitting!");
}
/* Scan through WDS list entries to compute total length */
wds = (WDSElement *)pb->u.EParms1.ePointer;
totalLength = 0;
do {
totalLength += wds->entryLength;
wds++;
} while (wds->entryLength);
/* Block transmission of oversized or unreasonably short frames. (Add 4 bytes
to calculated length to account for FCS field generated by ethernet
controller) */
if (unlikely(totalLength + 4 > 1518 || totalLength < 14)) {
DBGP("TX: bogus length %lu bytes!", totalLength);
return eLenErr;
}
/* Restore WDS pointer to start of list */
wds = (WDSElement *)pb->u.EParms1.ePointer;
dest = enc624j600_addr_to_ptr(&theGlobals->chip, ENC_TX_BUF_START);
/* Copy data from WDS into transmit buffer */
do {
#if defined(REV0_SUPPORT)
enc624j600_memcpy(dest, (Byte *)wds->entryPtr, wds->entryLength);
#else
BlockMoveData((Byte *)wds->entryPtr, dest, wds->entryLength);
#endif
dest += wds->entryLength;
wds++;
} while (wds->entryLength > 0);
/* Go back and copy our address into the source field */
dest = enc624j600_addr_to_ptr(&theGlobals->chip, ENC_TX_BUF_START + 6);
#if defined(REV0_SUPPORT)
enc624j600_memcpy(dest, theGlobals->info.ethernetAddress, 6);
#else
BlockMoveData(theGlobals->info.ethernetAddress, dest, 6);
#endif
if (unlikely(theGlobals->chip.link_state == LINK_DOWN)) {
/* don't bother trying to send packets on a down link */
return excessCollsns;
}
debug_log(theGlobals, txEvent, totalLength);
/* Send it! */
enc624j600_transmit(&theGlobals->chip, theGlobals->chip.base_address,
totalLength);
/* Return >0 to indicate operation in progress */
return 1;
}
/*
Shutdown procedure
Set to run at shutdown by ShutDwnInstall.
Mitigation for issue #4 (rev0 hardware produces spurious interrupts on warm
restart). Annoyingly, there is no good way to pass data to a shutdown procedure,
so we have to figure out where our cards are ourselves.
*/
static void doShutdown(void) {
enc624j600 chip;
#if defined(TARGET_SE30)
/* SEthernet/30: Use the Slot Manager to search for Functional sResources
that look like SEthernet/30 cards */
SpBlock spb = {.spCategory = catNetwork,
.spCType = typeEtherNet,
.spDrvrSW = SETHERNET30_DRSW,
.spDrvrHW = SETHERNET30_DRHW,
.spTBMask = 0,
.spSlot = 1,
.spID = 1,
.spExtDev = 0};
while (SGetTypeSRsrc(&spb) == noErr) {
if (SFindDevBase(&spb) == noErr) {
chip.base_address = (unsigned char *) spb.spResult;
enc624j600_reset(&chip);
}
}
#elif defined(TARGET_SE)
/* SEthernet: if we've gotten this far, just YOLO it and assume that the card
is there. We're shutting down anyway, so who cares? */
chip.base_address = (unsigned char *) ENC624J600_BASE;
enc624j600_reset(&chip);
#endif
}
/*
Entrypoint for Open call.
Called whenever software opens the ethernet driver, regardless of whether it is
already open.
If driver is not open, allocate storage, initialize data structures, and set the
chip up.
If driver is already open, do nothing.
*/
#pragma parameter __D0 driverOpen(__A0, __A1)
OSErr driverOpen(__attribute__((unused)) EParamBlkPtr pb, AuxDCEPtr dce) {
driverGlobalsPtr theGlobals;
Handle eadrResourceHandle;
OSErr error;
SysEnvRec sysEnv;
if (dce->dCtlStorage == nil) {
/*
Unlike classic Mac OS toolchains, Retro68 does NOT generate
position-independent code, and if you use -mpcrel to force it to, it'll
appear to work for simple programs but start to come unraveled as things get
more complicated (specifically, libgcc is not built with -mpcrel, so the
libgcc calls that gcc emits for, say, optimized math operations, will cause
a crash).
For simplicity's sake, it's much easier to just live with relocation rather
than fighting it.
For applications, the Retro68 runtime automatically relocates us at startup,
but for non-application code such as a driver, we have to call the relocator
ourselves before we can access global and static variables, or call
functions.
*/
RETRO68_RELOCATE();
/* 'panic button' - hold down the E key at boot (or any other time the
driver is loaded) to disable the driver (or drop into the debugger in debug
builds), just in case we get into a state where the driver is stopping the
system from booting. */
const unsigned char keycode = 0x0e; /* keyboard scan code for E key */
unsigned char keys[16];
GetKeys((unsigned long *)keys);
/* Test bit corresponding to keyboard scan code */
if ((keys[keycode>>3] >> (keycode & 0x7)) & 1) {
#if defined(DEBUG)
Debugger();
#else
return -1;
#endif
}
theGlobals = (driverGlobalsPtr)NewPtrSysClear(sizeof(driverGlobals));
if (!theGlobals) {
error = MemError();
DBGP("Couldn't allocate %lu bytes for globals!", sizeof(driverGlobals));
} else {
error = noErr;
/* dCtlStorage is technically a Handle, but since its use is entirely
user-defined we can just treat it as a pointer */
dce->dCtlStorage = (Handle)theGlobals;
/* Define some macros pointing at interesting parts of our globals */
DBGP(";MC driverGlobals '%08x'"
";MC driverLog '%08x'"
";MC driverLogHead '%08x'"
";g",
(unsigned int) theGlobals,
(unsigned int) &theGlobals->log.entries,
(unsigned int) &theGlobals->log.head);
/* Define macro to dump the debug event log */
DBGP(";MC dumpLog 'dm driverLog (@driverLogHead+1)*%lx';g",
sizeof(theGlobals->log.entries[0]));
if (trapAvailable(_Gestalt)) {
theGlobals->hasGestalt = 1;
}
if (trapAvailable(_SlotManager)) {
theGlobals->hasSlotMgr = 1;
}
error = SysEnvirons(curSysEnvVers, &sysEnv);
if (error == noErr) {
if (sysEnv.machineType == envSE) {
theGlobals->macSE = 1;
}
} else {
DBGP("SysEnvirons call failed with error %d", error);
goto done;
}
#if defined(TARGET_SE30)
/* SEThernet/30 driver requires the Slot Manager */
if (!(theGlobals->hasSlotMgr)) {
DBGS("\pNo slot manager!!!");
error = openErr;
goto done;
}
/* Set up chip base address the clever way. We could manually compute the
address from the slot number in dCtlSlot, but that means that we have to
hard-code the location of the chip in the card's address space.
dCtlDevBase points at the slot base address (i.e. Fs00 0000), with an
optional offset defined by sResources. This means that cards with
different address decoding will work with this driver so long as they have
appropriate sResources (MinorBaseOS and/or MajorBaseOS) in ROM. */
theGlobals->chip.base_address = (void *)dce->dCtlDevBase;
/* It's a ridiculous edge case, but the SE/30 can run System 6.0.3, while
the Gestalt Manager was only introduced in 6.0.4. VM wasn't introduced
until System 7, so if we don't have the Gestalt Manager, we can assume
that we're not running with VM */
if (theGlobals->hasGestalt) {
long gestaltResult;
/* Check if running under virtual memory, make ourselves VM-safe if so.
See 'Driver Considerations for Virtual Memory' in Technote NW-13 */
if ((Gestalt(gestaltVMAttr, &gestaltResult) == noErr) &&
(gestaltResult & (1 << gestaltVMPresent))) {
/* Ask the memory manager to not page our data out */
error = HoldMemory(theGlobals, sizeof(driverGlobals));
if (error == noErr) {
/* Only claim to be VM-safe if HoldMemory call succeeded */
theGlobals->vmEnabled = 1;
dce->dCtlFlags |= dVMImmuneMask; /* Tell the OS that we're VM-safe */
}
}
}
#elif defined(TARGET_SE)
/* Check to make sure we're actually running on a Macintosh SE */
if (! (theGlobals->macSE)) {
DBGS("\pSE driver running on a non-SE!");
error = openErr;
goto done;
}
/* SE: base address is hardcoded. */
theGlobals->chip.base_address = (unsigned char *) SETHERNET_BASEADDR;
#endif
/* Before we go any further, check to see if the ENC624J600 is actually
there */
if (enc624j600_detect(&theGlobals->chip) != 0) {
DBGP("Could not find ENC624J600 at %08x",
(unsigned int) theGlobals->chip.base_address);
error = openErr;
goto done;
}
/* Save our device control entry - we need this to signal completion of IO
at interrupt time */
theGlobals->driverDCE = dce;
if (dce->dCtlFlags & dRAMBasedMask) {
/* If loaded via a Handle, detach our driver resource. This means that
the Resource Manager can no longer 'see' it, preventing it from being
changed, released, etc. Unfortunately this also means that Macsbug's
heap analyzer can no longer identify it either :( */
DetachResource((Handle)dce->dCtlDriver);
}
/* Initialize protocol-handler table */
InitPHTable(theGlobals);
/* Reset the chip */
if (enc624j600_reset(&theGlobals->chip) != 0) {
DBGS("\pENC624J600 reset failed");
error = openErr;
goto done;
}
/*
Wait for the link to come back after the reset. According to the
datasheet, we must delay 25us for bus interface and MAC registers to come
up, plus an additional 256us for the PHY. However, the actual link may
take some indeterminate amount of time to come back, and if we don't wait
for it, the process that opened the driver may start blindly sending
packets on a down link, ignoring errors (AppleTalk does this as part of
its address-assignment process at startup, which could potentially lead to
multiple nodes taking the same address).
Empirically, 1.5 seconds seems to be enough time to bring up a link on the
various cheap and not-so-cheap switches I have lying around. Bit of a
shame to add such a big delay to the boot process, but we can't change
AppleTalk's behavior, so this is how it's gotta be.
*/
unsigned long finalTicks; /* unused, but Delay doesn't null-check its
out-params */
Delay(90, &finalTicks); /* 90 ticks @ 60Hz = 1.5 seconds */
/* Test the chip's memory just to be *really* sure it's working */
if (enc624j600_memtest(&theGlobals->chip) != 0) {
DBGS("\pENC624J600 memory test failed");
error = openErr;
goto done;
}
/* Initialize the ethernet controller. */
if (enc624j600_init(&theGlobals->chip, ENC_RX_BUF_START) != 0) {
DBGS("\pENC624J600 initialisation failed");
error = openErr;
goto done;
}
/* Install a shutdown procedure to reset the ENC624J600 as mitigation for
issue #4 (rev0 hardware produces spurious interrupts on warm restart) */
ShutDwnInstall(doShutdown, sdRestartOrPower);
/* Figure out our ethernet address. First we look for an 'eadr' resource
with an ID corresponding to our slot. If one exists, we save it to our
globals, and write it into the chip. Otherwise, we read the chip's address
(which the reset above restored to its factory-assigned value) into our
globals. */
eadrResourceHandle = GetResource(EAddrRType, dce->dCtlSlot);
if (eadrResourceHandle) {
copyEthAddrs((hwAddr *) theGlobals->info.ethernetAddress,
(hwAddr *) *eadrResourceHandle);
enc624j600_write_hwaddr(&theGlobals->chip, (Byte *)*eadrResourceHandle);
ReleaseResource(eadrResourceHandle);
} else {
enc624j600_read_hwaddr(&theGlobals->chip,
theGlobals->info.ethernetAddress);
}
#if defined(TARGET_SE30)
/* Install our interrupt handler using the Slot Manager */
theGlobals->theSInt.sqType = sIQType;
theGlobals->theSInt.sqPrio = 199; /* priority >=200 is reserved for Apple */
theGlobals->theSInt.sqAddr = isrWrapper;
theGlobals->theSInt.sqParm = (long)theGlobals;
error = SIntInstall(&theGlobals->theSInt, dce->dCtlSlot);
if (error != noErr) {
DBGP("SIntInstall call failed with error %d", error);
goto done;
}
#elif defined(TARGET_SE)
isrGlobals = theGlobals;
/* The 68000's interrupt vectors are in low RAM starting at 0x64 */
void ** const isrVectors = (void **) 0x64;
/* No Slot Manager on the SE, we hook the Level 1 Interrupt vector. Very
Commodore 64-style. Level 1 is normally used by the VIA and SCSI
controller, so we have to coexist with them. */
asm volatile (
/* Save current status register */
" MOVE.W %%sr, -(%%sp) \n\t"
/* Mask interrupts while we change out interrupt vectors */
" ORI.W %[srMaskInterrupts], %%sr \n\t"
/* Save the original vector */
" MOVE.L %[isrVector], %[originalInterruptVector] \n\t"
/* Install our own */
" MOVE.L %[isrWrapper], %[isrVector] \n\t"
/* Restore status register */
" MOVE.W (%%sp)+, %%sr"
: [originalInterruptVector] "=g" (originalInterruptVector)
: [isrWrapper] "g" (isrWrapper),
/* status register bits to set priority 7, masking all interrupts */
[srMaskInterrupts] "g" (0x700),
[isrVector] "g" (isrVectors[SETHERNET_INTERRUPT-1])
);
#endif
/* Let's go! */
enc624j600_start(&theGlobals->chip);
enc624j600_enable_irq(&theGlobals->chip,
IRQ_ENABLE | IRQ_LINK | IRQ_PKT | IRQ_RX_ABORT |
IRQ_PCNT_FULL | IRQ_TX | IRQ_TX_ABORT);
}
} else {
/* Driver was already open, nothing to do */
error = noErr;
}
done:
if (error != noErr) {
/* Tidy up if open failed */
if (dce->dCtlStorage != nil) {
ShutDwnRemove(doShutdown);
DisposePtr((Ptr) dce->dCtlStorage);
dce->dCtlStorage = nil;
}
}
return error;
}
/*
Entrypoint for Close call
Ethernet drivers don't generally get closed, as drivers don't (can't?) implement
reference counting and software has no way of knowing if other software is using
it.
However, Technote DV13 "_PBClose the Barn Door" states that in particular, the
A/UX boot process DOES close all open drivers, so we ought to implement it, and
do it properly so that the card is in a sane state for A/UX to take over.
*/
#pragma parameter __D0 driverClose(__A0, __A1)
OSErr driverClose(__attribute__((unused)) EParamBlkPtr pb, AuxDCEPtr dce) {
driverGlobalsPtr theGlobals = (driverGlobalsPtr)dce->dCtlStorage;
/* Reset the chip; this is just a 'big hammer' to stop transmitting, disable
receive, disable interrupts etc. */
enc624j600_reset(&theGlobals->chip);
#if defined(TARGET_SE30)
/* Uninstall our slot interrupt handler */
SIntRemove(&theGlobals->theSInt, dce->dCtlSlot);
if (theGlobals->vmEnabled) {
/* Unpin if running with virtual memory */
UnholdMemory(theGlobals, sizeof(driverGlobals));
}
#elif defined(TARGET_SE)
asm volatile (
/* Mask interrupts while we change out interrupt vectors */
"MOVE.W %%sr, -(%%sp) \n\t"
"ORI.W %[srMaskInterrupts], %%sr \n\t"
/* Restore the original interrupt vector */
"MOVE.L %[originalInterruptVector], 0x64 \n\t"
/* Restore interrupts */
"MOVE.W (%%sp)+, %%sr"
:
: [originalInterruptVector] "g" (originalInterruptVector),
[srMaskInterrupts] "g" (0x700)
);
#endif
/* Uninstall our shutdown procedure */
ShutDwnRemove(doShutdown);
DisposePtr((Ptr)theGlobals);
dce->dCtlStorage = nil;
return noErr;
}
/*
Control entrypoint
This is where the magic happens. Dispatch to various operations based on the
csCode in the parameter block.
Note that control operations can be asynchronous! The wrapper code in header.s
handles this for us, all we need to do is return a value <=0 when returning
synchronously (0 for success, <0 for error) or >0 for async operations that will
be completed by a later IODone call.
*/
#pragma parameter __D0 driverControl(__A0, __A1)
OSErr driverControl(EParamBlkPtr pb, AuxDCEPtr dce) {
driverGlobalsPtr theGlobals = (driverGlobalsPtr)dce->dCtlStorage;
switch (pb->csCode) {
case ENetDelMulti: /* Delete address from multicast table */
return doEDelMulti(theGlobals, pb);
case ENetAddMulti: /* Add address to multicast table */
return doEAddMulti(theGlobals, pb);
case ENetAttachPH: /* Attach receive handler for ethertype */
return doEAttachPH(theGlobals, pb);
case ENetDetachPH: /* Detach receive handler for ethertype */
return doEDetachPH(theGlobals, pb);
case ENetRead: /* Read packets directly without a handler routine */
DBGS("\pENetRead not implemented!");
return controlErr; /* TODO: support this */
case ENetRdCancel: /* Cancel a pending ENetRead */
DBGS("\pENetRdCancel not implemented!");
return controlErr; /* TODO: support this */
case ENetWrite: /* Send packet */
return doEWrite(theGlobals, pb);
case ENetGetInfo: /* Read hardware address and statistics */
/* We use an extended version of the driver info struct with some extra
fields tacked onto the end. Note that we do not have counters for all the
standard fields. */
if (pb->u.EParms1.eBuffSize > (short) sizeof(theGlobals->info)) {
pb->u.EParms1.eBuffSize = sizeof(theGlobals->info);
}
BlockMoveData(&theGlobals->info, pb->u.EParms1.ePointer,
pb->u.EParms1.eBuffSize);
return noErr;
case ENetSetGeneral: /* Enter 'general mode' */
/* ENEtSetGeneral tells the driver to prepare to transmit general Ethernet
packets rather than only AppleTalk packets. Drivers can use this to
rearrange TX/RX buffer boundaries for the longer maximum frame length
(1536 vs. 768 bytes). We have enough buffer to always operate in general
mode, so this is a no-op. */
return noErr;
#if 0
/* I've seen these csCodes in the wild but my headers don't define them, and
some drivers (e.g. MACE) that do include them don't actually do anything
with them. Probably safe to just not implement I guess? */
case EGetDot3Entry:
case ESetDot3Entry:
case EGetDot3Statistics:
case EGetDot3CollStats:
case LapGetLinkStatus:
return noErr;
case ENetEnablePromiscuous:
case ENetDisablePromiscuous:
reeturn controlErr;
#endif
default:
DBGP("Unhandled csCode %d", pb->csCode);
return controlErr;
}
}