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