arduino stuffs
Diffstat (limited to 'libraries/Ethernet/src/socket.cpp')
| -rw-r--r-- | libraries/Ethernet/src/socket.cpp | 539 |
1 files changed, 539 insertions, 0 deletions
diff --git a/libraries/Ethernet/src/socket.cpp b/libraries/Ethernet/src/socket.cpp new file mode 100644 index 0000000..f059dc9 --- /dev/null +++ b/libraries/Ethernet/src/socket.cpp @@ -0,0 +1,539 @@ +/* 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; +} + |