Added a C-only W5100 TCP driver.

This commit is contained in:
Oliver Schmidt 2015-08-11 14:21:56 +02:00
parent 9ac791e975
commit 2ec63af2e5
4 changed files with 582 additions and 3 deletions

273
supplement/w5100.c Normal file
View File

@ -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 <organization> 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
}

189
supplement/w5100.h Normal file
View File

@ -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 <organization> 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 <base_addr>. Use <ip_addr>, <submask> and <gateway> to configure the
// TCP/IP stack.
// Return <1> if a W5100 was found at <base_addr>, return <0> otherwise.
byte w5100_init(word base_addr, byte *ip_addr,
byte *submask,
byte *gateway);
// Connect to server with IP address <server_addr> on TCP port <server_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 <size> bytes from server. <size> 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 <size> bytes to server. <size> 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

View File

@ -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

113
test/w5100_main.c Normal file
View File

@ -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 <stdio.h>
#include <conio.h>
#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");
}