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");
+}