304 lines
7.9 KiB
C
304 lines
7.9 KiB
C
|
|
/*
|
||
|
|
* FreeModbus Libary: lwIP Port
|
||
|
|
* Copyright (C) 2006 Christian Walter <wolti@sil.at>
|
||
|
|
*
|
||
|
|
* This library is free software; you can redistribute it and/or
|
||
|
|
* modify it under the terms of the GNU Lesser General Public
|
||
|
|
* License as published by the Free Software Foundation; either
|
||
|
|
* version 2.1 of the License, or (at your option) any later version.
|
||
|
|
*
|
||
|
|
* This library is distributed in the hope that it will be useful,
|
||
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
|
|
* Lesser General Public License for more details.
|
||
|
|
*
|
||
|
|
* You should have received a copy of the GNU Lesser General Public
|
||
|
|
* License along with this library; if not, write to the Free Software
|
||
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||
|
|
*
|
||
|
|
* File: $Id$
|
||
|
|
*/
|
||
|
|
|
||
|
|
/* ----------------------- System includes ----------------------------------*/
|
||
|
|
#include <stdio.h>
|
||
|
|
|
||
|
|
#include "port.h"
|
||
|
|
#include "string.h"
|
||
|
|
/* ----------------------- lwIP includes ------------------------------------*/
|
||
|
|
#include "lwip/api.h"
|
||
|
|
#include "lwip/tcp.h"
|
||
|
|
|
||
|
|
/* ----------------------- Modbus includes ----------------------------------*/
|
||
|
|
#include "mb.h"
|
||
|
|
#include "mbport.h"
|
||
|
|
|
||
|
|
/* ----------------------- MBAP Header --------------------------------------*/
|
||
|
|
#define MB_TCP_UID 6
|
||
|
|
#define MB_TCP_LEN 4
|
||
|
|
#define MB_TCP_FUNC 7
|
||
|
|
|
||
|
|
/* ----------------------- Defines -----------------------------------------*/
|
||
|
|
#define MB_TCP_DEFAULT_PORT 502 /* TCP listening port. */
|
||
|
|
#define MB_TCP_BUF_SIZE ( 256 + 7 ) /* Must hold a complete Modbus TCP frame. */
|
||
|
|
|
||
|
|
/* ----------------------- Prototypes ---------------------------------------*/
|
||
|
|
|
||
|
|
/* ----------------------- Static variables ---------------------------------*/
|
||
|
|
static struct tcp_pcb *pxPCBListen;
|
||
|
|
static struct tcp_pcb *pxPCBClient;
|
||
|
|
|
||
|
|
static UCHAR aucTCPBuf[MB_TCP_BUF_SIZE];
|
||
|
|
static USHORT usTCPBufPos;
|
||
|
|
|
||
|
|
/* ----------------------- Static functions ---------------------------------*/
|
||
|
|
static err_t prvxMBTCPPortAccept( void *pvArg, struct tcp_pcb *pxPCB, err_t xErr );
|
||
|
|
static err_t prvxMBTCPPortReceive( void *pvArg, struct tcp_pcb *pxPCB, struct pbuf *p,
|
||
|
|
err_t xErr );
|
||
|
|
static void prvvMBTCPPortError( void *pvArg, err_t xErr );
|
||
|
|
|
||
|
|
/* ----------------------- Begin implementation -----------------------------*/
|
||
|
|
BOOL
|
||
|
|
xMBTCPPortInit( USHORT usTCPPort )
|
||
|
|
{
|
||
|
|
struct tcp_pcb *pxPCBListenNew, *pxPCBListenOld;
|
||
|
|
BOOL bOkay = FALSE;
|
||
|
|
USHORT usPort;
|
||
|
|
|
||
|
|
if( usTCPPort == 0 )
|
||
|
|
{
|
||
|
|
usPort = MB_TCP_DEFAULT_PORT;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
usPort = ( USHORT ) usTCPPort;
|
||
|
|
}
|
||
|
|
|
||
|
|
if( ( pxPCBListenNew = pxPCBListenOld = tcp_new( ) ) == NULL )
|
||
|
|
{
|
||
|
|
/* Can't create TCP socket. */
|
||
|
|
bOkay = FALSE;
|
||
|
|
}
|
||
|
|
else if( tcp_bind( pxPCBListenNew, IP_ADDR_ANY, ( u16_t ) usPort ) != ERR_OK )
|
||
|
|
{
|
||
|
|
/* Bind failed - Maybe illegal port value or in use. */
|
||
|
|
( void )tcp_close( pxPCBListenOld );
|
||
|
|
bOkay = FALSE;
|
||
|
|
}
|
||
|
|
else if( ( pxPCBListenNew = tcp_listen( pxPCBListenNew ) ) == NULL )
|
||
|
|
{
|
||
|
|
( void )tcp_close( pxPCBListenOld );
|
||
|
|
bOkay = FALSE;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
/* Register callback function for new clients. */
|
||
|
|
tcp_accept( pxPCBListenNew, prvxMBTCPPortAccept );
|
||
|
|
|
||
|
|
/* Everything okay. Set global variable. */
|
||
|
|
pxPCBListen = pxPCBListenNew;
|
||
|
|
|
||
|
|
|
||
|
|
}
|
||
|
|
bOkay = TRUE;
|
||
|
|
return bOkay;
|
||
|
|
}
|
||
|
|
|
||
|
|
void
|
||
|
|
prvvMBPortReleaseClient( struct tcp_pcb *pxPCB )
|
||
|
|
{
|
||
|
|
if( pxPCB != NULL )
|
||
|
|
{
|
||
|
|
if( tcp_close( pxPCB ) != ERR_OK )
|
||
|
|
{
|
||
|
|
tcp_abort( pxPCB );
|
||
|
|
}
|
||
|
|
if( pxPCB == pxPCBClient )
|
||
|
|
{
|
||
|
|
|
||
|
|
pxPCBClient = NULL;
|
||
|
|
}
|
||
|
|
if( pxPCB == pxPCBListen )
|
||
|
|
{
|
||
|
|
pxPCBListen = NULL;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
void
|
||
|
|
vMBTCPPortClose( )
|
||
|
|
{
|
||
|
|
/* Shutdown any open client sockets. */
|
||
|
|
prvvMBPortReleaseClient( pxPCBClient );
|
||
|
|
|
||
|
|
/* Shutdown or listening socket. */
|
||
|
|
prvvMBPortReleaseClient( pxPCBListen );
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
void
|
||
|
|
vMBTCPPortDisable( void )
|
||
|
|
{
|
||
|
|
prvvMBPortReleaseClient( pxPCBClient );
|
||
|
|
}
|
||
|
|
|
||
|
|
err_t
|
||
|
|
prvxMBTCPPortAccept( void *pvArg, struct tcp_pcb *pxPCB, err_t xErr )
|
||
|
|
{
|
||
|
|
err_t error;
|
||
|
|
|
||
|
|
if( xErr != ERR_OK )
|
||
|
|
{
|
||
|
|
return xErr;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* We can handle only one client. */
|
||
|
|
if( pxPCBClient == NULL )
|
||
|
|
{
|
||
|
|
/* Register the client. */
|
||
|
|
pxPCBClient = pxPCB;
|
||
|
|
|
||
|
|
/* Set up the receive function prvxMBTCPPortReceive( ) to be called when data
|
||
|
|
* arrives.
|
||
|
|
*/
|
||
|
|
tcp_recv( pxPCB, prvxMBTCPPortReceive );
|
||
|
|
|
||
|
|
/* Register error handler. */
|
||
|
|
tcp_err( pxPCB, prvvMBTCPPortError );
|
||
|
|
|
||
|
|
/* Set callback argument later used in the error handler. */
|
||
|
|
tcp_arg( pxPCB, pxPCB );
|
||
|
|
|
||
|
|
/* Reset the buffers and state variables. */
|
||
|
|
usTCPBufPos = 0;
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
error = ERR_OK;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
prvvMBPortReleaseClient( pxPCBClient );
|
||
|
|
prvvMBPortReleaseClient( pxPCB );
|
||
|
|
error = ERR_OK;
|
||
|
|
}
|
||
|
|
return error;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Called in case of an unrecoverable error. In any case we drop the client
|
||
|
|
* connection. */
|
||
|
|
void
|
||
|
|
prvvMBTCPPortError( void *pvArg, err_t xErr )
|
||
|
|
{
|
||
|
|
struct tcp_pcb *pxPCB = pvArg;
|
||
|
|
|
||
|
|
if( pxPCB != NULL )
|
||
|
|
{
|
||
|
|
|
||
|
|
prvvMBPortReleaseClient( pxPCB );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
err_t
|
||
|
|
prvxMBTCPPortReceive( void *pvArg, struct tcp_pcb *pxPCB, struct pbuf *p, err_t xErr )
|
||
|
|
{
|
||
|
|
USHORT usLength;
|
||
|
|
|
||
|
|
err_t error;
|
||
|
|
|
||
|
|
if( xErr != ERR_OK )
|
||
|
|
{
|
||
|
|
return xErr;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* If pbuf is NULL then remote end has closed connection. */
|
||
|
|
if( p == NULL )
|
||
|
|
{
|
||
|
|
prvvMBPortReleaseClient( pxPCB );
|
||
|
|
return ERR_OK;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Acknowledge that we have received the data bytes. */
|
||
|
|
tcp_recved( pxPCB, p->len );
|
||
|
|
|
||
|
|
/* Check for internal buffer overflow. In case of an error drop the
|
||
|
|
* client. */
|
||
|
|
if( ( usTCPBufPos + p->len ) >= MB_TCP_BUF_SIZE )
|
||
|
|
{
|
||
|
|
prvvMBPortReleaseClient( pxPCB );
|
||
|
|
error = ERR_OK;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
memcpy( &aucTCPBuf[usTCPBufPos], p->payload, p->len );
|
||
|
|
usTCPBufPos += p->len;
|
||
|
|
|
||
|
|
/* If we have received the MBAP header we can analyze it and calculate
|
||
|
|
* the number of bytes left to complete the current request. If complete
|
||
|
|
* notify the protocol stack.
|
||
|
|
*/
|
||
|
|
if( usTCPBufPos >= MB_TCP_FUNC )
|
||
|
|
{
|
||
|
|
/* Length is a byte count of Modbus PDU (function code + data) and the
|
||
|
|
* unit identifier. */
|
||
|
|
usLength = aucTCPBuf[MB_TCP_LEN] << 8U;
|
||
|
|
usLength |= aucTCPBuf[MB_TCP_LEN + 1];
|
||
|
|
|
||
|
|
/* Is the frame already complete. */
|
||
|
|
if( usTCPBufPos < ( MB_TCP_UID + usLength ) )
|
||
|
|
{
|
||
|
|
}
|
||
|
|
else if( usTCPBufPos == ( MB_TCP_UID + usLength ) )
|
||
|
|
{
|
||
|
|
|
||
|
|
( void )xMBPortEventPost( EV_FRAME_RECEIVED );
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
|
||
|
|
/* This should not happen. We can't deal with such a client and
|
||
|
|
* drop the connection for security reasons.
|
||
|
|
*/
|
||
|
|
prvvMBPortReleaseClient( pxPCB );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
pbuf_free( p );
|
||
|
|
return error;
|
||
|
|
}
|
||
|
|
|
||
|
|
BOOL
|
||
|
|
xMBTCPPortGetRequest( UCHAR ** ppucMBTCPFrame, USHORT * usTCPLength )
|
||
|
|
{
|
||
|
|
*ppucMBTCPFrame = &aucTCPBuf[0];
|
||
|
|
*usTCPLength = usTCPBufPos;
|
||
|
|
|
||
|
|
/* Reset the buffer. */
|
||
|
|
usTCPBufPos = 0;
|
||
|
|
return TRUE;
|
||
|
|
}
|
||
|
|
|
||
|
|
BOOL
|
||
|
|
xMBTCPPortSendResponse( const UCHAR * pucMBTCPFrame, USHORT usTCPLength )
|
||
|
|
{
|
||
|
|
BOOL bFrameSent = FALSE;
|
||
|
|
|
||
|
|
if( pxPCBClient )
|
||
|
|
{
|
||
|
|
/* Make sure we can send the packet. */
|
||
|
|
assert( tcp_sndbuf( pxPCBClient ) >= usTCPLength );
|
||
|
|
|
||
|
|
if( tcp_write( pxPCBClient, pucMBTCPFrame, ( u16_t ) usTCPLength, TCP_WRITE_FLAG_COPY ) == ERR_OK )
|
||
|
|
{
|
||
|
|
|
||
|
|
/* Make sure data gets sent immediately. */
|
||
|
|
( void )tcp_output( pxPCBClient );
|
||
|
|
bFrameSent = TRUE;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
/* Drop the connection in case of an write error. */
|
||
|
|
prvvMBPortReleaseClient( pxPCBClient );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return bFrameSent;
|
||
|
|
}
|