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.
Files changed (37) hide show
  1. freedos_micro_python/__init__.py +8 -0
  2. freedos_micro_python/cli.py +106 -0
  3. freedos_micro_python/gen_qstrdefs.py +275 -0
  4. freedos_micro_python/port/arch/bpstruct.h +2 -0
  5. freedos_micro_python/port/arch/cc.h +4 -0
  6. freedos_micro_python/port/arch/epstruct.h +1 -0
  7. freedos_micro_python/port/base64_uc386dos.c +164 -0
  8. freedos_micro_python/port/file_uc386dos.c +228 -0
  9. freedos_micro_python/port/lib/axtls/crypto/crypto.h +45 -0
  10. freedos_micro_python/port/lwip-arch-cc.h +46 -0
  11. freedos_micro_python/port/lwip_uc386dos.c +248 -0
  12. freedos_micro_python/port/lwipopts.h +117 -0
  13. freedos_micro_python/port/math_gamma.c +63 -0
  14. freedos_micro_python/port/modtime_uc386dos.c +60 -0
  15. freedos_micro_python/port/modtls_axtls_uc386dos.c +461 -0
  16. freedos_micro_python/port/mpconfigport.h +358 -0
  17. freedos_micro_python/port/mphal_uc386dos.c +103 -0
  18. freedos_micro_python/port/mphalport.h +11 -0
  19. freedos_micro_python/port/os_uc386dos.c +264 -0
  20. freedos_micro_python/port/path_uc386dos.c +307 -0
  21. freedos_micro_python/port/pktdrv_uc386dos.c +650 -0
  22. freedos_micro_python/port/qstrdefsport.h +2 -0
  23. freedos_micro_python/port/shutil_uc386dos.c +111 -0
  24. freedos_micro_python/port/tempfile_uc386dos.c +129 -0
  25. freedos_micro_python/port/time_real_uc386dos.c +77 -0
  26. freedos_micro_python/port/uc386_net_uc386dos.c +126 -0
  27. freedos_micro_python/port/urllib_parse_uc386dos.c +360 -0
  28. freedos_micro_python/port/urllib_uc386dos.c +29 -0
  29. freedos_micro_python/scripts/build.sh +641 -0
  30. freedos_micro_python/scripts/build_port.sh +241 -0
  31. freedos_micro_python/scripts/fetch.sh +238 -0
  32. freedos_micro_python-0.1.0.dist-info/METADATA +131 -0
  33. freedos_micro_python-0.1.0.dist-info/RECORD +37 -0
  34. freedos_micro_python-0.1.0.dist-info/WHEEL +5 -0
  35. freedos_micro_python-0.1.0.dist-info/entry_points.txt +2 -0
  36. freedos_micro_python-0.1.0.dist-info/licenses/LICENSE +25 -0
  37. 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; }
@@ -0,0 +1,2 @@
1
+ // qstrs specific to the uc386-dos port. Empty for now — same as the
2
+ // minimal port. *FORMAT-OFF*