1
0
mirror of https://github.com/elua/elua.git synced 2025-01-25 01:02:54 +08:00
elua/src/elua_uip.c
2011-10-09 19:48:37 +03:00

713 lines
19 KiB
C

#include "platform_conf.h"
#ifdef BUILD_UIP
// UIP "helper" for eLua
// Implements the eLua specific UIP application
#include "elua_uip.h"
#include "elua_net.h"
#include "type.h"
#include "uip.h"
#include "uip_arp.h"
#include "platform.h"
#include "utils.h"
#include "uip-split.h"
#include "dhcpc.h"
#include "resolv.h"
#include <string.h>
// UIP send buffer
extern void* uip_sappdata;
// Global "configured" flag
static volatile u8 elua_uip_configured;
// *****************************************************************************
// Platform independenet eLua UIP "main loop" implementation
// Timers
static u32 periodic_timer, arp_timer;
// Macro for accessing the Ethernet header information in the buffer.
#define BUF ((struct uip_eth_hdr *)&uip_buf[0])
// UIP Timers (in ms)
#define UIP_PERIODIC_TIMER_MS 500
#define UIP_ARP_TIMER_MS 10000
#define IP_TCP_HEADER_LENGTH 40
#define TOTAL_HEADER_LENGTH (IP_TCP_HEADER_LENGTH+UIP_LLH_LEN)
static void device_driver_send()
{
platform_eth_send_packet( uip_buf, uip_len );
}
// This gets called on both Ethernet RX interrupts and timer requests,
// but it's called only from the Ethernet interrupt handler
void elua_uip_mainloop()
{
u32 temp, packet_len;
// Increment uIP timers
temp = platform_eth_get_elapsed_time();
periodic_timer += temp;
arp_timer += temp;
// Check for an RX packet and read it
if( ( packet_len = platform_eth_get_packet_nb( uip_buf, sizeof( uip_buf ) ) ) > 0 )
{
// Set uip_len for uIP stack usage.
uip_len = ( unsigned short )packet_len;
// Process incoming IP packets here.
if( BUF->type == htons( UIP_ETHTYPE_IP ) )
{
uip_arp_ipin();
uip_input();
// If the above function invocation resulted in data that
// should be sent out on the network, the global variable
// uip_len is set to a value > 0.
if( uip_len > 0 )
{
uip_arp_out();
device_driver_send();
}
}
// Process incoming ARP packets here.
else if( BUF->type == htons( UIP_ETHTYPE_ARP ) )
{
uip_arp_arpin();
// If the above function invocation resulted in data that
// should be sent out on the network, the global variable
// uip_len is set to a value > 0.
if( uip_len > 0 )
device_driver_send();
}
}
// Process TCP/IP Periodic Timer here.
// Also process the "force interrupt" events (platform_eth_force_interrupt)
if( periodic_timer >= UIP_PERIODIC_TIMER_MS )
{
periodic_timer = 0;
uip_set_forced_poll( 0 );
}
else
uip_set_forced_poll( 1 );
for( temp = 0; temp < UIP_CONNS; temp ++ )
{
uip_periodic( temp );
// If the above function invocation resulted in data that
// should be sent out on the network, the global variable
// uip_len is set to a value > 0.
if( uip_len > 0 )
{
uip_arp_out();
device_driver_send();
}
}
#if UIP_UDP
for( temp = 0; temp < UIP_UDP_CONNS; temp ++ )
{
uip_udp_periodic( temp );
// If the above function invocation resulted in data that
// should be sent out on the network, the global variable
// uip_len is set to a value > 0.
if( uip_len > 0 )
{
uip_arp_out();
device_driver_send();
}
}
#endif // UIP_UDP
// Process ARP Timer here.
if( arp_timer >= UIP_ARP_TIMER_MS )
{
arp_timer = 0;
uip_arp_timer();
}
}
// *****************************************************************************
// DHCP callback
#ifdef BUILD_DHCPC
static void elua_uip_conf_static();
void dhcpc_configured(const struct dhcpc_state *s)
{
if( s->ipaddr[ 0 ] != 0 )
{
uip_sethostaddr( s->ipaddr );
uip_setnetmask( s->netmask );
uip_setdraddr( s->default_router );
resolv_conf( ( u16_t* )s->dnsaddr );
elua_uip_configured = 1;
}
else
elua_uip_conf_static();
}
#endif
// *****************************************************************************
// DNS callback
#ifdef BUILD_DNS
volatile static int elua_resolv_req_done;
static elua_net_ip elua_resolv_ip;
void resolv_found( char *name, u16_t *ipaddr )
{
if( !ipaddr )
elua_resolv_ip.ipaddr = 0;
else
{
elua_resolv_ip.ipwords[ 0 ] = ipaddr[ 0 ];
elua_resolv_ip.ipwords[ 1 ] = ipaddr[ 1 ];
}
elua_resolv_req_done = 1;
}
#endif
// *****************************************************************************
// Console over Ethernet support
#ifdef BUILD_CON_TCP
// TELNET specific data
#define TELNET_IAC_CHAR 255
#define TELNET_IAC_3B_FIRST 251
#define TELNET_IAC_3B_LAST 254
#define TELNET_SB_CHAR 250
#define TELNET_SE_CHAR 240
#define TELNET_EOF 236
// The telnet socket number
static int elua_uip_telnet_socket = -1;
// Utility function for TELNET: parse input buffer, skipping over
// TELNET specific sequences
// Returns the length of the buffer after processing
static void elua_uip_telnet_handle_input( volatile struct elua_uip_state* s )
{
u8 *dptr = ( u8* )uip_appdata;
char *orig = ( char* )s->ptr;
int skip;
elua_net_size maxsize = s->len;
// Traverse the input buffer, skipping over TELNET sequences
while( ( dptr < ( u8* )uip_appdata + uip_datalen() ) && ( s->ptr - orig < s->len ) )
{
if( *dptr != TELNET_IAC_CHAR ) // regular char, copy it to buffer
*s->ptr ++ = *dptr ++;
else
{
// Control sequence: 2 or 3 bytes?
if( ( dptr[ 1 ] >= TELNET_IAC_3B_FIRST ) && ( dptr[ 1 ] <= TELNET_IAC_3B_LAST ) )
skip = 3;
else
{
// Check EOF indication
if( dptr[ 1 ] == TELNET_EOF )
*s->ptr ++ = STD_CTRLZ_CODE;
skip = 2;
}
dptr += skip;
}
}
if( s->ptr > orig )
{
s->res = ELUA_NET_ERR_OK;
s->len = maxsize - ( s->ptr - orig );
uip_stop();
s->state = ELUA_UIP_STATE_IDLE;
}
}
// Utility function for TELNET: prepend all '\n' with '\r' in buffer
// Returns actual len
// It is assumed that the buffer is "sufficiently smaller" than the UIP
// buffer (which is true for the default configuration: 128 bytes buffer
// in Newlib for stdin/stdout, more than 1024 bytes UIP buffer)
static elua_net_size elua_uip_telnet_prep_send( const char* src, elua_net_size size )
{
elua_net_size actsize = size, i;
char* dest = ( char* )uip_sappdata;
for( i = 0; i < size; i ++ )
{
if( *src == '\n' )
{
*dest ++ = '\r';
actsize ++;
}
*dest ++ = *src ++;
}
return actsize;
}
#endif // #ifdef BUILD_CON_TCP
// *****************************************************************************
// eLua UIP application (used to implement the eLua TCP/IP services)
// Special handling for "accept"
volatile static u8 elua_uip_accept_request;
volatile static int elua_uip_accept_sock;
volatile static elua_net_ip elua_uip_accept_remote;
void elua_uip_appcall()
{
volatile struct elua_uip_state *s;
elua_net_size temp;
int sockno;
// If uIP is not yet configured (DHCP response not received), do nothing
if( !elua_uip_configured )
return;
s = ( struct elua_uip_state* )&( uip_conn->appstate );
// Need to find the actual socket location, since UIP doesn't provide this ...
for( temp = 0; temp < UIP_CONNS; temp ++ )
if( uip_conns + temp == uip_conn )
break;
sockno = ( int )temp;
if( uip_connected() )
{
#ifdef BUILD_CON_TCP
if( uip_conn->lport == HTONS( ELUA_NET_TELNET_PORT ) ) // special case: telnet server
{
if( elua_uip_telnet_socket != -1 )
{
uip_close();
return;
}
else
elua_uip_telnet_socket = sockno;
}
else
#endif
if( elua_uip_accept_request )
{
elua_uip_accept_sock = sockno;
elua_uip_accept_remote.ipwords[ 0 ] = uip_conn->ripaddr[ 0 ];
elua_uip_accept_remote.ipwords[ 1 ] = uip_conn->ripaddr[ 1 ];
elua_uip_accept_request = 0;
}
else if( s->state == ELUA_UIP_STATE_CONNECT )
s->state = ELUA_UIP_STATE_IDLE;
uip_stop();
return;
}
if( s->state == ELUA_UIP_STATE_IDLE )
return;
// Do we need to read?
if( s->state == ELUA_UIP_STATE_RECV )
{
// Re-enable data transfer on the socket and change state
s->state = ELUA_UIP_STATE_RECV_2;
uip_restart();
return;
}
if( uip_aborted() || uip_timedout() || uip_closed() )
{
// Signal this error
s->res = uip_aborted() ? ELUA_NET_ERR_ABORTED : ( uip_timedout() ? ELUA_NET_ERR_TIMEDOUT : ELUA_NET_ERR_CLOSED );
#ifdef BUILD_CON_TCP
if( sockno == elua_uip_telnet_socket )
elua_uip_telnet_socket = -1;
#endif
s->state = ELUA_UIP_STATE_IDLE;
return;
}
// Handle data send
if( ( uip_acked() || uip_rexmit() || uip_poll() ) && ( s->state == ELUA_UIP_STATE_SEND ) )
{
// Special translation for TELNET: prepend all '\n' with '\r'
// We write directly in UIP's buffer
if( uip_acked() )
{
elua_net_size minlen = UMIN( s->len, uip_mss() );
s->len -= minlen;
s->ptr += minlen;
if( s->len == 0 )
s->state = ELUA_UIP_STATE_IDLE;
}
if( s->len > 0 ) // need to (re)transmit?
{
#ifdef BUILD_CON_TCP
if( sockno == elua_uip_telnet_socket )
{
temp = elua_uip_telnet_prep_send( s->ptr, s->len );
uip_send( uip_sappdata, temp );
}
else
#endif
uip_send( s->ptr, UMIN( s->len, uip_mss() ) );
}
return;
}
// Handle close
if( s->state == ELUA_UIP_STATE_CLOSE )
{
uip_close();
s->state = ELUA_UIP_STATE_IDLE;
return;
}
// Handle data receive
if( uip_newdata() )
{
if( s->state == ELUA_UIP_STATE_RECV_2 )
{
#ifdef BUILD_CON_TCP
if( sockno == elua_uip_telnet_socket )
{
elua_uip_telnet_handle_input( s );
return;
}
#endif
int lastfound = 0;
// Check end of transmission
if( uip_datalen() < UIP_RECEIVE_WINDOW )
lastfound = 1;
// Check overflow
if( s->len < uip_datalen() )
{
s->res = ELUA_NET_ERR_OVERFLOW;
temp = s->len;
}
else
temp = uip_datalen();
if( s->readto != ELUA_NET_NO_LASTCHAR )
{
char *tptr = ( char* )uip_appdata;
char *last = ( char* )uip_appdata + temp - 1;
luaL_Buffer *pbuf = ( luaL_Buffer* )s->ptr;
char* dest = ( char* )s->ptr;
while( tptr <= last )
{
if( *tptr == s->readto )
{
lastfound = 1;
break;
}
if( *tptr != '\r' )
{
if( s->res )
luaL_addchar( pbuf, *tptr );
else
*dest ++ = *tptr;
s->len --;
}
tptr ++;
}
}
else
{
if( s->res )
luaL_addlstring( ( luaL_Buffer* )s->ptr, ( const char* )uip_appdata, temp );
else
memcpy( ( char* )s->ptr, ( const char* )uip_appdata, temp );
s->len -= temp;
}
// Do we need to read another packet?
if( s->len == 0 || lastfound )
{
uip_stop();
s->res = ELUA_NET_ERR_OK;
s->state = ELUA_UIP_STATE_IDLE;
}
}
else
uip_stop();
}
}
static void elua_uip_conf_static()
{
uip_ipaddr_t ipaddr;
uip_ipaddr( ipaddr, ELUA_CONF_IPADDR0, ELUA_CONF_IPADDR1, ELUA_CONF_IPADDR2, ELUA_CONF_IPADDR3 );
uip_sethostaddr( ipaddr );
uip_ipaddr( ipaddr, ELUA_CONF_NETMASK0, ELUA_CONF_NETMASK1, ELUA_CONF_NETMASK2, ELUA_CONF_NETMASK3 );
uip_setnetmask( ipaddr );
uip_ipaddr( ipaddr, ELUA_CONF_DEFGW0, ELUA_CONF_DEFGW1, ELUA_CONF_DEFGW2, ELUA_CONF_DEFGW3 );
uip_setdraddr( ipaddr );
uip_ipaddr( ipaddr, ELUA_CONF_DNS0, ELUA_CONF_DNS1, ELUA_CONF_DNS2, ELUA_CONF_DNS3 );
resolv_conf( ipaddr );
elua_uip_configured = 1;
}
// Init application
void elua_uip_init( const struct uip_eth_addr *paddr )
{
// Set hardware address
uip_setethaddr( (*paddr) );
// Initialize the uIP TCP/IP stack.
uip_init();
uip_arp_init();
#ifdef BUILD_DHCPC
dhcpc_init( paddr->addr, sizeof( *paddr ) );
dhcpc_request();
#else
elua_uip_conf_static();
#endif
resolv_init();
#ifdef BUILD_CON_TCP
uip_listen( HTONS( ELUA_NET_TELNET_PORT ) );
#endif
}
// *****************************************************************************
// eLua UIP UDP application (used for the DHCP client and the DNS resolver)
void elua_uip_udp_appcall()
{
resolv_appcall();
dhcpc_appcall();
}
// *****************************************************************************
// eLua TCP/IP services (from elua_net.h)
#define ELUA_UIP_IS_SOCK_OK( sock ) ( elua_uip_configured && sock >= 0 && sock < UIP_CONNS )
static void elua_prep_socket_state( volatile struct elua_uip_state *pstate, void* buf, elua_net_size len, s16 readto, u8 res, u8 state )
{
pstate->ptr = ( char* )buf;
pstate->len = len;
pstate->res = res;
pstate->readto = readto;
pstate->state = state;
}
int elua_net_socket( int type )
{
int i;
struct uip_conn* pconn;
int old_status;
// [TODO] add UDP support at some point.
if( type == ELUA_NET_SOCK_DGRAM )
return -1;
old_status = platform_cpu_set_global_interrupts( PLATFORM_CPU_DISABLE );
// Iterate through the list of connections, looking for a free one
for( i = 0; i < UIP_CONNS; i ++ )
{
pconn = uip_conns + i;
if( pconn->tcpstateflags == UIP_CLOSED )
{
// Found a free connection, reserve it for later use
uip_conn_reserve( i );
break;
}
}
platform_cpu_set_global_interrupts( old_status );
return i == UIP_CONNS ? -1 : i;
}
// Send data
elua_net_size elua_net_send( int s, const void* buf, elua_net_size len )
{
volatile struct elua_uip_state *pstate = ( volatile struct elua_uip_state* )&( uip_conns[ s ].appstate );
if( !ELUA_UIP_IS_SOCK_OK( s ) || !uip_conn_active( s ) )
return -1;
if( len == 0 )
return 0;
elua_prep_socket_state( pstate, ( void* )buf, len, ELUA_NET_NO_LASTCHAR, ELUA_NET_ERR_OK, ELUA_UIP_STATE_SEND );
platform_eth_force_interrupt();
while( pstate->state != ELUA_UIP_STATE_IDLE );
return len - pstate->len;
}
// Internal "read" function
static elua_net_size elua_net_recv_internal( int s, void* buf, elua_net_size maxsize, s16 readto, unsigned timer_id, timer_data_type to_us, int with_buffer )
{
volatile struct elua_uip_state *pstate = ( volatile struct elua_uip_state* )&( uip_conns[ s ].appstate );
timer_data_type tmrstart = 0;
int old_status;
if( !ELUA_UIP_IS_SOCK_OK( s ) || !uip_conn_active( s ) )
return -1;
if( maxsize == 0 )
return 0;
elua_prep_socket_state( pstate, buf, maxsize, readto, with_buffer, ELUA_UIP_STATE_RECV );
if( to_us > 0 )
tmrstart = platform_timer_start( timer_id );
while( 1 )
{
if( pstate->state == ELUA_UIP_STATE_IDLE )
break;
if( to_us > 0 && platform_timer_get_diff_crt( timer_id, tmrstart ) >= to_us )
{
old_status = platform_cpu_set_global_interrupts( PLATFORM_CPU_DISABLE );
if( pstate->state != ELUA_UIP_STATE_IDLE )
{
pstate->res = ELUA_NET_ERR_TIMEDOUT;
pstate->state = ELUA_UIP_STATE_IDLE;
}
platform_cpu_set_global_interrupts( old_status );
break;
}
}
return maxsize - pstate->len;
}
// Receive data in buf, upto "maxsize" bytes, or upto the 'readto' character if it's not -1
elua_net_size elua_net_recv( int s, void* buf, elua_net_size maxsize, s16 readto, unsigned timer_id, timer_data_type to_us )
{
return elua_net_recv_internal( s, buf, maxsize, readto, timer_id, to_us, 0 );
}
// Same thing, but with a Lua buffer as argument
elua_net_size elua_net_recvbuf( int s, luaL_Buffer* buf, elua_net_size maxsize, s16 readto, unsigned timer_id, timer_data_type to_us )
{
return elua_net_recv_internal( s, buf, maxsize, readto, timer_id, to_us, 1 );
}
// Return the socket associated with the "telnet" application (or -1 if it does
// not exist). The socket only exists if a client connected to the board.
int elua_net_get_telnet_socket()
{
int res = -1;
#ifdef BUILD_CON_TCP
if( elua_uip_telnet_socket != -1 )
if( uip_conn_active( elua_uip_telnet_socket ) )
res = elua_uip_telnet_socket;
#endif
return res;
}
// Close socket
int elua_net_close( int s )
{
volatile struct elua_uip_state *pstate = ( volatile struct elua_uip_state* )&( uip_conns[ s ].appstate );
if( !ELUA_UIP_IS_SOCK_OK( s ) || !uip_conn_active( s ) )
return -1;
elua_prep_socket_state( pstate, NULL, 0, ELUA_NET_NO_LASTCHAR, ELUA_NET_ERR_OK, ELUA_UIP_STATE_CLOSE );
platform_eth_force_interrupt();
while( pstate->state != ELUA_UIP_STATE_IDLE );
return pstate->res == ELUA_NET_ERR_OK ? 0 : -1;
}
// Get last error on specific socket
int elua_net_get_last_err( int s )
{
volatile struct elua_uip_state *pstate = ( volatile struct elua_uip_state* )&( uip_conns[ s ].appstate );
if( !ELUA_UIP_IS_SOCK_OK( s ) )
return -1;
return pstate->res;
}
// Accept a connection on the given port, return its socket id (and the IP of the remote host by side effect)
int elua_accept( u16 port, unsigned timer_id, timer_data_type to_us, elua_net_ip* pfrom )
{
timer_data_type tmrstart = 0;
int old_status;
if( !elua_uip_configured )
return -1;
#ifdef BUILD_CON_TCP
if( port == ELUA_NET_TELNET_PORT )
return -1;
#endif
old_status = platform_cpu_set_global_interrupts( PLATFORM_CPU_DISABLE );
uip_unlisten( htons( port ) );
uip_listen( htons( port ) );
platform_cpu_set_global_interrupts( old_status );
elua_uip_accept_sock = -1;
elua_uip_accept_request = 1;
if( to_us > 0 )
tmrstart = platform_timer_start( timer_id );
while( 1 )
{
if( elua_uip_accept_request == 0 )
break;
if( to_us > 0 && platform_timer_get_diff_crt( timer_id, tmrstart ) >= to_us )
{
elua_uip_accept_request = 0;
break;
}
}
*pfrom = elua_uip_accept_remote;
return elua_uip_accept_sock;
}
// Connect to a specified machine
int elua_net_connect( int s, elua_net_ip addr, u16 port )
{
volatile struct elua_uip_state *pstate = ( volatile struct elua_uip_state* )&( uip_conns[ s ].appstate );
uip_ipaddr_t ipaddr;
if( !ELUA_UIP_IS_SOCK_OK( s ) )
return -1;
// The socket should have been reserved by a previous call to "elua_net_socket"
if( !uip_conn_is_reserved( s ) )
return -1;
// Initiate the connect call
uip_ipaddr( ipaddr, addr.ipbytes[ 0 ], addr.ipbytes[ 1 ], addr.ipbytes[ 2 ], addr.ipbytes[ 3 ] );
elua_prep_socket_state( pstate, NULL, 0, ELUA_NET_NO_LASTCHAR, ELUA_NET_ERR_OK, ELUA_UIP_STATE_CONNECT );
if( uip_connect_socket( s, &ipaddr, htons( port ) ) == NULL )
return -1;
// And wait for it to finish
while( pstate->state != ELUA_UIP_STATE_IDLE );
return pstate->res == ELUA_NET_ERR_OK ? 0 : -1;
}
// Hostname lookup (resolver)
elua_net_ip elua_net_lookup( const char* hostname )
{
elua_net_ip res;
res.ipaddr = 0;
#ifdef BUILD_DNS
u16_t *data;
if( ( data = resolv_lookup( ( char* )hostname ) ) != NULL )
{
// Name already saved locally
res.ipwords[ 0 ] = data[ 0 ];
res.ipwords[ 1 ] = data[ 1 ];
}
else
{
// Name not saved locally, must make request
elua_resolv_req_done = 0;
resolv_query( ( char* )hostname );
platform_eth_force_interrupt();
while( elua_resolv_req_done == 0 );
res = elua_resolv_ip;
}
#endif
return res;
}
#endif // #ifdef BUILD_UIP