From 96402c8048b392105013df15639ecf315ca600ca Mon Sep 17 00:00:00 2001
From: Oliver Schmidt
Date: Tue, 11 Aug 2015 14:21:56 +0200
Subject: [PATCH] Added a C-only W5100 TCP driver.
---
supplement/w5100.c | 273 +++++++++++++++++++++++++++++++++++++++++++++
supplement/w5100.h | 189 +++++++++++++++++++++++++++++++
test/Makefile | 10 +-
test/w5100_main.c | 113 +++++++++++++++++++
4 files changed, 582 insertions(+), 3 deletions(-)
create mode 100644 supplement/w5100.c
create mode 100644 supplement/w5100.h
create mode 100644 test/w5100_main.c
diff --git a/supplement/w5100.c b/supplement/w5100.c
new file mode 100644
index 0000000..16d6595
--- /dev/null
+++ b/supplement/w5100.c
@@ -0,0 +1,273 @@
+/******************************************************************************
+
+Copyright (c) 2015, Oliver Schmidt
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL OLIVER SCHMIDT BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+******************************************************************************/
+
+// Both pragmas are obligatory to have cc65 generate code
+// suitable to access the W5100 auto-increment registers.
+#pragma optimize (on)
+#pragma static-locals (on)
+
+#include "w5100.h"
+
+#define MIN(a,b) (((a)<(b))?(a):(b))
+
+static volatile byte* w5100_mode;
+static volatile byte* w5100_addr_hi;
+static volatile byte* w5100_addr_lo;
+ volatile byte* w5100_data;
+
+static void set_addr(word addr)
+{
+ *w5100_addr_hi = addr >> 8;
+ *w5100_addr_lo = addr;
+}
+
+static byte get_byte(word addr)
+{
+ set_addr(addr);
+
+ return *w5100_data;
+}
+
+static void set_byte(word addr, byte data)
+{
+ set_addr(addr);
+
+ *w5100_data = data;
+}
+
+static word get_word(word addr)
+{
+ set_addr(addr);
+
+ {
+ // The variables are necessary to have cc65 generate code
+ // suitable to access the W5100 auto-increment registers.
+ byte data_hi = *w5100_data;
+ byte data_lo = *w5100_data;
+ return data_hi << 8 | data_lo;
+ }
+}
+
+static void set_word(word addr, word data)
+{
+ set_addr(addr);
+
+ {
+ // The variables are necessary to have cc65 generate code
+ // suitable to access the W5100 auto-increment registers.
+ byte data_hi = data >> 8;
+ byte data_lo = data;
+ *w5100_data = data_hi;
+ *w5100_data = data_lo;
+ }
+}
+
+static void set_bytes(word addr, byte data[], word size)
+{
+ set_addr(addr);
+
+ {
+ word i;
+ for (i = 0; i < size; ++i)
+ *w5100_data = data[i];
+ }
+}
+
+byte w5100_init(word base_addr, byte *ip_addr,
+ byte *submask,
+ byte *gateway)
+{
+ w5100_mode = (byte*)base_addr;
+ w5100_addr_hi = (byte*)base_addr + 1;
+ w5100_addr_lo = (byte*)base_addr + 2;
+ w5100_data = (byte*)base_addr + 3;
+
+ // Assert Indirect Bus I/F mode & Address Auto-Increment
+ *w5100_mode |= 0x03;
+
+ // Retry Time-value Register: Default ?
+ if (get_word(0x0017) != 2000)
+ return 0;
+
+ // S/W Reset
+ *w5100_mode = 0x80;
+ while (*w5100_mode & 0x80)
+ ;
+
+ // Indirect Bus I/F mode & Address Auto-Increment
+ *w5100_mode = 0x03;
+
+ // RX Memory Size Register: Assign 8KB to Socket 0
+ set_byte(0x001A, 0x03);
+
+ // TX Memory Size Register: Assign 8KB to Socket 0
+ set_byte(0x001B, 0x03);
+
+ // Source Hardware Address Register
+ {
+ static byte mac_addr[6] = {0x00, 0x08, 0xDC, // OUI of WIZnet
+ 0x11, 0x11, 0x11};
+ set_bytes(0x0009, mac_addr, sizeof(mac_addr));
+ }
+
+ // Source IP Address Register
+ set_bytes(0x000F, ip_addr, 4);
+
+ // Subnet Mask Register
+ set_bytes(0x0005, submask, 4);
+
+ // Gateway IP Address Register
+ set_bytes(0x0001, gateway, 4);
+
+ return 1;
+}
+
+byte w5100_connect(byte *server_addr, word server_port)
+{
+ // Socket 0 Mode Register: TCP
+ set_byte(0x0400, 0x01);
+
+ // Socket 0 Source Port Register
+ set_word(0x0404, 6502);
+
+ // Socket 0 Command Register: OPEN
+ set_byte(0x0401, 0x01);
+
+ // Socket 0 Status Register: SOCK_INIT ?
+ while (get_byte(0x0403) != 0x13)
+ ;
+
+ // Socket 0 Destination IP Address Register
+ set_bytes(0x040C, server_addr, 4);
+
+ // Socket 0 Destination Port Register
+ set_word(0x0410, server_port);
+
+ // Socket 0 Command Register: CONNECT
+ set_byte(0x0401, 0x04);
+
+ while (1)
+ {
+ // Socket 0 Status Register
+ switch (get_byte(0x0403))
+ {
+ case 0x00: return 0; // Socket Status: SOCK_CLOSED
+ case 0x17: return 1; // Socket Status: SOCK_ESTABLISHED
+ }
+ }
+}
+
+byte w5100_connected(void)
+{
+ // Socket 0 Status Register: SOCK_ESTABLISHED ?
+ return get_byte(0x0403) == 0x17;
+}
+
+void w5100_disconnect(void)
+{
+ // Socket 0 Command Register: Command Pending ?
+ while (get_byte(0x0401))
+ ;
+
+ // Socket 0 Command Register: DISCON
+ set_byte(0x0401, 0x08);
+
+ // Socket 0 Status Register: SOCK_CLOSED ?
+ while (get_byte(0x0403))
+ // Wait for disconnect to allow for reconnect
+ ;
+}
+
+word w5100_data_request(byte do_send)
+{
+ // Socket 0 Command Register: Command Pending ?
+ if (get_byte(0x0401))
+ return 0;
+
+ // Reread of nonzero RX Received Size Register / TX Free Size Register
+ // until its value settles ...
+ // - is present in the WIZnet driver - getSn_RX_RSR() / getSn_TX_FSR()
+ // - was additionally tested on 6502 machines to be actually necessary
+ {
+ word size = 0;
+ word prev_size;
+ do
+ {
+ prev_size = size;
+ {
+ static word reg[2] = {0x0426, // Socket 0 RX Received Size Register
+ 0x0420}; // Socket 0 TX Free Size Register
+ size = get_word(reg[do_send]);
+ }
+ }
+ while (size != prev_size);
+
+ if (!size)
+ return 0;
+
+ {
+ static word reg[2] = {0x0428, // Socket 0 RX Read Pointer Register
+ 0x0424}; // Socket 0 TX Write Pointer Register
+
+ static word bas[2] = {0x6000, // Socket 0 RX Memory Base
+ 0x4000}; // Socket 0 TX Memory Base
+
+ static word lim[2] = {0x8000, // Socket 0 RX Memory Limit
+ 0x6000}; // Socket 0 TX Memory Limit
+
+ // Calculate and set physical address
+ word addr = get_word(reg[do_send]) & 0x1FFF | bas[do_send];
+ set_addr(addr);
+
+ // Access to *w5100_data is limited both by ...
+ // - size of received / free space
+ // - end of physical address space
+ return MIN(size, lim[do_send] - addr);
+ }
+ }
+}
+
+void w5100_data_commit(byte do_send, word size)
+{
+ {
+ static word reg[2] = {0x0428, // Socket 0 RX Read Pointer Register
+ 0x0424}; // Socket 0 TX Write Pointer Register
+ set_word(reg[do_send], get_word(reg[do_send]) + size);
+ }
+
+ {
+ static byte cmd[2] = {0x40, // Socket Command: RECV
+ 0x20}; // Socket Command: SEND
+ // Socket 0 Command Register
+ set_byte(0x0401, cmd[do_send]);
+ }
+
+ // Do NOT wait for command completion here, rather
+ // let W5100 operation overlap with 6502 operation
+}
diff --git a/supplement/w5100.h b/supplement/w5100.h
new file mode 100644
index 0000000..9c42fc8
--- /dev/null
+++ b/supplement/w5100.h
@@ -0,0 +1,189 @@
+/******************************************************************************
+
+Copyright (c) 2014, Oliver Schmidt
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL OLIVER SCHMIDT BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+******************************************************************************/
+
+
+/******************************************************************************
+
+Some notes by Oliver Schmidt on the WIZnet W5100 Ethernet controller:
+
+1. Operation Modes
+
+1.1 MAC-Raw
+In MAC-Raw mode the W5100 behaves pretty much like a CS8900A or a LAN91C96. The
+W5100 is usually only configured with a MAC address which is used by the W5100
+to limit incoming frames to those sent to its MAC address (or broadcasts).
+
+1.2 IP-Raw
+IP-Raw mode is usable to implement non-UDP/non-TCP IP protocols like ICMP. The
+W5100 is usually configured with a full IP profile (IP addr, netmask, gateway).
+It transparently takes care of incoming/outgoing ARP and optionally of incoming
+ICMP Echo (aka Ping).
+
+1.3 UDP
+UDP mode is pretty simlar to IP-Raw mode but additionally takes care of header
+checksum calculation.
+
+1.4 TCP
+TCP mode is rather different from the other modes. Incoming/outgoing data isn't
+delimited by headers like in all other modes. Rather the W5100 behaves like a
+BSD socket delivering/taking a data stream - in chunks not necessarily related
+to data packets received/sent. The W5100 transparently takes care of TCP flow
+control by sending ACK packets. It advertises a receive window identical to the
+free space in the its receive memory buffer.
+
+The W5100 offers up to 4 'sockets' allowing to specify the operation mode for
+each socket individually. However MAC-Raw mode is only available for the first
+socket. It is possible to combine MAC-Raw mode with other modes for the other
+sockets - which is called 'hybrid TCP/IP stack'. I have no personal experience
+with this hybrid TCP/IP stack and see open questions:
+- Are packets delivered to other sockets filtered from the first socket?
+- Who takes care of incoming ARP and incoming ICMP Echo?
+
+The W5100 divides its 16kB memory buffer statically into 8kB for receive and
+8kB for send (in contrast to the CS8900A and the LAN91C96 which both do dynamic
+receive/send buffer division). When using several sockets it is additionally
+necessary to statically assign the two 8kB memory buffers to the sockets.
+
+2. Memory Buffer Access
+In 6502 machines the W5100 is accessed using its indirect bus interface. This
+interface optionally allows for pointer auto-increment (like the CS8900A and
+the LAN91C96). However in contrast to those two Ethernet controllers the W5100
+does NOT virtualize access to its memory buffer! So when reading/writing data
+from/to the W5100 and reaching the end of the memory buffer assigned to the
+socket it's the responsibility of the 6502 program to continue reading/writing
+at the begin of the memory buffer. Please note that the pointer auto-increment
+does NOT take care of that wraparound operation! I have implemented several
+ways to handle this difficulty.
+
+2.1 Copy Split
+If it is necessary or desired to have the interface to the upper layers being
+based on a receive/send buffer and one can afford the memory for a little more
+code than it is appropriate to check in advance if receive/send will require a
+wraparound and in that case split the copy from/to the buffer into two copy
+operations. That approach is used in all WIZnet code and I implemented it in
+pretty optimized 6502 code for the Contiki/IP65 MAC-Raw mode driver located in
+drivers/w5100.s - however the copy split technique is in general applicable to
+all W5100 operation modes.
+
+2.2 Shadow Register
+When it comes to using as little memory as possible I consider it in general
+questionable if a buffer is the right interface paradigm. In many scenarios it
+makes more sense to read/write bytes individually. This allows i.e. to directly
+write bytes from individual already existing data structures to the W5100 or
+analyze bytes directly on reading from the W5100 to decide on processing of
+subsequent bytes - and maybe ignore them altogether. This approach splits a
+receive/send operation into three phases: The initialization, the individual
+byte read/write and the finalization. The initialization sets up a 16-bit
+shadow register to be as far away from overflow as the auto-increment pointer
+is away from the necessary wraparound. The individual byte read/write then
+increments the shadow register and on its overflow resets the auto-increment
+pointer to the begin of the memory buffer. I implemented this approach in two
+drivers using heavily size-optimized 6502 code for the W5100 UDP mode and TCP
+mode showing that the shadow register technique yields the smallest code. They
+are located in supplement/w5100_udp.s and supplement/w5100_tcp.s with C test
+programs located in test/w5100_udp_main.c and test/w5100_tcp_main.c. There's a
+Win32 communication peer for the test programs located in test/w5100_peer.c.
+
+2.3 TCP Stream Split
+A correct BSD TCP socket program never presumes to be able to read/write any
+amount of data. Rather it is always prepared to call recv()/send() as often as
+necessary receive/send the expected amount data in whatever chuncks - and the
+very same holds true for any program using the W5100 TCP mode! But this already
+necessary complexity in the upper layers allows to handle W5100 memory buffer
+wraparounds transparently by artificially limiting the size of a read/write
+operation to the end of the memory buffer if necessary. The next read/write
+operation then works with the begin of the memory buffer. This approach shares
+the benefits of the shadow register technique while avoiding its performance
+penalties coming from maintaining the shadow register. Additionally it allows
+the upper layers to directly access the auto-increment W5100 data register for
+individual byte read/write because it is known to stay within the memory buffer
+limits. Therefore the TCP stream split technique avoids both the overhead of a
+buffer as well as the overhead of function calls for individual bytes. It sort
+of combines the best of both sides but it means larger code than the shadow
+register technique and is only applicable to the W5100 TCP mode. I implemented
+the TCP stream split technique in a C-only driver located in supplement/w5100.c
+with a test program representing the upper layers located in test/w5100_main.c
+being compatible with test/w5100_peer.c.
+
+******************************************************************************/
+
+#ifndef _W5100_H_
+#define _W5100_H_
+
+typedef unsigned char byte;
+typedef unsigned short word;
+
+word w5100_data_request(byte do_send);
+void w5100_data_commit(byte do_send, word size);
+
+// After w5100_receive_request() every read operation returns the next byte
+// from the server.
+// After w5100_send_request() every write operation prepares the next byte
+// to be sent to the server.
+extern volatile byte* w5100_data;
+
+// Initialize W5100 Ethernet controller with indirect bus interface located
+// at . Use , and to configure the
+// TCP/IP stack.
+// Return <1> if a W5100 was found at , return <0> otherwise.
+byte w5100_init(word base_addr, byte *ip_addr,
+ byte *submask,
+ byte *gateway);
+
+// Connect to server with IP address on TCP port .
+// Use <6502> as fixed local port.
+// Return <1> if the connection is established, return <0> otherwise.
+byte w5100_connect(byte *server_addr, word server_port);
+
+// Check if still connected to server.
+// Return <1> if the connection is established, return <0> otherwise.
+byte w5100_connected(void);
+
+// Disconnect from server.
+void w5100_disconnect(void);
+
+// Request to receive data from the server.
+// Return maximum number of bytes to be received by reading from *w5100_data.
+#define w5100_receive_request() w5100_data_request(0)
+
+// Commit receiving of bytes from server. may be smaller than
+// the return value of w5100_receive_request(). Not commiting at all just
+// makes the next request receive the same data again.
+#define w5100_receive_commit(size) w5100_data_commit(0, (size))
+
+// Request to send data to the server.
+// Return maximum number of bytes to be send by writing to *w5100_data.
+#define w5100_send_request() w5100_data_request(1)
+
+// Commit sending of bytes to server. is usually smaller than
+// the return value of w5100_send_request(). Not commiting at all just turns
+// the w5100_send_request() and - the writes to *w5100_data - into NOPs.
+#define w5100_send_commit(size) w5100_data_commit(1, (size))
+
+#endif
diff --git a/test/Makefile b/test/Makefile
index 2c94c2c..3830e36 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -118,13 +118,17 @@ ip65demo.dsk: httpd.bin httpd-slotscan.bin
java -jar $(AC) -cc65 $@ webserver-slot3 B < httpd.bin
java -jar $(AC) -cc65 $@ webserver-slotscan B < httpd-slotscan.bin
+w5100.bin: ../supplement/w5100.c w5100_main.c
+ cl65 -o $@ -t apple2enh -m $(basename $@).map $^
+
w5100_%.bin: ../supplement/w5100_%.s w5100_%_main.c
cl65 -o $@ -t apple2enh -m $(basename $@).map $^
-w5100.dsk: w5100_udp.bin w5100_tcp.bin
+w5100.dsk: w5100.bin w5100_udp.bin w5100_tcp.bin
cp prodos.dsk $@
- java -jar $(AC) -cc65 $@ udp bin < w5100_udp.bin
- java -jar $(AC) -cc65 $@ tcp bin < w5100_tcp.bin
+ java -jar $(AC) -cc65 $@ w5100 bin < w5100.bin
+ java -jar $(AC) -cc65 $@ w5100.udp bin < w5100_udp.bin
+ java -jar $(AC) -cc65 $@ w5100.tcp bin < w5100_tcp.bin
clean:
make -C ../ip65 clean
diff --git a/test/w5100_main.c b/test/w5100_main.c
new file mode 100644
index 0000000..ec2b283
--- /dev/null
+++ b/test/w5100_main.c
@@ -0,0 +1,113 @@
+// Both pragmas are obligatory to have cc65 generate code
+// suitable to access the W5100 auto-increment registers.
+#pragma optimize (on)
+#pragma static-locals (on)
+
+#include
+#include
+
+#include "../supplement/w5100.h"
+
+#define MIN(a,b) (((a)<(b))?(a):(b))
+
+byte ip_addr[4] = {192, 168, 0, 123};
+byte submask[4] = {255, 255, 255, 0};
+byte gateway[4] = {192, 168, 0, 1};
+
+byte server[4] = {192, 168, 0, 25}; // IP addr of machine running w5100_peer.c
+
+void main(void)
+{
+ char key;
+
+ videomode(VIDEOMODE_80COL);
+ printf("Init\n");
+ if (!w5100_init(0xC0B4, ip_addr,
+ submask,
+ gateway))
+ {
+ printf("No Hardware Found\n");
+ return;
+ }
+ printf("Connect\n");
+ if (!w5100_connect(server, 6502))
+ {
+ printf("Faild To Connect To %d.%d.%d.%d\n", server[0],
+ server[1],
+ server[2],
+ server[3]);
+ return;
+ }
+ printf("Connected To %d.%d.%d.%d\n", server[0],
+ server[1],
+ server[2],
+ server[3]);
+
+ printf("(S)end or e(X)it\n");
+ do
+ {
+ word len, all;
+
+ if (kbhit())
+ {
+ key = cgetc();
+ }
+ else
+ {
+ key = '\0';
+ }
+
+ if (key == 's')
+ {
+ all = 500;
+ printf("Send Len %d", all);
+ do
+ {
+ word i;
+
+ while (!(len = w5100_send_request()))
+ {
+ printf("!");
+ }
+printf(" All %d Len %d", all, len);
+ len = MIN(all, len);
+ for (i = 0; i < len; ++i)
+ {
+ *w5100_data = 500 - all + i;
+ }
+ w5100_send_commit(len);
+ all -= len;
+ }
+ while (all);
+ printf(".\n");
+ }
+
+ len = w5100_receive_request();
+ if (len)
+ {
+ word i;
+
+ printf("Recv Len %d", len);
+ for (i = 0; i < len; ++i)
+ {
+ if ((i % 24) == 0)
+ {
+ printf("\n$%04X:", i);
+ }
+ printf(" %02X", *w5100_data);
+ }
+ w5100_receive_commit(len);
+ printf(".\n");
+ }
+
+ if (!w5100_connected())
+ {
+ printf("Disconnect\n");
+ return;
+ }
+ }
+ while (key != 'x');
+
+ w5100_disconnect();
+ printf("Done\n");
+}