From 6680772b04c33957e62e229bb3543df4bf6986dd Mon Sep 17 00:00:00 2001 From: Oliver Schmidt Date: Sun, 5 Nov 2017 14:28:49 +0100 Subject: [PATCH] Introduced C interface to IP65. The IP5 usage of ld65 segments and zeropage variables was made compatible with cc65 C programs already a while ago. This commit is the next logical step which is to introduce the actual C interface to IP65. IP65 for C programs shares the the ip65.lib / ip65_tcp.lib with IP65 for assembler programs. However the various libraries from the 'drivers' are not reused. Instead there's exactly one library for every target named ip65_.lib. Those libraries contain only functions used by ip65.lib / ip65_tcp.lib. TODOs: - Introduce c64_timer.s and atr_timer.s. - Add a C interface to the rest of the IP65 functionality (especially TCP). --- drivers/Makefile | 83 ++++++++++++----- drivers/a2_input.s | 46 ++++++++++ drivers/a2_timer.s | 73 +++++++++++++++ drivers/atr_input.s | 49 ++++++++++ drivers/c64_input.s | 54 +++++++++++ inc/ip65.h | 181 +++++++++++++++++++++++++++++++++++++ ip65/Makefile | 7 ++ ip65/config_c.s | 27 ++++++ ip65/dhcp_c.s | 12 +++ ip65/dns_c.s | 25 ++++++ ip65/dottedquad_c.s | 92 +++++++++++++++++++ ip65/icmp_c.s | 18 ++++ ip65/ip65_c.s | 29 ++++++ ip65/udp_c.s | 83 +++++++++++++++++ test/Makefile | 23 ++++- test/peer.c | 212 ++++++++++++++++++++++++++++++++++++++++++++ test/udp.c | 114 ++++++++++++++++++++++++ 17 files changed, 1107 insertions(+), 21 deletions(-) create mode 100644 drivers/a2_input.s create mode 100644 drivers/a2_timer.s create mode 100644 drivers/atr_input.s create mode 100644 drivers/c64_input.s create mode 100644 inc/ip65.h create mode 100644 ip65/config_c.s create mode 100644 ip65/dhcp_c.s create mode 100644 ip65/dns_c.s create mode 100644 ip65/dottedquad_c.s create mode 100644 ip65/icmp_c.s create mode 100644 ip65/ip65_c.s create mode 100644 ip65/udp_c.s create mode 100644 test/peer.c create mode 100644 test/udp.c diff --git a/drivers/Makefile b/drivers/Makefile index 0c7bc4b..ef6cd97 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -1,3 +1,5 @@ +# For assembler programs +# ---------------------- # c64rrnet.lib : C64 with RR-Net (or clone) (default base addr: $de0x) # c64eth64.lib : C64 with ETH64 (default base addr: $de0x) # c64wiz811.lib : C64 with WIZ811MJ (default base addr: $de0x) @@ -9,20 +11,32 @@ # atrdragon.lib : ATARI 8-bit with Dragon Cart (default base addr: $d500) # vic20rrnet.lib : VIC20 with RR-Net or clone (default base addr: $980x) +# For C programs +# -------------- +# ip65_c64.lib : C64 with RR-Net or ETH64 or WIZ811MJ (default base addr: $de0x) +# ip65_apple2.lib : Apple ][ with Uthernet or LANceGS or Uthernet II (default slot: #3) +# ip65_atari.lib : ATARI 8-bit with Dragon Cart (default base addr: $d500) + DRIVERS=\ c64rrnet.lib \ c64eth64.lib \ c64wiz811.lib \ c64combo.lib \ + ip65_c64.lib \ a2uther.lib \ a2lancegs.lib \ a2uther2.lib \ a2combo.lib \ + ip65_apple2.lib \ atrdragon.lib \ + ip65_atari.lib \ vic20rrnet.lib all: $(DRIVERS) +$(DRIVERS): + ar65 a $@ $^ + %.o: %.s ca65 -D DYN_DRV=0 $< @@ -35,6 +49,10 @@ C64OBJS=\ c64vt100.o \ cbmcharconv.o +C64_OBJS=\ + c64timer.o \ + c64_input.o + A2OBJS=\ a2print.o \ a2timer.o \ @@ -44,6 +62,10 @@ A2OBJS=\ a2vt100.o \ a2charconv.o +A2_OBJS=\ + a2_timer.o \ + a2_input.o + ATROBJS=\ atrprint.o \ atrtimer.o \ @@ -54,6 +76,10 @@ ATROBJS=\ atrvt100font.o \ atrcharconv.o +ATR_OBJS=\ + atrtimer.o \ + atr_input.o + VIC20OBJS=\ vic20print.o \ vic20timer.o \ @@ -63,35 +89,52 @@ VIC20OBJS=\ vic20vt100.o \ cbmcharconv.o -c64rrnet.lib: rr-net.o cs8900a.o cs8900adriver.o ethernet.o c64init.o $(C64OBJS) - ar65 a $@ $^ +CS8900AOBJS=\ + cs8900a.o \ + cs8900adriver.o \ + ethernet.o -c64eth64.lib: eth64.o lan91c96.o lan91c96driver.o ethernet.o c64init.o $(C64OBJS) - ar65 a $@ $^ +LAN91C96OBJS=\ + lan91c96.o \ + lan91c96driver.o \ + ethernet.o -c64wiz811.lib: wiz811mj.o w5100.o w5100driver.o ethernet.o c64init.o $(C64OBJS) - ar65 a $@ $^ +W5100OBJS=\ + w5100.o \ + w5100driver.o \ + ethernet.o -c64combo.lib: rr-net.o cs8900a.o eth64.o lan91c96.o wiz811mj.o w5100.o ethernetcombo.o c64init.o $(C64OBJS) - ar65 a $@ $^ +COMBOOBJS=\ + cs8900a.o \ + lan91c96.o \ + w5100.o \ + ethernetcombo.o -a2uther.lib: uthernet.o cs8900a.o cs8900adriver.o ethernet.o a2init.o $(A2OBJS) - ar65 a $@ $^ +c64rrnet.lib: c64init.o rr-net.o $(CS8900AOBJS) $(C64OBJS) -a2lancegs.lib: lancegs.o lan91c96.o lan91c96driver.o ethernet.o a2init.o $(A2OBJS) - ar65 a $@ $^ +c64eth64.lib: c64init.o eth64.o $(LAN91C96OBJS) $(C64OBJS) -a2uther2.lib: uthernet2.o w5100.o w5100driver.o ethernet.o a2init.o $(A2OBJS) - ar65 a $@ $^ +c64wiz811.lib: c64init.o wiz811mj.o $(W5100OBJS) $(C64OBJS) -a2combo.lib: uthernet.o cs8900a.o lancegs.o lan91c96.o uthernet2.o w5100.o ethernetcombo.o a2initcombo.o $(A2OBJS) - ar65 a $@ $^ +c64combo.lib: c64init.o rr-net.o eth64.o wiz811mj.o $(COMBOOBJS) $(C64OBJS) -atrdragon.lib: dragoncart.o cs8900a.o cs8900adriver.o ethernet.o atrinit.o $(ATROBJS) - ar65 a $@ $^ +ip65_c64.lib: c64init.o rr-net.o eth64.o wiz811mj.o $(COMBOOBJS) $(C64_OBJS) -vic20rrnet.lib: vic20-rr-net.o cs8900a.o cs8900adriver.o ethernet.o vic20init.o $(VIC20OBJS) - ar65 a $@ $^ +a2uther.lib: a2init.o uthernet.o $(CS8900AOBJS) $(A2OBJS) + +a2lancegs.lib: a2init.o lancegs.o $(LAN91C96OBJS) $(A2OBJS) + +a2uther2.lib: a2init.o uthernet2.o $(W5100OBJS) $(A2OBJS) + +a2combo.lib: a2initcombo.o uthernet.o lancegs.o uthernet2.o $(COMBOOBJS) $(A2OBJS) + +ip65_apple2.lib: a2initcombo.o uthernet.o lancegs.o uthernet2.o $(COMBOOBJS) $(A2_OBJS) + +atrdragon.lib: atrinit.o dragoncart.o $(CS8900AOBJS) $(ATROBJS) + +ip65_atari.lib: atrinit.o dragoncart.o $(CS8900AOBJS) $(ATR_OBJS) + +vic20rrnet.lib: vic20init.o vic20-rr-net.o $(CS8900AOBJS) $(VIC20OBJS) clean: -rm -f *.o diff --git a/drivers/a2_input.s b/drivers/a2_input.s new file mode 100644 index 0000000..d1a4326 --- /dev/null +++ b/drivers/a2_input.s @@ -0,0 +1,46 @@ +.export check_for_abort_key +.export abort_key +.exportzp abort_key_default = $9b +.exportzp abort_key_disable = $80 + + +.data + +abort_key: .byte $9b ; ESC + + +.code + +; check whether the abort key is being pressed +; inputs: none +; outputs: sec if abort key pressed, clear otherwise +check_for_abort_key: + lda $c000 ; current key pressed + cmp abort_key + bne :+ + bit $c010 ; clear the keyboard strobe + sec + rts +: clc + rts + + + +; -- LICENSE FOR a2_inputc.s -- +; The contents of this file are subject to the Mozilla Public License +; Version 1.1 (the "License"); you may not use this file except in +; compliance with the License. You may obtain a copy of the License at +; http://www.mozilla.org/MPL/ +; +; Software distributed under the License is distributed on an "AS IS" +; basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +; License for the specific language governing rights and limitations +; under the License. +; +; The Original Code is ip65. +; +; The Initial Developer of the Original Code is Jonno Downes, +; jonno@jamtronix.com. +; Portions created by the Initial Developer are Copyright (C) 2009 +; Jonno Downes. All Rights Reserved. +; -- LICENSE END -- diff --git a/drivers/a2_timer.s b/drivers/a2_timer.s new file mode 100644 index 0000000..9ff28e8 --- /dev/null +++ b/drivers/a2_timer.s @@ -0,0 +1,73 @@ +; timer routines +; +; unfortunately the standard Apple 2 has no CIA or VBI, so for the moment, we will +; make each call to 'timer_read' delay for a little while +; this kludge will make the polling loops work at least +; +; timer_read is meant to return a counter with millisecond resolution + +.include "../inc/common.i" + +.export timer_init +.export timer_read +.export timer_seconds + + +.bss + +current_time_value: .res 2 + + +.code + +; reset timer to 0 +; inputs: none +; outputs: none +timer_init: + ldax #0 + stax current_time_value + rts + +; this SHOULD just read the current timer value +; but since a standard apple 2 has no dedicated timing circuit, +; each call to this function actually delays a while, then updates the current value +; inputs: none +; outputs: AX = current timer value (roughly equal to number of milliseconds since the last call to 'timer_init') +timer_read: + bit $c082 ; switch in ROM + lda #111 + jsr $fca8 ; wait for about 33ms + clc + lda #33 + adc current_time_value + sta current_time_value + bcc :+ + inc current_time_value+1 +: ldax current_time_value + bit $c080 ; switch in LC bank 2 for R/O + rts + +timer_seconds: + lda #0 + rts + + + +; -- LICENSE FOR a2_timerc.s -- +; The contents of this file are subject to the Mozilla Public License +; Version 1.1 (the "License"); you may not use this file except in +; compliance with the License. You may obtain a copy of the License at +; http://www.mozilla.org/MPL/ +; +; Software distributed under the License is distributed on an "AS IS" +; basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +; License for the specific language governing rights and limitations +; under the License. +; +; The Original Code is ip65. +; +; The Initial Developer of the Original Code is Jonno Downes, +; jonno@jamtronix.com. +; Portions created by the Initial Developer are Copyright (C) 2009 +; Jonno Downes. All Rights Reserved. +; -- LICENSE END -- diff --git a/drivers/atr_input.s b/drivers/atr_input.s new file mode 100644 index 0000000..defbd67 --- /dev/null +++ b/drivers/atr_input.s @@ -0,0 +1,49 @@ +.include "atari.inc" + +.export check_for_abort_key +.export abort_key +.exportzp abort_key_default = 1 +.exportzp abort_key_disable = 0 + + +.data + +abort_key: .byte 1 + + +.code + +; check whether the abort key is being pressed +; inputs: none +; outputs: sec if abort key pressed, clear otherwise +check_for_abort_key: + lda abort_key ; is "abort" enabled? + beq nokey ; no + lda BRKKEY + bne nokey + dec BRKKEY + sec + rts +nokey: + clc + rts + + +;-- LICENSE FOR atr_inputs.s -- +; The contents of this file are subject to the Mozilla Public License +; Version 1.1 (the "License"); you may not use this file except in +; compliance with the License. You may obtain a copy of the License at +; http://www.mozilla.org/MPL/ +; +; Software distributed under the License is distributed on an "AS IS" +; basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +; License for the specific language governing rights and limitations +; under the License. +; +; The Original Code is ip65. +; +; The Initial Developer of the Original Code is Jonno Downes, +; jonno@jamtronix.com. +; Portions created by the Initial Developer are Copyright (C) 2009 +; Jonno Downes. All Rights Reserved. +; -- LICENSE END -- diff --git a/drivers/c64_input.s b/drivers/c64_input.s new file mode 100644 index 0000000..d0457b8 --- /dev/null +++ b/drivers/c64_input.s @@ -0,0 +1,54 @@ +.export check_for_abort_key +.export abort_key +.exportzp abort_key_default = $3f +.exportzp abort_key_disable = $ff + + +.data + +abort_key: .byte $3f ; RUN/STOP + + +.code + +; check whether the abort key is being pressed +; inputs: none +; outputs: sec if abort key pressed, clear otherwise +check_for_abort_key: + lda $cb ; current key pressed + cmp abort_key + bne no_key +@flush_loop: + ldy #$ff + jsr $f142 ; not officially documented - where F13E (GETIN) falls through to if device # is 0 (KEYBD) + cpy #$ff ; Y gets modified iff there's a character available - this approach allows to read ^@ as 0 + bne @flush_loop + lda $cb ; current key pressed + cmp abort_key + beq @flush_loop + sec + rts +no_key: + clc + rts + + + +; -- LICENSE FOR c64_input.s -- +; The contents of this file are subject to the Mozilla Public License +; Version 1.1 (the "License"); you may not use this file except in +; compliance with the License. You may obtain a copy of the License at +; http://www.mozilla.org/MPL/ +; +; Software distributed under the License is distributed on an "AS IS" +; basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +; License for the specific language governing rights and limitations +; under the License. +; +; The Original Code is ip65. +; +; The Initial Developer of the Original Code is Jonno Downes, +; jonno@jamtronix.com. +; Portions created by the Initial Developer are Copyright (C) 2009 +; Jonno Downes. All Rights Reserved. +; -- LICENSE END -- diff --git a/inc/ip65.h b/inc/ip65.h new file mode 100644 index 0000000..6dc21b9 --- /dev/null +++ b/inc/ip65.h @@ -0,0 +1,181 @@ +#ifndef _IP65_H +#define _IP65_H + +// Error codes +// +#define IP65_ERROR_PORT_IN_USE $80 +#define IP65_ERROR_TIMEOUT_ON_RECEIVE $81 +#define IP65_ERROR_TRANSMIT_FAILED $82 +#define IP65_ERROR_TRANSMISSION_REJECTED_BY_PEER $83 +#define IP65_ERROR_INPUT_TOO_LARGE $84 +#define IP65_ERROR_DEVICE_FAILURE $85 +#define IP65_ERROR_ABORTED_BY_USER $86 +#define IP65_ERROR_LISTENER_NOT_AVAILABLE $87 +#define IP65_ERROR_CONNECTION_RESET_BY_PEER $89 +#define IP65_ERROR_CONNECTION_CLOSED $8A +#define IP65_ERROR_FILE_ACCESS_FAILURE $90 +#define IP65_ERROR_MALFORMED_URL $A0 +#define IP65_ERROR_DNS_LOOKUP_FAILED $A1 + +// Last error code +// +extern unsigned char ip65_error; + +// MAC address of local machine (will be overwritten if ip65_init is called) +// +extern unsigned char cfg_mac[6]; + +// IP address of local machine (will be overwritten if dhcp_init is called) +// +extern unsigned long cfg_ip; + +// Netmask of local network (will be overwritten if dhcp_init is called) +// +extern unsigned long cfg_netmask; + +// IP address of router on local network (will be overwritten if dhcp_init is called) +// +extern unsigned long cfg_gateway; + +// IP address of dns server to use (will be overwritten if dhcp_init is called) +// +extern unsigned long cfg_dns; + +// Will be set to address of DHCP server that configuration was obtained from +// +extern unsigned long dhcp_server; + +// +// +extern unsigned char* tcp_packet; +extern unsigned int tcp_packet_len; + +// Initialise the IP stack +// +// This calls the individual protocol & driver initialisations, so this is +// the only *_init routine that must be called by a user application, +// except for dhcp_init which must also be called if the application +// is using DHCP rather than hardcoded IP configuration. +// +// Inputs: None +// Output: 1 if there was an error, 0 otherwise +// +unsigned char ip65_init(void); + +// Main IP polling loop +// +// This routine should be periodically called by an application at any time +// that an inbound packet needs to be handled. +// It is 'non-blocking', i.e. it will return if there is no packet waiting to be +// handled. Any inbound packet will be handed off to the appropriate handler. +// +// Inputs: None +// Output: 1 if no packet was waiting or packet handling caused error, 0 otherwise +// +unsigned char ip65_process(void); + +// Generate a 'random' 16 bit word +// +// Entropy comes from the last ethernet frame, counters, and timer. +// +// Inputs: None +// Output: Pseudo-random 16 bit number +// +unsigned int ip65_random_word(void); + +// Convert 4 octets (IP address, netmask) into a string representing a dotted quad +// +// The string is returned in a statically allocated buffer, which subsequent calls +// will overwrite. +// +// Inputs: quad: IP address +// Output: Null terminated string containing dotted quad (e.g. "192.168.1.0") +// +char* __fastcall__ dotted_quad(unsigned long quad); + +// Convert a string representing a dotted quad (IP address, netmask) into 4 octets +// +// Inputs: quad: Null terminated string containing dotted quad (e.g. "192.168.1.0"), +// to simplify URL parsing, a ':' or '/' can also terminate the string. +// Output: IP address, 0 on error +// +unsigned long __fastcall__ parse_dotted_quad(char* quad); + +// Minimal DHCP client implementation +// +// IP addresses are requested from a DHCP server (aka 'leased') but are not renewed +// or released. Although this is not correct behaviour according to the DHCP RFC, +// this works fine in practice in a typical home network environment. +// +// Inputs: None (although ip65_init should be called first) +// Output: 0 if IP config has been sucesfully obtained and cfg_ip, cfg_netmask, +// cfg_gateway and cfg_dns will be set per response from dhcp server. +// dhcp_server will be set to address of server that provided configuration. +// 1 if there was an error +// +unsigned char dhcp_init(void); + +// Resolve a string containing a hostname (or a dotted quad) to an IP address +// +// Inputs: hostname: pointer to null terminated string that contains either +// a DNS hostname (e.g. "host.example.com") or an address +// in "dotted quad" format (e.g. "192.168.1.0"). +// Output: IP address of the hostname, 0 on error +// +unsigned long __fastcall__ dns_resolve(const char* hostname); + +// Send a ping (ICMP echo request) to a remote host, and wait for a response +// +// Inputs: dest: Destination IP address +// Output: 0 if no response, otherwise time (in miliseconds) for host to respond +// +unsigned int __fastcall__ icmp_ping(unsigned long dest); + +// Add a UDP listener +// +// Inputs: port: UDP port to listen on +// callback: Vector to call when UDP packet arrives on specified port +// Output: 1 if too may listeners already installed, 0 otherwise +// +unsigned char __fastcall__ udp_add_listener(unsigned int port, void (*callback)(void)); + +// Remove a UDP listener +// +// Inputs: port: UDP port to stop listening on +// Output: 0 if handler found and removed, +// 1 if handler for specified port not found +// +unsigned char __fastcall__ udp_remove_listener(unsigned int port); + +// Access to received UDP packet +// +// Access to the four items below is only valid in the context of a callback +// added with udp_add_listener. +// +extern unsigned char udp_recv_buf[1476]; // Buffer with data received + unsigned int udp_recv_len(void); // Length of data received + unsigned long udp_recv_src(void); // Source IP address + unsigned int udp_recv_src_port(void); // Source port + +// Send a UDP packet +// +// If the correct MAC address can't be found in the ARP cache then +// an ARP request is sent - and the UDP packet is NOT sent. The caller +// should wait a while calling ip65_process (to allow time for an ARP +// response to arrive) and then call upd_send again. This behavior +// makes sense as a UDP packet may get lost in transit at any time +// so the caller should to be prepared to resend it after a while +// anyway. +// +// Inputs: buf: Pointer to buffer containing data to be sent +// len: Length of data to send (exclusive of any headers) +// dest: Destination IP address +// dest_port: Destination port +// src_port: Source port +// Output: 1 if an error occured, 0 otherwise +// +unsigned char __fastcall__ udp_send(const unsigned char* buf, unsigned int len, + unsigned long dest, unsigned int dest_port, + unsigned int src_port); + +#endif diff --git a/ip65/Makefile b/ip65/Makefile index b4c2bd9..eaef312 100644 --- a/ip65/Makefile +++ b/ip65/Makefile @@ -13,14 +13,20 @@ IP65OBJS=\ arithmetic.o \ arp.o \ config.o \ + config_c.o \ copymem.o \ dhcp.o \ + dhcp_c.o \ dns.o \ + dns_c.o \ dottedquad.o \ + dottedquad_c.o \ eth.o \ http.o \ httpd.o \ + icmp_c.o \ ip65.o \ + ip65_c.o \ tftp.o \ timer.o \ output_buffer.o \ @@ -28,6 +34,7 @@ IP65OBJS=\ sntp.o \ string_utils.o \ udp.o \ + udp_c.o \ url.o ip65.lib: $(IP65OBJS) ip.o icmp.o diff --git a/ip65/config_c.s b/ip65/config_c.s new file mode 100644 index 0000000..47557e2 --- /dev/null +++ b/ip65/config_c.s @@ -0,0 +1,27 @@ +.include "../inc/common.i" + +.export _cfg_mac +.export _cfg_ip +.export _cfg_netmask +.export _cfg_gateway +.export _cfg_dns +.export _dhcp_server + +.import cfg_mac +.import cfg_ip +.import cfg_netmask +.import cfg_gateway +.import cfg_dns +.import dhcp_server + +_cfg_mac := cfg_mac + +_cfg_ip := cfg_ip + +_cfg_netmask := cfg_netmask + +_cfg_gateway := cfg_gateway + +_cfg_dns := cfg_dns + +_dhcp_server := dhcp_server diff --git a/ip65/dhcp_c.s b/ip65/dhcp_c.s new file mode 100644 index 0000000..28e98e0 --- /dev/null +++ b/ip65/dhcp_c.s @@ -0,0 +1,12 @@ +.include "../inc/common.i" + +.export _dhcp_init + +.import dhcp_init + +_dhcp_init: + jsr dhcp_init + ldx #$00 + txa + rol + rts diff --git a/ip65/dns_c.s b/ip65/dns_c.s new file mode 100644 index 0000000..68b2d88 --- /dev/null +++ b/ip65/dns_c.s @@ -0,0 +1,25 @@ +.include "../inc/common.i" + +.export _dns_resolve + +.import dns_set_hostname +.import dns_resolve +.import dns_ip + +.importzp sreg + +_dns_resolve: + jsr dns_set_hostname + bcs error + jsr dns_resolve + bcs error + ldax dns_ip+2 + stax sreg + ldax dns_ip + rts + +error: + ldx #$00 + txa + stax sreg + rts diff --git a/ip65/dottedquad_c.s b/ip65/dottedquad_c.s new file mode 100644 index 0000000..6eac97a --- /dev/null +++ b/ip65/dottedquad_c.s @@ -0,0 +1,92 @@ +.include "../inc/common.i" + +.export _dotted_quad +.export _parse_dotted_quad + +.import parse_dotted_quad +.import dotted_quad_value + +.importzp sreg, tmp1, tmp2, tmp3 + + +.bss + +dotted_quad: .res 4*4 ; "xxx.xxx.xxx.xxx\0" + +.code + +_dotted_quad: + stax dotted_quad_value + ldax sreg + stax dotted_quad_value+2 + + ldx #$00 + ldy #$00 +: jsr convert_byte + inx + cpx #4 + bcc :- + + dey + lda #$00 + sta dotted_quad,y ; replace last dot with '\0' + ldax #dotted_quad + rts + +convert_byte: +; hex to bcd routine taken from Andrew Jacob's code at http://www.6502.org/source/integers/hex2dec-more.htm + sed ; switch to decimal mode + lda #$00 ; ensure the result is clear + sta tmp1 ; BCD low + sta tmp2 ; BCD high + lda #8 ; the number of source bits + sta tmp3 +: asl dotted_quad_value,x ; shift out one bit + lda tmp1 ; and add into result + adc tmp1 + sta tmp1 + lda tmp2 ; propagating any carry + adc tmp2 + sta tmp2 + dec tmp3 ; and repeat for next bit + bne :- + cld ; back to binary + + lda tmp2 + beq :+ + ora #'0' + sta dotted_quad,y ; write x00 if not 0 + iny +: lda tmp1 + lsr + lsr + lsr + lsr + beq :+ + ora #'0' + sta dotted_quad,y ; write 0x0 if not 0 + iny +: lda tmp1 + and #$0F + ora #'0' + sta dotted_quad,y ; write 00x + iny + + lda #'.' + sta dotted_quad,y ; write dot + iny + rts + +_parse_dotted_quad: + jsr parse_dotted_quad + bcs error + ldax dotted_quad_value+2 + stax sreg + ldax dotted_quad_value + rts + +error: + ldx #$00 + txa + stax sreg + rts diff --git a/ip65/icmp_c.s b/ip65/icmp_c.s new file mode 100644 index 0000000..91fddbd --- /dev/null +++ b/ip65/icmp_c.s @@ -0,0 +1,18 @@ +.include "../inc/common.i" + +.export _icmp_ping + +.import icmp_echo_ip +.import icmp_ping + +.importzp sreg + +_icmp_ping: + stax icmp_echo_ip + ldax sreg + stax icmp_echo_ip+2 + jsr icmp_ping + bcc :+ + ldx #$00 + txa +: rts diff --git a/ip65/ip65_c.s b/ip65/ip65_c.s new file mode 100644 index 0000000..025bc2d --- /dev/null +++ b/ip65/ip65_c.s @@ -0,0 +1,29 @@ +.include "../inc/common.i" + +.export _ip65_init +.export _ip65_process +.export _ip65_random_word +.export _ip65_error + +.import ip65_init +.import ip65_process +.import ip65_random_word +.import ip65_error + +_ip65_init: + jsr ip65_init + ldx #$00 + txa + rol + rts + +_ip65_process: + jsr ip65_process + ldx #$00 + txa + rol + rts + +_ip65_random_word := ip65_random_word + +_ip65_error := ip65_error diff --git a/ip65/udp_c.s b/ip65/udp_c.s new file mode 100644 index 0000000..82c645f --- /dev/null +++ b/ip65/udp_c.s @@ -0,0 +1,83 @@ +.include "../inc/common.i" + +.export _udp_add_listener +.export _udp_remove_listener +.export _udp_recv_buf +.export _udp_recv_len +.export _udp_recv_src +.export _udp_recv_src_port +.export _udp_send + +.import udp_add_listener +.import udp_remove_listener +.import ip_inp +.import udp_inp +.import udp_send + +.import udp_callback +.importzp ip_src +.importzp udp_src_port +.importzp udp_len +.importzp udp_data +.import udp_send_len +.import udp_send_dest +.import udp_send_dest_port +.import udp_send_src_port + +.import popax, popeax +.importzp sreg + +_udp_add_listener: + stax udp_callback + jsr popax + jsr udp_add_listener + ldx #$00 + txa + rol + rts + +_udp_remove_listener: + jsr udp_remove_listener + ldx #$00 + txa + rol + rts + +_udp_recv_buf := udp_inp+udp_data + +_udp_recv_len: + lda udp_inp+udp_len+1 + ldx udp_inp+udp_len + sec + sbc #udp_data + bcs :+ + dex +: rts + +_udp_recv_src: + ldax ip_inp+ip_src+2 + stax sreg + ldax ip_inp+ip_src + rts + +_udp_recv_src_port: + lda udp_inp+udp_src_port+1 + ldx udp_inp+udp_src_port + rts + + _udp_send: + stax udp_send_src_port + jsr popax + stax udp_send_dest_port + jsr popeax + stax udp_send_dest + ldax sreg + stax udp_send_dest+2 + jsr popax + stax udp_send_len + jsr popax + jsr udp_send + ldx #$00 + txa + rol + rts diff --git a/test/Makefile b/test/Makefile index c6b78c2..9014027 100644 --- a/test/Makefile +++ b/test/Makefile @@ -29,6 +29,7 @@ UDP =\ parsequerystring \ sntp \ tftp \ + udp \ vt100 TCP =\ @@ -91,6 +92,8 @@ vt100.com: ATARI_CFG = ../apps/atrtelnet.cfg %-slotscan.o: %.s ca65 -D A2_SLOT_SCAN -o $@ $< +%.o: %.c + %.prg: %.o ip65 drivers $(INCFILES) ld65 -o $*.prg -C c64.cfg -m $*.c64.map -vm $< $(IP65LIB) $(C64DRIVERLIB) c64.lib @@ -103,6 +106,18 @@ vt100.com: ATARI_CFG = ../apps/atrtelnet.cfg %.vicprg: %.o ip65 drivers $(INCFILES) ld65 -o $*.vicprg -C vic20-32k.cfg -m $*.vic.map -vm $< $(IP65LIB) $(VICDRIVERLIB) vic20.lib +%.prg: %.c ip65 drivers $(INCFILES) + cl65 -o $*.prg -O -t c64 -m $*.c64.map -vm $< $(IP65LIB) ../drivers/ip65_c64.lib + rm $*.o + +%.bin: %.c ip65 drivers $(INCFILES) + cl65 -o $*.bin -O -t apple2 -m $*.a2.map -vm $< $(IP65LIB) ../drivers/ip65_apple2.lib + rm $*.o + +%.com: %.c ip65 drivers $(INCFILES) + cl65 -o $*.com -O -t atari -m $*.atr.map -vm $< $(IP65LIB) ../drivers/ip65_atari.lib + rm $*.o + ip65test.d64: prg $(C1541) -format ip65,00 d64 $@ $(C1541) -attach $@ -write dns.prg dns,p @@ -114,6 +129,7 @@ ip65test.d64: prg $(C1541) -attach $@ -write ping.prg ping,p $(C1541) -attach $@ -write sntp.prg sntp,p $(C1541) -attach $@ -write tftp.prg tftp,p + $(C1541) -attach $@ -write udp.prg udp,p $(C1541) -attach $@ -write vt100.prg vt100,p ip65test.dsk: bin @@ -128,6 +144,7 @@ ip65test.dsk: bin java -jar $(AC) -cc65 $@ ping bin < ping.bin java -jar $(AC) -cc65 $@ sntp bin < sntp.bin java -jar $(AC) -cc65 $@ tftp bin < tftp.bin + java -jar $(AC) -cc65 $@ udp bin < udp.bin java -jar $(AC) -cc65 $@ vt100 bin < vt100.bin ip65test.atr: com @@ -143,12 +160,16 @@ ip65test.atr: com cp ping.com atr/ping.com cp sntp.com atr/sntp.com cp tftp.com atr/tftp.com + cp udp.com atr/udp.com cp vt100.com atr/vt100.com $(DIR2ATR) -b Dos25 1040 $@ atr rm -r atr +%.exe: %.c + cl /Fe:$@ $^ + clean: make -C ../ip65 clean make -C ../drivers clean - -rm -f *.o *.prg *.bin *.com *.vicprg *.map + -rm -f *.o *.prg *.bin *.com *.vicprg *.map *.obj *.exe -rm -f ip65test.d64 ip65test.dsk ip65test.atr diff --git a/test/peer.c b/test/peer.c new file mode 100644 index 0000000..dee0e64 --- /dev/null +++ b/test/peer.c @@ -0,0 +1,212 @@ +#include +#include + +#define WIN32_LEAN_AND_MEAN +#include +#pragma comment(lib, "ws2_32.lib") + +#define LEN 200 + +static void dump(unsigned char *buf, unsigned len) +{ + unsigned i; + + for (i = 0; i < len; ++i) + { + if ((i % 24) == 0) + { + printf("\n$%04X:", i); + } + printf(" %02X", buf[i]); + } + printf(".\n"); +} + +void main(void) +{ + printf("Init\n"); + WSADATA wsa; + if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) + { + return; + } + + SOCKET udp = socket(AF_INET, SOCK_DGRAM , IPPROTO_UDP); + if (udp == INVALID_SOCKET) + { + return; + } + SOCKET srv = socket(AF_INET, SOCK_STREAM , IPPROTO_TCP); + if (srv == INVALID_SOCKET) + { + return; + } + + u_long arg = 1; + if (ioctlsocket(udp, FIONBIO, &arg) == SOCKET_ERROR) + { + return; + } + if (ioctlsocket(srv, FIONBIO, &arg) == SOCKET_ERROR) + { + return; + } + + SOCKADDR_IN local; + local.sin_family = AF_INET; + local.sin_addr.s_addr = INADDR_ANY; + local.sin_port = htons(6502); + if (bind(udp, (SOCKADDR *)&local, sizeof(local)) == SOCKET_ERROR) + { + return; + } + if (bind(srv, (SOCKADDR *)&local, sizeof(local)) == SOCKET_ERROR) + { + return; + } + + if (listen(srv, 1) == SOCKET_ERROR) + { + return; + } + + SOCKADDR_IN remote; + remote.sin_addr.s_addr = INADDR_NONE; + + SOCKET tcp = INVALID_SOCKET; + + printf("(U)DP, (T)CP or e(X)it\n"); + char key; + do + { + int len; + unsigned char buf[1500]; + + if (kbhit()) + { + key = getch(); + } + else + { + key = '\0'; + } + + if (key == 'u') + { + if (remote.sin_addr.s_addr == INADDR_NONE) + { + printf("Peer Unknown As Yet\n", len); + } + else + { + unsigned i; + + len = LEN; + for (i = 0; i < len; ++i) + { + buf[i] = i; + } + printf("Send Len %d To %s", len, inet_ntoa(remote.sin_addr)); + if (sendto(udp, buf, len, 0, (SOCKADDR *)&remote, sizeof(remote)) == SOCKET_ERROR) + { + return; + } + printf(".\n"); + } + } + + if (key == 't') + { + if (tcp == INVALID_SOCKET) + { + printf("No Connection\n", len); + } + else + { + unsigned i; + + len = LEN; + for (i = 0; i < len; ++i) + { + buf[i] = i; + } + printf("Send Len %d", len); + if (send(tcp, buf, len, 0) == SOCKET_ERROR) + { + return; + } + printf(".\n"); + } + } + + unsigned remote_size = sizeof(remote); + len = recvfrom(udp, buf, sizeof(buf), 0, (SOCKADDR *)&remote, &remote_size); + if (len == SOCKET_ERROR) + { + if (WSAGetLastError() != WSAEWOULDBLOCK) + { + return; + } + } + else if (len) + { + printf("Recv Len %d From %s", len, inet_ntoa(remote.sin_addr)); + dump(buf, len); + } + + if (tcp == INVALID_SOCKET) + { + SOCKADDR_IN conn; + unsigned conn_size = sizeof(conn); + tcp = accept(srv, (SOCKADDR *)&conn, &conn_size); + if (tcp == INVALID_SOCKET) + { + if (WSAGetLastError() != WSAEWOULDBLOCK) + { + return; + } + } + else + { + printf("Connect From %s\n", inet_ntoa(conn.sin_addr)); + + u_long arg = 1; + if (ioctlsocket(tcp, FIONBIO, &arg) == SOCKET_ERROR) + { + return; + } + } + } + else + { + len = recv(tcp, buf, sizeof(buf), 0); + if (len == SOCKET_ERROR) + { + if (WSAGetLastError() != WSAEWOULDBLOCK) + { + return; + } + } + else if (len) + { + printf("Recv Len %d", len); + dump(buf, len); + } + else + { + printf("Disconnect\n"); + closesocket(tcp); + tcp = INVALID_SOCKET; + } + } + + Sleep(10); + } + while (key != 'x'); + + closesocket(udp); + closesocket(tcp); + closesocket(srv); + WSACleanup(); + printf("Done\n"); +} diff --git a/test/udp.c b/test/udp.c new file mode 100644 index 0000000..b6e2d43 --- /dev/null +++ b/test/udp.c @@ -0,0 +1,114 @@ +#include +#include +#include +#include + +#include "../inc/ip65.h" + +#define LEN 500 +#define SRV "192.168.0.10" + +char buf[LEN]; + +void error_exit(void) +{ + printf("Error $%X\n", ip65_error); + if (doesclrscrafterexit()) + { + printf("Press any key\n"); + cgetc(); + } + exit(1); +} + +void udp_recv(void) +{ + unsigned len = udp_recv_len(); + unsigned i; + + printf("Recv Len %u From %s", len, dotted_quad(udp_recv_src())); + for (i = 0; i < len; ++i) + { + if ((i % 11) == 0) + { + printf("\n$%04X:", i); + } + printf(" %02X", udp_recv_buf[i]); + } + printf(".\n"); +} + +void main(void) +{ + unsigned i; + unsigned long srv; + char key; + + for (i = 0; i < LEN; ++i) + buf[i] = i; + + if(!(srv = parse_dotted_quad(SRV))) + { + error_exit(); + } + + printf("Init\n"); + if (ip65_init()) + { + error_exit(); + } + + printf("DHCP\n"); + if (dhcp_init()) + { + error_exit(); + } + + printf("IP Addr: %s\n", dotted_quad(cfg_ip)); + printf("Netmask: %s\n", dotted_quad(cfg_netmask)); + printf("Gateway: %s\n", dotted_quad(cfg_gateway)); + printf("DNS Srv: %s\n", dotted_quad(cfg_dns)); + + printf("Listen\n"); + if (udp_add_listener(6502, udp_recv)) + { + error_exit(); + } + + printf("(U)DP or e(X)it\n"); + do + { + ip65_process(); + + if (kbhit()) + { + key = cgetc(); + } + else + { + key = '\0'; + } + + if (key == 'u') + { + printf("Send Len %d To %s", LEN, SRV); + if (udp_send(buf, LEN, srv, 6502, 6502)) + { + printf("!\n"); + } + else + { + printf(".\n"); + } + } + } + while (key != 'x'); + + printf("Unlisten\n"); + if (udp_remove_listener(6502)) + { + error_exit(); + } + + printf("Done\n"); +}