freedos-micro-python 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- freedos_micro_python/__init__.py +8 -0
- freedos_micro_python/cli.py +106 -0
- freedos_micro_python/gen_qstrdefs.py +275 -0
- freedos_micro_python/port/arch/bpstruct.h +2 -0
- freedos_micro_python/port/arch/cc.h +4 -0
- freedos_micro_python/port/arch/epstruct.h +1 -0
- freedos_micro_python/port/base64_uc386dos.c +164 -0
- freedos_micro_python/port/file_uc386dos.c +228 -0
- freedos_micro_python/port/lib/axtls/crypto/crypto.h +45 -0
- freedos_micro_python/port/lwip-arch-cc.h +46 -0
- freedos_micro_python/port/lwip_uc386dos.c +248 -0
- freedos_micro_python/port/lwipopts.h +117 -0
- freedos_micro_python/port/math_gamma.c +63 -0
- freedos_micro_python/port/modtime_uc386dos.c +60 -0
- freedos_micro_python/port/modtls_axtls_uc386dos.c +461 -0
- freedos_micro_python/port/mpconfigport.h +358 -0
- freedos_micro_python/port/mphal_uc386dos.c +103 -0
- freedos_micro_python/port/mphalport.h +11 -0
- freedos_micro_python/port/os_uc386dos.c +264 -0
- freedos_micro_python/port/path_uc386dos.c +307 -0
- freedos_micro_python/port/pktdrv_uc386dos.c +650 -0
- freedos_micro_python/port/qstrdefsport.h +2 -0
- freedos_micro_python/port/shutil_uc386dos.c +111 -0
- freedos_micro_python/port/tempfile_uc386dos.c +129 -0
- freedos_micro_python/port/time_real_uc386dos.c +77 -0
- freedos_micro_python/port/uc386_net_uc386dos.c +126 -0
- freedos_micro_python/port/urllib_parse_uc386dos.c +360 -0
- freedos_micro_python/port/urllib_uc386dos.c +29 -0
- freedos_micro_python/scripts/build.sh +641 -0
- freedos_micro_python/scripts/build_port.sh +241 -0
- freedos_micro_python/scripts/fetch.sh +238 -0
- freedos_micro_python-0.1.0.dist-info/METADATA +131 -0
- freedos_micro_python-0.1.0.dist-info/RECORD +37 -0
- freedos_micro_python-0.1.0.dist-info/WHEEL +5 -0
- freedos_micro_python-0.1.0.dist-info/entry_points.txt +2 -0
- freedos_micro_python-0.1.0.dist-info/licenses/LICENSE +25 -0
- freedos_micro_python-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,650 @@
|
|
|
1
|
+
// Crynwr "FTP Software" packet-driver bindings (INT 0x60–0x7F).
|
|
2
|
+
//
|
|
3
|
+
// Detection: walk the IVT looking for the 8-byte signature
|
|
4
|
+
// "PKT DRVR" at offset +3 of the handler routine (the leading two
|
|
5
|
+
// bytes are a `JMP SHORT +8` over the signature). The IVT lives at
|
|
6
|
+
// linear 0x0000:0xNNNN — under PMODE/W's flat 32-bit model the
|
|
7
|
+
// real-mode IVT is reachable via the same flat address space, just
|
|
8
|
+
// at the bottom of memory.
|
|
9
|
+
//
|
|
10
|
+
// Calls used:
|
|
11
|
+
// AH=0x01 driver_info (probe — confirms a handle is valid)
|
|
12
|
+
// AH=0x02 access_type (register a receiver callback)
|
|
13
|
+
// AH=0x04 send_pkt (transmit a frame)
|
|
14
|
+
// AH=0x06 get_address (read the NIC's MAC)
|
|
15
|
+
// AH=0x05 release_type (unregister at shutdown — not yet wired)
|
|
16
|
+
//
|
|
17
|
+
// Receive is callback-driven: on packet arrival, the driver calls
|
|
18
|
+
// our handler twice — first with AX=0 to get a buffer pointer, then
|
|
19
|
+
// with AX=1 once it's copied the bytes in. We funnel both into a
|
|
20
|
+
// small RX queue (`pktdrv_rx_queue`) that ethdrv_recv polls.
|
|
21
|
+
//
|
|
22
|
+
// Real-DOS deployment notes (TODO): the AX=0/AX=1 callback runs in
|
|
23
|
+
// real mode; PMODE/W requires us to allocate a real-mode trampoline
|
|
24
|
+
// via DPMI INT 31h fn 0x0303 that ferries the call into 32-bit
|
|
25
|
+
// protected mode and translates the seg:offset return into a flat
|
|
26
|
+
// linear pointer. Under dos_emu the receiver is invoked directly as
|
|
27
|
+
// a 32-bit cdecl function — the emulator does the impedance match.
|
|
28
|
+
|
|
29
|
+
#include <stddef.h>
|
|
30
|
+
#include <string.h>
|
|
31
|
+
|
|
32
|
+
extern unsigned char pktdrv_int_invoke(unsigned int int_num,
|
|
33
|
+
unsigned int regs_in_out[8]);
|
|
34
|
+
|
|
35
|
+
// regs_in_out indices, mirrored in the asm wrapper.
|
|
36
|
+
#define R_EAX 0
|
|
37
|
+
#define R_EBX 1
|
|
38
|
+
#define R_ECX 2
|
|
39
|
+
#define R_EDX 3
|
|
40
|
+
#define R_ESI 4
|
|
41
|
+
#define R_EDI 5
|
|
42
|
+
#define R_DS 6
|
|
43
|
+
#define R_ES 7
|
|
44
|
+
|
|
45
|
+
static const unsigned char PKT_SIG[8] = "PKT DRVR";
|
|
46
|
+
|
|
47
|
+
static int pktdrv_int_num = 0; // 0 = not detected
|
|
48
|
+
static int pktdrv_handle = -1; // access_type result
|
|
49
|
+
static unsigned char pktdrv_mac_cache[6];
|
|
50
|
+
|
|
51
|
+
// RX ring: the receiver callback is split into TWO calls per packet
|
|
52
|
+
// (give-buffer / packet-copied). We hand back `pktdrv_rx_buf` from
|
|
53
|
+
// give-buffer, then mark `pktdrv_rx_pending = 1` once the copy is
|
|
54
|
+
// done. ethdrv_recv (in lwip_uc386dos.c) polls the pending flag,
|
|
55
|
+
// drains the buffer, clears the flag — single-slot ring is enough
|
|
56
|
+
// because lwip.callback() pumps after every TX and dos_emu is
|
|
57
|
+
// single-threaded so packets land one at a time.
|
|
58
|
+
#define PKTDRV_MAX_FRAME 1518
|
|
59
|
+
unsigned char pktdrv_rx_buf[PKTDRV_MAX_FRAME];
|
|
60
|
+
volatile int pktdrv_rx_pending = 0;
|
|
61
|
+
volatile unsigned int pktdrv_rx_len = 0;
|
|
62
|
+
|
|
63
|
+
// DPMI 0.9 "Real Mode Call Structure" — what fn 0x0303 saves the
|
|
64
|
+
// real-mode register state into when the trampoline fires. Field
|
|
65
|
+
// order is fixed by the DPMI spec; total size is 0x32 (50 bytes).
|
|
66
|
+
// __attribute__((packed)) keeps the 16-bit segment fields adjacent
|
|
67
|
+
// to their dword neighbors; uc386 honors packed (verified via the
|
|
68
|
+
// lwIP packed_struct_test self-check).
|
|
69
|
+
typedef struct {
|
|
70
|
+
unsigned int edi; // 0x00
|
|
71
|
+
unsigned int esi; // 0x04
|
|
72
|
+
unsigned int ebp; // 0x08
|
|
73
|
+
unsigned int reserved; // 0x0C
|
|
74
|
+
unsigned int ebx; // 0x10
|
|
75
|
+
unsigned int edx; // 0x14
|
|
76
|
+
unsigned int ecx; // 0x18
|
|
77
|
+
unsigned int eax; // 0x1C
|
|
78
|
+
unsigned short flags; // 0x20
|
|
79
|
+
unsigned short es; // 0x22
|
|
80
|
+
unsigned short ds; // 0x24
|
|
81
|
+
unsigned short fs; // 0x26
|
|
82
|
+
unsigned short gs; // 0x28
|
|
83
|
+
unsigned short ip; // 0x2A
|
|
84
|
+
unsigned short cs; // 0x2C
|
|
85
|
+
unsigned short sp; // 0x2E
|
|
86
|
+
unsigned short ss; // 0x30
|
|
87
|
+
} __attribute__((packed)) pktdrv_rmcs_t;
|
|
88
|
+
|
|
89
|
+
pktdrv_rmcs_t pktdrv_rmcs;
|
|
90
|
+
unsigned int pktdrv_dpmi_seg = 0; // real-mode trampoline segment
|
|
91
|
+
unsigned int pktdrv_dpmi_off = 0; // real-mode trampoline offset
|
|
92
|
+
|
|
93
|
+
// Diagnostic counters for the static-IP rig — exposed to Python so
|
|
94
|
+
// the test script can poll without rebuilding to add markers.
|
|
95
|
+
volatile unsigned int pktdrv_thunk_invocations = 0;
|
|
96
|
+
volatile unsigned int pktdrv_thunk_phase0_count = 0;
|
|
97
|
+
volatile unsigned int pktdrv_thunk_phase1_count = 0;
|
|
98
|
+
unsigned int pktdrv_last_handle = 0xDEADBEEF;
|
|
99
|
+
|
|
100
|
+
// Conventional-memory bounce buffer for talking to the real-mode
|
|
101
|
+
// Crynwr packet driver under PMODE/W. The driver expects ES:DI /
|
|
102
|
+
// DS:SI to point at real-mode-addressable memory (linear < 1 MB,
|
|
103
|
+
// representable as a 16-bit seg:offset). Our flat-32 BSS sits well
|
|
104
|
+
// above 1 MB on a port the size of MicroPython, so flat pointers
|
|
105
|
+
// can't be encoded that way.
|
|
106
|
+
//
|
|
107
|
+
// Allocated once at init via DPMI fn 0x0100. Sized to comfortably
|
|
108
|
+
// fit a max ethernet frame (1518 bytes) plus headroom, and reused
|
|
109
|
+
// for every TX / RX / get_addr — the packet driver is single-
|
|
110
|
+
// threaded from our perspective, so one shared buffer is fine.
|
|
111
|
+
//
|
|
112
|
+
// Linear address (seg << 4) is what we read/write from flat-32
|
|
113
|
+
// code. Standard DOS extenders (PMODE/W, DOS/4GW, CWSDPMI) map
|
|
114
|
+
// conventional memory at low linear addresses, so flat reads at
|
|
115
|
+
// `bounce_seg << 4` work directly.
|
|
116
|
+
#define PKTDRV_BOUNCE_SIZE 2048
|
|
117
|
+
static unsigned int pktdrv_bounce_seg = 0; // real-mode segment
|
|
118
|
+
static unsigned int pktdrv_bounce_sel = 0; // PM selector (unused, kept for free)
|
|
119
|
+
static unsigned int pktdrv_bounce_linear = 0; // flat-32 linear (= seg << 4)
|
|
120
|
+
|
|
121
|
+
// DPMI fn 0x0100 — Allocate DOS Memory.
|
|
122
|
+
// On entry: AX=0x0100, BX=number of paragraphs (16-byte units).
|
|
123
|
+
// On exit (CF clear): AX=real-mode segment, DX=PM selector.
|
|
124
|
+
// On error: CF set, AX=DOS error code, BX=largest paragraphs free.
|
|
125
|
+
static int pktdrv_alloc_bounce(void) {
|
|
126
|
+
if (pktdrv_bounce_seg != 0) {
|
|
127
|
+
return 0; // already allocated
|
|
128
|
+
}
|
|
129
|
+
unsigned int paragraphs = (PKTDRV_BOUNCE_SIZE + 15) / 16;
|
|
130
|
+
unsigned int regs[8] = {0};
|
|
131
|
+
regs[R_EAX] = 0x0100;
|
|
132
|
+
regs[R_EBX] = paragraphs;
|
|
133
|
+
unsigned char carry = pktdrv_int_invoke(0x31, regs);
|
|
134
|
+
if (carry) {
|
|
135
|
+
return -1;
|
|
136
|
+
}
|
|
137
|
+
pktdrv_bounce_seg = regs[R_EAX] & 0xFFFF;
|
|
138
|
+
pktdrv_bounce_sel = regs[R_EDX] & 0xFFFF;
|
|
139
|
+
pktdrv_bounce_linear = pktdrv_bounce_seg << 4;
|
|
140
|
+
return 0;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// DPMI fn 0x0300 — Simulate Real Mode Interrupt. Required to reach
|
|
144
|
+
// real-mode INT handlers (like the Crynwr packet driver at INT 0x60)
|
|
145
|
+
// from a protected-mode DPMI client like ours under PMODE/W. A bare
|
|
146
|
+
// `INT 0x60` instruction from prot mode goes through the IDT, which
|
|
147
|
+
// is *not* the real-mode IVT; the Crynwr handler lives in real-mode
|
|
148
|
+
// memory and only fires when reached via DPMI fn 0x0300.
|
|
149
|
+
//
|
|
150
|
+
// Inputs (regs[]): EAX, EBX, ECX, EDX, ESI, EDI all copied into the
|
|
151
|
+
// RMCS as-is (these are the real-mode register values the int
|
|
152
|
+
// handler sees on entry).
|
|
153
|
+
// Outputs (regs[]): EAX, EBX, ECX, EDX, ESI, EDI written back from
|
|
154
|
+
// the post-int RMCS.
|
|
155
|
+
// Return: bit 0 of the post-int real-mode flags (CF as the
|
|
156
|
+
// driver-success indicator), or 1 on outright DPMI failure.
|
|
157
|
+
//
|
|
158
|
+
// Only used for non-DPMI INT vectors (anything that needs to be
|
|
159
|
+
// dispatched into real mode). For DPMI services themselves —
|
|
160
|
+
// INT 31h fn 0x0200 / 0x0303 / etc. — call pktdrv_int_invoke
|
|
161
|
+
// directly: PMODE/W's IDT handles INT 31h in protected mode.
|
|
162
|
+
static unsigned char pktdrv_simulate_real_int(
|
|
163
|
+
unsigned int int_num, unsigned int regs[8]) {
|
|
164
|
+
static pktdrv_rmcs_t rm;
|
|
165
|
+
// Memset would be cleaner; uc386's libc has memset, but we
|
|
166
|
+
// also avoid the dependency by zeroing field-by-field.
|
|
167
|
+
rm.edi = regs[R_EDI];
|
|
168
|
+
rm.esi = regs[R_ESI];
|
|
169
|
+
rm.ebp = 0;
|
|
170
|
+
rm.reserved = 0;
|
|
171
|
+
rm.ebx = regs[R_EBX];
|
|
172
|
+
rm.edx = regs[R_EDX];
|
|
173
|
+
rm.ecx = regs[R_ECX];
|
|
174
|
+
rm.eax = regs[R_EAX];
|
|
175
|
+
rm.flags = 0;
|
|
176
|
+
rm.es = 0; rm.ds = 0; rm.fs = 0; rm.gs = 0;
|
|
177
|
+
rm.ip = 0; rm.cs = 0; rm.sp = 0; rm.ss = 0;
|
|
178
|
+
|
|
179
|
+
// INT 0x31 fn 0x0300: AX=0x0300, BL=int_num, BH=0,
|
|
180
|
+
// CX=words-to-copy=0, ES:EDI=ptr to RMCS.
|
|
181
|
+
unsigned int dpmi[8] = {0};
|
|
182
|
+
dpmi[R_EAX] = 0x0300;
|
|
183
|
+
dpmi[R_EBX] = int_num & 0xFF;
|
|
184
|
+
dpmi[R_ECX] = 0;
|
|
185
|
+
dpmi[R_EDI] = (unsigned int)(unsigned long)&rm;
|
|
186
|
+
unsigned char carry = pktdrv_int_invoke(0x31, dpmi);
|
|
187
|
+
if (carry) {
|
|
188
|
+
return 1;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
regs[R_EAX] = rm.eax;
|
|
192
|
+
regs[R_EBX] = rm.ebx;
|
|
193
|
+
regs[R_ECX] = rm.ecx;
|
|
194
|
+
regs[R_EDX] = rm.edx;
|
|
195
|
+
regs[R_ESI] = rm.esi;
|
|
196
|
+
regs[R_EDI] = rm.edi;
|
|
197
|
+
return (rm.flags & 1) ? 1 : 0;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Probe an IVT slot to see if a Crynwr packet driver is installed
|
|
201
|
+
// there. Two checks, in order:
|
|
202
|
+
//
|
|
203
|
+
// 1. Functional probe — issue the driver's `driver_info` call
|
|
204
|
+
// (AH=0x01, BX=0xFFFF). A Crynwr driver returns CF clear and
|
|
205
|
+
// a sensible class+type+version triple in BX/CX/DX/DH; an
|
|
206
|
+
// unrelated default INT vector either returns CF set or
|
|
207
|
+
// garbage. This is the canonical Crynwr-presence test and
|
|
208
|
+
// doesn't depend on conventional-memory mapping.
|
|
209
|
+
//
|
|
210
|
+
// 2. Fallback signature scan — DPMI fn 0x0200 + linear-address
|
|
211
|
+
// byte read at offset 3 ("PKT DRVR"). Works under dos_emu
|
|
212
|
+
// (where conventional memory is mapped flat at low linear
|
|
213
|
+
// addresses) but is fragile under PMODE/W on real DOS in
|
|
214
|
+
// QEMU+FreeDOS, where the linear-to-real mapping isn't
|
|
215
|
+
// uniformly available. Kept as a backstop for the dos_emu
|
|
216
|
+
// smoke tests where pktdrv_int_invoke's own probe path
|
|
217
|
+
// isn't fully wired.
|
|
218
|
+
//
|
|
219
|
+
// Returns the linear handler address (or just `int_num` rebadged
|
|
220
|
+
// as a positive non-zero token) on success, 0 on miss.
|
|
221
|
+
static unsigned int pktdrv_probe_slot(unsigned int int_num) {
|
|
222
|
+
// (1) driver_info call: AH=0x01, BX=0xFFFF (= "no handle yet").
|
|
223
|
+
// Routed through DPMI 0x0300 (Simulate Real Mode Interrupt) so
|
|
224
|
+
// it reaches the real-mode Crynwr handler under PMODE/W.
|
|
225
|
+
// Crynwr returns: DH=class, DL=type, BX=version, CL=number,
|
|
226
|
+
// CX=basic/extended, ES:SI=driver name string, CF=clear on
|
|
227
|
+
// success.
|
|
228
|
+
{
|
|
229
|
+
unsigned int regs[8] = {0};
|
|
230
|
+
regs[R_EAX] = 0x0100; // AH=0x01 driver_info, AL=0
|
|
231
|
+
regs[R_EBX] = 0xFFFF;
|
|
232
|
+
unsigned char carry = pktdrv_simulate_real_int(int_num, regs);
|
|
233
|
+
if (!carry) {
|
|
234
|
+
// Spot-check: a real Crynwr driver returns BX with a
|
|
235
|
+
// version like 0x0100..0x01FF (1.x), and class DH in
|
|
236
|
+
// {0x01..0x10} (Ethernet et al.). Reject obvious
|
|
237
|
+
// garbage from a default IVT vector that happened to
|
|
238
|
+
// not set CF.
|
|
239
|
+
unsigned int bx = regs[R_EBX] & 0xFFFF;
|
|
240
|
+
unsigned int dh = (regs[R_EDX] >> 8) & 0xFF;
|
|
241
|
+
if (bx >= 0x0100 && bx <= 0x01FF
|
|
242
|
+
&& dh >= 0x01 && dh <= 0x20) {
|
|
243
|
+
return int_num | 0x80000000; // non-zero token
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// (2) Signature-scan fallback for dos_emu compatibility.
|
|
249
|
+
unsigned int regs[8] = {0};
|
|
250
|
+
regs[R_EAX] = 0x0200;
|
|
251
|
+
regs[R_EBX] = int_num & 0xFF;
|
|
252
|
+
unsigned char carry = pktdrv_int_invoke(0x31, regs);
|
|
253
|
+
if (carry) {
|
|
254
|
+
return 0;
|
|
255
|
+
}
|
|
256
|
+
unsigned int seg = regs[R_ECX] & 0xFFFF;
|
|
257
|
+
unsigned int off = regs[R_EDX] & 0xFFFF;
|
|
258
|
+
unsigned int linear = seg * 16 + off;
|
|
259
|
+
if (linear == 0 || linear >= 0x110000) {
|
|
260
|
+
return 0;
|
|
261
|
+
}
|
|
262
|
+
unsigned char *handler = (unsigned char *)linear;
|
|
263
|
+
for (int i = 0; i < 8; i++) {
|
|
264
|
+
if (handler[3 + i] != PKT_SIG[i]) {
|
|
265
|
+
return 0;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return linear;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Find a Crynwr packet driver in the IVT. Returns the INT number
|
|
272
|
+
// it's installed on, or 0 if none. Caches in pktdrv_int_num.
|
|
273
|
+
int pktdrv_detect(void) {
|
|
274
|
+
if (pktdrv_int_num != 0) {
|
|
275
|
+
return pktdrv_int_num;
|
|
276
|
+
}
|
|
277
|
+
for (unsigned int i = 0x60; i < 0x80; i++) {
|
|
278
|
+
if (pktdrv_probe_slot(i) != 0) {
|
|
279
|
+
pktdrv_int_num = (int)i;
|
|
280
|
+
return pktdrv_int_num;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return 0;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// AH=0x06 get_address — fetch the NIC's MAC into `out[6]`.
|
|
287
|
+
// Routed through DPMI 0x0300; ES:DI points at the bounce buffer
|
|
288
|
+
// (real-mode addressable). After the call, copy the 6 MAC bytes
|
|
289
|
+
// from the bounce buffer's linear address into the caller's
|
|
290
|
+
// flat-32 `out` buffer.
|
|
291
|
+
//
|
|
292
|
+
// Under dos_emu (which emulates Crynwr at the prot-mode INT level
|
|
293
|
+
// and also intercepts DPMI 0x0300), the same code path works —
|
|
294
|
+
// dos_emu treats the seg:off ES:DI as a linear pointer and writes
|
|
295
|
+
// to bounce_linear directly.
|
|
296
|
+
static int pktdrv_get_addr(unsigned char out[6]) {
|
|
297
|
+
if (pktdrv_int_num == 0 || pktdrv_handle < 0) {
|
|
298
|
+
return -1;
|
|
299
|
+
}
|
|
300
|
+
if (pktdrv_bounce_seg == 0) {
|
|
301
|
+
// Fallback for the rare case the bounce buffer wasn't
|
|
302
|
+
// allocated (DPMI 0x0100 failed). Use the flat pointer —
|
|
303
|
+
// works under dos_emu, broken on real DOS but at least
|
|
304
|
+
// doesn't crash.
|
|
305
|
+
unsigned int regs[8] = {0};
|
|
306
|
+
regs[R_EAX] = 0x0600;
|
|
307
|
+
regs[R_EBX] = (unsigned int)pktdrv_handle;
|
|
308
|
+
regs[R_ECX] = 6;
|
|
309
|
+
regs[R_EDI] = (unsigned int)(unsigned long)out;
|
|
310
|
+
unsigned char carry = pktdrv_int_invoke(
|
|
311
|
+
(unsigned int)pktdrv_int_num, regs);
|
|
312
|
+
if (carry) {
|
|
313
|
+
return -1;
|
|
314
|
+
}
|
|
315
|
+
} else {
|
|
316
|
+
// Use the bounce buffer. ES gets set in the RMCS by the
|
|
317
|
+
// simulate-real-int wrapper variant below (the basic
|
|
318
|
+
// wrapper zeros ES; we need a custom path here).
|
|
319
|
+
static pktdrv_rmcs_t rm;
|
|
320
|
+
rm.edi = 0; // offset within the bounce buffer
|
|
321
|
+
rm.esi = 0;
|
|
322
|
+
rm.ebp = 0; rm.reserved = 0;
|
|
323
|
+
rm.ebx = (unsigned int)pktdrv_handle;
|
|
324
|
+
rm.edx = 0;
|
|
325
|
+
rm.ecx = 6;
|
|
326
|
+
rm.eax = 0x0600;
|
|
327
|
+
rm.flags = 0;
|
|
328
|
+
rm.es = (unsigned short)pktdrv_bounce_seg;
|
|
329
|
+
rm.ds = 0; rm.fs = 0; rm.gs = 0;
|
|
330
|
+
rm.ip = 0; rm.cs = 0; rm.sp = 0; rm.ss = 0;
|
|
331
|
+
unsigned int dpmi[8] = {0};
|
|
332
|
+
dpmi[R_EAX] = 0x0300;
|
|
333
|
+
dpmi[R_EBX] = (unsigned int)pktdrv_int_num & 0xFF;
|
|
334
|
+
dpmi[R_ECX] = 0;
|
|
335
|
+
dpmi[R_EDI] = (unsigned int)(unsigned long)&rm;
|
|
336
|
+
unsigned char carry = pktdrv_int_invoke(0x31, dpmi);
|
|
337
|
+
if (carry) {
|
|
338
|
+
return -1;
|
|
339
|
+
}
|
|
340
|
+
if (rm.flags & 1) {
|
|
341
|
+
return -1;
|
|
342
|
+
}
|
|
343
|
+
memcpy(out, (unsigned char *)pktdrv_bounce_linear, 6);
|
|
344
|
+
}
|
|
345
|
+
memcpy(pktdrv_mac_cache, out, 6);
|
|
346
|
+
return 0;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// AH=0x02 access_type — register `receiver` as the packet handler
|
|
350
|
+
// for `ethertype`. Use ethertype=0x0000 with type_len=0 to catch
|
|
351
|
+
// every protocol (broadcast netif sees ARP + IPv4 alike).
|
|
352
|
+
// AH=0x02 access_type — register the receiver with the driver.
|
|
353
|
+
// The `linear_receiver` argument is already encoded as a real-mode
|
|
354
|
+
// seg:offset packed into a single 32-bit value (high word = seg,
|
|
355
|
+
// low word = offset) by pktdrv_init when DPMI 0x0303 succeeded.
|
|
356
|
+
// We unpack that into the RMCS's ES:EDI directly. With type_len=0
|
|
357
|
+
// (catch-all), DS:SI is ignored.
|
|
358
|
+
//
|
|
359
|
+
// Under dos_emu the legacy bare-INT path remains as a fallback —
|
|
360
|
+
// dos_emu's AH=02 hook reads the linear receiver value out of EDI
|
|
361
|
+
// regardless.
|
|
362
|
+
static int pktdrv_access(unsigned int linear_receiver) {
|
|
363
|
+
if (pktdrv_int_num == 0) {
|
|
364
|
+
return -1;
|
|
365
|
+
}
|
|
366
|
+
static pktdrv_rmcs_t rm;
|
|
367
|
+
rm.edi = linear_receiver & 0xFFFF; // offset
|
|
368
|
+
rm.esi = 0;
|
|
369
|
+
rm.ebp = 0; rm.reserved = 0;
|
|
370
|
+
rm.ebx = 0; // if_type=0 (driver default)
|
|
371
|
+
rm.edx = 0; // if_number=0 (first card)
|
|
372
|
+
rm.ecx = 0; // type_len=0 (catch-all type)
|
|
373
|
+
rm.eax = 0x0201; // AH=02 access_type, AL=01 Ethernet DIX
|
|
374
|
+
rm.flags = 0;
|
|
375
|
+
rm.es = (unsigned short)((linear_receiver >> 16) & 0xFFFF);
|
|
376
|
+
rm.ds = 0; rm.fs = 0; rm.gs = 0;
|
|
377
|
+
rm.ip = 0; rm.cs = 0; rm.sp = 0; rm.ss = 0;
|
|
378
|
+
|
|
379
|
+
unsigned int dpmi[8] = {0};
|
|
380
|
+
dpmi[R_EAX] = 0x0300;
|
|
381
|
+
dpmi[R_EBX] = (unsigned int)pktdrv_int_num & 0xFF;
|
|
382
|
+
dpmi[R_ECX] = 0;
|
|
383
|
+
dpmi[R_EDI] = (unsigned int)(unsigned long)&rm;
|
|
384
|
+
unsigned char carry = pktdrv_int_invoke(0x31, dpmi);
|
|
385
|
+
if (carry) {
|
|
386
|
+
// DPMI 0x0300 itself failed (no DPMI host?). Try bare INT
|
|
387
|
+
// for dos_emu compatibility. dos_emu intercepts INT 0x60
|
|
388
|
+
// at the prot-mode level, so this works there.
|
|
389
|
+
unsigned int regs[8] = {0};
|
|
390
|
+
regs[R_EAX] = 0x0201; // AH=02 access_type, AL=01 Ethernet DIX
|
|
391
|
+
regs[R_EBX] = 0; // if_type=0 (driver default)
|
|
392
|
+
regs[R_ECX] = 0;
|
|
393
|
+
regs[R_EDX] = 0;
|
|
394
|
+
regs[R_EDI] = linear_receiver;
|
|
395
|
+
carry = pktdrv_int_invoke((unsigned int)pktdrv_int_num, regs);
|
|
396
|
+
if (carry) {
|
|
397
|
+
return -1;
|
|
398
|
+
}
|
|
399
|
+
pktdrv_handle = (int)(regs[R_EAX] & 0xFFFF);
|
|
400
|
+
pktdrv_last_handle = (unsigned int)(regs[R_EAX] & 0xFFFFFFFF);
|
|
401
|
+
return 0;
|
|
402
|
+
}
|
|
403
|
+
if (rm.flags & 1) {
|
|
404
|
+
return -1;
|
|
405
|
+
}
|
|
406
|
+
pktdrv_handle = (int)(rm.eax & 0xFFFF);
|
|
407
|
+
pktdrv_last_handle = (unsigned int)(rm.eax & 0xFFFFFFFF);
|
|
408
|
+
return 0;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// 32-bit receiver. cdecl signature; dos_emu's AH=0x99 polling path
|
|
412
|
+
// writes directly to pktdrv_rx_buf without ever calling this, but
|
|
413
|
+
// the DPMI thunk below DOES call it from real-mode-context after
|
|
414
|
+
// the packet driver has bounced through DPMI.
|
|
415
|
+
// phase==0: caller wants a buffer for `len` bytes. Return a flat
|
|
416
|
+
// pointer into pktdrv_rx_buf or NULL to drop.
|
|
417
|
+
// phase==1: caller has finished the copy. Mark the slot full.
|
|
418
|
+
unsigned char *uc386dos_pktdrv_receiver(int phase, unsigned int len) {
|
|
419
|
+
if (phase == 0) {
|
|
420
|
+
if (pktdrv_rx_pending || len > sizeof(pktdrv_rx_buf)) {
|
|
421
|
+
return NULL;
|
|
422
|
+
}
|
|
423
|
+
pktdrv_rx_len = len;
|
|
424
|
+
// On dos_emu the receiver writes directly to the flat
|
|
425
|
+
// pktdrv_rx_buf in BSS. On real DOS under PMODE/W the
|
|
426
|
+
// BSS lands above 1 MB so the seg:off encoding in the
|
|
427
|
+
// dpmi_thunk below would overflow — return the bounce
|
|
428
|
+
// buffer's flat-linear address instead so the encoding
|
|
429
|
+
// stays in range. After phase=1 we copy from bounce_linear
|
|
430
|
+
// to pktdrv_rx_buf for MP-side consumption.
|
|
431
|
+
if (pktdrv_bounce_seg != 0
|
|
432
|
+
&& len <= PKTDRV_BOUNCE_SIZE) {
|
|
433
|
+
return (unsigned char *)pktdrv_bounce_linear;
|
|
434
|
+
}
|
|
435
|
+
return pktdrv_rx_buf;
|
|
436
|
+
}
|
|
437
|
+
// phase == 1: real-mode driver has copied the frame in.
|
|
438
|
+
if (pktdrv_bounce_seg != 0 && pktdrv_rx_len <= sizeof(pktdrv_rx_buf)) {
|
|
439
|
+
memcpy(pktdrv_rx_buf,
|
|
440
|
+
(unsigned char *)pktdrv_bounce_linear,
|
|
441
|
+
pktdrv_rx_len);
|
|
442
|
+
}
|
|
443
|
+
pktdrv_rx_pending = 1;
|
|
444
|
+
return NULL;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// DPMI fn 0x0303 callback target — the 32-bit handler the DPMI
|
|
448
|
+
// host invokes when the real-mode trampoline fires. On entry:
|
|
449
|
+
// DS:ESI = ES:EDI = pointer to our pktdrv_rmcs (DPMI fills it
|
|
450
|
+
// from the saved real-mode register frame).
|
|
451
|
+
// Crynwr's calling convention puts phase in AX and length in CX,
|
|
452
|
+
// expects ES:DI = buffer on phase=0 return. We translate from
|
|
453
|
+
// the RMCS, drive the existing receiver, and write the buffer's
|
|
454
|
+
// real-mode seg:offset back into RMCS so DPMI can hand it to
|
|
455
|
+
// real mode on its way out.
|
|
456
|
+
//
|
|
457
|
+
// Cdecl signature so we can call it directly under emulation as
|
|
458
|
+
// well — dos_emu's DPMI fn 0x0303 emulation invokes this through
|
|
459
|
+
// the same path. (No-op under hardware DPMI; the host calls it.)
|
|
460
|
+
void uc386dos_pktdrv_dpmi_thunk(void) {
|
|
461
|
+
// Bump the invocation counter ASAP — IRQ context, can't safely
|
|
462
|
+
// call DOS for write(); the counter is what Python polls.
|
|
463
|
+
pktdrv_thunk_invocations++;
|
|
464
|
+
unsigned int phase = pktdrv_rmcs.eax & 0xFFFF;
|
|
465
|
+
unsigned int length = pktdrv_rmcs.ecx & 0xFFFF;
|
|
466
|
+
if (phase == 0) pktdrv_thunk_phase0_count++;
|
|
467
|
+
if (phase == 1) pktdrv_thunk_phase1_count++;
|
|
468
|
+
unsigned char *buf = uc386dos_pktdrv_receiver((int)phase, length);
|
|
469
|
+
if (phase == 0 && buf != NULL) {
|
|
470
|
+
unsigned int linear = (unsigned int)(unsigned long)buf;
|
|
471
|
+
// Real-mode seg:off encoding. The buffer must live in
|
|
472
|
+
// <1 MB conventional memory for this to be reachable;
|
|
473
|
+
// pktdrv_rx_buf is a static in our flat 32-bit BSS, so
|
|
474
|
+
// its linear address satisfies that on any reasonable
|
|
475
|
+
// PMODE/W layout (BSS lands well below 1 MB in our binary).
|
|
476
|
+
pktdrv_rmcs.es = (unsigned short)((linear >> 4) & 0xFFFF);
|
|
477
|
+
pktdrv_rmcs.edi = (linear & 0xF) | (pktdrv_rmcs.edi & 0xFFFF0000);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Allocate a real-mode callback via DPMI INT 0x31 fn 0x0303.
|
|
482
|
+
// Inputs (per spec):
|
|
483
|
+
// DS:ESI = address of our 32-bit handler
|
|
484
|
+
// ES:EDI = address of the RMCS buffer DPMI should populate
|
|
485
|
+
// Returns:
|
|
486
|
+
// CX:DX = real-mode segment:offset of the trampoline
|
|
487
|
+
// CF clear on success.
|
|
488
|
+
// On a host without DPMI (raw real-mode DOS), this returns CF=1
|
|
489
|
+
// and we leave pktdrv_dpmi_{seg,off} at 0; pktdrv_init then falls
|
|
490
|
+
// back to passing the flat receiver address to access_type, which
|
|
491
|
+
// works under dos_emu (with AH=0x99 polling) but not on real DOS.
|
|
492
|
+
static int pktdrv_alloc_dpmi_callback(void) {
|
|
493
|
+
unsigned int regs[8] = {0};
|
|
494
|
+
regs[R_EAX] = 0x0303;
|
|
495
|
+
regs[R_ESI] = (unsigned int)(unsigned long)
|
|
496
|
+
&uc386dos_pktdrv_dpmi_thunk;
|
|
497
|
+
regs[R_EDI] = (unsigned int)(unsigned long)&pktdrv_rmcs;
|
|
498
|
+
unsigned char carry = pktdrv_int_invoke(0x31, regs);
|
|
499
|
+
if (carry) {
|
|
500
|
+
return -1;
|
|
501
|
+
}
|
|
502
|
+
pktdrv_dpmi_seg = regs[R_ECX] & 0xFFFF;
|
|
503
|
+
pktdrv_dpmi_off = regs[R_EDX] & 0xFFFF;
|
|
504
|
+
return 0;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// AH=0x99 (uc386dos extension): hand the dos_emu harness pointers
|
|
508
|
+
// to pktdrv_rx_buf / pktdrv_rx_len / pktdrv_rx_pending so it can
|
|
509
|
+
// post inbound frames directly without going through the
|
|
510
|
+
// receiver-callback dance. Bypasses Crynwr's two-phase callback
|
|
511
|
+
// semantics — those need a real-mode trampoline that we'd allocate
|
|
512
|
+
// via DPMI INT 31h fn 0x0303 on hardware. Until that lands, this
|
|
513
|
+
// extension keeps the dos_emu test path strictly on the Crynwr INT
|
|
514
|
+
// number while still delivering RX frames. Real DOS Crynwr drivers
|
|
515
|
+
// don't implement AH=0x99; pktdrv_recv simply returns 0 there until
|
|
516
|
+
// the DPMI trampoline replaces this.
|
|
517
|
+
static void pktdrv_register_polling_rx(void) {
|
|
518
|
+
unsigned int regs[8] = {0};
|
|
519
|
+
regs[R_EAX] = 0x9900;
|
|
520
|
+
regs[R_EDI] = (unsigned int)(unsigned long)pktdrv_rx_buf;
|
|
521
|
+
regs[R_ESI] = (unsigned int)(unsigned long)&pktdrv_rx_pending;
|
|
522
|
+
regs[R_ECX] = (unsigned int)(unsigned long)&pktdrv_rx_len;
|
|
523
|
+
(void)pktdrv_int_invoke((unsigned int)pktdrv_int_num, regs);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Public init: detect, register, fetch MAC. Returns 0 on success.
|
|
527
|
+
//
|
|
528
|
+
// Order matters:
|
|
529
|
+
// 1. pktdrv_detect — find the Crynwr driver in the IVT.
|
|
530
|
+
// 2. pktdrv_alloc_dpmi_callback — get a real-mode trampoline
|
|
531
|
+
// address. On hardware that's required for AH=02 to register a
|
|
532
|
+
// callable receiver. On dos_emu we emulate fn 0x0303 too so
|
|
533
|
+
// the same code path runs uniformly.
|
|
534
|
+
// 3. pktdrv_access — register the trampoline (or, with DPMI
|
|
535
|
+
// missing, the flat receiver address — works under emulator,
|
|
536
|
+
// garbage on real DOS).
|
|
537
|
+
// 4. pktdrv_get_addr — read the MAC.
|
|
538
|
+
// 5. pktdrv_register_polling_rx — AH=0x99, the dos_emu RX
|
|
539
|
+
// bypass. No-op on real DOS where AH=0x99 isn't implemented.
|
|
540
|
+
int pktdrv_init(unsigned char mac[6]) {
|
|
541
|
+
if (pktdrv_detect() == 0) {
|
|
542
|
+
return -1;
|
|
543
|
+
}
|
|
544
|
+
// Allocate the conventional-memory bounce buffer first — used
|
|
545
|
+
// by pktdrv_get_addr / pktdrv_send below, and silently handed
|
|
546
|
+
// to the receiver thunk for the RX seg:offset encoding. On
|
|
547
|
+
// dos_emu DPMI 0x0100 returns success and we get a real
|
|
548
|
+
// segment; on a host without DPMI the call sets CF and we
|
|
549
|
+
// continue without a bounce buffer (fallback flat-pointer
|
|
550
|
+
// paths still work under emulation).
|
|
551
|
+
(void)pktdrv_alloc_bounce();
|
|
552
|
+
|
|
553
|
+
unsigned int receiver_linear;
|
|
554
|
+
extern int write(int fd, const void *buf, unsigned int n);
|
|
555
|
+
if (pktdrv_alloc_dpmi_callback() == 0) {
|
|
556
|
+
write(1, "[dpmi:cb-ok]", 12);
|
|
557
|
+
// DPMI trampoline succeeded — encode its real-mode
|
|
558
|
+
// seg:offset for access_type's ES:DI.
|
|
559
|
+
receiver_linear = (pktdrv_dpmi_seg << 16) | (pktdrv_dpmi_off & 0xFFFF);
|
|
560
|
+
} else {
|
|
561
|
+
write(1, "[dpmi:cb-fail]", 14);
|
|
562
|
+
receiver_linear = (unsigned int)(unsigned long)
|
|
563
|
+
&uc386dos_pktdrv_receiver;
|
|
564
|
+
}
|
|
565
|
+
if (pktdrv_access(receiver_linear) != 0) {
|
|
566
|
+
pktdrv_int_num = 0;
|
|
567
|
+
return -2;
|
|
568
|
+
}
|
|
569
|
+
if (pktdrv_get_addr(mac) != 0) {
|
|
570
|
+
pktdrv_int_num = 0;
|
|
571
|
+
return -3;
|
|
572
|
+
}
|
|
573
|
+
pktdrv_register_polling_rx();
|
|
574
|
+
return 0;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// AH=0x04 send_pkt — transmit a frame. The packet driver expects
|
|
578
|
+
// DS:SI to point at real-mode-addressable memory; copy `buf` into
|
|
579
|
+
// our DPMI-allocated bounce buffer and pass its seg:offset.
|
|
580
|
+
//
|
|
581
|
+
// Without a bounce buffer (early init or DPMI failure) we fall back
|
|
582
|
+
// to the bare-INT path with a flat-linear DS:SI value — dos_emu's
|
|
583
|
+
// AH=04 hook handles that, real DOS doesn't.
|
|
584
|
+
int pktdrv_send(const unsigned char *buf, unsigned int len) {
|
|
585
|
+
extern int write(int fd, const void *buf, unsigned int n);
|
|
586
|
+
write(1, "[ps:enter]", 10);
|
|
587
|
+
if (pktdrv_int_num == 0) {
|
|
588
|
+
return -1;
|
|
589
|
+
}
|
|
590
|
+
if (len > PKTDRV_BOUNCE_SIZE) {
|
|
591
|
+
return -1;
|
|
592
|
+
}
|
|
593
|
+
if (pktdrv_bounce_seg == 0) {
|
|
594
|
+
// Fallback path.
|
|
595
|
+
unsigned int regs[8] = {0};
|
|
596
|
+
regs[R_EAX] = 0x0400;
|
|
597
|
+
regs[R_ECX] = len;
|
|
598
|
+
regs[R_ESI] = (unsigned int)(unsigned long)buf;
|
|
599
|
+
unsigned char carry = pktdrv_int_invoke(
|
|
600
|
+
(unsigned int)pktdrv_int_num, regs);
|
|
601
|
+
return carry ? -1 : 0;
|
|
602
|
+
}
|
|
603
|
+
write(1, "[ps:cp]", 7);
|
|
604
|
+
memcpy((unsigned char *)pktdrv_bounce_linear, buf, len);
|
|
605
|
+
write(1, "[ps:rm]", 7);
|
|
606
|
+
static pktdrv_rmcs_t rm;
|
|
607
|
+
rm.edi = 0;
|
|
608
|
+
rm.esi = 0; // SI offset = 0 within bounce
|
|
609
|
+
rm.ebp = 0; rm.reserved = 0;
|
|
610
|
+
rm.ebx = (unsigned int)pktdrv_handle;
|
|
611
|
+
rm.edx = 0;
|
|
612
|
+
rm.ecx = len;
|
|
613
|
+
rm.eax = 0x0400;
|
|
614
|
+
rm.flags = 0;
|
|
615
|
+
rm.es = 0; rm.fs = 0; rm.gs = 0;
|
|
616
|
+
rm.ds = (unsigned short)pktdrv_bounce_seg;
|
|
617
|
+
rm.ip = 0; rm.cs = 0; rm.sp = 0; rm.ss = 0;
|
|
618
|
+
unsigned int dpmi[8] = {0};
|
|
619
|
+
dpmi[R_EAX] = 0x0300;
|
|
620
|
+
dpmi[R_EBX] = (unsigned int)pktdrv_int_num & 0xFF;
|
|
621
|
+
dpmi[R_ECX] = 0;
|
|
622
|
+
dpmi[R_EDI] = (unsigned int)(unsigned long)&rm;
|
|
623
|
+
write(1, "[ps:int]", 8);
|
|
624
|
+
unsigned char carry = pktdrv_int_invoke(0x31, dpmi);
|
|
625
|
+
write(1, "[ps:int-done]", 13);
|
|
626
|
+
if (carry || (rm.flags & 1)) {
|
|
627
|
+
return -1;
|
|
628
|
+
}
|
|
629
|
+
return 0;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// Drain the RX slot if one is pending. Returns the byte count
|
|
633
|
+
// written (clamped to maxlen) or 0 if nothing's queued. Truncates
|
|
634
|
+
// silently on overflow.
|
|
635
|
+
unsigned int pktdrv_recv(unsigned char *out, unsigned int maxlen) {
|
|
636
|
+
if (!pktdrv_rx_pending) {
|
|
637
|
+
return 0;
|
|
638
|
+
}
|
|
639
|
+
unsigned int n = pktdrv_rx_len;
|
|
640
|
+
if (n > maxlen) {
|
|
641
|
+
n = maxlen;
|
|
642
|
+
}
|
|
643
|
+
memcpy(out, pktdrv_rx_buf, n);
|
|
644
|
+
pktdrv_rx_pending = 0;
|
|
645
|
+
pktdrv_rx_len = 0;
|
|
646
|
+
return n;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// True when a Crynwr driver was successfully attached at init.
|
|
650
|
+
int pktdrv_is_active(void) { return pktdrv_int_num != 0; }
|