diff options
| author | user <user@clank> | 2026-07-17 12:39:01 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-07-17 12:39:01 +0200 |
| commit | 09ae37313a8b6d567a05a3ae886de78e88cd1afe (patch) | |
| tree | 8d4707fbd2c6c7fec56a1c9c31abbd9dc66cf7bb /src | |
| parent | net: ping - GB-originated ICMP echo (a Game Boy pings the real internet) (diff) | |
| download | gbos-09ae37313a8b6d567a05a3ae886de78e88cd1afe.tar.gz gbos-09ae37313a8b6d567a05a3ae886de78e88cd1afe.tar.xz gbos-09ae37313a8b6d567a05a3ae886de78e88cd1afe.zip | |
net: proper socket API in the kernel (SYS_NET) - no more SLIP in userland
The network stack moves into the kernel. src/socket.asm owns SLIP framing,
IPv4, RFC1071 checksums, and ICMP; programs now speak a socket API through one
syscall (SYS_NET, DE=&netreq dispatched by op): net_socket/connect/send/recv/
close/poll (c/sock.h). No program touches SLIP, IP headers, or checksums.
- Socket table (4 sockets) + tx/rx buffers in WRAM0; our IP = 10.0.0.2.
- net_pump: drains the link, reassembles SLIP frames, demuxes IPv4. Inbound
ICMP echo requests are auto-answered in-kernel, so the GB replies to pings
whenever any process pumps RX.
- ICMP sockets: send() emits an echo request to the connected peer; recv()
returns the matching reply (with a spin/yield timeout).
ping.c is now a ~15-line socket client; netd.c is just `for(;;){net_poll();
yield();}`. Verified over tunbridge:
/# ping 1.1.1.1 -> replies from the real internet (kernel builds it all)
host# ping 10.0.0.2 -> 4/4, 0% loss (kernel auto-answers)
Gotchas recorded: gbos.inc isn't a make dep (touch asm after editing); this
crt0 doesn't copy initializers (fill arrays at runtime); and the arg string at
0xA000 overlaps _DATA, so parse targets must sit past it (big buffer first).
UDP and TCP sockets build on this same core next.
Diffstat (limited to 'src')
| -rw-r--r-- | src/boot.asm | 1 | ||||
| -rw-r--r-- | src/socket.asm | 754 | ||||
| -rw-r--r-- | src/syscall.asm | 1 |
3 files changed, 756 insertions, 0 deletions
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: |
