diff options
| -rw-r--r-- | c/gbos.h | 1 | ||||
| -rw-r--r-- | c/libc.s | 7 | ||||
| -rw-r--r-- | c/netd.c | 80 | ||||
| -rw-r--r-- | c/ping.c | 85 | ||||
| -rw-r--r-- | c/sock.h | 52 | ||||
| -rw-r--r-- | include/gbos.inc | 31 | ||||
| -rw-r--r-- | src/boot.asm | 1 | ||||
| -rw-r--r-- | src/socket.asm | 754 | ||||
| -rw-r--r-- | src/syscall.asm | 1 |
9 files changed, 872 insertions, 140 deletions
@@ -17,6 +17,7 @@ void ssend(unsigned char b); /* link-port: transmit one byte */ unsigned char srecv(void); /* link-port: receive one byte (blocks) */ int srecv_nb(void); /* link-port: receive, or -1 if none */ unsigned char pollin(void); /* poll OSK for a typed char, or 0 */ +unsigned char sys_net(void *req); /* socket layer trap (see sock.h) */ /* helpers (c/libc.c) */ void putu(unsigned int n); /* print decimal */ @@ -1,6 +1,7 @@ .module libc .globl _writes, _putc, _puts, _nl, _strlen, _readc, _sexit, _getpid, _getargs .globl _open, _close, _fgetc, _fputc, _flist, _fremove, _pipe, _ssend, _srecv, _srecv_nb, _pollin + .globl _sys_net ;; SDCC sm83 sdcccall(1): 1st arg -> A (byte) or DE (pointer); ret A / BC. ;; rst $30 (the trap) clobbers BC/DE/HL; SDCC treats them caller-saved, so ;; wrappers need not preserve them - but we compute cleanly regardless. @@ -116,6 +117,12 @@ _srecv_nb: 1$: ld bc, #0xffff ret + ;; unsigned char sys_net(void *req /*DE*/) -> A -- socket layer trap +_sys_net: + ld c, #33 ; SYS_NET (DE already = &netreq) + rst #0x30 + ret + ;; unsigned char pollin(void) -> A (typed OSK char, or 0) _pollin: ld c, #32 ; SYS_POLLIN @@ -1,78 +1,12 @@ -#include "netlib.h" -/* netd - a minimal IP stack over SLIP. Our address is 10.0.0.2, peer 10.0.0.1. - Milestones: A) ICMP echo, B) UDP echo. Raw byte arrays, network byte order - (big-endian) handled explicitly. */ - -static unsigned char pkt[256]; - -/* one's-complement sum (RFC 1071) with a running seed, carry-folded each add */ -static unsigned int sum16(unsigned char *d, unsigned int len, unsigned int sum) { - unsigned int w, i = 0; - while (i + 1 < len) { - w = ((unsigned int)d[i] << 8) | d[i + 1]; - sum += w; if (sum < w) sum++; - i += 2; - } - if (len & 1) { w = (unsigned int)d[len - 1] << 8; sum += w; if (sum < w) sum++; } - return sum; -} - -/* pseudo-header sum for UDP/TCP: src+dst IP, protocol, segment length */ -static unsigned int pseudo(unsigned char proto, unsigned int seglen) { - unsigned int sum = sum16(pkt + 12, 8, 0), w; - w = proto; sum += w; if (sum < w) sum++; - w = seglen; sum += w; if (sum < w) sum++; - return sum; -} - -static void swap4(unsigned char a, unsigned char b) { - unsigned char i, t; - for (i = 0; i < 4; i++) { t = pkt[a + i]; pkt[a + i] = pkt[b + i]; pkt[b + i] = t; } -} -static void swap2(unsigned char a, unsigned char b) { - unsigned char t; - t = pkt[a]; pkt[a] = pkt[b]; pkt[b] = t; - t = pkt[a + 1]; pkt[a + 1] = pkt[b + 1]; pkt[b + 1] = t; -} - -/* recompute the IP header checksum over ihl bytes, then send 'len' total */ -static void ip_send(unsigned char ihl, unsigned int len) { - unsigned int c; - pkt[10] = 0; pkt[11] = 0; - c = ~sum16(pkt, ihl, 0); - pkt[10] = (unsigned char)(c >> 8); pkt[11] = (unsigned char)c; - slip_send((char *)pkt, (unsigned char)len); -} +#include "sock.h" +/* netd - the network daemon. It just pumps the kernel RX path; the kernel's + socket layer owns SLIP/IP and auto-answers inbound ICMP echo requests, so + running netd makes the Game Boy respond to pings. No SLIP/IP here anymore. */ void main(void) { - unsigned char len, ihl; - unsigned int c, seglen; - puts("netd: IP up (10.0.0.2)"); nl(); + puts("netd: up (10.0.0.2)"); nl(); for (;;) { - len = slip_recv((char *)pkt, 255); - if (len < 20 || (pkt[0] >> 4) != 4) continue; - ihl = (pkt[0] & 0x0f) << 2; - pkt[8] = 64; /* TTL for any reply */ - - if (pkt[9] == 1) { /* ICMP */ - if (len < (unsigned char)(ihl + 8) || pkt[ihl] != 8) continue; /* echo request */ - swap4(12, 16); - pkt[ihl] = 0; /* -> echo reply */ - pkt[ihl + 2] = 0; pkt[ihl + 3] = 0; - c = ~sum16(pkt + ihl, (unsigned int)(len - ihl), 0); - pkt[ihl + 2] = (unsigned char)(c >> 8); pkt[ihl + 3] = (unsigned char)c; - ip_send(ihl, len); - - } else if (pkt[9] == 17) { /* UDP echo */ - if (len < (unsigned char)(ihl + 8)) continue; - seglen = (unsigned int)(len - ihl); - swap4(12, 16); /* swap IP addresses */ - swap2(ihl, ihl + 2); /* swap UDP ports */ - pkt[ihl + 6] = 0; pkt[ihl + 7] = 0; - c = ~sum16(pkt + ihl, seglen, pseudo(17, seglen)); - if (c == 0) c = 0xffff; /* UDP: 0 means "none" */ - pkt[ihl + 6] = (unsigned char)(c >> 8); pkt[ihl + 7] = (unsigned char)c; - ip_send(ihl, len); - } + net_poll(); /* drain RX, answer pings */ + yield(); } } @@ -1,17 +1,14 @@ -#include "netlib.h" -/* ping - originate ICMP echo requests from the Game Boy (10.0.0.2) and print - the replies. Target defaults to 10.0.0.1 (the SLIP peer / host); with NAT on - the bridge it can reach real internet hosts too. Usage: ping [A.B.C.D] */ +#include "sock.h" +/* ping - originate ICMP echo requests via the kernel socket layer. No SLIP, + no IP, no checksums here: the kernel owns all of that. Usage: ping [A.B.C.D] + (default 10.0.0.1, the SLIP peer/host; reaches real hosts through NAT). */ -static unsigned char pkt[128]; +/* rbuf is declared first (and large) so the parse target dst[] lands well past + the argument string the shell leaves at 0xA000 - writing dst must not clobber + the arg while we're still reading it. */ +static unsigned char rbuf[96]; static unsigned char dst[4]; - -static unsigned int sum16(unsigned char *d, unsigned int len, unsigned int sum) { - unsigned int w, i = 0; - while (i + 1 < len) { w = ((unsigned int)d[i] << 8) | d[i + 1]; sum += w; if (sum < w) sum++; i += 2; } - if (len & 1) { w = (unsigned int)d[len - 1] << 8; sum += w; if (sum < w) sum++; } - return sum; -} +static unsigned char payload[4]; static unsigned char parse_ip(char *s, unsigned char *out) { unsigned char i, v; @@ -24,77 +21,33 @@ static unsigned char parse_ip(char *s, unsigned char *out) { } return 1; } - static void put_ip(unsigned char *ip) { unsigned char i; for (i = 0; i < 4; i++) { putu(ip[i]); if (i < 3) putc('.'); } } -static void send_echo(unsigned char seq) { - unsigned int c; - pkt[0] = 0x45; pkt[1] = 0; pkt[2] = 0; pkt[3] = 28; /* 20 IP + 8 ICMP */ - pkt[4] = 0; pkt[5] = 0; pkt[6] = 0; pkt[7] = 0; - pkt[8] = 64; pkt[9] = 1; pkt[10] = 0; pkt[11] = 0; /* TTL, proto ICMP */ - pkt[12] = 10; pkt[13] = 0; pkt[14] = 0; pkt[15] = 2; /* src 10.0.0.2 */ - pkt[16] = dst[0]; pkt[17] = dst[1]; pkt[18] = dst[2]; pkt[19] = dst[3]; - c = ~sum16(pkt, 20, 0); pkt[10] = (unsigned char)(c >> 8); pkt[11] = (unsigned char)c; - pkt[20] = 8; pkt[21] = 0; pkt[22] = 0; pkt[23] = 0; /* echo request */ - pkt[24] = 0x12; pkt[25] = 0x34; pkt[26] = 0; pkt[27] = seq; - c = ~sum16(pkt + 20, 8, 0); pkt[22] = (unsigned char)(c >> 8); pkt[23] = (unsigned char)c; - slip_send((char *)pkt, 28); -} - -/* wait for the ICMP echo reply for 'seq', skipping unrelated frames (the shared - 10.0.0.0/24 carries IGMP/mDNS/SSDP noise). Returns 1 on match, 0 on timeout. - The emulator runs uncapped, so the spin budget is generous to span the - wall-clock round trip through the host bridge. */ -static unsigned char ping_wait(unsigned char seq) { - unsigned int o, i; - unsigned char len = 0, c, ihl; - int b; - for (o = 0; o < 20; o++) { - for (i = 0; i < 60000; i++) { - b = srecv_nb(); - if (b < 0) continue; - c = (unsigned char)b; - if (c == SLIP_END) { - if (len) { - ihl = (unsigned char)((pkt[0] & 0x0f) << 2); - if (len >= (unsigned char)(ihl + 8) && pkt[9] == 1 - && pkt[ihl] == 0 && pkt[ihl + 7] == seq) return 1; - len = 0; /* not ours - keep waiting */ - } - continue; - } - if (c == SLIP_ESC) { - c = srecv(); - if (c == SLIP_ESC_END) c = SLIP_END; - else if (c == SLIP_ESC_ESC) c = SLIP_ESC; - } - if (len < 128) pkt[len++] = c; else len = 0; - } - } - return 0; -} - void main(void) { char *arg; - unsigned char seq, got = 0; + unsigned char s, seq, got = 0; arg = getargs(); if (!arg || !*arg) { dst[0] = 10; dst[1] = 0; dst[2] = 0; dst[3] = 1; } else if (!parse_ip(arg, dst)) { puts("ping: bad address"); nl(); sexit(1); } + payload[0] = 'g'; payload[1] = 'b'; payload[2] = 'o'; payload[3] = 's'; + s = net_socket(SOCK_ICMP); + if (s == 0xFF) { puts("ping: no socket"); nl(); sexit(1); } + net_connect(s, dst, 0); + puts("PING "); put_ip(dst); nl(); for (seq = 1; seq <= 4; seq++) { - send_echo(seq); - if (ping_wait(seq)) { + net_send(s, payload, 4); + if (net_recv(s, rbuf, sizeof(rbuf)) != 0xFF) { got++; - puts(" reply from "); put_ip(pkt + 12); - puts(" seq="); putu(seq); - puts(" ttl="); putu(pkt[8]); nl(); + puts(" reply from "); put_ip(dst); puts(" seq="); putu(seq); nl(); } else { puts(" seq="); putu(seq); puts(" timeout"); nl(); } } puts("-- "); putu(got); puts("/4 received"); nl(); + net_close(s); } diff --git a/c/sock.h b/c/sock.h new file mode 100644 index 0000000..9af2351 --- /dev/null +++ b/c/sock.h @@ -0,0 +1,52 @@ +#ifndef SOCK_H +#define SOCK_H +/* gbos socket API - the kernel owns SLIP/IP/ICMP/UDP/TCP; programs just do + socket()/connect()/send()/recv()/close(). Header-only: one copy per program. + The netreq layout must match include/gbos.inc (NR_* / NET_* / SOCK_*). */ +#include "gbos.h" + +#define SOCK_ICMP 1 +#define SOCK_UDP 2 +#define SOCK_TCP 3 + +struct netreq { + unsigned char op; + unsigned char sock; + unsigned char *buf; + unsigned char len; + unsigned char ip[4]; + unsigned char port_hi, port_lo; +}; +static struct netreq _nr; + +/* -> socket id, or 0xFF if the table is full */ +static unsigned char net_socket(unsigned char type) { + _nr.op = 0; _nr.sock = type; + return sys_net(&_nr); +} +/* set the remote address/port for send()/recv() */ +static unsigned char net_connect(unsigned char s, const unsigned char *ip, unsigned int port) { + unsigned char i; + _nr.op = 1; _nr.sock = s; + for (i = 0; i < 4; i++) _nr.ip[i] = ip[i]; + _nr.port_hi = (unsigned char)(port >> 8); _nr.port_lo = (unsigned char)port; + return sys_net(&_nr); +} +/* send 'len' payload bytes to the connected peer -> 0 ok / 0xFF error */ +static unsigned char net_send(unsigned char s, void *buf, unsigned char len) { + _nr.op = 2; _nr.sock = s; _nr.buf = (unsigned char *)buf; _nr.len = len; + return sys_net(&_nr); +} +/* receive up to 'max' payload bytes -> length, or 0xFF on timeout */ +static unsigned char net_recv(unsigned char s, void *buf, unsigned char max) { + _nr.op = 3; _nr.sock = s; _nr.buf = (unsigned char *)buf; _nr.len = max; + return sys_net(&_nr); +} +static void net_close(unsigned char s) { + _nr.op = 4; _nr.sock = s; sys_net(&_nr); +} +/* pump the RX path once (answers inbound pings) without blocking */ +static void net_poll(void) { + _nr.op = 6; sys_net(&_nr); +} +#endif diff --git a/include/gbos.inc b/include/gbos.inc index a80841c..a4c9d52 100644 --- a/include/gbos.inc +++ b/include/gbos.inc @@ -130,7 +130,36 @@ DEF SYS_SSEND EQU 29 ; link-port: transmit one byte (E=byte) DEF SYS_SRECV EQU 30 ; link-port: receive one byte (-> A=byte, blocks) DEF SYS_SRECVNB EQU 31 ; link-port: receive, non-blocking (-> A=byte, CF=none) DEF SYS_POLLIN EQU 32 ; poll OSK for a typed char (-> A=char or 0) -DEF SYS_MAX EQU 33 +DEF SYS_NET EQU 33 ; socket layer (DE=&netreq -> A=result) +DEF SYS_MAX EQU 34 + +; ---- socket layer (SYS_NET) -------------------------------------------------- +; netreq struct (in caller RAM; DE points to it): +DEF NR_OP EQU 0 ; operation (below) +DEF NR_SOCK EQU 1 ; socket id +DEF NR_BUF EQU 2 ; 2: pointer to payload buffer +DEF NR_LEN EQU 4 ; 1: payload length (in) / bytes received (out) +DEF NR_IP EQU 5 ; 4: IPv4 address +DEF NR_PORT EQU 9 ; 2: port (hi,lo) +DEF NR_SIZE EQU 11 +; ops +DEF NET_SOCKET EQU 0 ; NR_SOCK(type in) -> A=sockid or $FF +DEF NET_CONNECT EQU 1 ; NR_SOCK, NR_IP, NR_PORT -> set peer +DEF NET_SEND EQU 2 ; NR_SOCK, NR_BUF, NR_LEN -> A=0/$FF +DEF NET_RECV EQU 3 ; NR_SOCK, NR_BUF, NR_LEN(max) -> A=len or $FF(timeout); fills NR_IP +DEF NET_CLOSE EQU 4 ; NR_SOCK +DEF NET_BIND EQU 5 ; NR_SOCK, NR_PORT -> bind local port (UDP) +DEF NET_POLL EQU 6 ; pump RX once (answer pings) -> A=0 +; socket types +DEF SOCK_ICMP EQU 1 +DEF SOCK_UDP EQU 2 +DEF SOCK_TCP EQU 3 +DEF MAX_SOCKS EQU 4 +; SLIP framing (RFC 1055) +DEF SLIP_END EQU $C0 +DEF SLIP_ESC EQU $DB +DEF SLIP_ESC_END EQU $DC +DEF SLIP_ESC_ESC EQU $DD ; SYS_KILL (9) is now implemented (B=pid -> A=0/$FF; pid 1 is immortal) ; (SYS_OPEN=4 / SYS_CLOSE=5 are now implemented by the filesystem) diff --git a/src/boot.asm b/src/boot.asm index 23511c4..c29267d 100644 --- a/src/boot.asm +++ b/src/boot.asm @@ -56,6 +56,7 @@ KernelInit: ld a, HIGH(wKernelPCB) ld [wCurProc+1], a call pipe_init ; mark all pipes free + call net_init ; clear the socket table, set our IP (10.0.0.2) call fs_init ; mount the disk (format on first boot; persists) call term_init ; set up the LCD text terminal call osk_init ; build the on-screen keyboard (window layer) diff --git a/src/socket.asm b/src/socket.asm new file mode 100644 index 0000000..f0d8444 --- /dev/null +++ b/src/socket.asm @@ -0,0 +1,754 @@ +; ============================================================================= +; socket.asm - the kernel network stack + socket layer (SYS_NET). +; +; The kernel owns the link port: SLIP framing, IPv4, checksums, and the per- +; protocol logic all live here. Userland just does socket()/connect()/send()/ +; recv()/close() via one syscall (DE = &netreq). No program touches SLIP or IP. +; +; Our address is 10.0.0.2. Incoming ICMP echo requests are auto-answered while +; any process pumps RX (recv/poll), so the Game Boy responds to pings for real. +; +; Phase 1: ICMP (echo). UDP and TCP build on the same frame/checksum core. +; ============================================================================= +INCLUDE "include/gbos.inc" + +; ---- socket entry layout ---------------------------------------------------- +DEF SK_TYPE EQU 0 ; 0 = free +DEF SK_RIP EQU 1 ; 4 remote IP +DEF SK_RPORT EQU 5 ; 2 remote port +DEF SK_LPORT EQU 7 ; 2 local port +DEF SK_HASRX EQU 9 ; 1 rxbuf holds a datagram +DEF SK_RXLEN EQU 10 ; 1 payload length +DEF SK_SRCIP EQU 11 ; 4 sender IP +DEF SK_SRCPORT EQU 15 ; 2 sender port +DEF SK_RXBUF EQU 17 ; 96 payload +DEF SK_SIZE EQU 113 + +SECTION "net_bss", WRAM0 +wSockTab:: DS MAX_SOCKS * SK_SIZE +wNetOurIP:: DS 4 +wNetReq:: DS NR_SIZE +wNetSockPtr:: DS 2 +wNetIhl:: DS 1 +wNetSeq:: DS 1 +wNetTx:: DS 256 ; tx build buffer +wNetRxBuf:: DS 256 ; rx frame assembly / parse +wNetRxLen:: DS 1 +wNetRxEsc:: DS 1 + +SECTION "socket", ROM0 + +; net_init - clear the socket table and set our IP (called at boot). +net_init:: + ld hl, wSockTab + ld bc, MAX_SOCKS * SK_SIZE +.clr + xor a + ld [hl+], a + dec bc + ld a, b + or c + jr nz, .clr + ld a, 10 + ld [wNetOurIP+0], a + xor a + ld [wNetOurIP+1], a + ld [wNetOurIP+2], a + ld a, 2 + ld [wNetOurIP+3], a + xor a + ld [wNetRxLen], a + ld [wNetRxEsc], a + ld [wNetSeq], a + ret + +; ----------------------------------------------------------------------------- +; low-level link port (byte at a time). Preserve BC/DE/HL; use A only. +net_putbyte: ; A = byte to transmit + ld [rSB], a + ld a, $81 + ld [rSC], a +.w + ld a, [rSC] + bit 7, a + jr nz, .w + ret + +net_getbyte_nb: ; -> A = byte, CF set if none + ld a, $80 + ld [rSC], a + ld a, [rSC] + bit 7, a + jr z, .got + scf + ret +.got + ld a, [rSB] + and a ; CF = 0 + ret + +; ----------------------------------------------------------------------------- +; net_slip_send(HL = buf, B = len) - transmit a SLIP frame. +net_slip_send: + ld a, SLIP_END + call net_putbyte +.loop + ld a, b + or a + jr z, .end + ld a, [hl+] + cp SLIP_END + jr z, .esc_end + cp SLIP_ESC + jr z, .esc_esc + call net_putbyte +.cont + dec b + jr .loop +.esc_end + ld a, SLIP_ESC + call net_putbyte + ld a, SLIP_ESC_END + call net_putbyte + jr .cont +.esc_esc + ld a, SLIP_ESC + call net_putbyte + ld a, SLIP_ESC_ESC + call net_putbyte + jr .cont +.end + ld a, SLIP_END + call net_putbyte + ret + +; ----------------------------------------------------------------------------- +; net_cksum(HL = buf, B = len, DE = seed) -> HL = inverted 16-bit checksum. +; One's-complement sum (RFC 1071), carry-folded each add. len <= 255. +net_cksum: +.loop + ld a, b + cp 2 + jr c, .tail + ld a, [hl+] ; high byte + ld c, a + ld a, [hl+] ; low byte + add e + ld e, a + ld a, c + adc d + ld d, a + jr nc, .nc1 + inc de +.nc1 + dec b + dec b + jr .loop +.tail + ld a, b + or a + jr z, .fold + ld a, [hl] ; last odd byte -> high half + add d + ld d, a + jr nc, .fold + inc e + jr nz, .fold + inc d +.fold + ld a, d + cpl + ld h, a + ld a, e + cpl + ld l, a + ret + +; ----------------------------------------------------------------------------- +; net_sock_ptr(A = sockid) -> HL = &wSockTab[A] (clobbers B, DE) +net_sock_ptr: + ld hl, wSockTab + or a + ret z + ld b, a + ld de, SK_SIZE +.l + add hl, de + dec b + jr nz, .l + ret + +; ============================================================================= +; sys_net(DE = &netreq) -> A = result +; ============================================================================= +sys_net:: + ld hl, wNetReq ; copy the request out of process RAM + ld b, NR_SIZE +.cp + ld a, [de] + ld [hl+], a + inc de + dec b + jr nz, .cp + ld a, [wNetReq + NR_OP] + cp NET_SOCKET + jp z, net_op_socket + cp NET_CONNECT + jp z, net_op_connect + cp NET_SEND + jp z, net_op_send + cp NET_RECV + jp z, net_op_recv + cp NET_CLOSE + jp z, net_op_close + cp NET_POLL + jp z, net_op_poll + ld a, $FF + ret + +; ---- socket(type) -> A = sockid or $FF --------------------------------------- +net_op_socket: + ld c, 0 +.scan + ld a, c + call net_sock_ptr ; HL = &sock[c] + ld a, [hl] + or a ; SK_TYPE == 0 (free)? + jr z, .free + inc c + ld a, c + cp MAX_SOCKS + jr c, .scan + ld a, $FF ; table full + ret +.free + ld a, [wNetReq + NR_SOCK] ; requested type + ld [hl], a ; SK_TYPE + ld a, l ; clear SK_HASRX + add SK_HASRX + ld l, a + ld a, h + adc 0 + ld h, a + xor a + ld [hl], a + ld a, c ; return sockid + ret + +; ---- connect(sock, ip, port) - set remote -------------------------------- +net_op_connect: + ld a, [wNetReq + NR_SOCK] + call net_sock_ptr ; HL = &sock + ld a, l + add SK_RIP + ld l, a + ld a, h + adc 0 + ld h, a ; HL = &sock.RIP + ld de, wNetReq + NR_IP + ld b, 6 ; 4 IP + 2 port (contiguous: RIP then RPORT) +.cp + ld a, [de] + ld [hl+], a + inc de + dec b + jr nz, .cp + xor a + ret + +; ---- close(sock) ------------------------------------------------------------- +net_op_close: + ld a, [wNetReq + NR_SOCK] + call net_sock_ptr + xor a + ld [hl], a ; SK_TYPE = free + ret + +; ---- poll - pump RX once (answers pings) ------------------------------------- +net_op_poll: + call net_pump + xor a + ret + +; ---- send(sock, buf, len) ---------------------------------------------------- +net_op_send: + ld a, [wNetReq + NR_SOCK] + call net_sock_ptr + ld a, l + ld [wNetSockPtr], a + ld a, h + ld [wNetSockPtr+1], a + ld a, [hl] ; SK_TYPE + cp SOCK_ICMP + jp z, icmp_send + ld a, $FF + ret + +; ---- recv(sock, buf, maxlen) -> A = len or $FF (timeout) --------------------- +net_op_recv: + ld a, [wNetReq + NR_SOCK] + call net_sock_ptr + ld a, l + ld [wNetSockPtr], a + ld a, h + ld [wNetSockPtr+1], a + ld b, 200 ; outer timeout +.outer + ld de, 0 +.inner + call net_pump + call sock_has_rx ; Z clear (nz) if HASRX set + jr nz, .got + dec de + ld a, d + or e + jr nz, .inner + call SchedYield + dec b + jr nz, .outer + ld a, $FF ; timeout + ret +.got + ; copy sock.RXBUF -> process NR_BUF, len = sock.RXLEN, NR_IP = sock.SRCIP + call sock_ptr_hl ; HL = &sock + push hl + ld a, l + add SK_RXLEN + ld l, a + ld a, h + adc 0 + ld h, a + ld a, [hl] ; RXLEN + ld c, a ; C = length + pop hl + ; source IP -> wNetReq.NR_IP (returned to caller via buffer? no - copy to caller req is skipped; give src via NR_IP in kernel copy only) + push bc + push hl + ; dst = process buf pointer + ld a, [wNetReq + NR_BUF] + ld e, a + ld a, [wNetReq + NR_BUF + 1] + ld d, a ; DE = process buf + ld a, l + add SK_RXBUF + ld l, a + ld a, h + adc 0 + ld h, a ; HL = &sock.RXBUF + ld a, c + or a + jr z, .nocopy +.cpl + ld a, [hl+] + ld [de], a + inc de + dec c + jr nz, .cpl +.nocopy + pop hl ; &sock + pop bc ; C = length + ; clear HASRX + ld a, l + add SK_HASRX + ld l, a + ld a, h + adc 0 + ld h, a + xor a + ld [hl], a + ld a, c ; return length + ret + +; sock_ptr_hl -> HL = current socket pointer (from wNetSockPtr) +sock_ptr_hl: + ld a, [wNetSockPtr] + ld l, a + ld a, [wNetSockPtr+1] + ld h, a + ret + +; sock_has_rx -> Z clear (nz) if the current socket has a buffered datagram +sock_has_rx: + call sock_ptr_hl + ld a, l + add SK_HASRX + ld l, a + ld a, h + adc 0 + ld h, a + ld a, [hl] + or a + ret + +; ============================================================================= +; icmp_send - build + transmit an ICMP echo request from the current socket. +; ============================================================================= +icmp_send: + ; IP header (20 bytes) + ld a, $45 + ld [wNetTx+0], a + xor a + ld [wNetTx+1], a + ld [wNetTx+2], a ; total len hi + ld a, [wNetReq+NR_LEN] + add 28 + ld [wNetTx+3], a ; total len lo = 28 + payload + xor a + ld [wNetTx+4], a + ld [wNetTx+5], a ; id + ld [wNetTx+6], a + ld [wNetTx+7], a ; flags/frag + ld a, 64 + ld [wNetTx+8], a ; ttl + ld a, 1 + ld [wNetTx+9], a ; proto ICMP + xor a + ld [wNetTx+10], a + ld [wNetTx+11], a ; ip cksum + ld a, [wNetOurIP+0] + ld [wNetTx+12], a + ld a, [wNetOurIP+1] + ld [wNetTx+13], a + ld a, [wNetOurIP+2] + ld [wNetTx+14], a + ld a, [wNetOurIP+3] + ld [wNetTx+15], a + ; dst IP = sock.RIP + call sock_ptr_hl + ld a, l + add SK_RIP + ld l, a + ld a, h + adc 0 + ld h, a ; HL = &sock.RIP + ld de, wNetTx+16 + ld b, 4 +.dip + ld a, [hl+] + ld [de], a + inc de + dec b + jr nz, .dip + ; ICMP header (8 bytes) at wNetTx+20 + ld a, 8 + ld [wNetTx+20], a ; type = echo request + xor a + ld [wNetTx+21], a ; code + ld [wNetTx+22], a + ld [wNetTx+23], a ; cksum + ld a, $12 + ld [wNetTx+24], a ; id + ld a, $34 + ld [wNetTx+25], a + xor a + ld [wNetTx+26], a ; seq hi + ld a, [wNetSeq] + inc a + ld [wNetSeq], a + ld [wNetTx+27], a ; seq lo + ; payload from process buf + ld a, [wNetReq+NR_BUF] + ld l, a + ld a, [wNetReq+NR_BUF+1] + ld h, a + ld de, wNetTx+28 + ld a, [wNetReq+NR_LEN] + ld b, a + or a + jr z, .nopl +.cppl + ld a, [hl+] + ld [de], a + inc de + dec b + jr nz, .cppl +.nopl + ; ICMP checksum over 8 + payload + ld a, [wNetReq+NR_LEN] + add 8 + ld b, a + ld hl, wNetTx+20 + ld de, 0 + call net_cksum + ld a, h + ld [wNetTx+22], a + ld a, l + ld [wNetTx+23], a + ; IP checksum over 20 + ld hl, wNetTx + ld b, 20 + ld de, 0 + call net_cksum + ld a, h + ld [wNetTx+10], a + ld a, l + ld [wNetTx+11], a + ; transmit: 28 + payload + ld a, [wNetReq+NR_LEN] + add 28 + ld b, a + ld hl, wNetTx + call net_slip_send + xor a + ret + +; ============================================================================= +; net_pump - read available serial bytes, assemble SLIP frames, process each. +; Non-blocking: returns when the link has no more bytes ready. +; ============================================================================= +net_pump: +.next + call net_getbyte_nb + ret c ; no byte ready + ; SLIP decode using wNetRxLen / wNetRxEsc + cp SLIP_END + jr z, .frame + ld c, a ; save byte + ld a, [wNetRxEsc] + or a + jr nz, .escaped + ld a, c + cp SLIP_ESC + jr z, .setesc + jr .store +.escaped + ld a, c + cp SLIP_ESC_END + jr nz, .notend + ld c, SLIP_END +.notend + ld a, c + cp SLIP_ESC_ESC + jr nz, .noteesc + ld c, SLIP_ESC +.noteesc + xor a + ld [wNetRxEsc], a + ld a, c + jr .store2 +.setesc + ld a, 1 + ld [wNetRxEsc], a + jr .next +.store + ld a, c +.store2 + ; store A at wNetRxBuf[wNetRxLen] if room + ld c, a + ld a, [wNetRxLen] + cp 255 + jr nc, .next ; overflow: drop + ld e, a + ld d, 0 + ld hl, wNetRxBuf + add hl, de + ld a, c + ld [hl], a + ld a, [wNetRxLen] + inc a + ld [wNetRxLen], a + jr .next +.frame + ; END: if we have a frame, process it + xor a + ld [wNetRxEsc], a + ld a, [wNetRxLen] + or a + jr z, .next ; empty frame + push bc + call process_frame + pop bc + xor a + ld [wNetRxLen], a + jr .next + +; ----------------------------------------------------------------------------- +; process_frame - IPv4 demux of the frame in wNetRxBuf (wNetRxLen bytes). +process_frame: + ld a, [wNetRxBuf] + and $F0 + cp $40 + ret nz ; not IPv4 + ld a, [wNetRxBuf] + and $0F + add a, a + add a, a + ld [wNetIhl], a ; ihl in bytes + ld a, [wNetRxBuf+9] ; protocol + cp 1 + jr z, icmp_in + ret + +; ----------------------------------------------------------------------------- +; icmp_in - handle an inbound ICMP message. +icmp_in: + ld a, [wNetIhl] + ld e, a + ld d, 0 + ld hl, wNetRxBuf + add hl, de ; HL = &icmp + ld a, [hl] ; type + cp 8 + jr z, icmp_request + or a ; type 0 = echo reply + jr z, icmp_reply + ret + +; incoming echo request -> answer it in place +icmp_request: + ld a, [wNetIhl] + ld e, a + ld d, 0 + ld hl, wNetRxBuf + add hl, de ; HL = &icmp + xor a + ld [hl+], a ; type = 0 + inc hl ; skip code + xor a + ld [hl+], a + ld [hl], a ; cksum = 0 + call net_swap_ip + ; icmp cksum over (rxlen - ihl) + ld a, [wNetIhl] + ld e, a + ld d, 0 + ld hl, wNetRxBuf + add hl, de ; HL = &icmp + ld a, [wNetRxLen] + sub e + ld b, a + ld de, 0 + push hl + call net_cksum ; HL = cksum + pop de ; DE = &icmp + ld a, e + add 2 + ld e, a + ld a, d + adc 0 + ld d, a ; DE = &icmp+2 + ld a, h + ld [de], a + inc de + ld a, l + ld [de], a + ; IP cksum + xor a + ld [wNetRxBuf+10], a + ld [wNetRxBuf+11], a + ld a, [wNetIhl] + ld b, a + ld hl, wNetRxBuf + ld de, 0 + call net_cksum + ld a, h + ld [wNetRxBuf+10], a + ld a, l + ld [wNetRxBuf+11], a + ; send it back + ld a, [wNetRxLen] + ld b, a + ld hl, wNetRxBuf + call net_slip_send + ret + +; incoming echo reply -> deliver to the first ICMP socket +icmp_reply: + ld c, 0 +.scan + ld a, c + call net_sock_ptr ; HL = &sock + ld a, [hl] + cp SOCK_ICMP + jr z, .deliver + inc c + ld a, c + cp MAX_SOCKS + jr c, .scan + ret ; no ICMP socket +.deliver + ; HL = &sock. payload = wNetRxBuf + ihl + 8, len = rxlen - ihl - 8 + push hl + ; set HASRX = 1 + ld a, l + add SK_HASRX + ld l, a + ld a, h + adc 0 + ld h, a + ld a, 1 + ld [hl], a + pop hl + ; SRCIP = wNetRxBuf[12..15] + push hl + ld a, l + add SK_SRCIP + ld l, a + ld a, h + adc 0 + ld h, a + ld de, wNetRxBuf+12 + ld b, 4 +.sip + ld a, [de] + ld [hl+], a + inc de + dec b + jr nz, .sip + pop hl + ; RXLEN + RXBUF + ld a, [wNetIhl] + add 8 + ld e, a + ld d, 0 ; DE = ihl+8 (payload offset) + ld a, [wNetRxLen] + sub e + ld c, a ; C = payload length + push hl + ld a, l + add SK_RXLEN + ld l, a + ld a, h + adc 0 + ld h, a + ld a, c + ld [hl], a ; RXLEN + pop hl + ; copy payload + ld a, l + add SK_RXBUF + ld l, a + ld a, h + adc 0 + ld h, a ; HL = &sock.RXBUF (dest) + push hl + ld hl, wNetRxBuf + add hl, de ; HL = &payload (src) + pop de ; DE = dest + ld a, c + or a + ret z +.cp + ld a, [hl+] + ld [de], a + inc de + dec c + jr nz, .cp + ret + +; net_swap_ip - swap the src/dst IP fields of the frame in wNetRxBuf +net_swap_ip: + ld hl, wNetRxBuf+12 + ld de, wNetRxBuf+16 + ld b, 4 +.l + ld a, [hl] + ld c, a + ld a, [de] + ld [hl+], a + ld a, c + ld [de], a + inc de + dec b + jr nz, .l + ret diff --git a/src/syscall.asm b/src/syscall.asm index f926d89..5b40ef0 100644 --- a/src/syscall.asm +++ b/src/syscall.asm @@ -67,6 +67,7 @@ SyscallTable: dw sys_srecv ; 30 SRECV dw sys_srecv_nb ; 31 SRECVNB dw sys_pollin ; 32 POLLIN + dw sys_net ; 33 NET ; ----------------------------------------------------------------------------- sys_nosys: |
