From aa5992c6573fd3033ca3a71bafe0913ec65f7d3a Mon Sep 17 00:00:00 2001 From: Joerg Deckert Date: Tue, 10 Dec 2013 16:33:02 +0100 Subject: [PATCH] lib/driver_*.spin: correcting LF --- lib/driver_enc28j60.spin | 1043 +++++++++++++++++++++++++++++++- lib/driver_socket.spin | 1222 +++++++++++++++++++++++++++++++++++++- 2 files changed, 2263 insertions(+), 2 deletions(-) diff --git a/lib/driver_enc28j60.spin b/lib/driver_enc28j60.spin index 67a1724..c1c1eb0 100644 --- a/lib/driver_enc28j60.spin +++ b/lib/driver_enc28j60.spin @@ -1 +1,1042 @@ -{{ ENC28J60 Ethernet MAC / PHY Driver ---------------------------------- Copyright (c) 2006-2009 Harrison Pham 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. The latest version of this software can be obtained from http://hdpham.com/PropTCP and http://obex.parallax.com/ Constant Names / Code Logic based on code from Microchip Technology, Inc.'s enc28j60.c / enc28j60.h source files}}CON version = 6 ' major version release = 0 ' minor versionCON' ***************************************' ** ENC28J60 SRAM Defines **' *************************************** ' ENC28J60 Frequency enc_freq = 25_000_000 ' ENC28J60 SRAM Usage Constants MAXFRAME = 1518 ' 6 (src addr) + 6 (dst addr) + 2 (type) + 1500 (data) + 4 (FCS CRC) = 1518 bytes TX_BUFFER_SIZE = 1518 TXSTART = 8192 - (TX_BUFFER_SIZE + 8) TXEND = TXSTART + (TX_BUFFER_SIZE + 8) RXSTART = $0000 RXSTOP = (TXSTART - 2) | $0001 ' must be odd (B5 Errata) RXSIZE = (RXSTOP - RXSTART + 1)DAT' ***************************************' ** MAC Address Vars / Defaults **' *************************************** ' ** This is the default MAC address used by this driver. The parent object ' can override this by passing a pointer to a new MAC address in the public ' start() method. It is recommend that this is done to provide a level of ' abstraction and makes tcp stack design easier. ' ** This is the ethernet MAC address, it is critical that you change this ' if you have more than one device using this code on a local network. ' ** If you plan on commercial deployment, you must purchase MAC address ' groups from IEEE or some other standards organization. eth_mac byte $02, $00, $00, $00, $00, $01' ***************************************' ** Global Variables **' *************************************** rxlen word 0 tx_end word 0 packetheader byte 0[6] 'packet byte 0[MAXFRAME]PUB start(_cs, _sck, _si, _so, xtalout, macptr)'' Starts the driver (uses 1 cog for spi engine) ' Since some people don't have 25mhz crystals, we use the cog counters ' to generate a 25mhz frequency for the ENC28J60 (I love the Propeller) ' Note: This requires a main crystal that is a multiple of 25mhz (5mhz works). spi_start(_cs, _sck, _so, _si, xtalout) ' If a MAC address pointer is provided (addr > -1) then copy it into ' the MAC address array (this kind of wastes space, but simplifies usage). if macptr > -1 bytemove(@eth_mac, macptr, 6) delay_ms(50) init_ENC28J60 ' return the chip silicon version banksel(EREVID) return rd_cntlreg(EREVID)PUB stop'' Stops the driver, frees 1 cog spi_stopPUB rd_macreg(address) : data'' Read MAC Control Register spi_out_cs(cRCR | address) spi_out_cs(0) ' transmit dummy byte data := spi_in ' get actual dataPUB rd_cntlreg(address) : data'' Read ETH Control Register spi_out_cs(cRCR | address) data := spi_inPUB wr_reg(address, data)'' Write MAC and ETH Control Register spi_out_cs(cWCR | address) spi_out(data)PUB bfc_reg(address, data)'' Clear Control Register Bits spi_out_cs(cBFC | address) spi_out(data)PUB bfs_reg(address, data)'' Set Control Register Bits spi_out_cs(cBFS | address) spi_out(data)PUB soft_reset'' Soft Reset ENC28J60 spi_out(cSC)PUB banksel(register)'' Select Control Register Bank bfc_reg(ECON1, %0000_0011) bfs_reg(ECON1, register >> 8) ' high bytePUB rd_phy(register) | low, high'' Read ENC28J60 PHY Register banksel(MIREGADR) wr_reg(MIREGADR, register) wr_reg(MICMD, MICMD_MIIRD) banksel(MISTAT) repeat while ((rd_macreg(MISTAT) & MISTAT_BUSY) > 0) banksel(MIREGADR) wr_reg(MICMD, $00) low := rd_macreg(MIRDL) high := rd_macreg(MIRDH) return (high << 8) + lowPUB wr_phy(register, data)'' Write ENC28J60 PHY Register banksel(MIREGADR) wr_reg(MIREGADR, register) wr_reg(MIWRL, data) wr_reg(MIWRH, data >> 8) banksel(MISTAT) repeat while ((rd_macreg(MISTAT) & MISTAT_BUSY) > 0)PUB rd_sram : data'' Read ENC28J60 8k Buffer Memory spi_out_cs(cRBM) data := spi_inPUB wr_sram(data)'' Write ENC28J60 8k Buffer Memory spi_out_cs(cWBM) spi_out(data)PUB init_ENC28J60 | i'' Init ENC28J60 Chip repeat i := rd_cntlreg(ESTAT) while (i & $08) OR (!i & ESTAT_CLKRDY) soft_reset delay_ms(5) ' reset delay bfc_reg(ECON1, ECON1_RXEN) ' stop send / recv bfc_reg(ECON1, ECON1_TXRTS) bfs_reg(ECON2, ECON2_AUTOINC) ' enable auto increment of sram pointers (already default) packetheader[nextpacket_low] := RXSTART packetheader[nextpacket_high] := constant(RXSTART >> 8) banksel(ERDPTL) wr_reg(ERDPTL, RXSTART) wr_reg(ERDPTH, constant(RXSTART >> 8)) banksel(ERXSTL) wr_reg(ERXSTL, RXSTART) wr_reg(ERXSTH, constant(RXSTART >> 8)) wr_reg(ERXRDPTL, RXSTOP) wr_reg(ERXRDPTH, constant(RXSTOP >> 8)) wr_reg(ERXNDL, RXSTOP) wr_reg(ERXNDH, constant(RXSTOP >> 8)) wr_reg(ETXSTL, TXSTART) wr_reg(ETXSTH, constant(TXSTART >> 8)) banksel(MACON1) wr_reg(MACON1, constant(MACON1_TXPAUS | MACON1_RXPAUS | MACON1_MARXEN)) wr_reg(MACON3, constant(MACON3_TXCRCEN | MACON3_PADCFG0 | MACON3_FRMLNEN)) ' don't timeout transmissions on saturated media wr_reg(MACON4, MACON4_DEFER) ' collisions occur at 63rd byte wr_reg(MACLCON2, 63) wr_reg(MAIPGL, $12) wr_reg(MAIPGH, $0C) wr_reg(MAMXFLL, MAXFRAME) wr_reg(MAMXFLH, constant(MAXFRAME >> 8)) ' back-to-back inter-packet gap time ' full duplex = 0x15 (9.6us) ' half duplex = 0x12 (9.6us) wr_reg(MABBIPG, $12) wr_reg(MAIPGL, $12) wr_reg(MAIPGH, $0C) ' write mac address to the chip banksel(MAADR1) wr_reg(MAADR1, eth_mac[0]) wr_reg(MAADR2, eth_mac[1]) wr_reg(MAADR3, eth_mac[2]) wr_reg(MAADR4, eth_mac[3]) wr_reg(MAADR5, eth_mac[4]) wr_reg(MAADR6, eth_mac[5]) ' half duplex wr_phy(PHCON2, PHCON2_HDLDIS) wr_phy(PHCON1, $0000) ' set LED options wr_phy(PHLCON, $0742) ' $0472 => ledA = link, ledB = tx/rx ' $0742 => ledA = tx/rx, ledB = link ' enable packet reception bfs_reg(ECON1, ECON1_RXEN)PUB get_frame(pktptr) | packet_addr, new_rdptr'' Get Ethernet Frame from Buffer banksel(ERDPTL) wr_reg(ERDPTL, packetheader[nextpacket_low]) wr_reg(ERDPTH, packetheader[nextpacket_high]) repeat packet_addr from 0 to 5 packetheader[packet_addr] := rd_sram rxlen := (packetheader[rec_bytecnt_high] << 8) + packetheader[rec_bytecnt_low] 'bytefill(@packet, 0, MAXFRAME) ' Uncomment this if you want to clean out the buffer first ' otherwise, leave commented since it's faster to just leave stuff ' in the buffer ' protect from oversized packet if rxlen =< MAXFRAME rd_block(pktptr, rxlen) {repeat packet_addr from 0 to rxlen - 1 BYTE[@packet][packet_addr] := rd_sram} new_rdptr := (packetheader[nextpacket_high] << 8) + packetheader[nextpacket_low] ' handle errata read pointer start (must be odd) --new_rdptr if (new_rdptr < RXSTART) OR (new_rdptr > RXSTOP) new_rdptr := RXSTOP bfs_reg(ECON2, ECON2_PKTDEC) banksel(ERXRDPTL) wr_reg(ERXRDPTL, new_rdptr) wr_reg(ERXRDPTH, new_rdptr >> 8)PUB start_frame'' Start frame - Inits the NIC and sets stuff banksel(EWRPTL) wr_reg(EWRPTL, TXSTART) wr_reg(EWRPTH, constant(TXSTART >> 8)) tx_end := constant(TXSTART - 1) ' start location is really address 0, so we are sending a count of - 1 wr_frame(cTXCONTROL)PUB wr_frame(data)'' Write frame data wr_sram(data) ++tx_endPUB wr_block(startaddr, count) blockwrite(startaddr, count) tx_end += countPUB rd_block(startaddr, count) blockread(startaddr, count)PUB send_frame'' Sends frame'' Will retry on send failure up to 15 times with a 1ms delay in between repeats repeat 15 if p_send_frame ' send packet, if successful then quit retry loop quit delay_ms(1)PRI p_send_frame | i, eirval' Sends the frame banksel(ETXSTL) wr_reg(ETXSTL, TXSTART) wr_reg(ETXSTH, constant(TXSTART >> 8)) banksel(ETXNDL) wr_reg(ETXNDL, tx_end) wr_reg(ETXNDH, tx_end >> 8) ' B5 Errata #10 - Reset transmit logic before send bfs_reg(ECON1, ECON1_TXRST) bfc_reg(ECON1, ECON1_TXRST) ' B5 Errata #10 & #13: Reset interrupt error flags bfc_reg(EIR, constant(EIR_TXERIF | EIR_TXIF)) ' trigger send bfs_reg(ECON1, ECON1_TXRTS) ' fix for transmit stalls (derived from errata B5 #13), watches TXIF and TXERIF bits ' also implements a ~3.75ms (15 * 250us) timeout if send fails (occurs on random packet collisions) ' btw: this took over 10 hours to fix due to the elusive undocumented bug i := 0 repeat eirval := rd_cntlreg(EIR) if ((eirval & constant(EIR_TXERIF | EIR_TXIF)) > 0) quit if (++i => 15) eirval := EIR_TXERIF quit delay_us(250) ' B5 Errata #13 - Reset TXRTS if failed send then reset logic bfc_reg(ECON1, ECON1_TXRTS) if ((eirval & EIR_TXERIF) == 0) return true ' successful send (no error interrupt) else return false ' failed send (error interrupt)PUB get_mac_pointer'' Gets mac address pointer return @eth_macPUB get_rxlen'' Gets received packet length return rxlen - 4 ' knock off the 4 byte Frame Check Sequence CRC, not used anywhere outside of this driver (pg 31 datasheet)PRI delay_us(Duration) waitcnt(((clkfreq / 1_000_000 * Duration - 3928)) + cnt) PRI delay_ms(Duration) waitcnt(((clkfreq / 1_000 * Duration - 3932)) + cnt)' ***************************************' ** ASM SPI Engine **' *************************************** DAT cog long 0 command long 0 CON SPIOUT = %0000_0001 SPIIN = %0000_0010 SRAMWRITE = %0000_0100 SRAMREAD = %0000_1000 CSON = %0001_0000 CSOFF = %0010_0000 CKSUM = %0100_0000 SPIBITS = 8PRI spi_out(value) setcommand(constant(SPIOUT | CSON | CSOFF), @value) PRI spi_out_cs(value) setcommand(constant(SPIOUT | CSON), @value)PRI spi_in : value setcommand(constant(SPIIN | CSON | CSOFF), @value) PRI spi_in_cs : value setcommand(constant(SPIIN | CSON), @value)PRI blockwrite(startaddr, count) setcommand(SRAMWRITE, @startaddr)PRI blockread(startaddr, count) setcommand(SRAMREAD, @startaddr)PUB chksum_add(startaddr, count) setcommand(CKSUM, @startaddr) return startaddr PRI spi_start(_cs, _sck, _di, _do, _freqpin) spi_stop cspin := |< _cs dipin := |< _di dopin := |< _do clkpin := |< _sck ctramode := %0_00100_00_0000_0000_0000_0000_0000_0000 + _sck ctrbmode := %0_00100_00_0000_0000_0000_0000_0000_0000 + _do spi_setupfreqsynth(_freqpin) cog := cognew(@init, @command) + 1 PRI spi_stop if cog cogstop(cog~ - 1) ctra := 0 command~ PRI setcommand(cmd, argptr) command := cmd << 16 + argptr 'write command and pointer repeat while command 'wait for command to be cleared, signifying receiptPRI spi_setupfreqsynth(pin) if pin < 0 ' pin num was negative -> disable freq synth return dira[pin] := 1 ctra := constant(%00010 << 26) '..set PLL mode ctra |= constant((>|((enc_freq - 1) / 1_000_000)) << 23) 'set PLLDIV frqa := spi_fraction(enc_freq, CLKFREQ, constant(4 - (>|((enc_freq - 1) / 1_000_000)))) 'Compute FRQA/FRQB value ctra |= pin 'set PINA to complete CTRA/CTRB valuePRI spi_fraction(a, b, shift) : f if shift > 0 'if shift, pre-shift a or b left a <<= shift 'to maintain significant bits while if shift < 0 'insuring proper result b <<= -shift repeat 32 'perform long division of a/b f <<= 1 if a => b a -= b f++ a <<= 1DAT orginit or dira, cspin 'pin directions andn dira, dipin or dira, dopin or dira, clkpin or outa, cspin 'turn off cs (bring it high) mov frqb, #0 'disable ctrb increment mov ctrb, ctrbmode loop wrlong zero,par 'zero command (tell spin we are done processing):subloop rdlong t1,par wz 'wait for command if_z jmp #:subloop mov addr, t1 'used for holding return addr to spin vars rdlong arg0, t1 'arg0 add t1, #4 rdlong arg1, t1 'arg1 mov lkup, addr 'get the command var from spin shr lkup, #16 'extract the cmd from the command var test lkup, #CSON wz 'turn on cs if_nz andn outa, cspin test lkup, #SPIOUT wz 'spi out if_nz call #spi_out_ test lkup, #SPIIN wz 'spi in if_nz call #xspi_in_ test lkup, #SRAMWRITE wz 'sram block write if_nz jmp #sram_write_ test lkup, #SRAMREAD wz 'sram block read if_nz jmp #sram_read_ test lkup, #CSOFF wz 'cs off if_nz or outa, cspin test lkup, #CKSUM wz 'perform checksum if_nz call #csum16 jmp #loop ' no cmd found spi_out_ andn outa, clkpin shl arg0, #24 mov phsb, arg0 ' data to write mov frqa, freqw ' 20MHz write frequency mov phsa, #0 ' start at clocking at 0 mov ctra, ctramode ' send data @ 20MHz rol phsb, #1 rol phsb, #1 rol phsb, #1 rol phsb, #1 rol phsb, #1 rol phsb, #1 rol phsb, #1 mov ctra, #0 ' disable andn outa, clkpin spi_out__ret retspi_in_ andn outa, clkpin mov phsa, phsr ' start phs for clock mov frqa, freqr ' 10MHz read frequency nop mov ctra, ctramode ' start clocking test dipin, ina wc rcl arg0, #1 test dipin, ina wc rcl arg0, #1 test dipin, ina wc rcl arg0, #1 test dipin, ina wc rcl arg0, #1 test dipin, ina wc rcl arg0, #1 test dipin, ina wc rcl arg0, #1 test dipin, ina wc rcl arg0, #1 test dipin, ina wc mov ctra, #0 ' stop clocking rcl arg0, #1 andn outa, clkpin spi_in__ret retxspi_in_ call #spi_in_ wrbyte arg0, addr ' write byte back to spin result varxspi_in__ret ret' SRAM Block Read/Writesram_write_ ' block write (arg0=hub addr, arg1=count) mov t1, arg0 mov t2, arg1 andn outa, cspin mov arg0, #cWBM call #spi_out_:loop rdbyte arg0, t1 call #spi_out_ add t1, #1 djnz t2, #:loop or outa, cspin jmp #loop sram_read_ ' block read (arg0=hub addr, arg1=count) mov t1, arg0 mov t2, arg1 andn outa, cspin mov arg0, #cRBM call #spi_out_:loop call #spi_in_ wrbyte arg0, t1 add t1, #1 djnz t2, #:loop or outa, cspin jmp #loopcsum16 ' performs checksum 16bit additions on the data ' arg0=hub addr, arg1=length, writes sum to first arg mov t1, #0 ' clear sum:loop rdbyte t2, arg0 ' read two bytes (16 bits) add arg0, #1 rdbyte t3, arg0 add arg0, #1 shl t2, #8 ' build the word add t2, t3 add t1, t2 ' add numbers mov t2, t1 ' add lower and upper words together shr t2, #16 and t1, hffff add t1, t2 sub arg1, #2 cmp arg1, #1 wz, wc if_nc_and_nz jmp #:loop if_z rdbyte t2, arg0 ' add last byte (odd) if_z shl t2, #8 if_z add t1, t2 wrlong t1, addr ' return result back to SPINcsum16_ret retzero long 0 'constants 'values filled by spin code before launchingcspin long 0 ' chip select pindipin long 0 ' data in pin (enc28j60 -> prop)dopin long 0 ' data out pin (prop -> enc28j60)clkpin long 0 ' clock pin (prop -> enc28j60)ctramode long 0 ' ctr mode for CLKctrbmode long 0 ' ctr mode for SPI Outhffff long $FFFFfreqr long $2000_0000 'frequency of SCK /8 for receivefreqw long $4000_0000 'frequency of SCK /4 for sendphsr long $6000_0000 'temp variablest1 res 1 ' loop and cog shutdown t2 res 1 ' loop and cog shutdownt3 res 1 ' Used to hold DataValue SHIFTIN/SHIFTOUTt4 res 1 ' Used to hold # of Bitst5 res 1 ' Used for temporary data maskaddr res 1 ' Used to hold return address of first Argument passedlkup res 1 ' Used to hold command lookup 'arguments passed to/from high-level Spinarg0 res 1 ' bits / start addressarg1 res 1 ' value / count CON' ***************************************' ** ENC28J60 Control Constants **' *************************************** ' ENC28J60 opcodes (OR with 5bit address) cWCR = %010 << 5 ' write control register command cBFS = %100 << 5 ' bit field set command cBFC = %101 << 5 ' bit field clear command cRCR = %000 << 5 ' read control register command cRBM = (%001 << 5) | $1A ' read buffer memory command cWBM = (%011 << 5) | $1A ' write buffer memory command cSC = (%111 << 5) | $1F ' system command ' This is used to trigger TX in the ENC28J60, it shouldn't change, but you never know... cTXCONTROL = $0E ' Packet header format (tail of the receive packet in the ENC28J60 SRAM) #0,nextpacket_low,nextpacket_high,rec_bytecnt_low,rec_bytecnt_high,rec_status_low,rec_status_high' ***************************************' ** ENC28J60 Register Defines **' *************************************** ' Bank 0 registers -------- ERDPTL = $00 ERDPTH = $01 EWRPTL = $02 EWRPTH = $03 ETXSTL = $04 ETXSTH = $05 ETXNDL = $06 ETXNDH = $07 ERXSTL = $08 ERXSTH = $09 ERXNDL = $0A ERXNDH = $0B ERXRDPTL = $0C ERXRDPTH = $0D ERXWRPTL = $0E ERXWRPTH = $0F EDMASTL = $10 EDMASTH = $11 EDMANDL = $12 EDMANDH = $13 EDMADSTL = $14 EDMADSTH = $15 EDMACSL = $16 EDMACSH = $17 ' = $18 ' = $19 ' r = $1A EIE = $1B EIR = $1C ESTAT = $1D ECON2 = $1E ECON1 = $1F ' Bank 1 registers ----- EHT0 = $100 EHT1 = $101 EHT2 = $102 EHT3 = $103 EHT4 = $104 EHT5 = $105 EHT6 = $106 EHT7 = $107 EPMM0 = $108 EPMM1 = $109 EPMM2 = $10A EPMM3 = $10B EPMM4 = $10C EPMM5 = $10D EPMM6 = $10E EPMM7 = $10F EPMCSL = $110 EPMCSH = $111 ' = $112 ' = $113 EPMOL = $114 EPMOH = $115 EWOLIE = $116 EWOLIR = $117 ERXFCON = $118 EPKTCNT = $119 ' r = $11A ' EIE = $11B ' EIR = $11C ' ESTAT = $11D ' ECON2 = $11E ' ECON1 = $11F ' Bank 2 registers ----- MACON1 = $200 MACON2 = $201 MACON3 = $202 MACON4 = $203 MABBIPG = $204 ' = $205 MAIPGL = $206 MAIPGH = $207 MACLCON1 = $208 MACLCON2 = $209 MAMXFLL = $20A MAMXFLH = $20B ' r = $20C MAPHSUP = $20D ' r = $20E ' = $20F ' r = $210 MICON = $211 MICMD = $212 ' = $213 MIREGADR = $214 ' r = $215 MIWRL = $216 MIWRH = $217 MIRDL = $218 MIRDH = $219 ' r = $21A ' EIE = $21B ' EIR = $21C ' ESTAT = $21D ' ECON2 = $21E ' ECON1 = $21F ' Bank 3 registers ----- MAADR5 = $300 MAADR6 = $301 MAADR3 = $302 MAADR4 = $303 MAADR1 = $304 MAADR2 = $305 {MAADR1 = $300 MAADR0 = $301 MAADR3 = $302 MAADR2 = $303 MAADR5 = $304 MAADR4 = $305} EBSTSD = $306 EBSTCON = $307 EBSTCSL = $308 EBSTCSH = $309 MISTAT = $30A ' = $30B ' = $30C ' = $30D ' = $30E ' = $30F ' = $310 ' = $311 EREVID = $312 ' = $313 ' = $314 ECOCON = $315 ' EPHTST $316 EFLOCON = $317 EPAUSL = $318 EPAUSH = $319 ' r = $31A ' EIE = $31B ' EIR = $31C ' ESTAT = $31D ' ECON2 = $31E ' ECON1 = $31F {****************************************************************************** * PH Register Locations ******************************************************************************} PHCON1 = $00 PHSTAT1 = $01 PHID1 = $02 PHID2 = $03 PHCON2 = $10 PHSTAT2 = $11 PHIE = $12 PHIR = $13 PHLCON = $14 {****************************************************************************** * Individual Register Bits ******************************************************************************} ' ETH/MAC/MII bits ' EIE bits ---------- EIE_INTIE = (1<<7) EIE_PKTIE = (1<<6) EIE_DMAIE = (1<<5) EIE_LINKIE = (1<<4) EIE_TXIE = (1<<3) EIE_WOLIE = (1<<2) EIE_TXERIE = (1<<1) EIE_RXERIE = (1) ' EIR bits ---------- EIR_PKTIF = (1<<6) EIR_DMAIF = (1<<5) EIR_LINKIF = (1<<4) EIR_TXIF = (1<<3) EIR_WOLIF = (1<<2) EIR_TXERIF = (1<<1) EIR_RXERIF = (1) ' ESTAT bits --------- ESTAT_INT = (1<<7) ESTAT_LATECOL = (1<<4) ESTAT_RXBUSY = (1<<2) ESTAT_TXABRT = (1<<1) ESTAT_CLKRDY = (1) ' ECON2 bits -------- ECON2_AUTOINC = (1<<7) ECON2_PKTDEC = (1<<6) ECON2_PWRSV = (1<<5) ECON2_VRTP = (1<<4) ECON2_VRPS = (1<<3) ' ECON1 bits -------- ECON1_TXRST = (1<<7) ECON1_RXRST = (1<<6) ECON1_DMAST = (1<<5) ECON1_CSUMEN = (1<<4) ECON1_TXRTS = (1<<3) ECON1_RXEN = (1<<2) ECON1_BSEL1 = (1<<1) ECON1_BSEL0 = (1) ' EWOLIE bits ------- EWOLIE_UCWOLIE = (1<<7) EWOLIE_AWOLIE = (1<<6) EWOLIE_PMWOLIE = (1<<4) EWOLIE_MPWOLIE = (1<<3) EWOLIE_HTWOLIE = (1<<2) EWOLIE_MCWOLIE = (1<<1) EWOLIE_BCWOLIE = (1) ' EWOLIR bits ------- EWOLIR_UCWOLIF = (1<<7) EWOLIR_AWOLIF = (1<<6) EWOLIR_PMWOLIF = (1<<4) EWOLIR_MPWOLIF = (1<<3) EWOLIR_HTWOLIF = (1<<2) EWOLIR_MCWOLIF = (1<<1) EWOLIR_BCWOLIF = (1) ' ERXFCON bits ------ ERXFCON_UCEN = (1<<7) ERXFCON_ANDOR = (1<<6) ERXFCON_CRCEN = (1<<5) ERXFCON_PMEN = (1<<4) ERXFCON_MPEN = (1<<3) ERXFCON_HTEN = (1<<2) ERXFCON_MCEN = (1<<1) ERXFCON_BCEN = (1) ' MACON1 bits -------- MACON1_LOOPBK = (1<<4) MACON1_TXPAUS = (1<<3) MACON1_RXPAUS = (1<<2) MACON1_PASSALL = (1<<1) MACON1_MARXEN = (1) ' MACON2 bits -------- MACON2_MARST = (1<<7) MACON2_RNDRST = (1<<6) MACON2_MARXRST = (1<<3) MACON2_RFUNRST = (1<<2) MACON2_MATXRST = (1<<1) MACON2_TFUNRST = (1) ' MACON3 bits -------- MACON3_PADCFG2 = (1<<7) MACON3_PADCFG1 = (1<<6) MACON3_PADCFG0 = (1<<5) MACON3_TXCRCEN = (1<<4) MACON3_PHDRLEN = (1<<3) MACON3_HFRMEN = (1<<2) MACON3_FRMLNEN = (1<<1) MACON3_FULDPX = (1) ' MACON4 bits -------- MACON4_DEFER = (1<<6) MACON4_BPEN = (1<<5) MACON4_NOBKOFF = (1<<4) MACON4_LONGPRE = (1<<1) MACON4_PUREPRE = (1) ' MAPHSUP bits ---- MAPHSUP_RSTRMII = (1<<3) ' MICON bits -------- MICON_RSTMII = (1<<7) ' MICMD bits --------- MICMD_MIISCAN = (1<<1) MICMD_MIIRD = (1) ' EBSTCON bits ----- EBSTCON_PSV2 = (1<<7) EBSTCON_PSV1 = (1<<6) EBSTCON_PSV0 = (1<<5) EBSTCON_PSEL = (1<<4) EBSTCON_TMSEL1 = (1<<3) EBSTCON_TMSEL0 = (1<<2) EBSTCON_TME = (1<<1) EBSTCON_BISTST = (1) ' MISTAT bits -------- MISTAT_NVALID = (1<<2) MISTAT_SCAN = (1<<1) MISTAT_BUSY = (1) ' ECOCON bits ------- ECOCON_COCON2 = (1<<2) ECOCON_COCON1 = (1<<1) ECOCON_COCON0 = (1) ' EFLOCON bits ----- EFLOCON_FULDPXS = (1<<2) EFLOCON_FCEN1 = (1<<1) EFLOCON_FCEN0 = (1) ' PHY bits ' PHCON1 bits ---------- PHCON1_PRST = (1<<15) PHCON1_PLOOPBK = (1<<14) PHCON1_PPWRSV = (1<<11) PHCON1_PDPXMD = (1<<8) ' PHSTAT1 bits -------- PHSTAT1_PFDPX = (1<<12) PHSTAT1_PHDPX = (1<<11) PHSTAT1_LLSTAT = (1<<2) PHSTAT1_JBSTAT = (1<<1) ' PHID2 bits -------- PHID2_PID24 = (1<<15) PHID2_PID23 = (1<<14) PHID2_PID22 = (1<<13) PHID2_PID21 = (1<<12) PHID2_PID20 = (1<<11) PHID2_PID19 = (1<<10) PHID2_PPN5 = (1<<9) PHID2_PPN4 = (1<<8) PHID2_PPN3 = (1<<7) PHID2_PPN2 = (1<<6) PHID2_PPN1 = (1<<5) PHID2_PPN0 = (1<<4) PHID2_PREV3 = (1<<3) PHID2_PREV2 = (1<<2) PHID2_PREV1 = (1<<1) PHID2_PREV0 = (1) ' PHCON2 bits ---------- PHCON2_FRCLNK = (1<<14) PHCON2_TXDIS = (1<<13) PHCON2_JABBER = (1<<10) PHCON2_HDLDIS = (1<<8) ' PHSTAT2 bits -------- PHSTAT2_TXSTAT = (1<<13) PHSTAT2_RXSTAT = (1<<12) PHSTAT2_COLSTAT = (1<<11) PHSTAT2_LSTAT = (1<<10) PHSTAT2_DPXSTAT = (1<<9) PHSTAT2_PLRITY = (1<<5) ' PHIE bits ----------- PHIE_PLNKIE = (1<<4) PHIE_PGEIE = (1<<1) ' PHIR bits ----------- PHIR_PLNKIF = (1<<4) PHIR_PGIF = (1<<2) ' PHLCON bits ------- PHLCON_LACFG3 = (1<<11) PHLCON_LACFG2 = (1<<10) PHLCON_LACFG1 = (1<<9) PHLCON_LACFG0 = (1<<8) PHLCON_LBCFG3 = (1<<7) PHLCON_LBCFG2 = (1<<6) PHLCON_LBCFG1 = (1<<5) PHLCON_LBCFG0 = (1<<4) PHLCON_LFRQ1 = (1<<3) PHLCON_LFRQ0 = (1<<2) PHLCON_STRCH = (1<<1) \ No newline at end of file +{{ + ENC28J60 Ethernet MAC / PHY Driver + ---------------------------------- + + Copyright (c) 2006-2009 Harrison Pham + + 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. + + The latest version of this software can be obtained from + http://hdpham.com/PropTCP and http://obex.parallax.com/ + + Constant Names / Code Logic based on code from + Microchip Technology, Inc.'s enc28j60.c / enc28j60.h source files +}} + +CON + version = 6 ' major version + release = 0 ' minor version + +CON +' *************************************** +' ** ENC28J60 SRAM Defines ** +' *************************************** + ' ENC28J60 Frequency + enc_freq = 25_000_000 + + ' ENC28J60 SRAM Usage Constants + MAXFRAME = 1518 ' 6 (src addr) + 6 (dst addr) + 2 (type) + 1500 (data) + 4 (FCS CRC) = 1518 bytes + TX_BUFFER_SIZE = 1518 + + TXSTART = 8192 - (TX_BUFFER_SIZE + 8) + TXEND = TXSTART + (TX_BUFFER_SIZE + 8) + RXSTART = $0000 + RXSTOP = (TXSTART - 2) | $0001 ' must be odd (B5 Errata) + RXSIZE = (RXSTOP - RXSTART + 1) + +DAT +' *************************************** +' ** MAC Address Vars / Defaults ** +' *************************************** + ' ** This is the default MAC address used by this driver. The parent object + ' can override this by passing a pointer to a new MAC address in the public + ' start() method. It is recommend that this is done to provide a level of + ' abstraction and makes tcp stack design easier. + ' ** This is the ethernet MAC address, it is critical that you change this + ' if you have more than one device using this code on a local network. + ' ** If you plan on commercial deployment, you must purchase MAC address + ' groups from IEEE or some other standards organization. + eth_mac byte $02, $00, $00, $00, $00, $01 + +' *************************************** +' ** Global Variables ** +' *************************************** + rxlen word 0 + tx_end word 0 + + packetheader byte 0[6] + + 'packet byte 0[MAXFRAME] + +PUB start(_cs, _sck, _si, _so, xtalout, macptr) +'' Starts the driver (uses 1 cog for spi engine) + + ' Since some people don't have 25mhz crystals, we use the cog counters + ' to generate a 25mhz frequency for the ENC28J60 (I love the Propeller) + ' Note: This requires a main crystal that is a multiple of 25mhz (5mhz works). + spi_start(_cs, _sck, _so, _si, xtalout) + + ' If a MAC address pointer is provided (addr > -1) then copy it into + ' the MAC address array (this kind of wastes space, but simplifies usage). + if macptr > -1 + bytemove(@eth_mac, macptr, 6) + + delay_ms(50) + init_ENC28J60 + + ' return the chip silicon version + banksel(EREVID) + return rd_cntlreg(EREVID) + +PUB stop +'' Stops the driver, frees 1 cog + + spi_stop + +PUB rd_macreg(address) : data +'' Read MAC Control Register + + spi_out_cs(cRCR | address) + spi_out_cs(0) ' transmit dummy byte + data := spi_in ' get actual data + +PUB rd_cntlreg(address) : data +'' Read ETH Control Register + + spi_out_cs(cRCR | address) + data := spi_in + +PUB wr_reg(address, data) +'' Write MAC and ETH Control Register + + spi_out_cs(cWCR | address) + spi_out(data) + +PUB bfc_reg(address, data) +'' Clear Control Register Bits + + spi_out_cs(cBFC | address) + spi_out(data) + +PUB bfs_reg(address, data) +'' Set Control Register Bits + + spi_out_cs(cBFS | address) + spi_out(data) + +PUB soft_reset +'' Soft Reset ENC28J60 + + spi_out(cSC) + +PUB banksel(register) +'' Select Control Register Bank + + bfc_reg(ECON1, %0000_0011) + bfs_reg(ECON1, register >> 8) ' high byte + +PUB rd_phy(register) | low, high +'' Read ENC28J60 PHY Register + + banksel(MIREGADR) + wr_reg(MIREGADR, register) + wr_reg(MICMD, MICMD_MIIRD) + banksel(MISTAT) + repeat while ((rd_macreg(MISTAT) & MISTAT_BUSY) > 0) + banksel(MIREGADR) + wr_reg(MICMD, $00) + low := rd_macreg(MIRDL) + high := rd_macreg(MIRDH) + return (high << 8) + low + +PUB wr_phy(register, data) +'' Write ENC28J60 PHY Register + + banksel(MIREGADR) + wr_reg(MIREGADR, register) + wr_reg(MIWRL, data) + wr_reg(MIWRH, data >> 8) + banksel(MISTAT) + repeat while ((rd_macreg(MISTAT) & MISTAT_BUSY) > 0) + +PUB rd_sram : data +'' Read ENC28J60 8k Buffer Memory + + spi_out_cs(cRBM) + data := spi_in + +PUB wr_sram(data) +'' Write ENC28J60 8k Buffer Memory + + spi_out_cs(cWBM) + spi_out(data) + +PUB init_ENC28J60 | i +'' Init ENC28J60 Chip + + repeat + i := rd_cntlreg(ESTAT) + while (i & $08) OR (!i & ESTAT_CLKRDY) + + soft_reset + delay_ms(5) ' reset delay + + bfc_reg(ECON1, ECON1_RXEN) ' stop send / recv + bfc_reg(ECON1, ECON1_TXRTS) + + bfs_reg(ECON2, ECON2_AUTOINC) ' enable auto increment of sram pointers (already default) + + packetheader[nextpacket_low] := RXSTART + packetheader[nextpacket_high] := constant(RXSTART >> 8) + + banksel(ERDPTL) + wr_reg(ERDPTL, RXSTART) + wr_reg(ERDPTH, constant(RXSTART >> 8)) + + banksel(ERXSTL) + wr_reg(ERXSTL, RXSTART) + wr_reg(ERXSTH, constant(RXSTART >> 8)) + wr_reg(ERXRDPTL, RXSTOP) + wr_reg(ERXRDPTH, constant(RXSTOP >> 8)) + wr_reg(ERXNDL, RXSTOP) + wr_reg(ERXNDH, constant(RXSTOP >> 8)) + wr_reg(ETXSTL, TXSTART) + wr_reg(ETXSTH, constant(TXSTART >> 8)) + + banksel(MACON1) + wr_reg(MACON1, constant(MACON1_TXPAUS | MACON1_RXPAUS | MACON1_MARXEN)) + wr_reg(MACON3, constant(MACON3_TXCRCEN | MACON3_PADCFG0 | MACON3_FRMLNEN)) + + ' don't timeout transmissions on saturated media + wr_reg(MACON4, MACON4_DEFER) + ' collisions occur at 63rd byte + wr_reg(MACLCON2, 63) + + wr_reg(MAIPGL, $12) + wr_reg(MAIPGH, $0C) + wr_reg(MAMXFLL, MAXFRAME) + wr_reg(MAMXFLH, constant(MAXFRAME >> 8)) + + ' back-to-back inter-packet gap time + ' full duplex = 0x15 (9.6us) + ' half duplex = 0x12 (9.6us) + wr_reg(MABBIPG, $12) + wr_reg(MAIPGL, $12) + wr_reg(MAIPGH, $0C) + + ' write mac address to the chip + banksel(MAADR1) + wr_reg(MAADR1, eth_mac[0]) + wr_reg(MAADR2, eth_mac[1]) + wr_reg(MAADR3, eth_mac[2]) + wr_reg(MAADR4, eth_mac[3]) + wr_reg(MAADR5, eth_mac[4]) + wr_reg(MAADR6, eth_mac[5]) + + ' half duplex + wr_phy(PHCON2, PHCON2_HDLDIS) + wr_phy(PHCON1, $0000) + + ' set LED options + wr_phy(PHLCON, $0742) ' $0472 => ledA = link, ledB = tx/rx + ' $0742 => ledA = tx/rx, ledB = link + + ' enable packet reception + bfs_reg(ECON1, ECON1_RXEN) + +PUB get_frame(pktptr) | packet_addr, new_rdptr +'' Get Ethernet Frame from Buffer + + banksel(ERDPTL) + wr_reg(ERDPTL, packetheader[nextpacket_low]) + wr_reg(ERDPTH, packetheader[nextpacket_high]) + + repeat packet_addr from 0 to 5 + packetheader[packet_addr] := rd_sram + + rxlen := (packetheader[rec_bytecnt_high] << 8) + packetheader[rec_bytecnt_low] + + 'bytefill(@packet, 0, MAXFRAME) ' Uncomment this if you want to clean out the buffer first + ' otherwise, leave commented since it's faster to just leave stuff + ' in the buffer + + ' protect from oversized packet + if rxlen =< MAXFRAME + rd_block(pktptr, rxlen) + {repeat packet_addr from 0 to rxlen - 1 + BYTE[@packet][packet_addr] := rd_sram} + + new_rdptr := (packetheader[nextpacket_high] << 8) + packetheader[nextpacket_low] + + ' handle errata read pointer start (must be odd) + --new_rdptr + + if (new_rdptr < RXSTART) OR (new_rdptr > RXSTOP) + new_rdptr := RXSTOP + + bfs_reg(ECON2, ECON2_PKTDEC) + + banksel(ERXRDPTL) + wr_reg(ERXRDPTL, new_rdptr) + wr_reg(ERXRDPTH, new_rdptr >> 8) + +PUB start_frame +'' Start frame - Inits the NIC and sets stuff + + banksel(EWRPTL) + wr_reg(EWRPTL, TXSTART) + wr_reg(EWRPTH, constant(TXSTART >> 8)) + + tx_end := constant(TXSTART - 1) ' start location is really address 0, so we are sending a count of - 1 + + wr_frame(cTXCONTROL) + +PUB wr_frame(data) +'' Write frame data + + wr_sram(data) + ++tx_end + +PUB wr_block(startaddr, count) + blockwrite(startaddr, count) + tx_end += count + +PUB rd_block(startaddr, count) + blockread(startaddr, count) + +PUB send_frame +'' Sends frame +'' Will retry on send failure up to 15 times with a 1ms delay in between repeats + + repeat 15 + if p_send_frame ' send packet, if successful then quit retry loop + quit + delay_ms(1) + +PRI p_send_frame | i, eirval +' Sends the frame + banksel(ETXSTL) + wr_reg(ETXSTL, TXSTART) + wr_reg(ETXSTH, constant(TXSTART >> 8)) + + banksel(ETXNDL) + wr_reg(ETXNDL, tx_end) + wr_reg(ETXNDH, tx_end >> 8) + + ' B5 Errata #10 - Reset transmit logic before send + bfs_reg(ECON1, ECON1_TXRST) + bfc_reg(ECON1, ECON1_TXRST) + + ' B5 Errata #10 & #13: Reset interrupt error flags + bfc_reg(EIR, constant(EIR_TXERIF | EIR_TXIF)) + + ' trigger send + bfs_reg(ECON1, ECON1_TXRTS) + + ' fix for transmit stalls (derived from errata B5 #13), watches TXIF and TXERIF bits + ' also implements a ~3.75ms (15 * 250us) timeout if send fails (occurs on random packet collisions) + ' btw: this took over 10 hours to fix due to the elusive undocumented bug + i := 0 + repeat + eirval := rd_cntlreg(EIR) + if ((eirval & constant(EIR_TXERIF | EIR_TXIF)) > 0) + quit + if (++i => 15) + eirval := EIR_TXERIF + quit + delay_us(250) + + ' B5 Errata #13 - Reset TXRTS if failed send then reset logic + bfc_reg(ECON1, ECON1_TXRTS) + + if ((eirval & EIR_TXERIF) == 0) + return true ' successful send (no error interrupt) + else + return false ' failed send (error interrupt) + +PUB get_mac_pointer +'' Gets mac address pointer + return @eth_mac + +PUB get_rxlen +'' Gets received packet length + return rxlen - 4 ' knock off the 4 byte Frame Check Sequence CRC, not used anywhere outside of this driver (pg 31 datasheet) + +PRI delay_us(Duration) + waitcnt(((clkfreq / 1_000_000 * Duration - 3928)) + cnt) + +PRI delay_ms(Duration) + waitcnt(((clkfreq / 1_000 * Duration - 3932)) + cnt) + +' *************************************** +' ** ASM SPI Engine ** +' *************************************** +DAT + cog long 0 + command long 0 + +CON + SPIOUT = %0000_0001 + SPIIN = %0000_0010 + SRAMWRITE = %0000_0100 + SRAMREAD = %0000_1000 + CSON = %0001_0000 + CSOFF = %0010_0000 + CKSUM = %0100_0000 + + SPIBITS = 8 + +PRI spi_out(value) + setcommand(constant(SPIOUT | CSON | CSOFF), @value) + +PRI spi_out_cs(value) + setcommand(constant(SPIOUT | CSON), @value) + +PRI spi_in : value + setcommand(constant(SPIIN | CSON | CSOFF), @value) + +PRI spi_in_cs : value + setcommand(constant(SPIIN | CSON), @value) + +PRI blockwrite(startaddr, count) + setcommand(SRAMWRITE, @startaddr) + +PRI blockread(startaddr, count) + setcommand(SRAMREAD, @startaddr) + +PUB chksum_add(startaddr, count) + setcommand(CKSUM, @startaddr) + return startaddr + +PRI spi_start(_cs, _sck, _di, _do, _freqpin) + spi_stop + + cspin := |< _cs + dipin := |< _di + dopin := |< _do + clkpin := |< _sck + + ctramode := %0_00100_00_0000_0000_0000_0000_0000_0000 + _sck + ctrbmode := %0_00100_00_0000_0000_0000_0000_0000_0000 + _do + + spi_setupfreqsynth(_freqpin) + + cog := cognew(@init, @command) + 1 + +PRI spi_stop + if cog + cogstop(cog~ - 1) + ctra := 0 + command~ + +PRI setcommand(cmd, argptr) + command := cmd << 16 + argptr 'write command and pointer + repeat while command 'wait for command to be cleared, signifying receipt + +PRI spi_setupfreqsynth(pin) + + if pin < 0 + ' pin num was negative -> disable freq synth + return + + dira[pin] := 1 + + ctra := constant(%00010 << 26) '..set PLL mode + ctra |= constant((>|((enc_freq - 1) / 1_000_000)) << 23) 'set PLLDIV + + frqa := spi_fraction(enc_freq, CLKFREQ, constant(4 - (>|((enc_freq - 1) / 1_000_000)))) 'Compute FRQA/FRQB value + ctra |= pin 'set PINA to complete CTRA/CTRB value + +PRI spi_fraction(a, b, shift) : f + + if shift > 0 'if shift, pre-shift a or b left + a <<= shift 'to maintain significant bits while + if shift < 0 'insuring proper result + b <<= -shift + + repeat 32 'perform long division of a/b + f <<= 1 + if a => b + a -= b + f++ + a <<= 1 + +DAT + org +init or dira, cspin 'pin directions + andn dira, dipin + or dira, dopin + or dira, clkpin + + or outa, cspin 'turn off cs (bring it high) + + mov frqb, #0 'disable ctrb increment + mov ctrb, ctrbmode + + +loop wrlong zero,par 'zero command (tell spin we are done processing) +:subloop rdlong t1,par wz 'wait for command + if_z jmp #:subloop + + mov addr, t1 'used for holding return addr to spin vars + + rdlong arg0, t1 'arg0 + add t1, #4 + rdlong arg1, t1 'arg1 + + mov lkup, addr 'get the command var from spin + shr lkup, #16 'extract the cmd from the command var + + test lkup, #CSON wz 'turn on cs + if_nz andn outa, cspin + + test lkup, #SPIOUT wz 'spi out + if_nz call #spi_out_ + test lkup, #SPIIN wz 'spi in + if_nz call #xspi_in_ + test lkup, #SRAMWRITE wz 'sram block write + if_nz jmp #sram_write_ + test lkup, #SRAMREAD wz 'sram block read + if_nz jmp #sram_read_ + + test lkup, #CSOFF wz 'cs off + if_nz or outa, cspin + + test lkup, #CKSUM wz 'perform checksum + if_nz call #csum16 + + jmp #loop ' no cmd found + + +spi_out_ andn outa, clkpin + shl arg0, #24 + mov phsb, arg0 ' data to write + mov frqa, freqw ' 20MHz write frequency + mov phsa, #0 ' start at clocking at 0 + + mov ctra, ctramode ' send data @ 20MHz + rol phsb, #1 + rol phsb, #1 + rol phsb, #1 + rol phsb, #1 + rol phsb, #1 + rol phsb, #1 + rol phsb, #1 + mov ctra, #0 ' disable + andn outa, clkpin + +spi_out__ret ret + + +spi_in_ andn outa, clkpin + mov phsa, phsr ' start phs for clock + mov frqa, freqr ' 10MHz read frequency + nop + + mov ctra, ctramode ' start clocking + test dipin, ina wc + rcl arg0, #1 + test dipin, ina wc + rcl arg0, #1 + test dipin, ina wc + rcl arg0, #1 + test dipin, ina wc + rcl arg0, #1 + test dipin, ina wc + rcl arg0, #1 + test dipin, ina wc + rcl arg0, #1 + test dipin, ina wc + rcl arg0, #1 + test dipin, ina wc + mov ctra, #0 ' stop clocking + rcl arg0, #1 + andn outa, clkpin + +spi_in__ret ret + +xspi_in_ call #spi_in_ + wrbyte arg0, addr ' write byte back to spin result var +xspi_in__ret ret + +' SRAM Block Read/Write +sram_write_ ' block write (arg0=hub addr, arg1=count) + mov t1, arg0 + mov t2, arg1 + + andn outa, cspin + mov arg0, #cWBM + call #spi_out_ +:loop rdbyte arg0, t1 + call #spi_out_ + add t1, #1 + djnz t2, #:loop + or outa, cspin + + jmp #loop + +sram_read_ ' block read (arg0=hub addr, arg1=count) + mov t1, arg0 + mov t2, arg1 + + andn outa, cspin + mov arg0, #cRBM + call #spi_out_ +:loop call #spi_in_ + wrbyte arg0, t1 + add t1, #1 + djnz t2, #:loop + or outa, cspin + + jmp #loop + +csum16 ' performs checksum 16bit additions on the data + ' arg0=hub addr, arg1=length, writes sum to first arg + mov t1, #0 ' clear sum +:loop rdbyte t2, arg0 ' read two bytes (16 bits) + add arg0, #1 + rdbyte t3, arg0 + add arg0, #1 + shl t2, #8 ' build the word + add t2, t3 + add t1, t2 ' add numbers + mov t2, t1 ' add lower and upper words together + shr t2, #16 + and t1, hffff + add t1, t2 + sub arg1, #2 + cmp arg1, #1 wz, wc + if_nc_and_nz jmp #:loop + if_z rdbyte t2, arg0 ' add last byte (odd) + if_z shl t2, #8 + if_z add t1, t2 + wrlong t1, addr ' return result back to SPIN +csum16_ret ret + +zero long 0 'constants + + 'values filled by spin code before launching +cspin long 0 ' chip select pin +dipin long 0 ' data in pin (enc28j60 -> prop) +dopin long 0 ' data out pin (prop -> enc28j60) +clkpin long 0 ' clock pin (prop -> enc28j60) +ctramode long 0 ' ctr mode for CLK +ctrbmode long 0 ' ctr mode for SPI Out + +hffff long $FFFF + +freqr long $2000_0000 'frequency of SCK /8 for receive +freqw long $4000_0000 'frequency of SCK /4 for send +phsr long $6000_0000 + + 'temp variables +t1 res 1 ' loop and cog shutdown +t2 res 1 ' loop and cog shutdown +t3 res 1 ' Used to hold DataValue SHIFTIN/SHIFTOUT +t4 res 1 ' Used to hold # of Bits +t5 res 1 ' Used for temporary data mask + +addr res 1 ' Used to hold return address of first Argument passed +lkup res 1 ' Used to hold command lookup + + 'arguments passed to/from high-level Spin +arg0 res 1 ' bits / start address +arg1 res 1 ' value / count + +CON +' *************************************** +' ** ENC28J60 Control Constants ** +' *************************************** + ' ENC28J60 opcodes (OR with 5bit address) + cWCR = %010 << 5 ' write control register command + cBFS = %100 << 5 ' bit field set command + cBFC = %101 << 5 ' bit field clear command + cRCR = %000 << 5 ' read control register command + cRBM = (%001 << 5) | $1A ' read buffer memory command + cWBM = (%011 << 5) | $1A ' write buffer memory command + cSC = (%111 << 5) | $1F ' system command + + ' This is used to trigger TX in the ENC28J60, it shouldn't change, but you never know... + cTXCONTROL = $0E + + ' Packet header format (tail of the receive packet in the ENC28J60 SRAM) + #0,nextpacket_low,nextpacket_high,rec_bytecnt_low,rec_bytecnt_high,rec_status_low,rec_status_high + +' *************************************** +' ** ENC28J60 Register Defines ** +' *************************************** + ' Bank 0 registers -------- + ERDPTL = $00 + ERDPTH = $01 + EWRPTL = $02 + EWRPTH = $03 + ETXSTL = $04 + ETXSTH = $05 + ETXNDL = $06 + ETXNDH = $07 + ERXSTL = $08 + ERXSTH = $09 + ERXNDL = $0A + ERXNDH = $0B + ERXRDPTL = $0C + ERXRDPTH = $0D + ERXWRPTL = $0E + ERXWRPTH = $0F + EDMASTL = $10 + EDMASTH = $11 + EDMANDL = $12 + EDMANDH = $13 + EDMADSTL = $14 + EDMADSTH = $15 + EDMACSL = $16 + EDMACSH = $17 + ' = $18 + ' = $19 + ' r = $1A + EIE = $1B + EIR = $1C + ESTAT = $1D + ECON2 = $1E + ECON1 = $1F + + ' Bank 1 registers ----- + EHT0 = $100 + EHT1 = $101 + EHT2 = $102 + EHT3 = $103 + EHT4 = $104 + EHT5 = $105 + EHT6 = $106 + EHT7 = $107 + EPMM0 = $108 + EPMM1 = $109 + EPMM2 = $10A + EPMM3 = $10B + EPMM4 = $10C + EPMM5 = $10D + EPMM6 = $10E + EPMM7 = $10F + EPMCSL = $110 + EPMCSH = $111 + ' = $112 + ' = $113 + EPMOL = $114 + EPMOH = $115 + EWOLIE = $116 + EWOLIR = $117 + ERXFCON = $118 + EPKTCNT = $119 + ' r = $11A + ' EIE = $11B + ' EIR = $11C + ' ESTAT = $11D + ' ECON2 = $11E + ' ECON1 = $11F + + ' Bank 2 registers ----- + MACON1 = $200 + MACON2 = $201 + MACON3 = $202 + MACON4 = $203 + MABBIPG = $204 + ' = $205 + MAIPGL = $206 + MAIPGH = $207 + MACLCON1 = $208 + MACLCON2 = $209 + MAMXFLL = $20A + MAMXFLH = $20B + ' r = $20C + MAPHSUP = $20D + ' r = $20E + ' = $20F + ' r = $210 + MICON = $211 + MICMD = $212 + ' = $213 + MIREGADR = $214 + ' r = $215 + MIWRL = $216 + MIWRH = $217 + MIRDL = $218 + MIRDH = $219 + ' r = $21A + ' EIE = $21B + ' EIR = $21C + ' ESTAT = $21D + ' ECON2 = $21E + ' ECON1 = $21F + + ' Bank 3 registers ----- + + MAADR5 = $300 + MAADR6 = $301 + MAADR3 = $302 + MAADR4 = $303 + MAADR1 = $304 + MAADR2 = $305 + + {MAADR1 = $300 + MAADR0 = $301 + MAADR3 = $302 + MAADR2 = $303 + MAADR5 = $304 + MAADR4 = $305} + + EBSTSD = $306 + EBSTCON = $307 + EBSTCSL = $308 + EBSTCSH = $309 + MISTAT = $30A + ' = $30B + ' = $30C + ' = $30D + ' = $30E + ' = $30F + ' = $310 + ' = $311 + EREVID = $312 + ' = $313 + ' = $314 + ECOCON = $315 + ' EPHTST $316 + EFLOCON = $317 + EPAUSL = $318 + EPAUSH = $319 + ' r = $31A + ' EIE = $31B + ' EIR = $31C + ' ESTAT = $31D + ' ECON2 = $31E + ' ECON1 = $31F + + {****************************************************************************** + * PH Register Locations + ******************************************************************************} + PHCON1 = $00 + PHSTAT1 = $01 + PHID1 = $02 + PHID2 = $03 + PHCON2 = $10 + PHSTAT2 = $11 + PHIE = $12 + PHIR = $13 + PHLCON = $14 + + {****************************************************************************** + * Individual Register Bits + ******************************************************************************} + ' ETH/MAC/MII bits + + ' EIE bits ---------- + EIE_INTIE = (1<<7) + EIE_PKTIE = (1<<6) + EIE_DMAIE = (1<<5) + EIE_LINKIE = (1<<4) + EIE_TXIE = (1<<3) + EIE_WOLIE = (1<<2) + EIE_TXERIE = (1<<1) + EIE_RXERIE = (1) + + ' EIR bits ---------- + EIR_PKTIF = (1<<6) + EIR_DMAIF = (1<<5) + EIR_LINKIF = (1<<4) + EIR_TXIF = (1<<3) + EIR_WOLIF = (1<<2) + EIR_TXERIF = (1<<1) + EIR_RXERIF = (1) + + ' ESTAT bits --------- + ESTAT_INT = (1<<7) + ESTAT_LATECOL = (1<<4) + ESTAT_RXBUSY = (1<<2) + ESTAT_TXABRT = (1<<1) + ESTAT_CLKRDY = (1) + + ' ECON2 bits -------- + ECON2_AUTOINC = (1<<7) + ECON2_PKTDEC = (1<<6) + ECON2_PWRSV = (1<<5) + ECON2_VRTP = (1<<4) + ECON2_VRPS = (1<<3) + + ' ECON1 bits -------- + ECON1_TXRST = (1<<7) + ECON1_RXRST = (1<<6) + ECON1_DMAST = (1<<5) + ECON1_CSUMEN = (1<<4) + ECON1_TXRTS = (1<<3) + ECON1_RXEN = (1<<2) + ECON1_BSEL1 = (1<<1) + ECON1_BSEL0 = (1) + + ' EWOLIE bits ------- + EWOLIE_UCWOLIE = (1<<7) + EWOLIE_AWOLIE = (1<<6) + EWOLIE_PMWOLIE = (1<<4) + EWOLIE_MPWOLIE = (1<<3) + EWOLIE_HTWOLIE = (1<<2) + EWOLIE_MCWOLIE = (1<<1) + EWOLIE_BCWOLIE = (1) + + ' EWOLIR bits ------- + EWOLIR_UCWOLIF = (1<<7) + EWOLIR_AWOLIF = (1<<6) + EWOLIR_PMWOLIF = (1<<4) + EWOLIR_MPWOLIF = (1<<3) + EWOLIR_HTWOLIF = (1<<2) + EWOLIR_MCWOLIF = (1<<1) + EWOLIR_BCWOLIF = (1) + + ' ERXFCON bits ------ + ERXFCON_UCEN = (1<<7) + ERXFCON_ANDOR = (1<<6) + ERXFCON_CRCEN = (1<<5) + ERXFCON_PMEN = (1<<4) + ERXFCON_MPEN = (1<<3) + ERXFCON_HTEN = (1<<2) + ERXFCON_MCEN = (1<<1) + ERXFCON_BCEN = (1) + + ' MACON1 bits -------- + MACON1_LOOPBK = (1<<4) + MACON1_TXPAUS = (1<<3) + MACON1_RXPAUS = (1<<2) + MACON1_PASSALL = (1<<1) + MACON1_MARXEN = (1) + + ' MACON2 bits -------- + MACON2_MARST = (1<<7) + MACON2_RNDRST = (1<<6) + MACON2_MARXRST = (1<<3) + MACON2_RFUNRST = (1<<2) + MACON2_MATXRST = (1<<1) + MACON2_TFUNRST = (1) + + ' MACON3 bits -------- + MACON3_PADCFG2 = (1<<7) + MACON3_PADCFG1 = (1<<6) + MACON3_PADCFG0 = (1<<5) + MACON3_TXCRCEN = (1<<4) + MACON3_PHDRLEN = (1<<3) + MACON3_HFRMEN = (1<<2) + MACON3_FRMLNEN = (1<<1) + MACON3_FULDPX = (1) + + ' MACON4 bits -------- + MACON4_DEFER = (1<<6) + MACON4_BPEN = (1<<5) + MACON4_NOBKOFF = (1<<4) + MACON4_LONGPRE = (1<<1) + MACON4_PUREPRE = (1) + + ' MAPHSUP bits ---- + MAPHSUP_RSTRMII = (1<<3) + + ' MICON bits -------- + MICON_RSTMII = (1<<7) + + ' MICMD bits --------- + MICMD_MIISCAN = (1<<1) + MICMD_MIIRD = (1) + + ' EBSTCON bits ----- + EBSTCON_PSV2 = (1<<7) + EBSTCON_PSV1 = (1<<6) + EBSTCON_PSV0 = (1<<5) + EBSTCON_PSEL = (1<<4) + EBSTCON_TMSEL1 = (1<<3) + EBSTCON_TMSEL0 = (1<<2) + EBSTCON_TME = (1<<1) + EBSTCON_BISTST = (1) + + ' MISTAT bits -------- + MISTAT_NVALID = (1<<2) + MISTAT_SCAN = (1<<1) + MISTAT_BUSY = (1) + + ' ECOCON bits ------- + ECOCON_COCON2 = (1<<2) + ECOCON_COCON1 = (1<<1) + ECOCON_COCON0 = (1) + + ' EFLOCON bits ----- + EFLOCON_FULDPXS = (1<<2) + EFLOCON_FCEN1 = (1<<1) + EFLOCON_FCEN0 = (1) + + + + ' PHY bits + + ' PHCON1 bits ---------- + PHCON1_PRST = (1<<15) + PHCON1_PLOOPBK = (1<<14) + PHCON1_PPWRSV = (1<<11) + PHCON1_PDPXMD = (1<<8) + + ' PHSTAT1 bits -------- + PHSTAT1_PFDPX = (1<<12) + PHSTAT1_PHDPX = (1<<11) + PHSTAT1_LLSTAT = (1<<2) + PHSTAT1_JBSTAT = (1<<1) + + ' PHID2 bits -------- + PHID2_PID24 = (1<<15) + PHID2_PID23 = (1<<14) + PHID2_PID22 = (1<<13) + PHID2_PID21 = (1<<12) + PHID2_PID20 = (1<<11) + PHID2_PID19 = (1<<10) + PHID2_PPN5 = (1<<9) + PHID2_PPN4 = (1<<8) + PHID2_PPN3 = (1<<7) + PHID2_PPN2 = (1<<6) + PHID2_PPN1 = (1<<5) + PHID2_PPN0 = (1<<4) + PHID2_PREV3 = (1<<3) + PHID2_PREV2 = (1<<2) + PHID2_PREV1 = (1<<1) + PHID2_PREV0 = (1) + + ' PHCON2 bits ---------- + PHCON2_FRCLNK = (1<<14) + PHCON2_TXDIS = (1<<13) + PHCON2_JABBER = (1<<10) + PHCON2_HDLDIS = (1<<8) + + ' PHSTAT2 bits -------- + PHSTAT2_TXSTAT = (1<<13) + PHSTAT2_RXSTAT = (1<<12) + PHSTAT2_COLSTAT = (1<<11) + PHSTAT2_LSTAT = (1<<10) + PHSTAT2_DPXSTAT = (1<<9) + PHSTAT2_PLRITY = (1<<5) + + ' PHIE bits ----------- + PHIE_PLNKIE = (1<<4) + PHIE_PGEIE = (1<<1) + + ' PHIR bits ----------- + PHIR_PLNKIF = (1<<4) + PHIR_PGIF = (1<<2) + + ' PHLCON bits ------- + PHLCON_LACFG3 = (1<<11) + PHLCON_LACFG2 = (1<<10) + PHLCON_LACFG1 = (1<<9) + PHLCON_LACFG0 = (1<<8) + PHLCON_LBCFG3 = (1<<7) + PHLCON_LBCFG2 = (1<<6) + PHLCON_LBCFG1 = (1<<5) + PHLCON_LBCFG0 = (1<<4) + PHLCON_LFRQ1 = (1<<3) + PHLCON_LFRQ0 = (1<<2) + PHLCON_STRCH = (1<<1) \ No newline at end of file diff --git a/lib/driver_socket.spin b/lib/driver_socket.spin index dd75d88..b14d3c8 100644 --- a/lib/driver_socket.spin +++ b/lib/driver_socket.spin @@ -1 +1,1221 @@ -{{ Ethernet TCP/IP Socket Layer Driver (IPv4) ------------------------------------------ Copyright (c) 2006-2009 Harrison Pham 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. The latest version of this software can be obtained from http://hdpham.com/PropTCP and http://obex.parallax.com/}}'' NOTICE: All buffer sizes must be a power of 2!CON' ***************************************' ** Versioning Information **' *************************************** version = 5 ' major version release = 2 ' minor version apiversion = 8 ' api compatibility version' ***************************************' ** User Definable Settings **' *************************************** sNumSockets = 4 ' max number of concurrent registered sockets (max of 255)' *** End of user definable settings, don't edit anything below this line!!!' *** All IP/MAC settings are defined by calling the start(...) methodCON' ***************************************' ** Return Codes / Errors **' *************************************** RETBUFFEREMPTY = -1 ' no data available RETBUFFERFULL = -1 ' buffer full ERRGENERIC = -1 ' generic errors ERR = -100 ' error codes start at -100 ERRBADHANDLE = ERR - 1 ' bad socket handle ERROUTOFSOCKETS = ERR - 2 ' no free sockets available ERRSOCKETCLOSED = ERR - 3 ' socket closed, could not perform operationOBJ nic : "driver_enc28j60" 'ser : "SerialMirror" 'stk : "Stack Length"CON' ***************************************' ** Socket Constants and Offsets **' ***************************************' Socket states (user should never touch these) SCLOSED = 0 ' closed, handle not used SLISTEN = 1 ' listening, in server mode SSYNSENT = 2 ' SYN sent, server mode, waits for ACK SSYNSENTCL = 3 ' SYN sent, client mode, waits for SYN+ACK SESTABLISHED = 4 ' established connection (either SYN+ACK, or ACK+Data) SCLOSING = 5 ' connection is being forced closed by code SCLOSING2 = 6 ' closing, we are waiting for a fin now SFORCECLOSE = 7 ' force connection close (just RSTs, no waiting for FIN or anything) SCONNECTINGARP1 = 8 ' connecting, next step: send arp request SCONNECTINGARP2 = 9 ' connecting, next step: arp request sent, waiting for response SCONNECTINGARP2G = 10 ' connecting, next step: arp request sent, waiting for response [GATEWAY REQUEST] SCONNECTING = 11 ' connecting, next step: got mac address, send SYN' ***************************************' ** TCP State Management Constants **' *************************************** TIMEOUTMS = 500 ' (milliseconds) timeout before a retransmit occurs RSTTIMEOUTMS = 2000 ' (milliseconds) timeout before a RST is sent to close the connection WINDOWUPDATEMS = 25 ' (milliseconds) window advertisement frequency MAXUNACKS = 6 ' max number of unacknowledged retransmits before the stack auto closes the socket ' timeout = TIMEOUTMS * MAXUNACKS (default: 500ms * 5 = 3000ms) EPHPORTSTART = 49152 ' ephemeral port start EPHPORTEND = 65535 ' end MAXPAYLOAD = 1200 ' maximum TCP payload (data) in bytes, this only applies when your txbuffer_length > payload sizeDAT' ***************************************' ** Global Variables **' *************************************** cog long 0 ' cog index (for stopping / starting) stack long 0[128] ' stack for new cog (currently ~74 longs, using 128 for expansion) mac_ptr long 0 ' mac address pointer pkt_id long 0 ' packet fragmentation id pkt_isn long 0 ' packet initial sequence number ip_ephport word 0 ' packet ephemeral port number (49152 to 65535) pkt_count byte 0 ' packet count lock_id byte 0 ' socket handle lock packet byte 0[nic#MAXFRAME] ' the ethernet frame' ***************************************' ** IP Address Defaults **' *************************************** ' NOTE: All of the MAC/IP variables here contain default values that will ' be used if override values are not provided as parameters in start(). long ' long alignment for addresses ip_addr byte 10, 10, 1, 4 ' device's ip address ip_subnet byte 255, 255, 255, 0 ' network subnet ip_gateway byte 10, 10, 1, 254 ' network gateway (router) ip_dns byte 10, 10, 1, 254 ' network dns ' ***************************************' ** Socket Data Arrays **' *************************************** long SocketArrayStart lMySeqNum long 0[sNumSockets] lMyAckNum long 0[sNumSockets] lSrcIp long 0[sNumSockets] lTime long 0[sNumSockets] word wSrcPort word 0[sNumSockets] wDstPort word 0[sNumSockets] wLastWin word 0[sNumSockets] wLastTxLen word 0[sNumSockets] wNotAcked word 0[sNumSockets] byte bSrcMac byte 0[sNumSockets * 6] bConState byte 0[sNumSockets] SocketArrayEnd ' ***************************************' ** Circular Buffer Arrays **' *************************************** word FifoDataStart rx_head word 0[sNumSockets] ' rx head array rx_tail word 0[sNumSockets] ' rx tail array tx_head word 0[sNumSockets] ' tx head array tx_tail word 0[sNumSockets] ' tx tail array tx_tailnew word 0[sNumSockets] ' the new tx_tail value (unacked data) rxbuffer_length word 0[sNumSockets] ' each socket's buffer sizes txbuffer_length word 0[sNumSockets] rxbuffer_mask word 0[sNumSockets] ' each socket's buffer masks for capping buffer sizes txbuffer_mask word 0[sNumSockets] long tx_bufferptr long 0[sNumSockets] ' pointer addresses to each socket's buffer spaces rx_bufferptr long 0[sNumSockets] FifoDataEnd PUB start(cs, sck, si, so, xtalout, macptr, ipconfigptr)'' Start the TCP/IP Stack (requires 2 cogs)'' Only call this once, otherwise you will get conflicts'' macptr = HUB memory pointer (address) to 6 contiguous mac address bytes'' ipconfigptr = HUB memory pointer (address) to ip configuration block (16 bytes)'' Must be in order: ip_addr, ip_subnet, ip_gateway, ip_dns stop 'stk.Init(@stack, 128) ' zero socket data arrays (clean up any dead stuff from previous instance) bytefill(@SocketArrayStart, 0, @SocketArrayEnd - @SocketArrayStart) ' reset buffer pointers, zeros a contigous set of bytes, starting at rx_head bytefill(@FifoDataStart, 0, @FifoDataEnd - @FifoDataStart) ' start new cog with tcp stack cog := cognew(engine(cs, sck, si, so, xtalout, macptr, ipconfigptr), @stack) + 1PUB stop'' Stop the driver if cog cogstop(cog~ - 1) ' stop the tcp engine nic.stop ' stop nic driver (kills spi engine) lockclr(lock_id) ' clear lock before returning it to the pool lockret(lock_id) ' return the lock to the lock poolPRI engine(cs, sck, si, so, xtalout, macptr, ipconfigptr) | i lock_id := locknew ' checkout a lock from the HUB lockclr(lock_id) ' clear the lock, just in case it was in a bad state ' Start the ENC28J60 driver in a new cog nic.start(cs, sck, si, so, xtalout, macptr) ' init the nic if ipconfigptr > -1 ' init ip configuration bytemove(@ip_addr, ipconfigptr, 16) mac_ptr := nic.get_mac_pointer ' get the local mac address pointer ip_ephport := EPHPORTSTART ' set initial ephemeral port number (might want to random seed this later) i := 0 nic.banksel(nic#EPKTCNT) ' select packet count bank repeat pkt_count := nic.rd_cntlreg(nic#EPKTCNT) if pkt_count > 0 service_packet ' handle packet nic.banksel(nic#EPKTCNT) ' re-select the packet count bank ++i if i > 10 ' perform send tick repeat while lockset(lock_id) tick_tcpsend ' occurs every 10 cycles, since incoming packets more important lockclr(lock_id) i := 0 nic.banksel(nic#EPKTCNT) ' re-select the packet count bankPRI service_packet ' lets process this frame nic.get_frame(@packet) ' check for arp packet type (highest priority obviously) if packet[enetpacketType0] == $08 AND packet[enetpacketType1] == $06 if packet[constant(arp_hwtype + 1)] == $01 AND packet[arp_prtype] == $08 AND packet[constant(arp_prtype + 1)] == $00 AND packet[arp_hwlen] == $06 AND packet[arp_prlen] == $04 if packet[arp_tipaddr] == ip_addr[0] AND packet[constant(arp_tipaddr + 1)] == ip_addr[1] AND packet[constant(arp_tipaddr + 2)] == ip_addr[2] AND packet[constant(arp_tipaddr + 3)] == ip_addr[3] case packet[constant(arp_op + 1)] $01 : handle_arp $02 : repeat while lockset(lock_id) handle_arpreply lockclr(lock_id) '++count_arp else if packet[enetpacketType0] == $08 AND packet[enetpacketType1] == $00 if packet[ip_destaddr] == ip_addr[0] AND packet[constant(ip_destaddr + 1)] == ip_addr[1] AND packet[constant(ip_destaddr + 2)] == ip_addr[2] AND packet[constant(ip_destaddr + 3)] == ip_addr[3] case packet[ip_proto] 'PROT_ICMP : 'handle_ping 'ser.str(stk.GetLength(0, 0)) 'stk.GetLength(30, 19200) '++count_ping PROT_TCP : repeat while lockset(lock_id) \handle_tcp ' handles abort out of tcp handlers (no socket found) lockclr(lock_id) '++count_tcp 'PROT_UDP : ++count_udp' *******************************' ** Protocol Receive Handlers **' *******************************PRI handle_arp | i nic.start_frame ' destination mac address repeat i from 0 to 5 nic.wr_frame(packet[enetpacketSrc0 + i]) ' source mac address repeat i from 0 to 5 nic.wr_frame(BYTE[mac_ptr][i]) nic.wr_frame($08) ' arp packet nic.wr_frame($06) nic.wr_frame($00) ' 10mb ethernet nic.wr_frame($01) nic.wr_frame($08) ' ip proto nic.wr_frame($00) nic.wr_frame($06) ' mac addr len nic.wr_frame($04) ' proto addr len nic.wr_frame($00) ' arp reply nic.wr_frame($02) ' write ethernet module mac address repeat i from 0 to 5 nic.wr_frame(BYTE[mac_ptr][i]) ' write ethernet module ip address repeat i from 0 to 3 nic.wr_frame(ip_addr[i]) ' write remote mac address repeat i from 0 to 5 nic.wr_frame(packet[enetpacketSrc0 + i]) ' write remote ip address repeat i from 0 to 3 nic.wr_frame(packet[arp_sipaddr + i]) return nic.send_framePRI handle_arpreply | handle, ip, found ' Gets arp reply if it is a response to an ip we have ip := (packet[constant(arp_sipaddr + 3)] << 24) + (packet[constant(arp_sipaddr + 2)] << 16) + (packet[constant(arp_sipaddr + 1)] << 8) + (packet[arp_sipaddr]) found := false if ip == LONG[@ip_gateway] ' find a handle that wants gateway mac repeat handle from 0 to constant(sNumSockets - 1) if bConState[handle] == SCONNECTINGARP2G found := true quit else ' find the one that wants this arp repeat handle from 0 to constant(sNumSockets - 1) if bConState[handle] == SCONNECTINGARP2 if lSrcIp[handle] == ip found := true quit if found bytemove(@bSrcMac[handle * 6], @packet + arp_shaddr, 6) bConState[handle] := SCONNECTING'PRI handle_ping ' Not implemented yet (save on space!) PRI handle_tcp | i, ptr, handle, srcip, dstport, srcport, datain_len ' Handles incoming TCP packets srcip := packet[ip_srcaddr] << 24 + packet[constant(ip_srcaddr + 1)] << 16 + packet[constant(ip_srcaddr + 2)] << 8 + packet[constant(ip_srcaddr + 3)] dstport := packet[TCP_destport] << 8 + packet[constant(TCP_destport + 1)] srcport := packet[TCP_srcport] << 8 + packet[constant(TCP_srcport + 1)] handle := find_socket(srcip, dstport, srcport) ' if no sockets avail, it will abort out of this function ' at this point we assume we have an active socket, or a socket available to be used datain_len := ((packet[ip_pktlen] << 8) + packet[constant(ip_pktlen + 1)]) - ((packet[ip_vers_len] & $0F) * 4) - (((packet[TCP_hdrlen] & $F0) >> 4) * 4) if (bConState[handle] == SSYNSENT OR bConState[handle] == SESTABLISHED) AND (packet[TCP_hdrflags] & TCP_ACK) AND datain_len > 0 ' ACK, without SYN, with data ' set socket state, established session bConState[handle] := SESTABLISHED i := packet[constant(TCP_seqnum + 3)] << 24 + packet[constant(TCP_seqnum + 2)] << 16 + packet[constant(TCP_seqnum + 1)] << 8 + packet[TCP_seqnum] if lMyAckNum[handle] == i if datain_len =< (rxbuffer_mask[handle] - ((rx_head[handle] - rx_tail[handle]) & rxbuffer_mask[handle])) ' we have buffer space ptr := rx_bufferptr[handle] if (datain_len + rx_head[handle]) > rxbuffer_length[handle] bytemove(ptr + rx_head[handle], @packet[TCP_data], rxbuffer_length[handle] - rx_head[handle]) bytemove(ptr, @packet[TCP_data] + (rxbuffer_length[handle] - rx_head[handle]), datain_len - (rxbuffer_length[handle] - rx_head[handle])) else bytemove(ptr + rx_head[handle], @packet[TCP_data], datain_len) rx_head[handle] := (rx_head[handle] + datain_len) & rxbuffer_mask[handle] else datain_len := 0 else ' we had a bad ack number, meaning lost or out of order packet ' we have to wait for the remote host to retransmit in order datain_len := 0 ' recalculate ack number lMyAckNum[handle] := conv_endianlong(conv_endianlong(lMyAckNum[handle]) + datain_len) ' ACK response build_ipheaderskeleton(handle) build_tcpskeleton(handle, TCP_ACK) send_tcpfinal(handle, 0) elseif (bConState[handle] == SSYNSENTCL) AND (packet[TCP_hdrflags] & TCP_SYN) AND (packet[TCP_hdrflags] & TCP_ACK) ' We got a server response, so we ACK it bytemove(@lMySeqNum[handle], @packet + TCP_acknum, 4) bytemove(@lMyAckNum[handle], @packet + TCP_seqnum, 4) lMyAckNum[handle] := conv_endianlong(conv_endianlong(lMyAckNum[handle]) + 1) ' ACK response build_ipheaderskeleton(handle) build_tcpskeleton(handle, TCP_ACK) send_tcpfinal(handle, 0) ' set socket state, established session bConState[handle] := SESTABLISHED elseif (bConState[handle] == SLISTEN) AND (packet[TCP_hdrflags] & TCP_SYN) ' Reply to SYN with SYN + ACK ' copy mac address so we don't have to keep an ARP table bytemove(@bSrcMac[handle * 6], @packet + enetpacketSrc0, 6) ' copy ip, port data bytemove(@lSrcIp[handle], @packet + ip_srcaddr, 4) bytemove(@wSrcPort[handle], @packet + TCP_srcport, 2) bytemove(@wDstPort[handle], @packet + TCP_destport, 2) ' get updated ack numbers bytemove(@lMyAckNum[handle], @packet + TCP_seqnum, 4) lMyAckNum[handle] := conv_endianlong(conv_endianlong(lMyAckNum[handle]) + 1) lMySeqNum[handle] := conv_endianlong(++pkt_isn) ' Initial seq num (random) build_ipheaderskeleton(handle) build_tcpskeleton(handle, constant(TCP_SYN | TCP_ACK)) send_tcpfinal(handle, 0) ' incremement the sequence number for the next packet (it will be for an established connection) lMySeqNum[handle] := conv_endianlong(conv_endianlong(lMySeqNum[handle]) + 1) ' set socket state, waiting for establish bConState[handle] := SSYNSENT elseif (bConState[handle] == SESTABLISHED OR bConState[handle] == SCLOSING2) AND (packet[TCP_hdrflags] & TCP_FIN) ' Reply to FIN with RST ' get updated sequence and ack numbers (gaurantee we have correct ones to kill connection with) bytemove(@lMySeqNum[handle], @packet + TCP_acknum, 4) bytemove(@lMyAckNum[handle], @packet + TCP_seqnum, 4) 'LONG[handle_addr + sMyAckNum] := conv_endianlong(conv_endianlong(LONG[handle_addr + sMyAckNum]) + 1) build_ipheaderskeleton(handle) build_tcpskeleton(handle, TCP_RST) send_tcpfinal(handle, 0) ' set socket state, now free bConState[handle] := SCLOSED return elseif (bConState[handle] == SSYNSENT) AND (packet[TCP_hdrflags] & TCP_ACK) ' if just an ack, and we sent a syn before, then it's established ' this just gives us the ability to send on connect bConState[handle] := SESTABLISHED elseif (packet[TCP_hdrflags] & TCP_RST) ' Reset, reset states bConState[handle] := SCLOSED return if (bConState[handle] == SESTABLISHED OR bConState[handle] == SCLOSING) AND (packet[TCP_hdrflags] & TCP_ACK) wNotAcked[handle] := 0 ' reset retransmit counter ' check to see if our last sent data has been ack'd i := packet[TCP_acknum] << 24 + packet[constant(TCP_acknum + 1)] << 16 + packet[constant(TCP_acknum + 2)] << 8 + packet[constant(TCP_acknum + 3)] if i == (conv_endianlong(lMySeqNum[handle]) + wLastTxLen[handle]) ' we received an ack for our last sent packet, so we update our sequence number and buffer pointers lMySeqNum[handle] := conv_endianlong(conv_endianlong(lMySeqNum[handle]) + wLastTxLen[handle]) tx_tail[handle] := tx_tailnew[handle] wLastTxLen[handle] := 0 tcpsend(handle) ' send dataPRI build_ipheaderskeleton(handle) | hdrlen, hdr_chksum bytemove(@packet + ip_destaddr, @lSrcIp[handle], 4) ' Set destination address bytemove(@packet + ip_srcaddr, @ip_addr, 4) ' Set source address bytemove(@packet + enetpacketDest0, @bSrcMac[handle * 6], 6) ' Set destination mac address bytemove(@packet + enetpacketSrc0, mac_ptr, 6) ' Set source mac address packet[enetpacketType0] := $08 packet[constant(enetpacketType0 + 1)] := $00 packet[ip_vers_len] := $45 packet[ip_tos] := $00 ++pkt_id packet[ip_id] := pkt_id >> 8 ' Used for fragmentation packet[constant(ip_id + 1)] := pkt_id packet[ip_frag_offset] := $40 ' Don't fragment packet[constant(ip_frag_offset + 1)] := 0 packet[ip_ttl] := $80 ' TTL = 128 packet[ip_proto] := $06 ' TCP protocolPRI build_tcpskeleton(handle, flags) | size bytemove(@packet + TCP_srcport, @wDstPort[handle], 2) ' Source port bytemove(@packet + TCP_destport, @wSrcPort[handle], 2) ' Destination port bytemove(@packet + TCP_seqnum, @lMySeqNum[handle], 4) ' Seq Num bytemove(@packet + TCP_acknum, @lMyAckNum[handle], 4) ' Ack Num packet[TCP_hdrlen] := $50 ' Header length packet[TCP_hdrflags] := flags ' TCP state flags ' we have to recalculate the window size often otherwise our stack ' might explode from too much data :( size := (rxbuffer_mask[handle] - ((rx_head[handle] - rx_tail[handle]) & rxbuffer_mask[handle])) wLastWin[handle] := size packet[TCP_window] := (size & $FF00) >> 8 packet[constant(TCP_window + 1)] := size & $FF PRI send_tcpfinal(handle, datalen) | i, tcplen, hdrlen, hdr_chksum tcplen := 40 + datalen ' real length = data + headers packet[ip_pktlen] := tcplen >> 8 packet[constant(ip_pktlen + 1)] := tcplen ' calc ip header checksum packet[ip_hdr_cksum] := $00 packet[constant(ip_hdr_cksum + 1)] := $00 hdrlen := (packet[ip_vers_len] & $0F) * 4 hdr_chksum := calc_chksum(@packet[ip_vers_len], hdrlen) packet[ip_hdr_cksum] := hdr_chksum >> 8 packet[constant(ip_hdr_cksum + 1)] := hdr_chksum ' calc checksum packet[TCP_cksum] := $00 packet[constant(TCP_cksum + 1)] := $00 hdr_chksum := nic.chksum_add(@packet[ip_srcaddr], 8) hdr_chksum += packet[ip_proto] i := tcplen - ((packet[ip_vers_len] & $0F) * 4) hdr_chksum += i hdr_chksum += nic.chksum_add(@packet[TCP_srcport], i) hdr_chksum := calc_chksumfinal(hdr_chksum) packet[TCP_cksum] := hdr_chksum >> 8 packet[constant(TCP_cksum + 1)] := hdr_chksum tcplen += 14 if tcplen < 60 tcplen := 60 ' protect from buffer overrun if tcplen => nic#TX_BUFFER_SIZE return ' send the packet nic.start_frame nic.wr_block(@packet, tcplen) nic.send_frame lTime[handle] := cnt ' update last sent time (for timeout detection) PRI find_socket(srcip, dstport, srcport) | handle, free_handle, listen_handle ' Search for socket, matches ip address, port states ' Returns handle address (start memory location of socket) ' If no matches, will abort with -1 ' If supplied with srcip = 0 then will return free unused handle, aborts with -1 if none avail free_handle := -1 listen_handle := -1 repeat handle from 0 to constant(sNumSockets - 1) if bConState[handle] <> SCLOSED if (lSrcIp[handle] == 0) OR (lSrcIp[handle] == conv_endianlong(srcip)) ' ip match, ip socket srcip = 0, then will try to match dst port (find listening socket) if (wDstPort[handle] == conv_endianword(dstport)) {AND (WORD[handle_addr + sSrcPort] == 0 OR WORD[handle_addr + sSrcPort] == conv_endianword(srcport))} if wSrcPort[handle] == conv_endianword(srcport) ' found exact socket match (established socket) return handle elseif wSrcPort[handle] == 0 ' found a partial match (listening socket with no peer) listen_handle := handle elseif srcip == 0 ' found a closed (unallocated) socket, save this as a free handle if we are searching for a free handle free_handle := handle ' we found a free handle, may need this later if srcip <> 0 ' return the listening handle we found if listen_handle <> -1 return listen_handle else ' searched for a free handle if free_handle <> -1 return free_handle ' could not find a matching socket / free socket... abort -1' ******************************' ** Transmit Buffer Handlers **' ******************************PRI tcpsend(handle) | ptr, len ' Check buffers for data to send (called in main loop) if tx_tail[handle] == tx_head[handle] ' no data in buffer, so just quit return ' we have data to send, so send it ptr := tx_bufferptr[handle] len := ((tx_head[handle] - tx_tail[handle]) & txbuffer_mask[handle]) <# MAXPAYLOAD if (len + tx_tail[handle]) > txbuffer_length[handle] bytemove(@packet[TCP_data], ptr + tx_tail[handle], txbuffer_length[handle] - tx_tail[handle]) bytemove(@packet[TCP_data] + (txbuffer_length[handle] - tx_tail[handle]), ptr, len - (txbuffer_length[handle] - tx_tail[handle])) else bytemove(@packet[TCP_data], ptr + tx_tail[handle], len) tx_tailnew[handle] := (tx_tail[handle] + len) & txbuffer_mask[handle] wLastTxLen[handle] := len build_ipheaderskeleton(handle) build_tcpskeleton(handle, TCP_ACK {constant(TCP_ACK | TCP_PSH)}) send_tcpfinal(handle, len) ' send actual data send_tcpfinal(handle, 0) ' send an empty packet to force the other side to ACK (hack to get around delayed acks) wNotAcked[handle]++ ' increment unacked packet counter PRI tick_tcpsend | handle, state, len repeat handle from 0 to constant(sNumSockets - 1) state := bConState[handle] if state == SESTABLISHED OR state == SCLOSING len := (rxbuffer_mask[handle] - ((rx_head[handle] - rx_tail[handle]) & rxbuffer_mask[handle])) if wLastWin[handle] <> len AND len => (rxbuffer_length[handle] / 2) AND ((cnt - lTime[handle]) / (clkfreq / 1000) > WINDOWUPDATEMS) ' update window size build_ipheaderskeleton(handle) build_tcpskeleton(handle, TCP_ACK) send_tcpfinal(handle, 0) if ((cnt - lTime[handle]) / (clkfreq / 1000) > TIMEOUTMS) OR wLastTxLen[handle] == 0 ' send new data OR retransmit our last packet since the other side seems to have lost it ' the remote host will respond with another dup ack, and we will get back on track (hopefully) tcpsend(handle) if (state == SCLOSING) build_ipheaderskeleton(handle) build_tcpskeleton(handle, constant(TCP_ACK | TCP_FIN)) send_tcpfinal(handle, 0) ' we now wait for the other side to terminate bConState[handle] := SCLOSING2 elseif state == SCONNECTINGARP1 ' We need to send an arp request arp_request_checkgateway(handle) elseif state == SCONNECTING ' Yea! We got an arp response previously, so now we can send the SYN lMySeqNum[handle] := conv_endianlong(++pkt_isn) lMyAckNum[handle] := 0 build_ipheaderskeleton(handle) build_tcpskeleton(handle, TCP_SYN) send_tcpfinal(handle, 0) bConState[handle] := SSYNSENTCL elseif (state == SFORCECLOSE) OR (state == SESTABLISHED AND wNotAcked[handle] => MAXUNACKS) OR (lookdown(state: SCLOSING2, SSYNSENT, SSYNSENTCL, SCONNECTINGARP2, SCONNECTINGARP2G) {(state == SCLOSING2 OR state == SSYNSENT)} AND ((cnt - lTime[handle]) / (clkfreq / 1000) > RSTTIMEOUTMS)) ' Force close (send RST, and say the socket is closed!) ' This is triggered when any of the following happens: ' 1 - we don't get a response to our SSYNSENT state ' 2 - we exceeded MAXUNACKS tcp retransmits (remote host lost) ' 3 - we get stuck in the SSCLOSING2 state ' 4 - we don't get a response to our client SYNSENTCL state ' 5 - we don't get an ARP response state SCONNECTINGARP2 or SCONNECTINGARP2G build_ipheaderskeleton(handle) build_tcpskeleton(handle, TCP_RST) send_tcpfinal(handle, 0) bConState[handle] := SCLOSEDPRI arp_request_checkgateway(handle) | ip_ptr ip_ptr := @lSrcIp[handle] if (BYTE[ip_ptr] & ip_subnet[0]) == (ip_addr[0] & ip_subnet[0]) AND (BYTE[ip_ptr + 1] & ip_subnet[1]) == (ip_addr[1] & ip_subnet[1]) AND (BYTE[ip_ptr + 2] & ip_subnet[2]) == (ip_addr[2] & ip_subnet[2]) AND (BYTE[ip_ptr + 3] & ip_subnet[3]) == (ip_addr[3] & ip_subnet[3]) arp_request(conv_endianlong(LONG[ip_ptr])) bConState[handle] := SCONNECTINGARP2 else arp_request(conv_endianlong(LONG[@ip_gateway])) bConState[handle] := SCONNECTINGARP2G lTime[handle] := cnt PRI arp_request(ip) | i nic.start_frame ' destination mac address (broadcast mac) repeat i from 0 to 5 nic.wr_frame($FF) ' source mac address (this device) repeat i from 0 to 5 nic.wr_frame(BYTE[mac_ptr][i]) nic.wr_frame($08) ' arp packet nic.wr_frame($06) nic.wr_frame($00) ' 10mb ethernet nic.wr_frame($01) nic.wr_frame($08) ' ip proto nic.wr_frame($00) nic.wr_frame($06) ' mac addr len nic.wr_frame($04) ' proto addr len nic.wr_frame($00) ' arp request nic.wr_frame($01) ' source mac address (this device) repeat i from 0 to 5 nic.wr_frame(BYTE[mac_ptr][i]) ' source ip address (this device) repeat i from 0 to 3 nic.wr_frame(ip_addr[i]) ' unknown mac address area repeat i from 0 to 5 nic.wr_frame($00) ' figure out if we need router arp request or host arp request ' this means some subnet masking ' dest ip address repeat i from 3 to 0 nic.wr_frame(ip.byte[i]) ' send the request return nic.send_frame ' *******************************' ** IP Packet Helpers (Calcs) **' ******************************* PRI calc_chksum(ptr, hdrlen) : chksum ' Calculates IP checksums ' packet = pointer to IP packet ' returns: chksum ' http://www.geocities.com/SiliconValley/2072/bit33.txt 'chksum := calc_chksumhalf(packet, hdrlen) chksum := nic.chksum_add(ptr, hdrlen) chksum := calc_chksumfinal(chksum)PRI calc_chksumfinal(chksumin) : chksum ' Performs the final part of checksums chksum := (chksumin >> 16) + (chksumin & $FFFF) chksum := (!chksum) & $FFFF {PRI calc_chksumhalf(packet, hdrlen) : chksum ' Calculates checksum without doing the final stage of calculations chksum := 0 repeat while hdrlen > 1 chksum += (BYTE[packet++] << 8) + BYTE[packet++] chksum := (chksum >> 16) + (chksum & $FFFF) hdrlen -= 2 if hdrlen > 0 chksum += BYTE[packet] << 8}' ***************************' ** Memory Access Helpers **' *************************** PRI conv_endianlong(in) 'return (in << 24) + ((in & $FF00) << 8) + ((in & $FF0000) >> 8) + (in >> 24) ' we can sometimes get away with shifting without masking, since shifts kill extra bits anyways return (in.byte[0] << 24) + (in.byte[1] << 16) + (in.byte[2] << 8) + (in.byte[3]) PRI conv_endianword(in) 'return ((in & $FF) << 8) + ((in & $FF00) >> 8) return (in.byte[0] << 8) + (in.byte[1])PRI _handleConvert(userHandle, ptrHandle) | handle' Checks to see if a handle index is valid' Aborts if the handle is invalid handle := userHandle.byte[0] ' extract the handle index from the lower 8 bits if handle < 0 OR handle > constant(sNumSockets - 1) ' check the handle index to make sure we don't go out of bounds abort ERRBADHANDLE ' check handle to make sure it's the one we want (rid ourselves of bad user handles) ' the current check method is as follows: ' - compare sDstPort if wDstPort[handle] <> ((userHandle.byte[2] << 8) + userHandle.byte[1]) abort ERRBADHANDLE ' if we got here without aborting then we can assume the handle is good LONG[ptrHandle] := handle' ************************************' ** Public Accessors (Thread Safe) **' ************************************PUB listen(port, _ptrrxbuff, _rxlen, _ptrtxbuff, _txlen) | handle'' Sets up a socket for listening on a port'' port = port number to listen on'' ptrrxbuff = pointer to the rxbuffer array'' rxlen = length of the rxbuffer array (must be power of 2)'' ptrtxbuff = pointer to the txbuffer array'' txlen = length of the txbuffer array (must be power of 2)'' Returns handle if available, ERROUTOFSOCKETS if none available'' Nonblocking repeat while lockset(lock_id) ' just find any avail closed socket handle := \find_socket(0, 0, 0) if handle < 0 lockclr(lock_id) abort ERROUTOFSOCKETS rx_bufferptr[handle] := _ptrrxbuff tx_bufferptr[handle] := _ptrtxbuff rxbuffer_length[handle] := _rxlen txbuffer_length[handle] := _txlen rxbuffer_mask[handle] := _rxlen - 1 txbuffer_mask[handle] := _txlen - 1 lMySeqNum[handle] := 0 lMyAckNum[handle] := 0 lSrcIp[handle] := 0 lTime[handle] := 0 wLastTxLen[handle] := 0 wNotAcked[handle] := 0 bytefill(@bSrcMac[handle * 6], 0, 6) wSrcPort[handle] := 0 ' no source port yet wDstPort[handle] := conv_endianword(port) ' we do have a dest port though wLastWin[handle] := rxbuffer_length[handle] tx_head[handle] := 0 tx_tail[handle] := 0 tx_tailnew[handle] := 0 rx_head[handle] := 0 rx_tail[handle] := 0 ' it's now listening bConState[handle] := SLISTEN lockclr(lock_id) return ((port.byte[0] << 16) + (port.byte[1] << 8)) + handle PUB connect(ipaddr, remoteport, _ptrrxbuff, _rxlen, _ptrtxbuff, _txlen) | handle, user_handle'' Connect to remote host'' ipaddr = ipv4 address packed into a long (ie: 1.2.3.4 => $01_02_03_04)'' remoteport = port number to connect to'' ptrrxbuff = pointer to the rxbuffer array'' rxlen = length of the rxbuffer array (must be power of 2)'' ptrtxbuff = pointer to the txbuffer array'' txlen = length of the txbuffer array (must be power of 2)'' Returns handle to new socket, ERROUTOFSOCKETS if no socket available'' Nonblocking repeat while lockset(lock_id) ' just find any avail closed socket handle := \find_socket(0, 0, 0) if handle < 0 lockclr(lock_id) abort ERROUTOFSOCKETS rx_bufferptr[handle] := _ptrrxbuff tx_bufferptr[handle] := _ptrtxbuff rxbuffer_length[handle] := _rxlen txbuffer_length[handle] := _txlen rxbuffer_mask[handle] := _rxlen - 1 txbuffer_mask[handle] := _txlen - 1 lMySeqNum[handle] := 0 lMyAckNum[handle] := 0 lTime[handle] := 0 wLastTxLen[handle] := 0 wNotAcked[handle] := 0 bytefill(@bSrcMac[handle * 6], 0, 6) if(ip_ephport => EPHPORTEND) ' constrain ephport to specified range ip_ephport := EPHPORTSTART user_handle := ((ip_ephport.byte[0] << 16) + (ip_ephport.byte[1] << 8)) + handle ' copy in ip, port data (with respect to the remote host, since we use same code as server) lSrcIp[handle] := conv_endianlong(ipaddr) wSrcPort[handle] := conv_endianword(remoteport) wDstPort[handle] := conv_endianword(ip_ephport++) wLastWin[handle] := rxbuffer_length[handle] tx_head[handle] := 0 tx_tail[handle] := 0 tx_tailnew[handle] := 0 rx_head[handle] := 0 rx_tail[handle] := 0 bConState[handle] := SCONNECTINGARP1 lockclr(lock_id) return user_handlePUB close(user_handle) | handle, state'' Closes a connection _handleConvert(user_handle, @handle) repeat while lockset(lock_id) state := bConState[handle] if state == SESTABLISHED ' try to gracefully close the connection bConState[handle] := SCLOSING elseif state <> SCLOSING AND state <> SCLOSING2 ' we only do an ungraceful close if we are not in ESTABLISHED, CLOSING, or CLOSING2 bConState[handle] := SCLOSED lockclr(lock_id) ' wait for the socket to close, this is very important to prevent the client app from reusing the buffers repeat until (bConState[handle] == SCLOSING2) or (bConState[handle] == SCLOSED)PUB isConnected(user_handle) | handle'' Returns true if the socket is connected, false otherwise if \_handleConvert(user_handle, @handle) <> 0 return false return (bConState[handle] == SESTABLISHED) PUB isValidHandle(user_handle) | handle'' Checks to see if the handle is valid, handles will become invalid once they are used'' In other words, a closed listening socket is now invalid, etc {if handle < 0 OR handle > constant(sNumSockets - 1) ' obviously the handle index is out of range, so it's not valid! return false} if \_handleConvert(user_handle, @handle) < 0 return false return (bConState[handle] <> SCLOSED)PUB readDataNonBlocking(user_handle, ptr, maxlen) | handle, len, rxptr'' Reads bytes from the socket'' Returns number of read bytes'' Not blocking (returns RETBUFFEREMPTY if no data) _handleConvert(user_handle, @handle) if rx_tail[handle] == rx_head[handle] return RETBUFFEREMPTY len := (rx_head[handle] - rx_tail[handle]) & rxbuffer_mask[handle] if maxlen < len len := maxlen rxptr := rx_bufferptr[handle] if (len + rx_tail[handle]) > rxbuffer_length[handle] bytemove(ptr, rxptr + rx_tail[handle], rxbuffer_length[handle] - rx_tail[handle]) bytemove(ptr + (rxbuffer_length[handle] - rx_tail[handle]), rxptr, len - (rxbuffer_length[handle] - rx_tail[handle])) else bytemove(ptr, rxptr + rx_tail[handle], len) rx_tail[handle] := (rx_tail[handle] + len) & rxbuffer_mask[handle] return len PUB readData(user_handle, ptr, maxlen) : len | handle'' Reads bytes from the socket'' Returns the number of read bytes'' Will block until data is received _handleConvert(user_handle, @handle) repeat while (len := readDataNonBlocking(user_handle, ptr, maxlen)) < 0 ifnot isConnected(user_handle) abort ERRSOCKETCLOSEDPUB readByteNonBlocking(user_handle) : rxbyte | handle, ptr'' Read a byte from the specified socket'' Will not block (returns RETBUFFEREMPTY if no byte avail) _handleConvert(user_handle, @handle) rxbyte := RETBUFFEREMPTY if rx_tail[handle] <> rx_head[handle] ptr := rx_bufferptr[handle] rxbyte := BYTE[ptr][rx_tail[handle]] rx_tail[handle] := (rx_tail[handle] + 1) & rxbuffer_mask[handle] PUB readByte(user_handle) : rxbyte | handle, ptr'' Read a byte from the specified socket'' Will block until a byte is received _handleConvert(user_handle, @handle) repeat while (rxbyte := readByteNonBlocking(user_handle)) < 0 ifnot isConnected(user_handle) abort ERRSOCKETCLOSEDPUB writeDataNonBlocking(user_handle, ptr, len) | handle, txptr'' Writes bytes to the socket'' Will not write anything unless your data fits in the buffer'' Non blocking (returns RETBUFFERFULL if can't fit data) _handleConvert(user_handle, @handle) if (txbuffer_mask[handle] - ((tx_head[handle] - tx_tail[handle]) & txbuffer_mask[handle])) < len return RETBUFFERFULL txptr := tx_bufferptr[handle] if (len + tx_head[handle]) > txbuffer_length[handle] bytemove(txptr + tx_head[handle], ptr, txbuffer_length[handle] - tx_head[handle]) bytemove(txptr, ptr + (txbuffer_length[handle] - tx_head[handle]), len - (txbuffer_length[handle] - tx_head[handle])) else bytemove(txptr + tx_head[handle], ptr, len) tx_head[handle] := (tx_head[handle] + len) & txbuffer_mask[handle] return lenPUB writeData(user_handle, ptr, len) | handle'' Writes data to the specified socket'' Will block until all data is queued to be sent _handleConvert(user_handle, @handle) repeat while len > txbuffer_mask[handle] repeat while writeDataNonBlocking(user_handle, ptr, txbuffer_mask[handle]) < 0 ifnot isConnected(user_handle) abort ERRSOCKETCLOSED len -= txbuffer_mask[handle] ptr += txbuffer_mask[handle] repeat while writeDataNonBlocking(user_handle, ptr, len) < 0 ifnot isConnected(user_handle) abort ERRSOCKETCLOSEDPUB writeByteNonBlocking(user_handle, txbyte) | handle, ptr'' Writes a byte to the specified socket'' Will not block (returns RETBUFFERFULL if no buffer space available) _handleConvert(user_handle, @handle) ifnot (tx_tail[handle] <> (tx_head[handle] + 1) & txbuffer_mask[handle]) return RETBUFFERFULL ptr := tx_bufferptr[handle] BYTE[ptr][tx_head[handle]] := txbyte tx_head[handle] := (tx_head[handle] + 1) & txbuffer_mask[handle] return txbytePUB writeByte(user_handle, txbyte) | handle'' Write a byte to the specified socket'' Will block until space is available for byte to be sent _handleConvert(user_handle, @handle) repeat while writeByteNonBlocking(user_handle, txbyte) < 0 ifnot isConnected(user_handle) abort ERRSOCKETCLOSEDPUB resetBuffers(user_handle) | handle'' Resets send/receive buffers for the specified socket _handleConvert(user_handle, @handle) rx_tail[handle] := rx_head[handle] tx_head[handle] := tx_tail[handle]PUB flush(user_handle) | handle'' Flushes the send buffer (waits till the buffer is empty)'' Will block until all tx data is sent _handleConvert(user_handle, @handle) repeat while isConnected(user_handle) AND tx_tail[handle] <> tx_head[handle]PUB getSocketState(user_handle) | handle'' Gets the socket state (internal state numbers)'' You can include driver_socket in any object and use the S... state constants for comparison _handleConvert(user_handle, @handle) return bConState[handle]PUB getReceiveBufferCount(user_handle) | handle'' Returns the number of bytes in the receive buffer _handleConvert(user_handle, @handle) return (rx_head[handle] - rx_tail[handle]) & rxbuffer_mask[handle] CON '****************************************************************** '* TCP Flags '****************************************************************** TCP_FIN = 1 TCP_SYN = 2 TCP_RST = 4 TCP_PSH = 8 TCP_ACK = 16 TCP_URG = 32 TCP_ECE = 64 TCP_CWR = 128 '****************************************************************** '* Ethernet Header Layout '****************************************************************** enetpacketDest0 = $00 'destination mac address enetpacketDest1 = $01 enetpacketDest2 = $02 enetpacketDest3 = $03 enetpacketDest4 = $04 enetpacketDest5 = $05 enetpacketSrc0 = $06 'source mac address enetpacketSrc1 = $07 enetpacketSrc2 = $08 enetpacketSrc3 = $09 enetpacketSrc4 = $0A enetpacketSrc5 = $0B enetpacketType0 = $0C 'type/length field enetpacketType1 = $0D enetpacketData = $0E 'IP data area begins here '****************************************************************** '* ARP Layout '****************************************************************** arp_hwtype = $0E arp_prtype = $10 arp_hwlen = $12 arp_prlen = $13 arp_op = $14 arp_shaddr = $16 'arp source mac address arp_sipaddr = $1C 'arp source ip address arp_thaddr = $20 'arp target mac address arp_tipaddr = $26 'arp target ip address '****************************************************************** '* IP Header Layout '****************************************************************** ip_vers_len = $0E 'IP version and header length 1a19 ip_tos = $0F 'IP type of service ip_pktlen = $10 'packet length ip_id = $12 'datagram id ip_frag_offset = $14 'fragment offset ip_ttl = $16 'time to live ip_proto = $17 'protocol (ICMP=1, TCP=6, UDP=11) ip_hdr_cksum = $18 'header checksum 1a23 ip_srcaddr = $1A 'IP address of source ip_destaddr = $1E 'IP addess of destination ip_data = $22 'IP data area '****************************************************************** '* TCP Header Layout '****************************************************************** TCP_srcport = $22 'TCP source port TCP_destport = $24 'TCP destination port TCP_seqnum = $26 'sequence number TCP_acknum = $2A 'acknowledgement number TCP_hdrlen = $2E '4-bit header len (upper 4 bits) TCP_hdrflags = $2F 'TCP flags TCP_window = $30 'window size TCP_cksum = $32 'TCP checksum TCP_urgentptr = $34 'urgent pointer TCP_data = $36 'option/data '****************************************************************** '* IP Protocol Types '****************************************************************** PROT_ICMP = $01 PROT_TCP = $06 PROT_UDP = $11 '****************************************************************** '* ICMP Header '****************************************************************** ICMP_type = ip_data ICMP_code = ICMP_type+1 ICMP_cksum = ICMP_code+1 ICMP_id = ICMP_cksum+2 ICMP_seqnum = ICMP_id+2 ICMP_data = ICMP_seqnum+2 '****************************************************************** '* UDP Header '****************************************************************** UDP_srcport = ip_data UDP_destport = UDP_srcport+2 UDP_len = UDP_destport+2 UDP_cksum = UDP_len+2 UDP_data = UDP_cksum+2 '****************************************************************** '* DHCP Message '****************************************************************** DHCP_op = UDP_data DHCP_htype = DHCP_op+1 DHCP_hlen = DHCP_htype+1 DHCP_hops = DHCP_hlen+1 DHCP_xid = DHCP_hops+1 DHCP_secs = DHCP_xid+4 DHCP_flags = DHCP_secs+2 DHCP_ciaddr = DHCP_flags+2 DHCP_yiaddr = DHCP_ciaddr+4 DHCP_siaddr = DHCP_yiaddr+4 DHCP_giaddr = DHCP_siaddr+4 DHCP_chaddr = DHCP_giaddr+4 DHCP_sname = DHCP_chaddr+16 DHCP_file = DHCP_sname+64 DHCP_options = DHCP_file+128 DHCP_message_end = DHCP_options+312 \ No newline at end of file +{{ + Ethernet TCP/IP Socket Layer Driver (IPv4) + ------------------------------------------ + + Copyright (c) 2006-2009 Harrison Pham + + 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. + + The latest version of this software can be obtained from + http://hdpham.com/PropTCP and http://obex.parallax.com/ +}} + +'' NOTICE: All buffer sizes must be a power of 2! + +CON +' *************************************** +' ** Versioning Information ** +' *************************************** + version = 5 ' major version + release = 2 ' minor version + apiversion = 8 ' api compatibility version + +' *************************************** +' ** User Definable Settings ** +' *************************************** + sNumSockets = 4 ' max number of concurrent registered sockets (max of 255) + +' *** End of user definable settings, don't edit anything below this line!!! +' *** All IP/MAC settings are defined by calling the start(...) method + +CON +' *************************************** +' ** Return Codes / Errors ** +' *************************************** + + RETBUFFEREMPTY = -1 ' no data available + RETBUFFERFULL = -1 ' buffer full + + ERRGENERIC = -1 ' generic errors + + ERR = -100 ' error codes start at -100 + ERRBADHANDLE = ERR - 1 ' bad socket handle + ERROUTOFSOCKETS = ERR - 2 ' no free sockets available + ERRSOCKETCLOSED = ERR - 3 ' socket closed, could not perform operation + +OBJ + nic : "driver_enc28j60" + + 'ser : "SerialMirror" + 'stk : "Stack Length" + +CON +' *************************************** +' ** Socket Constants and Offsets ** +' *************************************** + +' Socket states (user should never touch these) + SCLOSED = 0 ' closed, handle not used + SLISTEN = 1 ' listening, in server mode + SSYNSENT = 2 ' SYN sent, server mode, waits for ACK + SSYNSENTCL = 3 ' SYN sent, client mode, waits for SYN+ACK + SESTABLISHED = 4 ' established connection (either SYN+ACK, or ACK+Data) + SCLOSING = 5 ' connection is being forced closed by code + SCLOSING2 = 6 ' closing, we are waiting for a fin now + SFORCECLOSE = 7 ' force connection close (just RSTs, no waiting for FIN or anything) + SCONNECTINGARP1 = 8 ' connecting, next step: send arp request + SCONNECTINGARP2 = 9 ' connecting, next step: arp request sent, waiting for response + SCONNECTINGARP2G = 10 ' connecting, next step: arp request sent, waiting for response [GATEWAY REQUEST] + SCONNECTING = 11 ' connecting, next step: got mac address, send SYN + +' *************************************** +' ** TCP State Management Constants ** +' *************************************** + TIMEOUTMS = 500 ' (milliseconds) timeout before a retransmit occurs + RSTTIMEOUTMS = 2000 ' (milliseconds) timeout before a RST is sent to close the connection + WINDOWUPDATEMS = 25 ' (milliseconds) window advertisement frequency + + MAXUNACKS = 6 ' max number of unacknowledged retransmits before the stack auto closes the socket + ' timeout = TIMEOUTMS * MAXUNACKS (default: 500ms * 5 = 3000ms) + + EPHPORTSTART = 49152 ' ephemeral port start + EPHPORTEND = 65535 ' end + + MAXPAYLOAD = 1200 ' maximum TCP payload (data) in bytes, this only applies when your txbuffer_length > payload size + +DAT +' *************************************** +' ** Global Variables ** +' *************************************** + cog long 0 ' cog index (for stopping / starting) + stack long 0[128] ' stack for new cog (currently ~74 longs, using 128 for expansion) + + mac_ptr long 0 ' mac address pointer + + pkt_id long 0 ' packet fragmentation id + pkt_isn long 0 ' packet initial sequence number + + ip_ephport word 0 ' packet ephemeral port number (49152 to 65535) + + pkt_count byte 0 ' packet count + + lock_id byte 0 ' socket handle lock + + packet byte 0[nic#MAXFRAME] ' the ethernet frame + +' *************************************** +' ** IP Address Defaults ** +' *************************************** + ' NOTE: All of the MAC/IP variables here contain default values that will + ' be used if override values are not provided as parameters in start(). + long ' long alignment for addresses + ip_addr byte 10, 10, 1, 4 ' device's ip address + ip_subnet byte 255, 255, 255, 0 ' network subnet + ip_gateway byte 10, 10, 1, 254 ' network gateway (router) + ip_dns byte 10, 10, 1, 254 ' network dns + +' *************************************** +' ** Socket Data Arrays ** +' *************************************** + + long + SocketArrayStart + lMySeqNum long 0[sNumSockets] + lMyAckNum long 0[sNumSockets] + lSrcIp long 0[sNumSockets] + lTime long 0[sNumSockets] + + word + wSrcPort word 0[sNumSockets] + wDstPort word 0[sNumSockets] + wLastWin word 0[sNumSockets] + wLastTxLen word 0[sNumSockets] + wNotAcked word 0[sNumSockets] + + byte + bSrcMac byte 0[sNumSockets * 6] + bConState byte 0[sNumSockets] + SocketArrayEnd + +' *************************************** +' ** Circular Buffer Arrays ** +' *************************************** + word + FifoDataStart + rx_head word 0[sNumSockets] ' rx head array + rx_tail word 0[sNumSockets] ' rx tail array + tx_head word 0[sNumSockets] ' tx head array + tx_tail word 0[sNumSockets] ' tx tail array + + tx_tailnew word 0[sNumSockets] ' the new tx_tail value (unacked data) + + rxbuffer_length word 0[sNumSockets] ' each socket's buffer sizes + txbuffer_length word 0[sNumSockets] + + rxbuffer_mask word 0[sNumSockets] ' each socket's buffer masks for capping buffer sizes + txbuffer_mask word 0[sNumSockets] + + long + tx_bufferptr long 0[sNumSockets] ' pointer addresses to each socket's buffer spaces + rx_bufferptr long 0[sNumSockets] + FifoDataEnd + +PUB start(cs, sck, si, so, xtalout, macptr, ipconfigptr) +'' Start the TCP/IP Stack (requires 2 cogs) +'' Only call this once, otherwise you will get conflicts +'' macptr = HUB memory pointer (address) to 6 contiguous mac address bytes +'' ipconfigptr = HUB memory pointer (address) to ip configuration block (16 bytes) +'' Must be in order: ip_addr, ip_subnet, ip_gateway, ip_dns + + stop + 'stk.Init(@stack, 128) + + ' zero socket data arrays (clean up any dead stuff from previous instance) + bytefill(@SocketArrayStart, 0, @SocketArrayEnd - @SocketArrayStart) + + ' reset buffer pointers, zeros a contigous set of bytes, starting at rx_head + bytefill(@FifoDataStart, 0, @FifoDataEnd - @FifoDataStart) + + ' start new cog with tcp stack + cog := cognew(engine(cs, sck, si, so, xtalout, macptr, ipconfigptr), @stack) + 1 + +PUB stop +'' Stop the driver + + if cog + cogstop(cog~ - 1) ' stop the tcp engine + nic.stop ' stop nic driver (kills spi engine) + lockclr(lock_id) ' clear lock before returning it to the pool + lockret(lock_id) ' return the lock to the lock pool + +PRI engine(cs, sck, si, so, xtalout, macptr, ipconfigptr) | i + + lock_id := locknew ' checkout a lock from the HUB + lockclr(lock_id) ' clear the lock, just in case it was in a bad state + + ' Start the ENC28J60 driver in a new cog + nic.start(cs, sck, si, so, xtalout, macptr) ' init the nic + + if ipconfigptr > -1 ' init ip configuration + bytemove(@ip_addr, ipconfigptr, 16) + + mac_ptr := nic.get_mac_pointer ' get the local mac address pointer + + ip_ephport := EPHPORTSTART ' set initial ephemeral port number (might want to random seed this later) + + i := 0 + nic.banksel(nic#EPKTCNT) ' select packet count bank + repeat + pkt_count := nic.rd_cntlreg(nic#EPKTCNT) + if pkt_count > 0 + service_packet ' handle packet + nic.banksel(nic#EPKTCNT) ' re-select the packet count bank + + ++i + if i > 10 ' perform send tick + repeat while lockset(lock_id) + tick_tcpsend ' occurs every 10 cycles, since incoming packets more important + lockclr(lock_id) + + i := 0 + nic.banksel(nic#EPKTCNT) ' re-select the packet count bank + +PRI service_packet + + ' lets process this frame + nic.get_frame(@packet) + + ' check for arp packet type (highest priority obviously) + if packet[enetpacketType0] == $08 AND packet[enetpacketType1] == $06 + if packet[constant(arp_hwtype + 1)] == $01 AND packet[arp_prtype] == $08 AND packet[constant(arp_prtype + 1)] == $00 AND packet[arp_hwlen] == $06 AND packet[arp_prlen] == $04 + if packet[arp_tipaddr] == ip_addr[0] AND packet[constant(arp_tipaddr + 1)] == ip_addr[1] AND packet[constant(arp_tipaddr + 2)] == ip_addr[2] AND packet[constant(arp_tipaddr + 3)] == ip_addr[3] + case packet[constant(arp_op + 1)] + $01 : handle_arp + $02 : repeat while lockset(lock_id) + handle_arpreply + lockclr(lock_id) + '++count_arp + else + if packet[enetpacketType0] == $08 AND packet[enetpacketType1] == $00 + if packet[ip_destaddr] == ip_addr[0] AND packet[constant(ip_destaddr + 1)] == ip_addr[1] AND packet[constant(ip_destaddr + 2)] == ip_addr[2] AND packet[constant(ip_destaddr + 3)] == ip_addr[3] + case packet[ip_proto] + 'PROT_ICMP : 'handle_ping + 'ser.str(stk.GetLength(0, 0)) + 'stk.GetLength(30, 19200) + '++count_ping + PROT_TCP : repeat while lockset(lock_id) + \handle_tcp ' handles abort out of tcp handlers (no socket found) + lockclr(lock_id) + '++count_tcp + 'PROT_UDP : ++count_udp + +' ******************************* +' ** Protocol Receive Handlers ** +' ******************************* +PRI handle_arp | i + nic.start_frame + + ' destination mac address + repeat i from 0 to 5 + nic.wr_frame(packet[enetpacketSrc0 + i]) + + ' source mac address + repeat i from 0 to 5 + nic.wr_frame(BYTE[mac_ptr][i]) + + nic.wr_frame($08) ' arp packet + nic.wr_frame($06) + + nic.wr_frame($00) ' 10mb ethernet + nic.wr_frame($01) + + nic.wr_frame($08) ' ip proto + nic.wr_frame($00) + + nic.wr_frame($06) ' mac addr len + nic.wr_frame($04) ' proto addr len + + nic.wr_frame($00) ' arp reply + nic.wr_frame($02) + + ' write ethernet module mac address + repeat i from 0 to 5 + nic.wr_frame(BYTE[mac_ptr][i]) + + ' write ethernet module ip address + repeat i from 0 to 3 + nic.wr_frame(ip_addr[i]) + + ' write remote mac address + repeat i from 0 to 5 + nic.wr_frame(packet[enetpacketSrc0 + i]) + + ' write remote ip address + repeat i from 0 to 3 + nic.wr_frame(packet[arp_sipaddr + i]) + + return nic.send_frame + +PRI handle_arpreply | handle, ip, found + ' Gets arp reply if it is a response to an ip we have + + ip := (packet[constant(arp_sipaddr + 3)] << 24) + (packet[constant(arp_sipaddr + 2)] << 16) + (packet[constant(arp_sipaddr + 1)] << 8) + (packet[arp_sipaddr]) + + found := false + if ip == LONG[@ip_gateway] + ' find a handle that wants gateway mac + repeat handle from 0 to constant(sNumSockets - 1) + if bConState[handle] == SCONNECTINGARP2G + found := true + quit + else + ' find the one that wants this arp + repeat handle from 0 to constant(sNumSockets - 1) + if bConState[handle] == SCONNECTINGARP2 + if lSrcIp[handle] == ip + found := true + quit + + if found + bytemove(@bSrcMac[handle * 6], @packet + arp_shaddr, 6) + bConState[handle] := SCONNECTING + +'PRI handle_ping + ' Not implemented yet (save on space!) + +PRI handle_tcp | i, ptr, handle, srcip, dstport, srcport, datain_len + ' Handles incoming TCP packets + + srcip := packet[ip_srcaddr] << 24 + packet[constant(ip_srcaddr + 1)] << 16 + packet[constant(ip_srcaddr + 2)] << 8 + packet[constant(ip_srcaddr + 3)] + dstport := packet[TCP_destport] << 8 + packet[constant(TCP_destport + 1)] + srcport := packet[TCP_srcport] << 8 + packet[constant(TCP_srcport + 1)] + + handle := find_socket(srcip, dstport, srcport) ' if no sockets avail, it will abort out of this function + + ' at this point we assume we have an active socket, or a socket available to be used + datain_len := ((packet[ip_pktlen] << 8) + packet[constant(ip_pktlen + 1)]) - ((packet[ip_vers_len] & $0F) * 4) - (((packet[TCP_hdrlen] & $F0) >> 4) * 4) + + if (bConState[handle] == SSYNSENT OR bConState[handle] == SESTABLISHED) AND (packet[TCP_hdrflags] & TCP_ACK) AND datain_len > 0 + ' ACK, without SYN, with data + + ' set socket state, established session + bConState[handle] := SESTABLISHED + + i := packet[constant(TCP_seqnum + 3)] << 24 + packet[constant(TCP_seqnum + 2)] << 16 + packet[constant(TCP_seqnum + 1)] << 8 + packet[TCP_seqnum] + if lMyAckNum[handle] == i + if datain_len =< (rxbuffer_mask[handle] - ((rx_head[handle] - rx_tail[handle]) & rxbuffer_mask[handle])) + ' we have buffer space + ptr := rx_bufferptr[handle] + if (datain_len + rx_head[handle]) > rxbuffer_length[handle] + bytemove(ptr + rx_head[handle], @packet[TCP_data], rxbuffer_length[handle] - rx_head[handle]) + bytemove(ptr, @packet[TCP_data] + (rxbuffer_length[handle] - rx_head[handle]), datain_len - (rxbuffer_length[handle] - rx_head[handle])) + else + bytemove(ptr + rx_head[handle], @packet[TCP_data], datain_len) + rx_head[handle] := (rx_head[handle] + datain_len) & rxbuffer_mask[handle] + else + datain_len := 0 + + else + ' we had a bad ack number, meaning lost or out of order packet + ' we have to wait for the remote host to retransmit in order + datain_len := 0 + + ' recalculate ack number + lMyAckNum[handle] := conv_endianlong(conv_endianlong(lMyAckNum[handle]) + datain_len) + + ' ACK response + build_ipheaderskeleton(handle) + build_tcpskeleton(handle, TCP_ACK) + send_tcpfinal(handle, 0) + + elseif (bConState[handle] == SSYNSENTCL) AND (packet[TCP_hdrflags] & TCP_SYN) AND (packet[TCP_hdrflags] & TCP_ACK) + ' We got a server response, so we ACK it + + bytemove(@lMySeqNum[handle], @packet + TCP_acknum, 4) + bytemove(@lMyAckNum[handle], @packet + TCP_seqnum, 4) + + lMyAckNum[handle] := conv_endianlong(conv_endianlong(lMyAckNum[handle]) + 1) + + ' ACK response + build_ipheaderskeleton(handle) + build_tcpskeleton(handle, TCP_ACK) + send_tcpfinal(handle, 0) + + ' set socket state, established session + bConState[handle] := SESTABLISHED + + elseif (bConState[handle] == SLISTEN) AND (packet[TCP_hdrflags] & TCP_SYN) + ' Reply to SYN with SYN + ACK + + ' copy mac address so we don't have to keep an ARP table + bytemove(@bSrcMac[handle * 6], @packet + enetpacketSrc0, 6) + + ' copy ip, port data + bytemove(@lSrcIp[handle], @packet + ip_srcaddr, 4) + bytemove(@wSrcPort[handle], @packet + TCP_srcport, 2) + bytemove(@wDstPort[handle], @packet + TCP_destport, 2) + + ' get updated ack numbers + bytemove(@lMyAckNum[handle], @packet + TCP_seqnum, 4) + + lMyAckNum[handle] := conv_endianlong(conv_endianlong(lMyAckNum[handle]) + 1) + lMySeqNum[handle] := conv_endianlong(++pkt_isn) ' Initial seq num (random) + + build_ipheaderskeleton(handle) + build_tcpskeleton(handle, constant(TCP_SYN | TCP_ACK)) + send_tcpfinal(handle, 0) + + ' incremement the sequence number for the next packet (it will be for an established connection) + lMySeqNum[handle] := conv_endianlong(conv_endianlong(lMySeqNum[handle]) + 1) + + ' set socket state, waiting for establish + bConState[handle] := SSYNSENT + + elseif (bConState[handle] == SESTABLISHED OR bConState[handle] == SCLOSING2) AND (packet[TCP_hdrflags] & TCP_FIN) + ' Reply to FIN with RST + + ' get updated sequence and ack numbers (gaurantee we have correct ones to kill connection with) + bytemove(@lMySeqNum[handle], @packet + TCP_acknum, 4) + bytemove(@lMyAckNum[handle], @packet + TCP_seqnum, 4) + + 'LONG[handle_addr + sMyAckNum] := conv_endianlong(conv_endianlong(LONG[handle_addr + sMyAckNum]) + 1) + + build_ipheaderskeleton(handle) + build_tcpskeleton(handle, TCP_RST) + send_tcpfinal(handle, 0) + + ' set socket state, now free + bConState[handle] := SCLOSED + return + + elseif (bConState[handle] == SSYNSENT) AND (packet[TCP_hdrflags] & TCP_ACK) + ' if just an ack, and we sent a syn before, then it's established + ' this just gives us the ability to send on connect + bConState[handle] := SESTABLISHED + + elseif (packet[TCP_hdrflags] & TCP_RST) + ' Reset, reset states + bConState[handle] := SCLOSED + return + + if (bConState[handle] == SESTABLISHED OR bConState[handle] == SCLOSING) AND (packet[TCP_hdrflags] & TCP_ACK) + wNotAcked[handle] := 0 ' reset retransmit counter + ' check to see if our last sent data has been ack'd + i := packet[TCP_acknum] << 24 + packet[constant(TCP_acknum + 1)] << 16 + packet[constant(TCP_acknum + 2)] << 8 + packet[constant(TCP_acknum + 3)] + if i == (conv_endianlong(lMySeqNum[handle]) + wLastTxLen[handle]) + ' we received an ack for our last sent packet, so we update our sequence number and buffer pointers + lMySeqNum[handle] := conv_endianlong(conv_endianlong(lMySeqNum[handle]) + wLastTxLen[handle]) + tx_tail[handle] := tx_tailnew[handle] + wLastTxLen[handle] := 0 + + tcpsend(handle) ' send data + +PRI build_ipheaderskeleton(handle) | hdrlen, hdr_chksum + + bytemove(@packet + ip_destaddr, @lSrcIp[handle], 4) ' Set destination address + + bytemove(@packet + ip_srcaddr, @ip_addr, 4) ' Set source address + + bytemove(@packet + enetpacketDest0, @bSrcMac[handle * 6], 6) ' Set destination mac address + + bytemove(@packet + enetpacketSrc0, mac_ptr, 6) ' Set source mac address + + packet[enetpacketType0] := $08 + packet[constant(enetpacketType0 + 1)] := $00 + + packet[ip_vers_len] := $45 + packet[ip_tos] := $00 + + ++pkt_id + + packet[ip_id] := pkt_id >> 8 ' Used for fragmentation + packet[constant(ip_id + 1)] := pkt_id + + packet[ip_frag_offset] := $40 ' Don't fragment + packet[constant(ip_frag_offset + 1)] := 0 + + packet[ip_ttl] := $80 ' TTL = 128 + + packet[ip_proto] := $06 ' TCP protocol + +PRI build_tcpskeleton(handle, flags) | size + + bytemove(@packet + TCP_srcport, @wDstPort[handle], 2) ' Source port + bytemove(@packet + TCP_destport, @wSrcPort[handle], 2) ' Destination port + + bytemove(@packet + TCP_seqnum, @lMySeqNum[handle], 4) ' Seq Num + bytemove(@packet + TCP_acknum, @lMyAckNum[handle], 4) ' Ack Num + + packet[TCP_hdrlen] := $50 ' Header length + + packet[TCP_hdrflags] := flags ' TCP state flags + + ' we have to recalculate the window size often otherwise our stack + ' might explode from too much data :( + size := (rxbuffer_mask[handle] - ((rx_head[handle] - rx_tail[handle]) & rxbuffer_mask[handle])) + wLastWin[handle] := size + + packet[TCP_window] := (size & $FF00) >> 8 + packet[constant(TCP_window + 1)] := size & $FF + +PRI send_tcpfinal(handle, datalen) | i, tcplen, hdrlen, hdr_chksum + + tcplen := 40 + datalen ' real length = data + headers + + packet[ip_pktlen] := tcplen >> 8 + packet[constant(ip_pktlen + 1)] := tcplen + + ' calc ip header checksum + packet[ip_hdr_cksum] := $00 + packet[constant(ip_hdr_cksum + 1)] := $00 + hdrlen := (packet[ip_vers_len] & $0F) * 4 + hdr_chksum := calc_chksum(@packet[ip_vers_len], hdrlen) + packet[ip_hdr_cksum] := hdr_chksum >> 8 + packet[constant(ip_hdr_cksum + 1)] := hdr_chksum + + ' calc checksum + packet[TCP_cksum] := $00 + packet[constant(TCP_cksum + 1)] := $00 + hdr_chksum := nic.chksum_add(@packet[ip_srcaddr], 8) + hdr_chksum += packet[ip_proto] + i := tcplen - ((packet[ip_vers_len] & $0F) * 4) + hdr_chksum += i + hdr_chksum += nic.chksum_add(@packet[TCP_srcport], i) + hdr_chksum := calc_chksumfinal(hdr_chksum) + packet[TCP_cksum] := hdr_chksum >> 8 + packet[constant(TCP_cksum + 1)] := hdr_chksum + + tcplen += 14 + if tcplen < 60 + tcplen := 60 + + ' protect from buffer overrun + if tcplen => nic#TX_BUFFER_SIZE + return + + ' send the packet + nic.start_frame + nic.wr_block(@packet, tcplen) + nic.send_frame + + lTime[handle] := cnt ' update last sent time (for timeout detection) + +PRI find_socket(srcip, dstport, srcport) | handle, free_handle, listen_handle + ' Search for socket, matches ip address, port states + ' Returns handle address (start memory location of socket) + ' If no matches, will abort with -1 + ' If supplied with srcip = 0 then will return free unused handle, aborts with -1 if none avail + + free_handle := -1 + listen_handle := -1 + repeat handle from 0 to constant(sNumSockets - 1) + if bConState[handle] <> SCLOSED + if (lSrcIp[handle] == 0) OR (lSrcIp[handle] == conv_endianlong(srcip)) + ' ip match, ip socket srcip = 0, then will try to match dst port (find listening socket) + if (wDstPort[handle] == conv_endianword(dstport)) {AND (WORD[handle_addr + sSrcPort] == 0 OR WORD[handle_addr + sSrcPort] == conv_endianword(srcport))} + if wSrcPort[handle] == conv_endianword(srcport) + ' found exact socket match (established socket) + return handle + elseif wSrcPort[handle] == 0 + ' found a partial match (listening socket with no peer) + listen_handle := handle + elseif srcip == 0 + ' found a closed (unallocated) socket, save this as a free handle if we are searching for a free handle + free_handle := handle ' we found a free handle, may need this later + + if srcip <> 0 + ' return the listening handle we found + if listen_handle <> -1 + return listen_handle + else + ' searched for a free handle + if free_handle <> -1 + return free_handle + + ' could not find a matching socket / free socket... + abort -1 + +' ****************************** +' ** Transmit Buffer Handlers ** +' ****************************** +PRI tcpsend(handle) | ptr, len + ' Check buffers for data to send (called in main loop) + + if tx_tail[handle] == tx_head[handle] + ' no data in buffer, so just quit + return + + ' we have data to send, so send it + ptr := tx_bufferptr[handle] + len := ((tx_head[handle] - tx_tail[handle]) & txbuffer_mask[handle]) <# MAXPAYLOAD + if (len + tx_tail[handle]) > txbuffer_length[handle] + bytemove(@packet[TCP_data], ptr + tx_tail[handle], txbuffer_length[handle] - tx_tail[handle]) + bytemove(@packet[TCP_data] + (txbuffer_length[handle] - tx_tail[handle]), ptr, len - (txbuffer_length[handle] - tx_tail[handle])) + else + bytemove(@packet[TCP_data], ptr + tx_tail[handle], len) + tx_tailnew[handle] := (tx_tail[handle] + len) & txbuffer_mask[handle] + + wLastTxLen[handle] := len + + build_ipheaderskeleton(handle) + build_tcpskeleton(handle, TCP_ACK {constant(TCP_ACK | TCP_PSH)}) + send_tcpfinal(handle, len) ' send actual data + + send_tcpfinal(handle, 0) ' send an empty packet to force the other side to ACK (hack to get around delayed acks) + + wNotAcked[handle]++ ' increment unacked packet counter + +PRI tick_tcpsend | handle, state, len + + repeat handle from 0 to constant(sNumSockets - 1) + state := bConState[handle] + + if state == SESTABLISHED OR state == SCLOSING + len := (rxbuffer_mask[handle] - ((rx_head[handle] - rx_tail[handle]) & rxbuffer_mask[handle])) + if wLastWin[handle] <> len AND len => (rxbuffer_length[handle] / 2) AND ((cnt - lTime[handle]) / (clkfreq / 1000) > WINDOWUPDATEMS) + ' update window size + build_ipheaderskeleton(handle) + build_tcpskeleton(handle, TCP_ACK) + send_tcpfinal(handle, 0) + + if ((cnt - lTime[handle]) / (clkfreq / 1000) > TIMEOUTMS) OR wLastTxLen[handle] == 0 + ' send new data OR retransmit our last packet since the other side seems to have lost it + ' the remote host will respond with another dup ack, and we will get back on track (hopefully) + tcpsend(handle) + + if (state == SCLOSING) + + build_ipheaderskeleton(handle) + build_tcpskeleton(handle, constant(TCP_ACK | TCP_FIN)) + send_tcpfinal(handle, 0) + + ' we now wait for the other side to terminate + bConState[handle] := SCLOSING2 + + elseif state == SCONNECTINGARP1 + ' We need to send an arp request + + arp_request_checkgateway(handle) + + elseif state == SCONNECTING + ' Yea! We got an arp response previously, so now we can send the SYN + + lMySeqNum[handle] := conv_endianlong(++pkt_isn) + lMyAckNum[handle] := 0 + + build_ipheaderskeleton(handle) + build_tcpskeleton(handle, TCP_SYN) + send_tcpfinal(handle, 0) + + bConState[handle] := SSYNSENTCL + + elseif (state == SFORCECLOSE) OR (state == SESTABLISHED AND wNotAcked[handle] => MAXUNACKS) OR (lookdown(state: SCLOSING2, SSYNSENT, SSYNSENTCL, SCONNECTINGARP2, SCONNECTINGARP2G) {(state == SCLOSING2 OR state == SSYNSENT)} AND ((cnt - lTime[handle]) / (clkfreq / 1000) > RSTTIMEOUTMS)) + ' Force close (send RST, and say the socket is closed!) + + ' This is triggered when any of the following happens: + ' 1 - we don't get a response to our SSYNSENT state + ' 2 - we exceeded MAXUNACKS tcp retransmits (remote host lost) + ' 3 - we get stuck in the SSCLOSING2 state + ' 4 - we don't get a response to our client SYNSENTCL state + ' 5 - we don't get an ARP response state SCONNECTINGARP2 or SCONNECTINGARP2G + + build_ipheaderskeleton(handle) + build_tcpskeleton(handle, TCP_RST) + send_tcpfinal(handle, 0) + + bConState[handle] := SCLOSED + +PRI arp_request_checkgateway(handle) | ip_ptr + + ip_ptr := @lSrcIp[handle] + + if (BYTE[ip_ptr] & ip_subnet[0]) == (ip_addr[0] & ip_subnet[0]) AND (BYTE[ip_ptr + 1] & ip_subnet[1]) == (ip_addr[1] & ip_subnet[1]) AND (BYTE[ip_ptr + 2] & ip_subnet[2]) == (ip_addr[2] & ip_subnet[2]) AND (BYTE[ip_ptr + 3] & ip_subnet[3]) == (ip_addr[3] & ip_subnet[3]) + arp_request(conv_endianlong(LONG[ip_ptr])) + bConState[handle] := SCONNECTINGARP2 + else + arp_request(conv_endianlong(LONG[@ip_gateway])) + bConState[handle] := SCONNECTINGARP2G + + lTime[handle] := cnt + +PRI arp_request(ip) | i + nic.start_frame + + ' destination mac address (broadcast mac) + repeat i from 0 to 5 + nic.wr_frame($FF) + + ' source mac address (this device) + repeat i from 0 to 5 + nic.wr_frame(BYTE[mac_ptr][i]) + + nic.wr_frame($08) ' arp packet + nic.wr_frame($06) + + nic.wr_frame($00) ' 10mb ethernet + nic.wr_frame($01) + + nic.wr_frame($08) ' ip proto + nic.wr_frame($00) + + nic.wr_frame($06) ' mac addr len + nic.wr_frame($04) ' proto addr len + + nic.wr_frame($00) ' arp request + nic.wr_frame($01) + + ' source mac address (this device) + repeat i from 0 to 5 + nic.wr_frame(BYTE[mac_ptr][i]) + + ' source ip address (this device) + repeat i from 0 to 3 + nic.wr_frame(ip_addr[i]) + + ' unknown mac address area + repeat i from 0 to 5 + nic.wr_frame($00) + + ' figure out if we need router arp request or host arp request + ' this means some subnet masking + + ' dest ip address + repeat i from 3 to 0 + nic.wr_frame(ip.byte[i]) + + ' send the request + return nic.send_frame + +' ******************************* +' ** IP Packet Helpers (Calcs) ** +' ******************************* +PRI calc_chksum(ptr, hdrlen) : chksum + ' Calculates IP checksums + ' packet = pointer to IP packet + ' returns: chksum + ' http://www.geocities.com/SiliconValley/2072/bit33.txt + 'chksum := calc_chksumhalf(packet, hdrlen) + chksum := nic.chksum_add(ptr, hdrlen) + chksum := calc_chksumfinal(chksum) + +PRI calc_chksumfinal(chksumin) : chksum + ' Performs the final part of checksums + chksum := (chksumin >> 16) + (chksumin & $FFFF) + chksum := (!chksum) & $FFFF + +{PRI calc_chksumhalf(packet, hdrlen) : chksum + ' Calculates checksum without doing the final stage of calculations + chksum := 0 + repeat while hdrlen > 1 + chksum += (BYTE[packet++] << 8) + BYTE[packet++] + chksum := (chksum >> 16) + (chksum & $FFFF) + hdrlen -= 2 + if hdrlen > 0 + chksum += BYTE[packet] << 8} + +' *************************** +' ** Memory Access Helpers ** +' *************************** +PRI conv_endianlong(in) + 'return (in << 24) + ((in & $FF00) << 8) + ((in & $FF0000) >> 8) + (in >> 24) ' we can sometimes get away with shifting without masking, since shifts kill extra bits anyways + return (in.byte[0] << 24) + (in.byte[1] << 16) + (in.byte[2] << 8) + (in.byte[3]) + +PRI conv_endianword(in) + 'return ((in & $FF) << 8) + ((in & $FF00) >> 8) + return (in.byte[0] << 8) + (in.byte[1]) + +PRI _handleConvert(userHandle, ptrHandle) | handle +' Checks to see if a handle index is valid +' Aborts if the handle is invalid + + handle := userHandle.byte[0] ' extract the handle index from the lower 8 bits + + if handle < 0 OR handle > constant(sNumSockets - 1) ' check the handle index to make sure we don't go out of bounds + abort ERRBADHANDLE + + ' check handle to make sure it's the one we want (rid ourselves of bad user handles) + ' the current check method is as follows: + ' - compare sDstPort + + if wDstPort[handle] <> ((userHandle.byte[2] << 8) + userHandle.byte[1]) + abort ERRBADHANDLE + + ' if we got here without aborting then we can assume the handle is good + LONG[ptrHandle] := handle + +' ************************************ +' ** Public Accessors (Thread Safe) ** +' ************************************ +PUB listen(port, _ptrrxbuff, _rxlen, _ptrtxbuff, _txlen) | handle +'' Sets up a socket for listening on a port +'' port = port number to listen on +'' ptrrxbuff = pointer to the rxbuffer array +'' rxlen = length of the rxbuffer array (must be power of 2) +'' ptrtxbuff = pointer to the txbuffer array +'' txlen = length of the txbuffer array (must be power of 2) +'' Returns handle if available, ERROUTOFSOCKETS if none available +'' Nonblocking + + repeat while lockset(lock_id) + + ' just find any avail closed socket + handle := \find_socket(0, 0, 0) + + if handle < 0 + lockclr(lock_id) + abort ERROUTOFSOCKETS + + rx_bufferptr[handle] := _ptrrxbuff + tx_bufferptr[handle] := _ptrtxbuff + rxbuffer_length[handle] := _rxlen + txbuffer_length[handle] := _txlen + rxbuffer_mask[handle] := _rxlen - 1 + txbuffer_mask[handle] := _txlen - 1 + + lMySeqNum[handle] := 0 + lMyAckNum[handle] := 0 + lSrcIp[handle] := 0 + lTime[handle] := 0 + wLastTxLen[handle] := 0 + wNotAcked[handle] := 0 + bytefill(@bSrcMac[handle * 6], 0, 6) + + wSrcPort[handle] := 0 ' no source port yet + wDstPort[handle] := conv_endianword(port) ' we do have a dest port though + + wLastWin[handle] := rxbuffer_length[handle] + + tx_head[handle] := 0 + tx_tail[handle] := 0 + tx_tailnew[handle] := 0 + rx_head[handle] := 0 + rx_tail[handle] := 0 + + ' it's now listening + bConState[handle] := SLISTEN + + lockclr(lock_id) + + return ((port.byte[0] << 16) + (port.byte[1] << 8)) + handle + +PUB connect(ipaddr, remoteport, _ptrrxbuff, _rxlen, _ptrtxbuff, _txlen) | handle, user_handle +'' Connect to remote host +'' ipaddr = ipv4 address packed into a long (ie: 1.2.3.4 => $01_02_03_04) +'' remoteport = port number to connect to +'' ptrrxbuff = pointer to the rxbuffer array +'' rxlen = length of the rxbuffer array (must be power of 2) +'' ptrtxbuff = pointer to the txbuffer array +'' txlen = length of the txbuffer array (must be power of 2) +'' Returns handle to new socket, ERROUTOFSOCKETS if no socket available +'' Nonblocking + + repeat while lockset(lock_id) + + ' just find any avail closed socket + handle := \find_socket(0, 0, 0) + + if handle < 0 + lockclr(lock_id) + abort ERROUTOFSOCKETS + + rx_bufferptr[handle] := _ptrrxbuff + tx_bufferptr[handle] := _ptrtxbuff + rxbuffer_length[handle] := _rxlen + txbuffer_length[handle] := _txlen + rxbuffer_mask[handle] := _rxlen - 1 + txbuffer_mask[handle] := _txlen - 1 + + lMySeqNum[handle] := 0 + lMyAckNum[handle] := 0 + lTime[handle] := 0 + wLastTxLen[handle] := 0 + wNotAcked[handle] := 0 + bytefill(@bSrcMac[handle * 6], 0, 6) + + if(ip_ephport => EPHPORTEND) ' constrain ephport to specified range + ip_ephport := EPHPORTSTART + + user_handle := ((ip_ephport.byte[0] << 16) + (ip_ephport.byte[1] << 8)) + handle + + ' copy in ip, port data (with respect to the remote host, since we use same code as server) + lSrcIp[handle] := conv_endianlong(ipaddr) + wSrcPort[handle] := conv_endianword(remoteport) + wDstPort[handle] := conv_endianword(ip_ephport++) + + wLastWin[handle] := rxbuffer_length[handle] + + tx_head[handle] := 0 + tx_tail[handle] := 0 + tx_tailnew[handle] := 0 + rx_head[handle] := 0 + rx_tail[handle] := 0 + + bConState[handle] := SCONNECTINGARP1 + + lockclr(lock_id) + + return user_handle + +PUB close(user_handle) | handle, state +'' Closes a connection + + _handleConvert(user_handle, @handle) + + repeat while lockset(lock_id) + + state := bConState[handle] + + if state == SESTABLISHED + ' try to gracefully close the connection + bConState[handle] := SCLOSING + elseif state <> SCLOSING AND state <> SCLOSING2 + ' we only do an ungraceful close if we are not in ESTABLISHED, CLOSING, or CLOSING2 + bConState[handle] := SCLOSED + + lockclr(lock_id) + + ' wait for the socket to close, this is very important to prevent the client app from reusing the buffers + repeat until (bConState[handle] == SCLOSING2) or (bConState[handle] == SCLOSED) + +PUB isConnected(user_handle) | handle +'' Returns true if the socket is connected, false otherwise + + if \_handleConvert(user_handle, @handle) <> 0 + return false + + return (bConState[handle] == SESTABLISHED) + +PUB isValidHandle(user_handle) | handle +'' Checks to see if the handle is valid, handles will become invalid once they are used +'' In other words, a closed listening socket is now invalid, etc + + {if handle < 0 OR handle > constant(sNumSockets - 1) + ' obviously the handle index is out of range, so it's not valid! + return false} + + if \_handleConvert(user_handle, @handle) < 0 + return false + + return (bConState[handle] <> SCLOSED) + +PUB readDataNonBlocking(user_handle, ptr, maxlen) | handle, len, rxptr +'' Reads bytes from the socket +'' Returns number of read bytes +'' Not blocking (returns RETBUFFEREMPTY if no data) + + _handleConvert(user_handle, @handle) + + if rx_tail[handle] == rx_head[handle] + return RETBUFFEREMPTY + + len := (rx_head[handle] - rx_tail[handle]) & rxbuffer_mask[handle] + if maxlen < len + len := maxlen + + rxptr := rx_bufferptr[handle] + + if (len + rx_tail[handle]) > rxbuffer_length[handle] + bytemove(ptr, rxptr + rx_tail[handle], rxbuffer_length[handle] - rx_tail[handle]) + bytemove(ptr + (rxbuffer_length[handle] - rx_tail[handle]), rxptr, len - (rxbuffer_length[handle] - rx_tail[handle])) + else + bytemove(ptr, rxptr + rx_tail[handle], len) + + rx_tail[handle] := (rx_tail[handle] + len) & rxbuffer_mask[handle] + + return len + +PUB readData(user_handle, ptr, maxlen) : len | handle +'' Reads bytes from the socket +'' Returns the number of read bytes +'' Will block until data is received + + _handleConvert(user_handle, @handle) + + repeat while (len := readDataNonBlocking(user_handle, ptr, maxlen)) < 0 + ifnot isConnected(user_handle) + abort ERRSOCKETCLOSED + +PUB readByteNonBlocking(user_handle) : rxbyte | handle, ptr +'' Read a byte from the specified socket +'' Will not block (returns RETBUFFEREMPTY if no byte avail) + + _handleConvert(user_handle, @handle) + + rxbyte := RETBUFFEREMPTY + if rx_tail[handle] <> rx_head[handle] + ptr := rx_bufferptr[handle] + rxbyte := BYTE[ptr][rx_tail[handle]] + rx_tail[handle] := (rx_tail[handle] + 1) & rxbuffer_mask[handle] + +PUB readByte(user_handle) : rxbyte | handle, ptr +'' Read a byte from the specified socket +'' Will block until a byte is received + + _handleConvert(user_handle, @handle) + + repeat while (rxbyte := readByteNonBlocking(user_handle)) < 0 + ifnot isConnected(user_handle) + abort ERRSOCKETCLOSED + +PUB writeDataNonBlocking(user_handle, ptr, len) | handle, txptr +'' Writes bytes to the socket +'' Will not write anything unless your data fits in the buffer +'' Non blocking (returns RETBUFFERFULL if can't fit data) + + _handleConvert(user_handle, @handle) + + if (txbuffer_mask[handle] - ((tx_head[handle] - tx_tail[handle]) & txbuffer_mask[handle])) < len + return RETBUFFERFULL + + txptr := tx_bufferptr[handle] + + if (len + tx_head[handle]) > txbuffer_length[handle] + bytemove(txptr + tx_head[handle], ptr, txbuffer_length[handle] - tx_head[handle]) + bytemove(txptr, ptr + (txbuffer_length[handle] - tx_head[handle]), len - (txbuffer_length[handle] - tx_head[handle])) + else + bytemove(txptr + tx_head[handle], ptr, len) + + tx_head[handle] := (tx_head[handle] + len) & txbuffer_mask[handle] + + return len + +PUB writeData(user_handle, ptr, len) | handle +'' Writes data to the specified socket +'' Will block until all data is queued to be sent + + _handleConvert(user_handle, @handle) + + repeat while len > txbuffer_mask[handle] + repeat while writeDataNonBlocking(user_handle, ptr, txbuffer_mask[handle]) < 0 + ifnot isConnected(user_handle) + abort ERRSOCKETCLOSED + len -= txbuffer_mask[handle] + ptr += txbuffer_mask[handle] + + repeat while writeDataNonBlocking(user_handle, ptr, len) < 0 + ifnot isConnected(user_handle) + abort ERRSOCKETCLOSED + +PUB writeByteNonBlocking(user_handle, txbyte) | handle, ptr +'' Writes a byte to the specified socket +'' Will not block (returns RETBUFFERFULL if no buffer space available) + + _handleConvert(user_handle, @handle) + + ifnot (tx_tail[handle] <> (tx_head[handle] + 1) & txbuffer_mask[handle]) + return RETBUFFERFULL + + ptr := tx_bufferptr[handle] + BYTE[ptr][tx_head[handle]] := txbyte + tx_head[handle] := (tx_head[handle] + 1) & txbuffer_mask[handle] + + return txbyte + +PUB writeByte(user_handle, txbyte) | handle +'' Write a byte to the specified socket +'' Will block until space is available for byte to be sent + + _handleConvert(user_handle, @handle) + + repeat while writeByteNonBlocking(user_handle, txbyte) < 0 + ifnot isConnected(user_handle) + abort ERRSOCKETCLOSED + +PUB resetBuffers(user_handle) | handle +'' Resets send/receive buffers for the specified socket + + _handleConvert(user_handle, @handle) + + rx_tail[handle] := rx_head[handle] + tx_head[handle] := tx_tail[handle] + +PUB flush(user_handle) | handle +'' Flushes the send buffer (waits till the buffer is empty) +'' Will block until all tx data is sent + + _handleConvert(user_handle, @handle) + + repeat while isConnected(user_handle) AND tx_tail[handle] <> tx_head[handle] + +PUB getSocketState(user_handle) | handle +'' Gets the socket state (internal state numbers) +'' You can include driver_socket in any object and use the S... state constants for comparison + + _handleConvert(user_handle, @handle) + + return bConState[handle] + +PUB getReceiveBufferCount(user_handle) | handle +'' Returns the number of bytes in the receive buffer + + _handleConvert(user_handle, @handle) + + return (rx_head[handle] - rx_tail[handle]) & rxbuffer_mask[handle] + +CON + '****************************************************************** + '* TCP Flags + '****************************************************************** + TCP_FIN = 1 + TCP_SYN = 2 + TCP_RST = 4 + TCP_PSH = 8 + TCP_ACK = 16 + TCP_URG = 32 + TCP_ECE = 64 + TCP_CWR = 128 + '****************************************************************** + '* Ethernet Header Layout + '****************************************************************** + enetpacketDest0 = $00 'destination mac address + enetpacketDest1 = $01 + enetpacketDest2 = $02 + enetpacketDest3 = $03 + enetpacketDest4 = $04 + enetpacketDest5 = $05 + enetpacketSrc0 = $06 'source mac address + enetpacketSrc1 = $07 + enetpacketSrc2 = $08 + enetpacketSrc3 = $09 + enetpacketSrc4 = $0A + enetpacketSrc5 = $0B + enetpacketType0 = $0C 'type/length field + enetpacketType1 = $0D + enetpacketData = $0E 'IP data area begins here + '****************************************************************** + '* ARP Layout + '****************************************************************** + arp_hwtype = $0E + arp_prtype = $10 + arp_hwlen = $12 + arp_prlen = $13 + arp_op = $14 + arp_shaddr = $16 'arp source mac address + arp_sipaddr = $1C 'arp source ip address + arp_thaddr = $20 'arp target mac address + arp_tipaddr = $26 'arp target ip address + '****************************************************************** + '* IP Header Layout + '****************************************************************** + ip_vers_len = $0E 'IP version and header length 1a19 + ip_tos = $0F 'IP type of service + ip_pktlen = $10 'packet length + ip_id = $12 'datagram id + ip_frag_offset = $14 'fragment offset + ip_ttl = $16 'time to live + ip_proto = $17 'protocol (ICMP=1, TCP=6, UDP=11) + ip_hdr_cksum = $18 'header checksum 1a23 + ip_srcaddr = $1A 'IP address of source + ip_destaddr = $1E 'IP addess of destination + ip_data = $22 'IP data area + '****************************************************************** + '* TCP Header Layout + '****************************************************************** + TCP_srcport = $22 'TCP source port + TCP_destport = $24 'TCP destination port + TCP_seqnum = $26 'sequence number + TCP_acknum = $2A 'acknowledgement number + TCP_hdrlen = $2E '4-bit header len (upper 4 bits) + TCP_hdrflags = $2F 'TCP flags + TCP_window = $30 'window size + TCP_cksum = $32 'TCP checksum + TCP_urgentptr = $34 'urgent pointer + TCP_data = $36 'option/data + '****************************************************************** + '* IP Protocol Types + '****************************************************************** + PROT_ICMP = $01 + PROT_TCP = $06 + PROT_UDP = $11 + '****************************************************************** + '* ICMP Header + '****************************************************************** + ICMP_type = ip_data + ICMP_code = ICMP_type+1 + ICMP_cksum = ICMP_code+1 + ICMP_id = ICMP_cksum+2 + ICMP_seqnum = ICMP_id+2 + ICMP_data = ICMP_seqnum+2 + '****************************************************************** + '* UDP Header + '****************************************************************** + UDP_srcport = ip_data + UDP_destport = UDP_srcport+2 + UDP_len = UDP_destport+2 + UDP_cksum = UDP_len+2 + UDP_data = UDP_cksum+2 + '****************************************************************** + '* DHCP Message + '****************************************************************** + DHCP_op = UDP_data + DHCP_htype = DHCP_op+1 + DHCP_hlen = DHCP_htype+1 + DHCP_hops = DHCP_hlen+1 + DHCP_xid = DHCP_hops+1 + DHCP_secs = DHCP_xid+4 + DHCP_flags = DHCP_secs+2 + DHCP_ciaddr = DHCP_flags+2 + DHCP_yiaddr = DHCP_ciaddr+4 + DHCP_siaddr = DHCP_yiaddr+4 + DHCP_giaddr = DHCP_siaddr+4 + DHCP_chaddr = DHCP_giaddr+4 + DHCP_sname = DHCP_chaddr+16 + DHCP_file = DHCP_sname+64 + DHCP_options = DHCP_file+128 + DHCP_message_end = DHCP_options+312 \ No newline at end of file