540 lines
15 KiB
C++
540 lines
15 KiB
C++
/* Copyright 2018 Paul Stoffregen
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
|
* software and associated documentation files (the "Software"), to deal in the Software
|
|
* without restriction, including without limitation the rights to use, copy, modify,
|
|
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
|
* permit persons to whom the Software is furnished to do so, subject to the following
|
|
* conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
|
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include <Arduino.h>
|
|
#include "Ethernet.h"
|
|
#include "utility/w5100.h"
|
|
|
|
#if ARDUINO >= 156 && !defined(ARDUINO_ARCH_PIC32)
|
|
extern void yield(void);
|
|
#else
|
|
#define yield()
|
|
#endif
|
|
|
|
// TODO: randomize this when not using DHCP, but how?
|
|
static uint16_t local_port = 49152; // 49152 to 65535
|
|
|
|
typedef struct {
|
|
uint16_t RX_RSR; // Number of bytes received
|
|
uint16_t RX_RD; // Address to read
|
|
uint16_t TX_FSR; // Free space ready for transmit
|
|
uint8_t RX_inc; // how much have we advanced RX_RD
|
|
} socketstate_t;
|
|
|
|
static socketstate_t state[MAX_SOCK_NUM];
|
|
|
|
|
|
static uint16_t getSnTX_FSR(uint8_t s);
|
|
static uint16_t getSnRX_RSR(uint8_t s);
|
|
static void write_data(uint8_t s, uint16_t offset, const uint8_t *data, uint16_t len);
|
|
static void read_data(uint8_t s, uint16_t src, uint8_t *dst, uint16_t len);
|
|
|
|
|
|
|
|
/*****************************************/
|
|
/* Socket management */
|
|
/*****************************************/
|
|
|
|
|
|
void EthernetClass::socketPortRand(uint16_t n)
|
|
{
|
|
n &= 0x3FFF;
|
|
local_port ^= n;
|
|
//Serial.printf("socketPortRand %d, srcport=%d\n", n, local_port);
|
|
}
|
|
|
|
uint8_t EthernetClass::socketBegin(uint8_t protocol, uint16_t port)
|
|
{
|
|
uint8_t s, status[MAX_SOCK_NUM], chip, maxindex=MAX_SOCK_NUM;
|
|
|
|
// first check hardware compatibility
|
|
chip = W5100.getChip();
|
|
if (!chip) return MAX_SOCK_NUM; // immediate error if no hardware detected
|
|
#if MAX_SOCK_NUM > 4
|
|
if (chip == 51) maxindex = 4; // W5100 chip never supports more than 4 sockets
|
|
#endif
|
|
//Serial.printf("W5000socket begin, protocol=%d, port=%d\n", protocol, port);
|
|
SPI.beginTransaction(SPI_ETHERNET_SETTINGS);
|
|
// look at all the hardware sockets, use any that are closed (unused)
|
|
for (s=0; s < maxindex; s++) {
|
|
status[s] = W5100.readSnSR(s);
|
|
if (status[s] == SnSR::CLOSED) goto makesocket;
|
|
}
|
|
//Serial.printf("W5000socket step2\n");
|
|
// as a last resort, forcibly close any already closing
|
|
for (s=0; s < maxindex; s++) {
|
|
uint8_t stat = status[s];
|
|
if (stat == SnSR::LAST_ACK) goto closemakesocket;
|
|
if (stat == SnSR::TIME_WAIT) goto closemakesocket;
|
|
if (stat == SnSR::FIN_WAIT) goto closemakesocket;
|
|
if (stat == SnSR::CLOSING) goto closemakesocket;
|
|
}
|
|
#if 0
|
|
Serial.printf("W5000socket step3\n");
|
|
// next, use any that are effectively closed
|
|
for (s=0; s < MAX_SOCK_NUM; s++) {
|
|
uint8_t stat = status[s];
|
|
// TODO: this also needs to check if no more data
|
|
if (stat == SnSR::CLOSE_WAIT) goto closemakesocket;
|
|
}
|
|
#endif
|
|
SPI.endTransaction();
|
|
return MAX_SOCK_NUM; // all sockets are in use
|
|
closemakesocket:
|
|
//Serial.printf("W5000socket close\n");
|
|
W5100.execCmdSn(s, Sock_CLOSE);
|
|
makesocket:
|
|
//Serial.printf("W5000socket %d\n", s);
|
|
EthernetServer::server_port[s] = 0;
|
|
delayMicroseconds(250); // TODO: is this needed??
|
|
W5100.writeSnMR(s, protocol);
|
|
W5100.writeSnIR(s, 0xFF);
|
|
if (port > 0) {
|
|
W5100.writeSnPORT(s, port);
|
|
} else {
|
|
// if don't set the source port, set local_port number.
|
|
if (++local_port < 49152) local_port = 49152;
|
|
W5100.writeSnPORT(s, local_port);
|
|
}
|
|
W5100.execCmdSn(s, Sock_OPEN);
|
|
state[s].RX_RSR = 0;
|
|
state[s].RX_RD = W5100.readSnRX_RD(s); // always zero?
|
|
state[s].RX_inc = 0;
|
|
state[s].TX_FSR = 0;
|
|
//Serial.printf("W5000socket prot=%d, RX_RD=%d\n", W5100.readSnMR(s), state[s].RX_RD);
|
|
SPI.endTransaction();
|
|
return s;
|
|
}
|
|
|
|
// multicast version to set fields before open thd
|
|
uint8_t EthernetClass::socketBeginMulticast(uint8_t protocol, IPAddress ip, uint16_t port)
|
|
{
|
|
uint8_t s, status[MAX_SOCK_NUM], chip, maxindex=MAX_SOCK_NUM;
|
|
|
|
// first check hardware compatibility
|
|
chip = W5100.getChip();
|
|
if (!chip) return MAX_SOCK_NUM; // immediate error if no hardware detected
|
|
#if MAX_SOCK_NUM > 4
|
|
if (chip == 51) maxindex = 4; // W5100 chip never supports more than 4 sockets
|
|
#endif
|
|
//Serial.printf("W5000socket begin, protocol=%d, port=%d\n", protocol, port);
|
|
SPI.beginTransaction(SPI_ETHERNET_SETTINGS);
|
|
// look at all the hardware sockets, use any that are closed (unused)
|
|
for (s=0; s < maxindex; s++) {
|
|
status[s] = W5100.readSnSR(s);
|
|
if (status[s] == SnSR::CLOSED) goto makesocket;
|
|
}
|
|
//Serial.printf("W5000socket step2\n");
|
|
// as a last resort, forcibly close any already closing
|
|
for (s=0; s < maxindex; s++) {
|
|
uint8_t stat = status[s];
|
|
if (stat == SnSR::LAST_ACK) goto closemakesocket;
|
|
if (stat == SnSR::TIME_WAIT) goto closemakesocket;
|
|
if (stat == SnSR::FIN_WAIT) goto closemakesocket;
|
|
if (stat == SnSR::CLOSING) goto closemakesocket;
|
|
}
|
|
#if 0
|
|
Serial.printf("W5000socket step3\n");
|
|
// next, use any that are effectively closed
|
|
for (s=0; s < MAX_SOCK_NUM; s++) {
|
|
uint8_t stat = status[s];
|
|
// TODO: this also needs to check if no more data
|
|
if (stat == SnSR::CLOSE_WAIT) goto closemakesocket;
|
|
}
|
|
#endif
|
|
SPI.endTransaction();
|
|
return MAX_SOCK_NUM; // all sockets are in use
|
|
closemakesocket:
|
|
//Serial.printf("W5000socket close\n");
|
|
W5100.execCmdSn(s, Sock_CLOSE);
|
|
makesocket:
|
|
//Serial.printf("W5000socket %d\n", s);
|
|
EthernetServer::server_port[s] = 0;
|
|
delayMicroseconds(250); // TODO: is this needed??
|
|
W5100.writeSnMR(s, protocol);
|
|
W5100.writeSnIR(s, 0xFF);
|
|
if (port > 0) {
|
|
W5100.writeSnPORT(s, port);
|
|
} else {
|
|
// if don't set the source port, set local_port number.
|
|
if (++local_port < 49152) local_port = 49152;
|
|
W5100.writeSnPORT(s, local_port);
|
|
}
|
|
// Calculate MAC address from Multicast IP Address
|
|
byte mac[] = { 0x01, 0x00, 0x5E, 0x00, 0x00, 0x00 };
|
|
mac[3] = ip[1] & 0x7F;
|
|
mac[4] = ip[2];
|
|
mac[5] = ip[3];
|
|
W5100.writeSnDIPR(s, ip.raw_address()); //239.255.0.1
|
|
W5100.writeSnDPORT(s, port);
|
|
W5100.writeSnDHAR(s, mac);
|
|
W5100.execCmdSn(s, Sock_OPEN);
|
|
state[s].RX_RSR = 0;
|
|
state[s].RX_RD = W5100.readSnRX_RD(s); // always zero?
|
|
state[s].RX_inc = 0;
|
|
state[s].TX_FSR = 0;
|
|
//Serial.printf("W5000socket prot=%d, RX_RD=%d\n", W5100.readSnMR(s), state[s].RX_RD);
|
|
SPI.endTransaction();
|
|
return s;
|
|
}
|
|
// Return the socket's status
|
|
//
|
|
uint8_t EthernetClass::socketStatus(uint8_t s)
|
|
{
|
|
SPI.beginTransaction(SPI_ETHERNET_SETTINGS);
|
|
uint8_t status = W5100.readSnSR(s);
|
|
SPI.endTransaction();
|
|
return status;
|
|
}
|
|
|
|
// Immediately close. If a TCP connection is established, the
|
|
// remote host is left unaware we closed.
|
|
//
|
|
void EthernetClass::socketClose(uint8_t s)
|
|
{
|
|
SPI.beginTransaction(SPI_ETHERNET_SETTINGS);
|
|
W5100.execCmdSn(s, Sock_CLOSE);
|
|
SPI.endTransaction();
|
|
}
|
|
|
|
|
|
// Place the socket in listening (server) mode
|
|
//
|
|
uint8_t EthernetClass::socketListen(uint8_t s)
|
|
{
|
|
SPI.beginTransaction(SPI_ETHERNET_SETTINGS);
|
|
if (W5100.readSnSR(s) != SnSR::INIT) {
|
|
SPI.endTransaction();
|
|
return 0;
|
|
}
|
|
W5100.execCmdSn(s, Sock_LISTEN);
|
|
SPI.endTransaction();
|
|
return 1;
|
|
}
|
|
|
|
|
|
// establish a TCP connection in Active (client) mode.
|
|
//
|
|
void EthernetClass::socketConnect(uint8_t s, uint8_t * addr, uint16_t port)
|
|
{
|
|
// set destination IP
|
|
SPI.beginTransaction(SPI_ETHERNET_SETTINGS);
|
|
W5100.writeSnDIPR(s, addr);
|
|
W5100.writeSnDPORT(s, port);
|
|
W5100.execCmdSn(s, Sock_CONNECT);
|
|
SPI.endTransaction();
|
|
}
|
|
|
|
|
|
|
|
// Gracefully disconnect a TCP connection.
|
|
//
|
|
void EthernetClass::socketDisconnect(uint8_t s)
|
|
{
|
|
SPI.beginTransaction(SPI_ETHERNET_SETTINGS);
|
|
W5100.execCmdSn(s, Sock_DISCON);
|
|
SPI.endTransaction();
|
|
}
|
|
|
|
|
|
|
|
/*****************************************/
|
|
/* Socket Data Receive Functions */
|
|
/*****************************************/
|
|
|
|
|
|
static uint16_t getSnRX_RSR(uint8_t s)
|
|
{
|
|
#if 1
|
|
uint16_t val, prev;
|
|
|
|
prev = W5100.readSnRX_RSR(s);
|
|
while (1) {
|
|
val = W5100.readSnRX_RSR(s);
|
|
if (val == prev) {
|
|
return val;
|
|
}
|
|
prev = val;
|
|
}
|
|
#else
|
|
uint16_t val = W5100.readSnRX_RSR(s);
|
|
return val;
|
|
#endif
|
|
}
|
|
|
|
static void read_data(uint8_t s, uint16_t src, uint8_t *dst, uint16_t len)
|
|
{
|
|
uint16_t size;
|
|
uint16_t src_mask;
|
|
uint16_t src_ptr;
|
|
|
|
//Serial.printf("read_data, len=%d, at:%d\n", len, src);
|
|
src_mask = (uint16_t)src & W5100.SMASK;
|
|
src_ptr = W5100.RBASE(s) + src_mask;
|
|
|
|
if (W5100.hasOffsetAddressMapping() || src_mask + len <= W5100.SSIZE) {
|
|
W5100.read(src_ptr, dst, len);
|
|
} else {
|
|
size = W5100.SSIZE - src_mask;
|
|
W5100.read(src_ptr, dst, size);
|
|
dst += size;
|
|
W5100.read(W5100.RBASE(s), dst, len - size);
|
|
}
|
|
}
|
|
|
|
// Receive data. Returns size, or -1 for no data, or 0 if connection closed
|
|
//
|
|
int EthernetClass::socketRecv(uint8_t s, uint8_t *buf, int16_t len)
|
|
{
|
|
// Check how much data is available
|
|
int ret = state[s].RX_RSR;
|
|
SPI.beginTransaction(SPI_ETHERNET_SETTINGS);
|
|
if (ret < len) {
|
|
uint16_t rsr = getSnRX_RSR(s);
|
|
ret = rsr - state[s].RX_inc;
|
|
state[s].RX_RSR = ret;
|
|
//Serial.printf("Sock_RECV, RX_RSR=%d, RX_inc=%d\n", ret, state[s].RX_inc);
|
|
}
|
|
if (ret == 0) {
|
|
// No data available.
|
|
uint8_t status = W5100.readSnSR(s);
|
|
if ( status == SnSR::LISTEN || status == SnSR::CLOSED ||
|
|
status == SnSR::CLOSE_WAIT ) {
|
|
// The remote end has closed its side of the connection,
|
|
// so this is the eof state
|
|
ret = 0;
|
|
} else {
|
|
// The connection is still up, but there's no data waiting to be read
|
|
ret = -1;
|
|
}
|
|
} else {
|
|
if (ret > len) ret = len; // more data available than buffer length
|
|
uint16_t ptr = state[s].RX_RD;
|
|
if (buf) read_data(s, ptr, buf, ret);
|
|
ptr += ret;
|
|
state[s].RX_RD = ptr;
|
|
state[s].RX_RSR -= ret;
|
|
uint16_t inc = state[s].RX_inc + ret;
|
|
if (inc >= 250 || state[s].RX_RSR == 0) {
|
|
state[s].RX_inc = 0;
|
|
W5100.writeSnRX_RD(s, ptr);
|
|
W5100.execCmdSn(s, Sock_RECV);
|
|
//Serial.printf("Sock_RECV cmd, RX_RD=%d, RX_RSR=%d\n",
|
|
// state[s].RX_RD, state[s].RX_RSR);
|
|
} else {
|
|
state[s].RX_inc = inc;
|
|
}
|
|
}
|
|
SPI.endTransaction();
|
|
//Serial.printf("socketRecv, ret=%d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
uint16_t EthernetClass::socketRecvAvailable(uint8_t s)
|
|
{
|
|
uint16_t ret = state[s].RX_RSR;
|
|
if (ret == 0) {
|
|
SPI.beginTransaction(SPI_ETHERNET_SETTINGS);
|
|
uint16_t rsr = getSnRX_RSR(s);
|
|
SPI.endTransaction();
|
|
ret = rsr - state[s].RX_inc;
|
|
state[s].RX_RSR = ret;
|
|
//Serial.printf("sockRecvAvailable s=%d, RX_RSR=%d\n", s, ret);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
// get the first byte in the receive queue (no checking)
|
|
//
|
|
uint8_t EthernetClass::socketPeek(uint8_t s)
|
|
{
|
|
uint8_t b;
|
|
SPI.beginTransaction(SPI_ETHERNET_SETTINGS);
|
|
uint16_t ptr = state[s].RX_RD;
|
|
W5100.read((ptr & W5100.SMASK) + W5100.RBASE(s), &b, 1);
|
|
SPI.endTransaction();
|
|
return b;
|
|
}
|
|
|
|
|
|
|
|
/*****************************************/
|
|
/* Socket Data Transmit Functions */
|
|
/*****************************************/
|
|
|
|
static uint16_t getSnTX_FSR(uint8_t s)
|
|
{
|
|
uint16_t val, prev;
|
|
|
|
prev = W5100.readSnTX_FSR(s);
|
|
while (1) {
|
|
val = W5100.readSnTX_FSR(s);
|
|
if (val == prev) {
|
|
state[s].TX_FSR = val;
|
|
return val;
|
|
}
|
|
prev = val;
|
|
}
|
|
}
|
|
|
|
|
|
static void write_data(uint8_t s, uint16_t data_offset, const uint8_t *data, uint16_t len)
|
|
{
|
|
uint16_t ptr = W5100.readSnTX_WR(s);
|
|
ptr += data_offset;
|
|
uint16_t offset = ptr & W5100.SMASK;
|
|
uint16_t dstAddr = offset + W5100.SBASE(s);
|
|
|
|
if (W5100.hasOffsetAddressMapping() || offset + len <= W5100.SSIZE) {
|
|
W5100.write(dstAddr, data, len);
|
|
} else {
|
|
// Wrap around circular buffer
|
|
uint16_t size = W5100.SSIZE - offset;
|
|
W5100.write(dstAddr, data, size);
|
|
W5100.write(W5100.SBASE(s), data + size, len - size);
|
|
}
|
|
ptr += len;
|
|
W5100.writeSnTX_WR(s, ptr);
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief This function used to send the data in TCP mode
|
|
* @return 1 for success else 0.
|
|
*/
|
|
uint16_t EthernetClass::socketSend(uint8_t s, const uint8_t * buf, uint16_t len)
|
|
{
|
|
uint8_t status=0;
|
|
uint16_t ret=0;
|
|
uint16_t freesize=0;
|
|
|
|
if (len > W5100.SSIZE) {
|
|
ret = W5100.SSIZE; // check size not to exceed MAX size.
|
|
} else {
|
|
ret = len;
|
|
}
|
|
|
|
// if freebuf is available, start.
|
|
do {
|
|
SPI.beginTransaction(SPI_ETHERNET_SETTINGS);
|
|
freesize = getSnTX_FSR(s);
|
|
status = W5100.readSnSR(s);
|
|
SPI.endTransaction();
|
|
if ((status != SnSR::ESTABLISHED) && (status != SnSR::CLOSE_WAIT)) {
|
|
ret = 0;
|
|
break;
|
|
}
|
|
yield();
|
|
} while (freesize < ret);
|
|
|
|
// copy data
|
|
SPI.beginTransaction(SPI_ETHERNET_SETTINGS);
|
|
write_data(s, 0, (uint8_t *)buf, ret);
|
|
W5100.execCmdSn(s, Sock_SEND);
|
|
|
|
/* +2008.01 bj */
|
|
while ( (W5100.readSnIR(s) & SnIR::SEND_OK) != SnIR::SEND_OK ) {
|
|
/* m2008.01 [bj] : reduce code */
|
|
if ( W5100.readSnSR(s) == SnSR::CLOSED ) {
|
|
SPI.endTransaction();
|
|
return 0;
|
|
}
|
|
SPI.endTransaction();
|
|
yield();
|
|
SPI.beginTransaction(SPI_ETHERNET_SETTINGS);
|
|
}
|
|
/* +2008.01 bj */
|
|
W5100.writeSnIR(s, SnIR::SEND_OK);
|
|
SPI.endTransaction();
|
|
return ret;
|
|
}
|
|
|
|
uint16_t EthernetClass::socketSendAvailable(uint8_t s)
|
|
{
|
|
uint8_t status=0;
|
|
uint16_t freesize=0;
|
|
SPI.beginTransaction(SPI_ETHERNET_SETTINGS);
|
|
freesize = getSnTX_FSR(s);
|
|
status = W5100.readSnSR(s);
|
|
SPI.endTransaction();
|
|
if ((status == SnSR::ESTABLISHED) || (status == SnSR::CLOSE_WAIT)) {
|
|
return freesize;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
uint16_t EthernetClass::socketBufferData(uint8_t s, uint16_t offset, const uint8_t* buf, uint16_t len)
|
|
{
|
|
//Serial.printf(" bufferData, offset=%d, len=%d\n", offset, len);
|
|
uint16_t ret =0;
|
|
SPI.beginTransaction(SPI_ETHERNET_SETTINGS);
|
|
uint16_t txfree = getSnTX_FSR(s);
|
|
if (len > txfree) {
|
|
ret = txfree; // check size not to exceed MAX size.
|
|
} else {
|
|
ret = len;
|
|
}
|
|
write_data(s, offset, buf, ret);
|
|
SPI.endTransaction();
|
|
return ret;
|
|
}
|
|
|
|
bool EthernetClass::socketStartUDP(uint8_t s, uint8_t* addr, uint16_t port)
|
|
{
|
|
if ( ((addr[0] == 0x00) && (addr[1] == 0x00) && (addr[2] == 0x00) && (addr[3] == 0x00)) ||
|
|
((port == 0x00)) ) {
|
|
return false;
|
|
}
|
|
SPI.beginTransaction(SPI_ETHERNET_SETTINGS);
|
|
W5100.writeSnDIPR(s, addr);
|
|
W5100.writeSnDPORT(s, port);
|
|
SPI.endTransaction();
|
|
return true;
|
|
}
|
|
|
|
bool EthernetClass::socketSendUDP(uint8_t s)
|
|
{
|
|
SPI.beginTransaction(SPI_ETHERNET_SETTINGS);
|
|
W5100.execCmdSn(s, Sock_SEND);
|
|
|
|
/* +2008.01 bj */
|
|
while ( (W5100.readSnIR(s) & SnIR::SEND_OK) != SnIR::SEND_OK ) {
|
|
if (W5100.readSnIR(s) & SnIR::TIMEOUT) {
|
|
/* +2008.01 [bj]: clear interrupt */
|
|
W5100.writeSnIR(s, (SnIR::SEND_OK|SnIR::TIMEOUT));
|
|
SPI.endTransaction();
|
|
//Serial.printf("sendUDP timeout\n");
|
|
return false;
|
|
}
|
|
SPI.endTransaction();
|
|
yield();
|
|
SPI.beginTransaction(SPI_ETHERNET_SETTINGS);
|
|
}
|
|
|
|
/* +2008.01 bj */
|
|
W5100.writeSnIR(s, SnIR::SEND_OK);
|
|
SPI.endTransaction();
|
|
|
|
//Serial.printf("sendUDP ok\n");
|
|
/* Sent ok */
|
|
return true;
|
|
}
|
|
|