adjust root folder

This commit is contained in:
Steve Karg
2019-10-08 23:47:53 -05:00
parent b6fc50ddea
commit a42e8f507c
1258 changed files with 26 additions and 214 deletions
+387
View File
@@ -0,0 +1,387 @@
/*####COPYRIGHTBEGIN####
-------------------------------------------
Copyright (C) 2005 Steve Karg
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to:
The Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA.
As a special exception, if other files instantiate templates or
use macros or inline functions from this file, or you compile
this file and link it with other works to produce a work based
on this file, this file does not by itself cause the resulting
work to be covered by the GNU General Public License. However
the source code for this file must still be made available in
accordance with section (3) of the GNU General Public License.
This exception does not invalidate any other reasons why a work
based on this file might be covered by the GNU General Public
License.
-------------------------------------------
####COPYRIGHTEND####*/
#include <stdint.h>
#include "bacdef.h"
#include "npdu.h"
#include "arcnet.h"
#include "net.h"
/** @file linux/arcnet.c Provides Linux-specific functions for Arcnet. */
/* my local device data - MAC address */
uint8_t ARCNET_MAC_Address = 0;
/* ARCNET file handle */
static int ARCNET_Sock_FD = -1;
/* ARCNET socket address (has the interface name) */
static struct sockaddr ARCNET_Socket_Address;
/* Broadcast address */
#define ARCNET_BROADCAST 0
/*
Hints:
When using a PCI20-485D ARCNET card from Contemporary Controls,
you might need to know about the following settings:
Assuming a 20MHz clock on the COM20020 chip:
clockp Clock Prescaler DataRate
------ --------------- --------
0 8 2.5 Mbps
1 16 1.25 Mbps
2 32 625 Kbps
3 64 312.5 Kbps
4 128 156.25Kbps
1. Install the arcnet driver and arcnet raw mode driver:
# modprobe com20020_pci clockp=4
# modprobe arc_rawmode
2. Use ifconfig to bring up the interface
# ifconfig arc0 up
3. The hardware address (MAC address) is set using the dipswitch
on the back of the card. 0 is broadcast, so don't use 0.
4. The backplane mode on the PCI20-485D card is done in hardware,
so the driver does not need to do backplane mode. If you
use another type of PCI20 card, you could pass in backplane=1 or
backplane=0 as an option to the modprobe of com20020_pci.
*/
bool arcnet_valid(
void)
{
return (ARCNET_Sock_FD >= 0);
}
void arcnet_cleanup(
void)
{
if (arcnet_valid())
close(ARCNET_Sock_FD);
ARCNET_Sock_FD = -1;
return;
}
static int arcnet_bind(
char *interface_name)
{
int sock_fd = -1; /* return value */
struct ifreq ifr;
int rv; /* return value - error value from df or ioctl call */
int uid = 0;
/* check to see if we are being run as root */
uid = getuid();
if (uid != 0) {
fprintf(stderr,
"arcnet: Unable to open an af_packet socket. "
"Try running with root priveleges.\n");
return sock_fd;
}
fprintf(stderr, "arcnet: opening \"%s\"\n", interface_name);
/* note: on some systems you may have to add or enable in */
/* modules.conf (or in modutils/alias on Debian with update-modules) */
/* alias net-pf-17 af_packet */
/* Then follow it by: # modprobe af_packet */
if ((sock_fd = socket(PF_PACKET, SOCK_PACKET, htons(ETH_P_ALL))) < 0) {
/* Error occured */
fprintf(stderr, "arcnet: Error opening socket: %s\n", strerror(errno));
fprintf(stderr,
"You might need to add the following to modules.conf\n"
"(or in /etc/modutils/alias on Debian with update-modules):\n"
"alias net-pf-17 af_packet\n"
"Also, add af_packet to /etc/modules.\n" "Then follow it by:\n"
"# modprobe af_packet\n");
exit(-1);
}
if (ARCNET_Sock_FD >= 0) {
/* Bind the socket to an interface name so we only get packets from it */
ARCNET_Socket_Address.sa_family = ARPHRD_ARCNET;
/*ARCNET_Socket_Address.sa_family = PF_INET; */
/* Clear the memory before copying */
memset(ARCNET_Socket_Address.sa_data, '\0',
sizeof(ARCNET_Socket_Address.sa_data));
/* Strcpy the interface name into the address */
strncpy(ARCNET_Socket_Address.sa_data, interface_name,
sizeof(ARCNET_Socket_Address.sa_data) - 1);
fprintf(stderr, "arcnet: binding \"%s\"\n",
ARCNET_Socket_Address.sa_data);
if (bind(sock_fd, &ARCNET_Socket_Address,
sizeof(ARCNET_Socket_Address)) != 0) {
/* Bind problem, close socket and return */
fprintf(stderr, "arcnet: Unable to bind socket : %s\n",
strerror(errno));
fprintf(stderr,
"You might need to add the following to modules.conf\n"
"(or in /etc/modutils/alias on Debian with update-modules):\n"
"alias net-pf-17 af_packet\n"
"Also, add af_packet to /etc/modules.\n" "Then follow it by:\n"
"# modprobe af_packet\n");
/* Close the socket */
close(sock_fd);
exit(-1);
}
}
strncpy(ifr.ifr_name, interface_name, sizeof(ifr.ifr_name));
rv = ioctl(sock_fd, SIOCGIFHWADDR, &ifr);
if (rv != -1) /* worked okay */
ARCNET_MAC_Address = ifr.ifr_hwaddr.sa_data[0];
/* copy this info into the local copy since bind wiped it out */
ARCNET_Socket_Address.sa_family = ARPHRD_ARCNET;
/*ARCNET_Socket_Address.sa_family = PF_INET; */
/* Clear the memory before copying */
memset(ARCNET_Socket_Address.sa_data, '\0',
sizeof(ARCNET_Socket_Address.sa_data));
/* Strcpy the interface name into the address */
strncpy(ARCNET_Socket_Address.sa_data, interface_name,
sizeof(ARCNET_Socket_Address.sa_data) - 1);
fprintf(stderr, "arcnet: MAC=%02Xh iface=\"%s\"\n", ARCNET_MAC_Address,
ARCNET_Socket_Address.sa_data);
atexit(arcnet_cleanup);
return sock_fd;
}
bool arcnet_init(
char *interface_name)
{
if (interface_name)
ARCNET_Sock_FD = arcnet_bind(interface_name);
else
ARCNET_Sock_FD = arcnet_bind("arc0");
return arcnet_valid();
}
/* function to send a PDU out the socket */
/* returns number of bytes sent on success, negative on failure */
int arcnet_send_pdu(
BACNET_ADDRESS * dest, /* destination address */
BACNET_NPDU_DATA * npdu_data, /* network information */
uint8_t * pdu, /* any data to be sent - may be null */
unsigned pdu_len)
{ /* number of bytes of data */
BACNET_ADDRESS src = { 0 }; /* source address */
int bytes = 0;
uint8_t mtu[512] = { 0 };
int mtu_len = 0;
struct archdr *pkt = (struct archdr *) mtu;
(void) npdu_data;
src.mac[0] = ARCNET_MAC_Address;
src.mac_len = 1;
/* don't waste time if the socket is not valid */
if (ARCNET_Sock_FD < 0) {
fprintf(stderr, "arcnet: socket is invalid!\n");
return -1;
}
/* load destination MAC address */
if (dest->mac_len == 1)
pkt->hard.dest = dest->mac[0];
else {
fprintf(stderr, "arcnet: invalid destination MAC address!\n");
return -2;
}
if (src.mac_len == 1)
pkt->hard.source = src.mac[0];
else {
fprintf(stderr, "arcnet: invalid source MAC address!\n");
return -3;
}
/* Logical PDU portion */
pkt->soft.raw[0] = 0xCD; /* SC for BACnet */
pkt->soft.raw[1] = 0x82; /* DSAP for BACnet */
pkt->soft.raw[2] = 0x82; /* SSAP for BACnet */
pkt->soft.raw[3] = 0x03; /* LLC Control byte in header */
/* packet length */
mtu_len = ARC_HDR_SIZE + 4 /*SC,DSAP,SSAP,LLC */ + pdu_len;
if (mtu_len > 512) {
fprintf(stderr, "arcnet: PDU is too big to send!\n");
return -4;
}
memcpy(&pkt->soft.raw[4], pdu, pdu_len);
/* Send the packet */
bytes =
sendto(ARCNET_Sock_FD, &mtu, mtu_len, 0,
(struct sockaddr *) &ARCNET_Socket_Address,
sizeof(ARCNET_Socket_Address));
/* did it get sent? */
if (bytes < 0)
fprintf(stderr, "arcnet: Error sending packet: %s\n", strerror(errno));
return bytes;
}
/* receives an framed packet */
/* returns the number of octets in the PDU, or zero on failure */
uint16_t arcnet_receive(
BACNET_ADDRESS * src, /* source address */
uint8_t * pdu, /* PDU data */
uint16_t max_pdu, /* amount of space available in the PDU */
unsigned timeout)
{ /* milliseconds to wait for a packet */
int received_bytes;
uint8_t buf[512] = { 0 }; /* data */
uint16_t pdu_len = 0; /* return value */
fd_set read_fds;
int max;
struct timeval select_timeout;
struct archdr *pkt = (struct archdr *) buf;
/* Make sure the socket is open */
if (ARCNET_Sock_FD <= 0)
return 0;
/* we could just use a non-blocking socket, but that consumes all
the CPU time. We can use a timeout; it is only supported as
a select. */
if (timeout >= 1000) {
select_timeout.tv_sec = timeout / 1000;
select_timeout.tv_usec =
1000 * (timeout - select_timeout.tv_sec * 1000);
} else {
select_timeout.tv_sec = 0;
select_timeout.tv_usec = 1000 * timeout;
}
FD_ZERO(&read_fds);
FD_SET(ARCNET_Sock_FD, &read_fds);
max = ARCNET_Sock_FD;
if (select(max + 1, &read_fds, NULL, NULL, &select_timeout) > 0)
received_bytes = read(ARCNET_Sock_FD, &buf[0], sizeof(buf));
else
return 0;
/* See if there is a problem */
if (received_bytes < 0) {
/* EAGAIN Non-blocking I/O has been selected */
/* using O_NONBLOCK and no data */
/* was immediately available for reading. */
if (errno != EAGAIN)
fprintf(stderr, "ethernet: Read error in receiving packet: %s\n",
strerror(errno));
return 0;
}
if (received_bytes == 0)
return 0;
/* printf("arcnet: received %u bytes (offset=%02Xh %02Xh) "
"from %02Xh (proto==%02Xh)\n",
received_bytes, pkt->offset[0], pkt->offset[1],
pkt->hard.source, pkt->soft.raw[0]);
*/
if (pkt->hard.source == ARCNET_MAC_Address) {
fprintf(stderr, "arcnet: self sent packet?\n");
return 0;
}
if (pkt->soft.raw[0] != 0xCD) {
/* fprintf(stderr,"arcnet: Non-BACnet packet.\n"); */
return 0;
}
if ((pkt->hard.dest != ARCNET_MAC_Address) &&
(pkt->hard.dest != ARCNET_BROADCAST)) {
fprintf(stderr, "arcnet: This packet is not for us.\n");
return 0;
}
if ((pkt->soft.raw[1] != 0x82) || /* DSAP */
(pkt->soft.raw[2] != 0x82) || /* LSAP */
(pkt->soft.raw[3] != 0x03)) { /* LLC Control */
fprintf(stderr, "arcnet: BACnet packet has invalid LLC.\n");
return 0;
}
/* It must be addressed to us or be a Broadcast */
if ((pkt->hard.dest != ARCNET_MAC_Address) &&
(pkt->hard.dest != ARCNET_BROADCAST)) {
fprintf(stderr, "arcnet: This packet is not for us.\n");
return 0;
}
/* copy the source address */
src->mac_len = 1;
src->mac[0] = pkt->hard.source;
/* compute the PDU length */
pdu_len = received_bytes - ARC_HDR_SIZE;
pdu_len -= 4 /* SC, DSAP, SSAP, LLC Control */ ;
/* copy the buffer into the PDU */
if (pdu_len < max_pdu)
memmove(&pdu[0], &pkt->soft.raw[4], pdu_len);
/* silently ignore packets that are too large */
else
pdu_len = 0;
return pdu_len;
}
void arcnet_get_my_address(
BACNET_ADDRESS * my_address)
{
int i = 0;
my_address->mac_len = 1;
my_address->mac[0] = ARCNET_MAC_Address;
my_address->net = 0; /* DNET=0 is local only, no routing */
my_address->len = 0;
for (i = 0; i < MAX_MAC_LEN; i++) {
my_address->adr[i] = 0;
}
return;
}
void arcnet_get_broadcast_address(
BACNET_ADDRESS * dest)
{ /* destination address */
int i = 0; /* counter */
if (dest) {
dest->mac[0] = ARCNET_BROADCAST;
dest->mac_len = 1;
dest->net = BACNET_BROADCAST_NETWORK;
dest->len = 0; /* always zero when DNET is broadcast */
for (i = 0; i < MAX_MAC_LEN; i++) {
dest->adr[i] = 0;
}
}
return;
}
+281
View File
@@ -0,0 +1,281 @@
-- bacnet_BIPV6.lua
--
-- Dissector for BACnet/IPv6 [135-2016 Annex X]
--
-- Copyright 2011, Siemens Building Technolgies
-- Maintainer Philippe Goetz <philippe.goetz@siemens.com>
-- Updated by Steve Karg <skarg@users.sourceforge.net>
--
-- This program is free software; you can redistribute it and/or
-- modify it under the terms of the GNU General Public License
-- as published by the Free Software Foundation; either version 2
-- of the License, or (at your option) any later version.
--
-- This program 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 General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program; if not, write to the Free Software
-- Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
-- Add the following to the end of init.lua in the Wireshark install directory:
-- dofile("bacnet_ipv6.lua")
-- [in] tree,pf,buffer,offset,len
-- [out] offset, value, ti, bf
function uint_dissector(tree,pf,buffer,offset,len)
if (offset + len) <= buffer:len() then
local bf = buffer(offset,len)
local ti = nil
if tree ~= nil then
ti = tree:add(pf,bf)
end
return offset + len, bf:uint(), ti, bf
end
local ti = tree:add(pf,buffer(offset))
ti:add_expert_info(PI_MALFORMED, PI_ERROR, "Data shortage")
error("Data shortage")
end
function bytes_dissector(tree,pf,buffer,offset,len)
if (offset + len) <= buffer:len() then
local bf = buffer(offset,len)
local ti = nil
if tree ~= nil then
ti = tree:add(pf,bf)
end
return offset + len, bf:bytes(), ti, bf
end
local ti = tree:add(pf,buffer(offset))
ti:add_expert_info(PI_MALFORMED, PI_ERROR, "Data shortage")
error("Data shortage")
end
p_BACnetBIPV6 = Proto("lua_BACnetBIPV6", "B/IPv6 BACnet Virtual Link Control");
p_BACnet_Port = Proto("BACnet-Port", "BACnet UDP Port Handoff")
p_dissector_bacnet = Dissector.get("bacnet")
p_dissector_bipv4 = Dissector.get("bvlc")
do
p_BACnetBIPV6.init = function()
debug("p_BACnetBIPV6.init")
end
local t_BACnetBIPV6_Types = {
[0x82]="B/IPv6 (Annex X)"
}
local t_BACnetBIPV6_Functions = {
-- Annex X, PPR3 Draft 22
[0x00]="BVLC-Result",
[0x01]="Original-Unicast-NPDU",
[0x02]="Original-Broadcast-NPDU",
[0x03]="Address-Resolution",
[0x04]="Forwarded-Address-Resolution",
[0x05]="Address-Resolution-Ack",
[0x06]="Virtual-Address-Resolution",
[0x07]="Virtual-Address-Resolution-Ack",
[0x08]="Forwarded-NPDU",
[0x09]="Register-Foreign-Device",
[0x0A]="Delete-Foreign-Device",
[0x0B]="Secure-BVLL",
[0x0C]="Distribute-Broadcast-To-Network"
}
local pf_BIPV6Data = ProtoField.bytes ("BIPV6.data", "B/IPV6 Data")
p_BACnetBIPV6.fields = {
pf_BIPV6Data
}
local t_BACnetBIPV6_dissectors = {}
-- local dt_BIPV6_npdu = DissectorTable.new("lua_BACnetBIPV6.npdu","BIPV6 NPDU",ftypes.UINT8,base.HEX)
-- BIPV6 header fields
local pf_BIPV6 = {}
pf_BIPV6.Type = ProtoField.uint8 ("BIPV6.type","Type",base.HEX,t_BACnetBIPV6_Types)
table.insert(p_BACnetBIPV6.fields,pf_BIPV6.Type)
pf_BIPV6.Function = ProtoField.uint8 ("BIPV6.function","Function",base.HEX,t_BACnetBIPV6_Functions,0,"BIPV6 Function")
table.insert(p_BACnetBIPV6.fields,pf_BIPV6.Function)
pf_BIPV6.Length = ProtoField.uint16("BIPV6.length","BIPV6-Length",base.DEC,nil,0,"Length of BIPV6")
table.insert(p_BACnetBIPV6.fields,pf_BIPV6.Length)
pf_BIPV6.ResultCode = ProtoField.uint16("BIPV6.resultCode","Result-Code",base.DEC,nil,0,"Result Code")
table.insert(p_BACnetBIPV6.fields,pf_BIPV6.ResultCode)
pf_BIPV6.SourceVirtualAddress = ProtoField.uint24 ("BIPV6.sourceVirtualAddress","Source-Virtual-Address")
table.insert(p_BACnetBIPV6.fields,pf_BIPV6.SourceVirtualAddress)
pf_BIPV6.DestinationVirtualAddress = ProtoField.uint24 ("BIPV6.destinationVirtualAddress","Destination-Virtual-Address")
table.insert(p_BACnetBIPV6.fields,pf_BIPV6.DestinationVirtualAddress)
pf_BIPV6.OriginalSourceEffectiveAddress = ProtoField.bytes ("BIPV6.originalSourceEffectiveAddress","Original-Source-B/IPv6-Address")
table.insert(p_BACnetBIPV6.fields,pf_BIPV6.OriginalSourceEffectiveAddress)
pf_BIPV6.OriginalSourceEffectiveAddress_ip = ProtoField.ipv6 ("BIPV6.originalSourceEffectiveAddress.ip","Original-Source-B/IPv6-Address (ip)")
table.insert(p_BACnetBIPV6.fields,pf_BIPV6.OriginalSourceEffectiveAddress_ip)
pf_BIPV6.OriginalSourceEffectiveAddress_port = ProtoField.uint16 ("BIPV6.originalSourceEffectiveAddress.port","Original-Source-B/IPv6-Address (port)")
table.insert(p_BACnetBIPV6.fields,pf_BIPV6.OriginalSourceEffectiveAddress_port)
pf_BIPV6.OriginalSourceVirtualAddress = ProtoField.uint24 ("BIPV6.originalSourceVirtualAddress","Original-Source-Virtual-Address")
table.insert(p_BACnetBIPV6.fields,pf_BIPV6.OriginalSourceVirtualAddress)
pf_BIPV6.DestinationEffectiveAddress = ProtoField.bytes ("BIPV6.destinationEffectiveAddress","Original-Source-B/IPv6-Address")
table.insert(p_BACnetBIPV6.fields,pf_BIPV6.DestinationEffectiveAddress)
pf_BIPV6.DestinationEffectiveAddress_ip = ProtoField.ipv6 ("BIPV6.destinationEffectiveAddress.ip","Original-Source-B/IPv6-Address (ip)")
table.insert(p_BACnetBIPV6.fields,pf_BIPV6.DestinationEffectiveAddress_ip)
pf_BIPV6.DestinationEffectiveAddress_port = ProtoField.uint16 ("BIPV6.destinationEffectiveAddress.port","Original-Source-B/IPv6-Address (port)")
table.insert(p_BACnetBIPV6.fields,pf_BIPV6.DestinationEffectiveAddress_port)
pf_BIPV6.TimeToLive = ProtoField.uint16 ("BIPV6.timeToLive","Time-To-Live")
table.insert(p_BACnetBIPV6.fields,pf_BIPV6.TimeToLive)
function p_BACnetBIPV6.dissector(buffer,pkt,tree)
pkt.cols["protocol"] = "B/IPv6"
pkt.cols["info"] = "BACnet Building Automation and Control Network"
local offset = 0
local v_BIPV6 = {}
local ti_BIPV6 = {}
ti_BIPV6.ti = tree:add(p_BACnetBIPV6,buffer(0))
offset, v_BIPV6.Type, ti_BIPV6.Type = uint_dissector(ti_BIPV6.ti, pf_BIPV6.Type, buffer, offset, 1)
--if v_BIPV6.Type == 0x81 then
-- p_dissector_bipv4:call(buffer, pkt, tree)
-- elseif v_BIPV6 ~= 0x82 then
-- ti_BIPV6.Type:add_expert_info(PI_MALFORMED, PI_WARN, "Unknown BVLC Type")
-- return
--end
if v_BIPV6.Type ~= 0x82 then
ti_BIPV6.Type:add_expert_info(PI_MALFORMED, PI_WARN, "Unknown BVLC Type")
return
end
offset, v_BIPV6.Function, ti_BIPV6.Function = uint_dissector(ti_BIPV6.ti, pf_BIPV6.Function, buffer, offset, 1)
offset, v_BIPV6.Length, ti_BIPV6.Length = uint_dissector(ti_BIPV6.ti, pf_BIPV6.Length, buffer, offset, 2)
local BIPV6Dissector = t_BACnetBIPV6_dissectors[v_BIPV6.Function]
local npduData = nil
if BIPV6Dissector ~= nil then
pkt.cols["info"] = t_BACnetBIPV6_Functions[v_BIPV6.Function]
offset, npduData = BIPV6Dissector(buffer,pkt,ti_BIPV6.ti,offset,v_BIPV6,tree)
end
if npduData ~= nil then
if p_BACnetNPDU ~= nil then
ti_BIPV6.ti:set_len(offset)
p_BACnetNPDU.dissector:call(npduData:tvb(), pkt, tree)
offset = offset + npduData:len()
else
p_dissector_bacnet:call(npduData:tvb(), pkt, tree)
end
end
-- CLB don't show the raw data
-- if offset ~= buffer:len() then
-- ti_BIPV6.ti:add(pf_BIPV6Data,buffer(offset))
-- end
ti_BIPV6 = nil
collectgarbage("collect")
end
-- [0x00] BVLC-Result
t_BACnetBIPV6_dissectors[0x00] = function(buffer,pkt,tree,offset,npdu)
offset = bytes_dissector(tree,pf_BIPV6.SourceVirtualAddress,buffer,offset,3)
offset = bytes_dissector(tree,pf_BIPV6.ResultCode,buffer,offset,2)
return offset
end
-- [0x01] Original-Unicast-NPDU
t_BACnetBIPV6_dissectors[0x01] = function(buffer,pkt,tree,offset,npdu)
offset = bytes_dissector(tree,pf_BIPV6.SourceVirtualAddress,buffer,offset,3)
offset = bytes_dissector(tree,pf_BIPV6.DestinationVirtualAddress,buffer,offset,3)
return offset, buffer(offset)
end
-- [0x02] Original-Broadcast-NPDU
t_BACnetBIPV6_dissectors[0x02] = function(buffer,pkt,tree,offset,npdu)
offset = bytes_dissector(tree,pf_BIPV6.SourceVirtualAddress,buffer,offset,3)
return offset, buffer(offset)
end
-- [0x03] Address-Resolution
t_BACnetBIPV6_dissectors[0x03] = function(buffer,pkt,tree,offset,npdu)
offset = bytes_dissector(tree,pf_BIPV6.SourceVirtualAddress,buffer,offset,3)
offset = bytes_dissector(tree,pf_BIPV6.DestinationVirtualAddress,buffer,offset,3)
return offset
end
-- [0x04] Forwarded-Address-Resolution
t_BACnetBIPV6_dissectors[0x04] = function(buffer,pkt,tree,offset,npdu)
offset = bytes_dissector(tree,pf_BIPV6.OriginalSourceVirtualAddress,buffer,offset,3)
offset = bytes_dissector(tree,pf_BIPV6.DestinationVirtualAddress,buffer,offset,3)
local subtree = tree:add(pf_BIPV6.OriginalSourceEffectiveAddress,buffer(offset,18))
offset = bytes_dissector(subtree,pf_BIPV6.OriginalSourceEffectiveAddress_ip,buffer,offset,16)
offset = uint_dissector(subtree,pf_BIPV6.OriginalSourceEffectiveAddress_port,buffer,offset,2)
return offset
end
-- [0x05] Address-Resolution-Ack
t_BACnetBIPV6_dissectors[0x05] = function(buffer,pkt,tree,offset,npdu)
offset = bytes_dissector(tree,pf_BIPV6.SourceVirtualAddress,buffer,offset,3)
offset = bytes_dissector(tree,pf_BIPV6.DestinationVirtualAddress,buffer,offset,3)
return offset
end
-- [0x06] Virtual-Address-Resolution
t_BACnetBIPV6_dissectors[0x06] = function(buffer,pkt,tree,offset,npdu)
offset = bytes_dissector(tree,pf_BIPV6.SourceVirtualAddress,buffer,offset,3)
return offset
end
-- [0x07] Virtual-Address-Resolution-Ack
t_BACnetBIPV6_dissectors[0x07] = function(buffer,pkt,tree,offset,npdu)
offset = bytes_dissector(tree,pf_BIPV6.SourceVirtualAddress,buffer,offset,3)
return offset
end
-- [0x08] Forwarded-NPDU
t_BACnetBIPV6_dissectors[0x08] = function(buffer,pkt,tree,offset,npdu)
offset = bytes_dissector(tree,pf_BIPV6.OriginalSourceVirtualAddress,buffer,offset,3)
local subtree = tree:add(pf_BIPV6.OriginalSourceEffectiveAddress,buffer(offset,18))
offset = bytes_dissector(subtree,pf_BIPV6.OriginalSourceEffectiveAddress_ip,buffer,offset,16)
offset = uint_dissector(subtree,pf_BIPV6.OriginalSourceEffectiveAddress_port,buffer,offset,2)
return offset, buffer(offset)
end
-- [0x09] Register-Foreign-Device
t_BACnetBIPV6_dissectors[0x09] = function(buffer,pkt,tree,offset,npdu)
offset = bytes_dissector(tree,pf_BIPV6.SourceVirtualAddress,buffer,offset,3)
offset = uint_dissector(tree,pf_BIPV6.TimeToLive,buffer,offset,2)
return offset
end
-- [0x0A] Delete-Foreign-Device
t_BACnetBIPV6_dissectors[0x0A] = function(buffer,pkt,tree,offset,npdu)
offset = bytes_dissector(tree,pf_BIPV6.SourceVirtualAddress,buffer,offset,3)
-- FDT Entry decoding
return offset
end
-- [0x0B] Secure-BVLL
t_BACnetBIPV6_dissectors[0x0B] = function(buffer,pkt,tree,offset,npdu)
return offset
end
-- [0x0C] Distribute-Broadcast-To-Network
t_BACnetBIPV6_dissectors[0x0C] = function(buffer,pkt,tree,offset,npdu)
offset = bytes_dissector(tree,pf_BIPV6.OriginalSourceVirtualAddress,buffer,offset,3)
local subtree = tree:add(pf_BIPV6.OriginalSourceEffectiveAddress,buffer(offset,18))
offset = bytes_dissector(subtree,pf_BIPV6.OriginalSourceEffectiveAddress_ip,buffer,offset,16)
offset = uint_dissector(subtree,pf_BIPV6.OriginalSourceEffectiveAddress_port,buffer,offset,2)
return offset, buffer(offset)
end
function p_BACnet_Port.dissector(tvb, pinfo, tree)
local BIP_Type = tvb(0,1):uint()
if BIP_Type == 0x81 then
p_dissector_bipv4:call(tvb, pinfo, tree)
elseif BIP_Type == 0x82 then
p_BACnetBIPV6.dissector(tvb, pinfo, tree)
end
end
-- load the udp.port table
local dt_udp_port = DissectorTable.get("udp.port")
-- p_dissector_bvlc = dt_udp_port:get_dissector(47808)
-- dt_udp_port:remove(47808, p_dissector_bvlc)
-- dt_udp_port:add(47808, p_BACnetBIPV6)
dt_udp_port:add(47808, p_BACnet_Port)
end
+303
View File
@@ -0,0 +1,303 @@
/**************************************************************************
*
* Copyright (C) 2015 Nikola Jelic <nikola.jelic@euroicc.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*********************************************************************/
#include "bacsec.h"
#include <string.h>
#include <time.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#define TMP_BUF_LEN 2048
/* structures for keys - we use existing structures */
BACNET_KEY_ENTRY master_key;
BACNET_UPDATE_DISTRIBUTION_KEY distribution_key;
BACNET_UPDATE_KEY_SET key_sets;
static bool rand_set = false;
static HMAC_CTX hmac_ctx;
static EVP_CIPHER_CTX evp_ctx;
static uint8_t tmp_buf[TMP_BUF_LEN];
static uint16_t next_mult_of_16(uint16_t arg)
{
if ((arg & 0xF) == 0)
return arg;
else
return ((arg >> 4) + 1) << 4;
}
/* signing/verification and encryption/decryption - port specific */
int key_sign_msg(BACNET_KEY_ENTRY * key,
uint8_t * msg,
uint32_t msg_len,
uint8_t * signature)
{
uint8_t full_signature[32]; /* longest case */
HMAC_CTX_init(&hmac_ctx);
switch (key_algorithm(key->key_identifier)) {
case KIA_AES_MD5:
HMAC_Init_ex(&hmac_ctx, &key->key[16], 16, EVP_md5(), NULL);
break;
case KIA_AES_SHA256:
HMAC_Init_ex(&hmac_ctx, &key->key[16], 32, EVP_sha256(), NULL);
break;
default:
return -1;
}
HMAC_Update(&hmac_ctx, msg, msg_len);
HMAC_Final(&hmac_ctx, full_signature, NULL); /* we ignore the signature size */
HMAC_CTX_cleanup(&hmac_ctx);
memcpy(signature, full_signature, SIGNATURE_LEN);
return 0;
}
bool key_verify_sign_msg(BACNET_KEY_ENTRY * key,
uint8_t * msg,
uint32_t msg_len,
uint8_t * signature)
{
uint8_t full_signature[32]; /* longest case */
HMAC_CTX_init(&hmac_ctx);
switch (key_algorithm(key->key_identifier)) {
case KIA_AES_MD5:
HMAC_Init_ex(&hmac_ctx, &key->key[16], 16, EVP_md5(), NULL);
break;
case KIA_AES_SHA256:
HMAC_Init_ex(&hmac_ctx, &key->key[16], 32, EVP_sha256(), NULL);
break;
default:
return false;
}
HMAC_Update(&hmac_ctx, msg, msg_len);
HMAC_Final(&hmac_ctx, full_signature, NULL); /* we ignore the signature size */
HMAC_CTX_cleanup(&hmac_ctx);
return (memcmp(signature, full_signature,
SIGNATURE_LEN) == 0 ? true : false);
}
int key_encrypt_msg(BACNET_KEY_ENTRY * key,
uint8_t * msg,
uint32_t msg_len,
uint8_t * signature)
{
int outlen, outlen2;
switch (key_algorithm(key->key_identifier)) {
case KIA_AES_MD5:
case KIA_AES_SHA256:
EVP_EncryptInit_ex(&evp_ctx, EVP_aes_128_cbc(), NULL, key->key,
signature);
break;
default:
return -1;
}
EVP_EncryptUpdate(&evp_ctx, tmp_buf, &outlen, msg, msg_len);
EVP_EncryptFinal(&evp_ctx, &msg[outlen], &outlen2);
EVP_CIPHER_CTX_cleanup(&evp_ctx);
if (outlen2 != 0)
return -1;
memcpy(msg, tmp_buf, msg_len);
return 0;
}
bool key_decrypt_msg(BACNET_KEY_ENTRY * key,
uint8_t * msg,
uint32_t msg_len,
uint8_t * signature)
{
int outlen, outlen2;
switch (key_algorithm(key->key_identifier)) {
case KIA_AES_MD5:
case KIA_AES_SHA256:
EVP_DecryptInit_ex(&evp_ctx, EVP_aes_128_cbc(), NULL, key->key,
signature);
break;
default:
return false;
}
if (EVP_DecryptUpdate(&evp_ctx, tmp_buf, &outlen, msg, msg_len) == 0)
return false;
if (EVP_DecryptFinal(&evp_ctx, &msg[outlen], &outlen2) == 0)
return false;
EVP_CIPHER_CTX_cleanup(&evp_ctx);
if (outlen2 != 0)
return false;
memcpy(msg, tmp_buf, msg_len);
return true;
}
void key_set_padding(BACNET_KEY_ENTRY * key,
int enc_len,
uint16_t * padding_len,
uint8_t * padding)
{
/* in the future, we should check for the block size, but for now it is always 16 */
int i;
uint16_t padlen = next_mult_of_16(enc_len + 2);
(void) key;
(void) padding_len;
if (!rand_set) {
srand(time(NULL));
rand_set = true;
}
if (padlen > 2)
for (i = 0; i < padlen - 2; i++)
padding[i] = rand();
}
BACNET_SECURITY_RESPONSE_CODE bacnet_master_key_set(BACNET_SET_MASTER_KEY *
key)
{
memcpy(&master_key, &key->key, sizeof(BACNET_KEY_ENTRY));
return SEC_RESP_SUCCESS;
}
BACNET_SECURITY_RESPONSE_CODE
bacnet_distribution_key_update(BACNET_UPDATE_DISTRIBUTION_KEY * key)
{
memcpy(&distribution_key, key, sizeof(BACNET_KEY_ENTRY));
return SEC_RESP_SUCCESS;
}
BACNET_SECURITY_RESPONSE_CODE bacnet_key_set_update(BACNET_UPDATE_KEY_SET *
update_key_sets)
{
int i, j, k, l;
bool found;
for (i = 0; i < 2; i++) {
if (update_key_sets->set_rae[i]) {
found = false;
/* try with a valid set */
for (j = 0; j < 2; j++)
if ((key_sets.set_key_revision[j] ==
update_key_sets->set_key_revision[i]) &&
key_sets.set_rae[j]) {
found = true;
break;
}
/* try with an empty set */
if (!found) {
for (j = 0; j < 2; j++)
if (!key_sets.set_rae[j]) {
found = true;
break;
}
}
/* failure */
if (!found)
return -SEC_RESP_UNKNOWN_KEY_REVISION;
/* just in case we're writing over an empty set */
key_sets.set_key_revision[j] =
update_key_sets->set_key_revision[i];
/* update revision activation and expiration time */
key_sets.set_activation_time[j] =
update_key_sets->set_activation_time[i];
key_sets.set_expiration_time[j] =
update_key_sets->set_expiration_time[i];
/* should we clear the key set? */
if (update_key_sets->set_clr[i]) {
key_sets.set_key_count[j] = 0;
}
for (k = 0; k < update_key_sets->set_key_count[i]; k++) {
found = false;
for (l = 0; l < key_sets.set_key_count[j]; l++)
if (update_key_sets->set_keys[i][k].key_identifier ==
key_sets.set_keys[j][l].key_identifier) {
found = true;
break;
}
if (!found) {
if (!update_key_sets->remove) { /* add key */
/* check for available space */
if (key_sets.set_key_count[j] == MAX_UPDATE_KEY_COUNT)
return -SEC_RESP_TOO_MANY_KEYS;
memcpy(&key_sets.set_keys[j][key_sets.
set_key_count[j]],
&update_key_sets->set_keys[i][k],
sizeof(BACNET_KEY_ENTRY));
key_sets.set_key_count[j]++;
} /* else do nothing, successfuly */
} else {
if (!update_key_sets->remove) { /* update key */
memcpy(&key_sets.set_keys[j][l],
&update_key_sets->set_keys[i][k],
sizeof(BACNET_KEY_ENTRY));
} else { /* remove key */
memmove(&key_sets.set_keys[j][l],
&key_sets.set_keys[j][l + 1],
sizeof(BACNET_KEY_ENTRY) *
(key_sets.set_key_count[j] - l));
key_sets.set_key_count[j]--;
}
}
}
}
}
return SEC_RESP_SUCCESS;
}
BACNET_SECURITY_RESPONSE_CODE bacnet_find_key(uint8_t revision,
BACNET_KEY_ENTRY * key)
{
int i, j;
unsigned int current_time = time(NULL);
switch (key_number(key->key_identifier)) {
case KIKN_DEVICE_MASTER:
if (revision != 0)
return -SEC_RESP_UNKNOWN_KEY_REVISION;
else
memcpy(key, &master_key, sizeof(BACNET_KEY_ENTRY));
break;
case KIKN_DISTRIBUTION:
if (revision != distribution_key.key_revision)
return -SEC_RESP_UNKNOWN_KEY_REVISION;
else
memcpy(key, &distribution_key.key, sizeof(BACNET_KEY_ENTRY));
break;
default: /* all other keys must be in a key set */
for (i = 0; i < 2; i++) {
if ((revision == key_sets.set_key_revision[i]) &&
(key_sets.set_activation_time[i] <= current_time) &&
(current_time <= key_sets.set_expiration_time[i])) {
for (j = 0; j < key_sets.set_key_count[i]; j++)
if (key->key_identifier ==
key_sets.set_keys[i][j].key_identifier) {
memcpy(key,
&key_sets.set_keys[i][j].key_identifier,
sizeof(BACNET_KEY_ENTRY));
return SEC_RESP_SUCCESS;
}
}
}
return -SEC_RESP_UNKNOWN_KEY_REVISION;
}
return SEC_RESP_SUCCESS;
}
+248
View File
@@ -0,0 +1,248 @@
/*####COPYRIGHTBEGIN####
-------------------------------------------
Copyright (C) 2005 Steve Karg
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to:
The Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA.
As a special exception, if other files instantiate templates or
use macros or inline functions from this file, or you compile
this file and link it with other works to produce a work based
on this file, this file does not by itself cause the resulting
work to be covered by the GNU General Public License. However
the source code for this file must still be made available in
accordance with section (3) of the GNU General Public License.
This exception does not invalidate any other reasons why a work
based on this file might be covered by the GNU General Public
License.
-------------------------------------------
####COPYRIGHTEND####*/
#include <stdint.h> /* for standard integer types uint8_t etc. */
#include <stdbool.h> /* for the standard bool type. */
#include "bacdcode.h"
#include "bip.h"
#include "net.h"
/** @file linux/bip-init.c Initializes BACnet/IP interface (Linux). */
bool BIP_Debug = false;
/* gets an IP address by name, where name can be a
string that is an IP address in dotted form, or
a name that is a domain name
returns 0 if not found, or
an IP address in network byte order */
long bip_getaddrbyname(
const char *host_name)
{
struct hostent *host_ent;
if ((host_ent = gethostbyname(host_name)) == NULL)
return 0;
return *(long *) host_ent->h_addr;
}
static int get_local_ifr_ioctl(
char *ifname,
struct ifreq *ifr,
int request)
{
int fd;
int rv; /* return value */
strncpy(ifr->ifr_name, ifname, sizeof(ifr->ifr_name));
fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
if (fd < 0) {
rv = fd;
} else {
rv = ioctl(fd, request, ifr);
close(fd);
}
return rv;
}
/* forward prototype required for compilers */
int get_local_address_ioctl(
char *ifname,
struct in_addr *addr,
int request);
int get_local_address_ioctl(
char *ifname,
struct in_addr *addr,
int request)
{
struct ifreq ifr = { {{0}} };
struct sockaddr_in *tcpip_address;
int rv; /* return value */
rv = get_local_ifr_ioctl(ifname, &ifr, request);
if (rv >= 0) {
tcpip_address = (struct sockaddr_in *) &ifr.ifr_addr;
memcpy(addr, &tcpip_address->sin_addr, sizeof(struct in_addr));
}
return rv;
}
/** Gets the local IP address and local broadcast address from the system,
* and saves it into the BACnet/IP data structures.
*
* @param ifname [in] The named interface to use for the network layer.
* Eg, for Linux, ifname is eth0, ath0, arc0, and others.
*/
void bip_set_interface(
char *ifname)
{
struct in_addr local_address;
struct in_addr broadcast_address;
struct in_addr netmask;
int rv = 0;
/* setup local address */
rv = get_local_address_ioctl(ifname, &local_address, SIOCGIFADDR);
if (rv < 0) {
local_address.s_addr = 0;
}
bip_set_addr(local_address.s_addr);
if (BIP_Debug) {
fprintf(stderr, "Interface: %s\n", ifname);
fprintf(stderr, "IP Address: %s\n", inet_ntoa(local_address));
}
/* setup local broadcast address */
rv = get_local_address_ioctl(ifname, &netmask, SIOCGIFNETMASK);
if (rv < 0) {
broadcast_address.s_addr = ~0;
}
else {
broadcast_address = local_address;
broadcast_address.s_addr |= (~netmask.s_addr);
}
bip_set_broadcast_addr(broadcast_address.s_addr);
if (BIP_Debug) {
fprintf(stderr, "IP Broadcast Address: %s\n",
inet_ntoa(broadcast_address));
fprintf(stderr, "UDP Port: 0x%04X [%hu]\n", ntohs(bip_get_port()),
ntohs(bip_get_port()));
}
}
/** Initialize the BACnet/IP services at the given interface.
* @ingroup DLBIP
* -# Gets the local IP address and local broadcast address from the system,
* and saves it into the BACnet/IP data structures.
* -# Opens a UDP socket
* -# Configures the socket for sending and receiving
* -# Configures the socket so it can send broadcasts
* -# Binds the socket to the local IP address at the specified port for
* BACnet/IP (by default, 0xBAC0 = 47808).
*
* @note For Linux, ifname is eth0, ath0, arc0, and others.
*
* @param ifname [in] The named interface to use for the network layer.
* If NULL, the "eth0" interface is assigned.
* @return True if the socket is successfully opened for BACnet/IP,
* else False if the socket functions fail.
*/
bool bip_init(
char *ifname)
{
int status = 0; /* return from socket lib calls */
struct sockaddr_in sin;
int sockopt = 0;
int sock_fd = -1;
if (ifname)
bip_set_interface(ifname);
else
bip_set_interface("eth0");
/* assumes that the driver has already been initialized */
sock_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
bip_set_socket(sock_fd);
if (sock_fd < 0)
return false;
/* Allow us to use the same socket for sending and receiving */
/* This makes sure that the src port is correct when sending */
sockopt = 1;
status =
setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &sockopt,
sizeof(sockopt));
if (status < 0) {
close(sock_fd);
bip_set_socket(-1);
return status;
}
/* allow us to send a broadcast */
status =
setsockopt(sock_fd, SOL_SOCKET, SO_BROADCAST, &sockopt,
sizeof(sockopt));
if (status < 0) {
close(sock_fd);
bip_set_socket(-1);
return false;
}
/* bind the socket to the local port number and IP address */
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = htonl(INADDR_ANY);
sin.sin_port = bip_get_port();
memset(&(sin.sin_zero), '\0', sizeof(sin.sin_zero));
status =
bind(sock_fd, (const struct sockaddr *) &sin, sizeof(struct sockaddr));
if (status < 0) {
close(sock_fd);
bip_set_socket(-1);
return false;
}
return true;
}
/** Cleanup and close out the BACnet/IP services by closing the socket.
* @ingroup DLBIP
*/
void bip_cleanup(
void)
{
int sock_fd = 0;
if (bip_valid()) {
sock_fd = bip_socket();
close(sock_fd);
}
bip_set_socket(-1);
return;
}
/** Get the netmask of the BACnet/IP's interface via an ioctl() call.
* @param netmask [out] The netmask, in host order.
* @return 0 on success, else the error from the ioctl() call.
*/
int bip_get_local_netmask(
struct in_addr *netmask)
{
int rv;
char *ifname = getenv("BACNET_IFACE"); /* will probably be null */
if (ifname == NULL)
ifname = "eth0";
rv = get_local_address_ioctl(ifname, netmask, SIOCGIFNETMASK);
return rv;
}
+453
View File
@@ -0,0 +1,453 @@
/*####COPYRIGHTBEGIN####
-------------------------------------------
Copyright (C) 2016 Steve Karg
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to:
The Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA.
As a special exception, if other files instantiate templates or
use macros or inline functions from this file, or you compile
this file and link it with other works to produce a work based
on this file, this file does not by itself cause the resulting
work to be covered by the GNU General Public License. However
the source code for this file must still be made available in
accordance with section (3) of the GNU General Public License.
This exception does not invalidate any other reasons why a work
based on this file might be covered by the GNU General Public
License.
-------------------------------------------
####COPYRIGHTEND####*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h> /* for standard integer types uint8_t etc. */
#include <stdbool.h> /* for the standard bool type. */
#include "bacdcode.h"
#include "config.h"
#include "bip6.h"
#include "debug.h"
#include "device.h"
#include "net.h"
#include <ifaddrs.h>
static void debug_print_ipv6(const char *str, const struct in6_addr * addr) {
debug_printf( "BIP6: %s %02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x\n",
str,
(int)addr->s6_addr[0], (int)addr->s6_addr[1],
(int)addr->s6_addr[2], (int)addr->s6_addr[3],
(int)addr->s6_addr[4], (int)addr->s6_addr[5],
(int)addr->s6_addr[6], (int)addr->s6_addr[7],
(int)addr->s6_addr[8], (int)addr->s6_addr[9],
(int)addr->s6_addr[10], (int)addr->s6_addr[11],
(int)addr->s6_addr[12], (int)addr->s6_addr[13],
(int)addr->s6_addr[14], (int)addr->s6_addr[15]);
}
/** @file linux/bip6.c Initializes BACnet/IPv6 interface (Linux). */
/* unix socket */
static int BIP6_Socket = -1;
/* local address - filled by init functions */
static BACNET_IP6_ADDRESS BIP6_Addr;
static BACNET_IP6_ADDRESS BIP6_Broadcast_Addr;
/**
* Set the interface name. On Linux, ifname is the /dev/ name of the interface.
*
* @param ifname - C string for name or text address
*/
void bip6_set_interface(
char *ifname)
{
struct ifaddrs *ifa, *ifa_tmp;
struct sockaddr_in6 *sin;
bool found = false;
if (getifaddrs(&ifa) == -1) {
perror("BIP6: getifaddrs failed");
exit(1);
}
ifa_tmp = ifa;
debug_printf("BIP6: seeking interface: %s\n", ifname);
while (ifa_tmp) {
if ((ifa_tmp->ifa_addr) &&
(ifa_tmp->ifa_addr->sa_family == AF_INET6)) {
debug_printf("BIP6: found interface: %s\n", ifa_tmp->ifa_name);
}
if ((ifa_tmp->ifa_addr) &&
(ifa_tmp->ifa_addr->sa_family == AF_INET6) &&
(strcasecmp(ifa_tmp->ifa_name,ifname) == 0)) {
sin = (struct sockaddr_in6*) ifa_tmp->ifa_addr;
bvlc6_address_set(&BIP6_Addr,
ntohs(sin->sin6_addr.s6_addr16[0]),
ntohs(sin->sin6_addr.s6_addr16[1]),
ntohs(sin->sin6_addr.s6_addr16[2]),
ntohs(sin->sin6_addr.s6_addr16[3]),
ntohs(sin->sin6_addr.s6_addr16[4]),
ntohs(sin->sin6_addr.s6_addr16[5]),
ntohs(sin->sin6_addr.s6_addr16[6]),
ntohs(sin->sin6_addr.s6_addr16[7]));
debug_print_ipv6(ifname, (&sin->sin6_addr));
found = true;
break;
}
ifa_tmp = ifa_tmp->ifa_next;
}
if (!found) {
debug_printf("BIP6: unable to set interface: %s\n", ifname);
exit(1);
}
}
/**
* Set the BACnet IPv6 UDP port number
*
* @param port - IPv6 UDP port number
*/
void bip6_set_port(
uint16_t port)
{
BIP6_Addr.port = port;
BIP6_Broadcast_Addr.port = port;
}
/**
* Get the BACnet IPv6 UDP port number
*
* @return IPv6 UDP port number
*/
uint16_t bip6_get_port(
void)
{
return BIP6_Addr.port;
}
/**
* Get the BACnet broadcast address for my interface.
* Used as dest address in messages sent as BROADCAST
*
* @param addr - IPv6 source address
*/
void bip6_get_broadcast_address(
BACNET_ADDRESS * addr)
{
if (addr) {
addr->net = BACNET_BROADCAST_NETWORK;
addr->mac_len = 0;
addr->len = 0;
}
}
/**
* Get the IPv6 address for my interface. Used as src address in messages sent.
*
* @param addr - IPv6 source address
*/
void bip6_get_my_address(
BACNET_ADDRESS * addr)
{
uint32_t device_id = 0;
if (addr) {
device_id = Device_Object_Instance_Number();
bvlc6_vmac_address_set(addr, device_id);
}
}
/**
* Set the BACnet/IP address
*
* @param addr - network IPv6 address
*/
bool bip6_set_addr(
BACNET_IP6_ADDRESS *addr)
{
return bvlc6_address_copy(&BIP6_Addr, addr);
}
/**
* Get the BACnet/IP address
*
* @return BACnet/IP address
*/
bool bip6_get_addr(
BACNET_IP6_ADDRESS *addr)
{
return bvlc6_address_copy(addr, &BIP6_Addr);
}
/**
* Set the BACnet/IP address
*
* @param addr - network IPv6 address
*/
bool bip6_set_broadcast_addr(
BACNET_IP6_ADDRESS *addr)
{
return bvlc6_address_copy(&BIP6_Broadcast_Addr, addr);
}
/**
* Get the BACnet/IP address
*
* @return BACnet/IP address
*/
bool bip6_get_broadcast_addr(
BACNET_IP6_ADDRESS *addr)
{
return bvlc6_address_copy(addr, &BIP6_Broadcast_Addr);
}
/**
* The send function for BACnet/IPv6 driver layer
*
* @param dest - Points to a BACNET_IP6_ADDRESS structure containing the
* destination address.
* @param mtu - the bytes of data to send
* @param mtu_len - the number of bytes of data to send
*
* @return Upon successful completion, returns the number of bytes sent.
* Otherwise, -1 shall be returned and errno set to indicate the error.
*/
int bip6_send_mpdu(
BACNET_IP6_ADDRESS *dest,
uint8_t * mtu,
uint16_t mtu_len)
{
struct sockaddr_in6 bvlc_dest = { 0 };
uint16_t addr16[8];
/* assumes that the driver has already been initialized */
if (BIP6_Socket < 0) {
return 0;
}
/* load destination IP address */
bvlc_dest.sin6_family = AF_INET6;
bvlc6_address_get(dest, &addr16[0], &addr16[1], &addr16[2], &addr16[3],
&addr16[4], &addr16[5], &addr16[6], &addr16[7]);
bvlc_dest.sin6_addr.s6_addr16[0] = htons(addr16[0]);
bvlc_dest.sin6_addr.s6_addr16[1] = htons(addr16[1]);
bvlc_dest.sin6_addr.s6_addr16[2] = htons(addr16[2]);
bvlc_dest.sin6_addr.s6_addr16[3] = htons(addr16[3]);
bvlc_dest.sin6_addr.s6_addr16[4] = htons(addr16[4]);
bvlc_dest.sin6_addr.s6_addr16[5] = htons(addr16[5]);
bvlc_dest.sin6_addr.s6_addr16[6] = htons(addr16[6]);
bvlc_dest.sin6_addr.s6_addr16[7] = htons(addr16[7]);
bvlc_dest.sin6_port = htons(dest->port);
debug_print_ipv6("Sending MPDU->", &bvlc_dest.sin6_addr);
/* Send the packet */
return sendto(BIP6_Socket, (char *) mtu, mtu_len, 0,
(struct sockaddr *) &bvlc_dest, sizeof(bvlc_dest));
}
/**
* BACnet/IP Datalink Receive handler.
*
* @param src - returns the source address
* @param npdu - returns the NPDU buffer
* @param max_npdu -maximum size of the NPDU buffer
* @param timeout - number of milliseconds to wait for a packet
*
* @return Number of bytes received, or 0 if none or timeout.
*/
uint16_t bip6_receive(
BACNET_ADDRESS * src,
uint8_t * npdu,
uint16_t max_npdu,
unsigned timeout)
{
uint16_t npdu_len = 0; /* return value */
fd_set read_fds;
int max = 0;
struct timeval select_timeout;
struct sockaddr_in6 sin = { 0 };
BACNET_IP6_ADDRESS addr = {{ 0 }};
socklen_t sin_len = sizeof(sin);
int received_bytes = 0;
int offset = 0;
uint16_t i = 0;
/* Make sure the socket is open */
if (BIP6_Socket < 0) {
return 0;
}
/* we could just use a non-blocking socket, but that consumes all
the CPU time. We can use a timeout; it is only supported as
a select. */
if (timeout >= 1000) {
select_timeout.tv_sec = timeout / 1000;
select_timeout.tv_usec =
1000 * (timeout - select_timeout.tv_sec * 1000);
} else {
select_timeout.tv_sec = 0;
select_timeout.tv_usec = 1000 * timeout;
}
FD_ZERO(&read_fds);
FD_SET(BIP6_Socket, &read_fds);
max = BIP6_Socket;
/* see if there is a packet for us */
if (select(max + 1, &read_fds, NULL, NULL, &select_timeout) > 0) {
received_bytes =
recvfrom(BIP6_Socket, (char *) &npdu[0], max_npdu, 0,
(struct sockaddr *) &sin, &sin_len);
} else {
return 0;
}
/* See if there is a problem */
if (received_bytes < 0) {
return 0;
}
/* no problem, just no bytes */
if (received_bytes == 0) {
return 0;
}
/* the signature of a BACnet/IPv6 packet */
if (npdu[0] != BVLL_TYPE_BACNET_IP6) {
return 0;
}
/* pass the packet into the BBMD handler */
debug_print_ipv6("Received MPDU->", &sin.sin6_addr);
bvlc6_address_set(&addr,
ntohs(sin.sin6_addr.s6_addr16[0]),
ntohs(sin.sin6_addr.s6_addr16[1]),
ntohs(sin.sin6_addr.s6_addr16[2]),
ntohs(sin.sin6_addr.s6_addr16[3]),
ntohs(sin.sin6_addr.s6_addr16[4]),
ntohs(sin.sin6_addr.s6_addr16[5]),
ntohs(sin.sin6_addr.s6_addr16[6]),
ntohs(sin.sin6_addr.s6_addr16[7]));
addr.port = ntohs(sin.sin6_port);
offset = bvlc6_handler(&addr, src, npdu, received_bytes);
if (offset > 0) {
npdu_len = received_bytes - offset;
if (npdu_len <= max_npdu) {
/* shift the buffer to return a valid NPDU */
for (i = 0; i < npdu_len; i++) {
npdu[i] = npdu[offset + i];
}
} else {
npdu_len = 0;
}
}
return npdu_len;
}
/** Cleanup and close out the BACnet/IP services by closing the socket.
* @ingroup DLBIP6
*/
void bip6_cleanup(
void)
{
if (BIP6_Socket != -1) {
close(BIP6_Socket);
}
BIP6_Socket = -1;
return;
}
/** Initialize the BACnet/IP services at the given interface.
* @ingroup DLBIP6
* -# Gets the local IP address and local broadcast address from the system,
* and saves it into the BACnet/IPv6 data structures.
* -# Opens a UDP socket
* -# Configures the socket for sending and receiving
* -# Configures the socket so it can send multicasts
* -# Binds the socket to the local IP address at the specified port for
* BACnet/IPv6 (by default, 0xBAC0 = 47808).
*
* @note For Linux, ifname is eth0, ath0, arc0, and others.
*
* @param ifname [in] The named interface to use for the network layer.
* If NULL, the "eth0" interface is assigned.
* @return True if the socket is successfully opened for BACnet/IP,
* else False if the socket functions fail.
*/
bool bip6_init(
char *ifname)
{
int status = 0; /* return from socket lib calls */
struct sockaddr_in6 server = {0};
struct in6_addr broadcast_address;
struct ipv6_mreq join_request;
int sockopt = 0;
if (ifname) {
bip6_set_interface(ifname);
} else {
bip6_set_interface("eth0");
}
if (BIP6_Addr.port == 0) {
bip6_set_port(0xBAC0);
}
debug_printf("BIP6: IPv6 UDP port: 0x%04X\n", htons(BIP6_Addr.port));
if (BIP6_Broadcast_Addr.address[0] == 0) {
bvlc6_address_set(&BIP6_Broadcast_Addr,
BIP6_MULTICAST_SITE_LOCAL, 0, 0, 0, 0, 0, 0,
BIP6_MULTICAST_GROUP_ID);
}
/* assumes that the driver has already been initialized */
BIP6_Socket = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
if (BIP6_Socket < 0)
return false;
/* Allow us to use the same socket for sending and receiving */
/* This makes sure that the src port is correct when sending */
sockopt = 1;
status =
setsockopt(BIP6_Socket, SOL_SOCKET, SO_REUSEADDR, &sockopt,
sizeof(sockopt));
if (status < 0) {
close(BIP6_Socket);
BIP6_Socket = -1;
return status;
}
/* allow us to send a broadcast */
status =
setsockopt(BIP6_Socket, SOL_SOCKET, SO_BROADCAST, &sockopt,
sizeof(sockopt));
if (status < 0) {
close(BIP6_Socket);
BIP6_Socket = -1;
return false;
}
/* subscribe to a multicast address */
memcpy(&broadcast_address.s6_addr[0], &BIP6_Broadcast_Addr.address[0], IP6_ADDRESS_MAX);
memcpy(&join_request.ipv6mr_multiaddr, &broadcast_address, sizeof(struct in6_addr));
/* Let system choose the interface */
join_request.ipv6mr_interface = 0;
status = setsockopt(BIP6_Socket, IPPROTO_IPV6, IPV6_JOIN_GROUP,
&join_request, sizeof(join_request));
if (status < 0) {
perror("BIP: setsockopt(IPV6_JOIN_GROUP)");
}
/* bind the socket to the local port number and IP address */
server.sin6_family = AF_INET6;
server.sin6_addr = in6addr_any;
server.sin6_port = htons(BIP6_Addr.port);
status = bind(BIP6_Socket, (const void*)&server, sizeof(server));
if (status < 0) {
perror("BIP: bind");
close(BIP6_Socket);
BIP6_Socket = -1;
return false;
}
bvlc6_init();
return true;
}
+821
View File
@@ -0,0 +1,821 @@
/**************************************************************************
*
* Copyright (C) 2008 Steve Karg <skarg@users.sourceforge.net>
* Updated by Nikola Jelic 2011 <nikola.jelic@euroicc.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*********************************************************************/
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/time.h>
#include "bacdef.h"
#include "bacaddr.h"
#include "mstp.h"
#include "dlmstp.h"
#include "rs485.h"
#include "npdu.h"
#include "bits.h"
#include "ringbuf.h"
#include "debug.h"
/* OS Specific include */
#include "net.h"
/** @file linux/dlmstp.c Provides Linux-specific DataLink functions for MS/TP. */
/* Number of MS/TP Packets Rx/Tx */
uint16_t MSTP_Packets = 0;
/* packet queues */
static DLMSTP_PACKET Receive_Packet;
/* mechanism to wait for a packet */
/*
static RT_COND Receive_Packet_Flag;
static RT_MUTEX Receive_Packet_Mutex;
*/
static pthread_cond_t Receive_Packet_Flag;
static pthread_mutex_t Receive_Packet_Mutex;
/* mechanism to wait for a frame in state machine */
/*
static RT_COND Received_Frame_Flag;
static RT_MUTEX Received_Frame_Mutex;
*/
static pthread_cond_t Received_Frame_Flag;
static pthread_mutex_t Received_Frame_Mutex;
static pthread_cond_t Master_Done_Flag;
static pthread_mutex_t Master_Done_Mutex;
/*RT_TASK Receive_Task, Fsm_Task;*/
/* local MS/TP port data - shared with RS-485 */
static volatile struct mstp_port_struct_t MSTP_Port;
/* buffers needed by mstp port struct */
static uint8_t TxBuffer[MAX_MPDU];
static uint8_t RxBuffer[MAX_MPDU];
/* data structure for MS/TP PDU Queue */
struct mstp_pdu_packet {
bool data_expecting_reply;
uint8_t destination_mac;
uint16_t length;
uint8_t buffer[MAX_MPDU];
};
/* count must be a power of 2 for ringbuf library */
#ifndef MSTP_PDU_PACKET_COUNT
#define MSTP_PDU_PACKET_COUNT 8
#endif
static struct mstp_pdu_packet PDU_Buffer[MSTP_PDU_PACKET_COUNT];
static RING_BUFFER PDU_Queue;
/* The minimum time without a DataAvailable or ReceiveError event */
/* that a node must wait for a station to begin replying to a */
/* confirmed request: 255 milliseconds. (Implementations may use */
/* larger values for this timeout, not to exceed 300 milliseconds.) */
static uint16_t Treply_timeout = 300;
/* The minimum time without a DataAvailable or ReceiveError event that a */
/* node must wait for a remote node to begin using a token or replying to */
/* a Poll For Master frame: 20 milliseconds. (Implementations may use */
/* larger values for this timeout, not to exceed 100 milliseconds.) */
static uint8_t Tusage_timeout = 100;
/* Timer that indicates line silence - and functions */
static struct timespec start;
/**
* Calculate the time difference between two timespec values.
*
* @param l - The minued (time from which we subtract).
* @param r - The subtrahend (time that is being subtracted).
*
* @returns True if the difference is negative, otherwise 0.
*/
static int timespec_subtract(struct timespec *result,
const struct timespec *l,
const struct timespec *r)
{
# define NS_PER_S 1000000000 // nano-seconds per second
struct timespec right = *r;
int secs;
// Perform the carry for the later subtraction by updating y.
if (l->tv_nsec < right.tv_nsec) {
secs = (right.tv_nsec - l->tv_nsec) / NS_PER_S + 1;
right.tv_nsec -= NS_PER_S * secs;
right.tv_sec += secs;
}
if (l->tv_nsec - right.tv_nsec > NS_PER_S) {
secs = (l->tv_nsec - right.tv_nsec) / NS_PER_S;
right.tv_nsec += NS_PER_S * secs;
right.tv_sec -= secs;
}
// Compute the time remaining. tv_nsec is certainly positive.
result->tv_sec = l->tv_sec - right.tv_sec;
result->tv_nsec = l->tv_nsec - right.tv_nsec;
return l->tv_sec < right.tv_sec;
}
/**
* Add a certain number of nanoseconds to the specified time.
*
* @param ts - The time to which to add to.
* @param ns - The number of nanoseconds to add. Allowed range
* is -NS_PER_S..NS_PER_S (i.e., plus minus one second).
*/
static void timespec_add_ns(struct timespec *ts, long ns)
{
ts->tv_nsec += ns;
if (ts->tv_nsec > NS_PER_S) {
ts->tv_nsec -= NS_PER_S;
ts->tv_sec += 1;
} else if (ts->tv_nsec < 0) {
ts->tv_nsec += NS_PER_S;
ts->tv_sec -= 1;
}
}
static uint32_t Timer_Silence(
void *pArg)
{
struct timespec now, diff;
int32_t res;
clock_gettime(CLOCK_MONOTONIC, &now);
timespec_subtract(&diff, &now, &start);
res = ((diff.tv_sec) * 1000 + (diff.tv_nsec) / 1000000);
return (res >= 0 ? res : 0);
}
static void Timer_Silence_Reset(
void *pArg)
{
clock_gettime(CLOCK_MONOTONIC, &start);
}
static void get_abstime(
struct timespec *abstime,
unsigned long milliseconds)
{
clock_gettime(CLOCK_MONOTONIC, abstime);
if (milliseconds > 1000) {
fprintf(stderr, "DLMSTP: limited timeout of %lums to 1000ms\n",
milliseconds);
milliseconds = 1000;
}
timespec_add_ns(abstime, 1000000 * milliseconds);
}
void dlmstp_cleanup(
void)
{
pthread_cond_destroy(&Received_Frame_Flag);
pthread_cond_destroy(&Receive_Packet_Flag);
pthread_cond_destroy(&Master_Done_Flag);
pthread_mutex_destroy(&Received_Frame_Mutex);
pthread_mutex_destroy(&Receive_Packet_Mutex);
pthread_mutex_destroy(&Master_Done_Mutex);
}
/* returns number of bytes sent on success, zero on failure */
int dlmstp_send_pdu(
BACNET_ADDRESS * dest, /* destination address */
BACNET_NPDU_DATA * npdu_data, /* network information */
uint8_t * pdu, /* any data to be sent - may be null */
unsigned pdu_len)
{ /* number of bytes of data */
int bytes_sent = 0;
struct mstp_pdu_packet *pkt;
unsigned i = 0;
pkt = (struct mstp_pdu_packet *) Ringbuf_Data_Peek(&PDU_Queue);
if (pkt) {
pkt->data_expecting_reply = npdu_data->data_expecting_reply;
for (i = 0; i < pdu_len; i++) {
pkt->buffer[i] = pdu[i];
}
pkt->length = pdu_len;
if (dest && dest->mac_len) {
pkt->destination_mac = dest->mac[0];
} else {
/* mac_len = 0 is a broadcast address */
pkt->destination_mac = MSTP_BROADCAST_ADDRESS;
}
if (Ringbuf_Data_Put(&PDU_Queue, (uint8_t *)pkt)) {
bytes_sent = pdu_len;
}
}
return bytes_sent;
}
uint16_t dlmstp_receive(
BACNET_ADDRESS * src, /* source address */
uint8_t * pdu, /* PDU data */
uint16_t max_pdu, /* amount of space available in the PDU */
unsigned timeout)
{ /* milliseconds to wait for a packet */
uint16_t pdu_len = 0;
struct timespec abstime;
(void) max_pdu;
/* see if there is a packet available, and a place
to put the reply (if necessary) and process it */
pthread_mutex_lock(&Receive_Packet_Mutex);
get_abstime(&abstime, timeout);
pthread_cond_timedwait(&Receive_Packet_Flag, &Receive_Packet_Mutex,
&abstime);
if (Receive_Packet.ready) {
if (Receive_Packet.pdu_len) {
MSTP_Packets++;
if (src) {
memmove(src, &Receive_Packet.address,
sizeof(Receive_Packet.address));
}
if (pdu) {
memmove(pdu, &Receive_Packet.pdu,
sizeof(Receive_Packet.pdu));
}
pdu_len = Receive_Packet.pdu_len;
}
Receive_Packet.ready = false;
}
pthread_mutex_unlock(&Receive_Packet_Mutex);
return pdu_len;
}
static void *dlmstp_master_fsm_task(
void *pArg)
{
uint32_t silence = 0;
bool run_master = false;
(void) pArg;
for (;;) {
if (MSTP_Port.ReceivedValidFrame == false &&
MSTP_Port.ReceivedInvalidFrame == false) {
RS485_Check_UART_Data(&MSTP_Port);
MSTP_Receive_Frame_FSM(&MSTP_Port);
}
if (MSTP_Port.ReceivedValidFrame || MSTP_Port.ReceivedInvalidFrame) {
run_master = true;
} else {
silence = MSTP_Port.SilenceTimer(NULL);
switch (MSTP_Port.master_state) {
case MSTP_MASTER_STATE_IDLE:
if (silence >= Tno_token)
run_master = true;
break;
case MSTP_MASTER_STATE_WAIT_FOR_REPLY:
if (silence >= Treply_timeout)
run_master = true;
break;
case MSTP_MASTER_STATE_POLL_FOR_MASTER:
if (silence >= Tusage_timeout)
run_master = true;
break;
default:
run_master = true;
break;
}
}
if (run_master) {
if (MSTP_Port.This_Station <= 127) {
while (MSTP_Master_Node_FSM(&MSTP_Port)) {
/* do nothing while immediate transitioning */
}
} else if (MSTP_Port.This_Station < 255) {
MSTP_Slave_Node_FSM(&MSTP_Port);
}
}
}
return NULL;
}
void dlmstp_fill_bacnet_address(
BACNET_ADDRESS * src,
uint8_t mstp_address)
{
int i = 0;
if (mstp_address == MSTP_BROADCAST_ADDRESS) {
/* mac_len = 0 if broadcast address */
src->mac_len = 0;
src->mac[0] = 0;
} else {
src->mac_len = 1;
src->mac[0] = mstp_address;
}
/* fill with 0's starting with index 1; index 0 filled above */
for (i = 1; i < MAX_MAC_LEN; i++) {
src->mac[i] = 0;
}
src->net = 0;
src->len = 0;
for (i = 0; i < MAX_MAC_LEN; i++) {
src->adr[i] = 0;
}
}
/* for the MS/TP state machine to use for putting received data */
uint16_t MSTP_Put_Receive(
volatile struct mstp_port_struct_t *mstp_port)
{
uint16_t pdu_len = 0;
pthread_mutex_lock(&Receive_Packet_Mutex);
if (Receive_Packet.ready) {
debug_printf("MS/TP: Dropped! Not Ready.\n");
} else {
/* bounds check - maybe this should send an abort? */
pdu_len = mstp_port->DataLength;
if (pdu_len > sizeof(Receive_Packet.pdu)) {
pdu_len = sizeof(Receive_Packet.pdu);
}
if (pdu_len == 0) {
debug_printf("MS/TP: PDU Length is 0!\n");
}
memmove((void *) &Receive_Packet.pdu[0],
(void *) &mstp_port->InputBuffer[0], pdu_len);
dlmstp_fill_bacnet_address(&Receive_Packet.address,
mstp_port->SourceAddress);
Receive_Packet.pdu_len = mstp_port->DataLength;
Receive_Packet.ready = true;
pthread_cond_signal(&Receive_Packet_Flag);
}
pthread_mutex_unlock(&Receive_Packet_Mutex);
return pdu_len;
}
/* for the MS/TP state machine to use for getting data to send */
/* Return: amount of PDU data */
uint16_t MSTP_Get_Send(
volatile struct mstp_port_struct_t * mstp_port,
unsigned timeout)
{ /* milliseconds to wait for a packet */
uint16_t pdu_len = 0;
uint8_t frame_type = 0;
struct mstp_pdu_packet *pkt;
(void) timeout;
if (Ringbuf_Empty(&PDU_Queue)) {
return 0;
}
pkt = (struct mstp_pdu_packet *) Ringbuf_Peek(&PDU_Queue);
if (pkt->data_expecting_reply) {
frame_type = FRAME_TYPE_BACNET_DATA_EXPECTING_REPLY;
} else {
frame_type = FRAME_TYPE_BACNET_DATA_NOT_EXPECTING_REPLY;
}
/* convert the PDU into the MSTP Frame */
pdu_len = MSTP_Create_Frame(&mstp_port->OutputBuffer[0], /* <-- loading this */
mstp_port->OutputBufferSize, frame_type, pkt->destination_mac,
mstp_port->This_Station, (uint8_t *) & pkt->buffer[0], pkt->length);
(void) Ringbuf_Pop(&PDU_Queue, NULL);
return pdu_len;
}
static bool dlmstp_compare_data_expecting_reply(
uint8_t * request_pdu,
uint16_t request_pdu_len,
uint8_t src_address,
uint8_t * reply_pdu,
uint16_t reply_pdu_len,
uint8_t dest_address)
{
uint16_t offset;
/* One way to check the message is to compare NPDU
src, dest, along with the APDU type, invoke id.
Seems a bit overkill */
struct DER_compare_t {
BACNET_NPDU_DATA npdu_data;
BACNET_ADDRESS address;
uint8_t pdu_type;
uint8_t invoke_id;
uint8_t service_choice;
};
struct DER_compare_t request;
struct DER_compare_t reply;
/* unused parameters */
request_pdu_len = request_pdu_len;
reply_pdu_len = reply_pdu_len;
/* decode the request data */
request.address.mac[0] = src_address;
request.address.mac_len = 1;
offset =
npdu_decode(&request_pdu[0], NULL, &request.address,
&request.npdu_data);
if (request.npdu_data.network_layer_message) {
#if PRINT_ENABLED
fprintf(stderr,
"DLMSTP: DER Compare failed: " "Request is Network message.\n");
#endif
return false;
}
request.pdu_type = request_pdu[offset] & 0xF0;
if (request.pdu_type != PDU_TYPE_CONFIRMED_SERVICE_REQUEST) {
#if PRINT_ENABLED
fprintf(stderr,
"DLMSTP: DER Compare failed: " "Not Confirmed Request.\n");
#endif
return false;
}
request.invoke_id = request_pdu[offset + 2];
/* segmented message? */
if (request_pdu[offset] & BIT3) {
request.service_choice = request_pdu[offset + 5];
} else {
request.service_choice = request_pdu[offset + 3];
}
/* decode the reply data */
reply.address.mac[0] = dest_address;
reply.address.mac_len = 1;
offset =
npdu_decode(&reply_pdu[0], &reply.address, NULL, &reply.npdu_data);
if (reply.npdu_data.network_layer_message) {
#if PRINT_ENABLED
fprintf(stderr,
"DLMSTP: DER Compare failed: " "Reply is Network message.\n");
#endif
return false;
}
/* reply could be a lot of things:
confirmed, simple ack, abort, reject, error */
reply.pdu_type = reply_pdu[offset] & 0xF0;
switch (reply.pdu_type) {
case PDU_TYPE_SIMPLE_ACK:
reply.invoke_id = reply_pdu[offset + 1];
reply.service_choice = reply_pdu[offset + 2];
break;
case PDU_TYPE_COMPLEX_ACK:
reply.invoke_id = reply_pdu[offset + 1];
/* segmented message? */
if (reply_pdu[offset] & BIT3) {
reply.service_choice = reply_pdu[offset + 4];
} else {
reply.service_choice = reply_pdu[offset + 2];
}
break;
case PDU_TYPE_ERROR:
reply.invoke_id = reply_pdu[offset + 1];
reply.service_choice = reply_pdu[offset + 2];
break;
case PDU_TYPE_REJECT:
case PDU_TYPE_ABORT:
reply.invoke_id = reply_pdu[offset + 1];
break;
default:
return false;
}
/* these don't have service choice included */
if ((reply.pdu_type == PDU_TYPE_REJECT) ||
(reply.pdu_type == PDU_TYPE_ABORT)) {
if (request.invoke_id != reply.invoke_id) {
#if PRINT_ENABLED
fprintf(stderr,
"DLMSTP: DER Compare failed: " "Invoke ID mismatch.\n");
#endif
return false;
}
} else {
if (request.invoke_id != reply.invoke_id) {
#if PRINT_ENABLED
fprintf(stderr,
"DLMSTP: DER Compare failed: " "Invoke ID mismatch.\n");
#endif
return false;
}
if (request.service_choice != reply.service_choice) {
#if PRINT_ENABLED
fprintf(stderr,
"DLMSTP: DER Compare failed: " "Service choice mismatch.\n");
#endif
return false;
}
}
if (request.npdu_data.protocol_version != reply.npdu_data.protocol_version) {
#if PRINT_ENABLED
fprintf(stderr,
"DLMSTP: DER Compare failed: "
"NPDU Protocol Version mismatch.\n");
#endif
return false;
}
#if 0
/* the NDPU priority doesn't get passed through the stack, and
all outgoing messages have NORMAL priority */
if (request.npdu_data.priority != reply.npdu_data.priority) {
#if PRINT_ENABLED
fprintf(stderr,
"DLMSTP: DER Compare failed: " "NPDU Priority mismatch.\n");
#endif
return false;
}
#endif
if (!bacnet_address_same(&request.address, &reply.address)) {
#if PRINT_ENABLED
fprintf(stderr,
"DLMSTP: DER Compare failed: " "BACnet Address mismatch.\n");
#endif
return false;
}
return true;
}
/* Get the reply to a DATA_EXPECTING_REPLY frame, or nothing */
uint16_t MSTP_Get_Reply(
volatile struct mstp_port_struct_t * mstp_port,
unsigned timeout)
{ /* milliseconds to wait for a packet */
uint16_t pdu_len = 0; /* return value */
bool matched = false;
uint8_t frame_type = 0;
struct mstp_pdu_packet *pkt;
(void) timeout;
if (Ringbuf_Empty(&PDU_Queue)) {
return 0;
}
pkt = (struct mstp_pdu_packet *) Ringbuf_Peek(&PDU_Queue);
/* is this the reply to the DER? */
matched =
dlmstp_compare_data_expecting_reply(&mstp_port->InputBuffer[0],
mstp_port->DataLength, mstp_port->SourceAddress,
(uint8_t *) & pkt->buffer[0], pkt->length, pkt->destination_mac);
if (!matched) {
return 0;
}
if (pkt->data_expecting_reply) {
frame_type = FRAME_TYPE_BACNET_DATA_EXPECTING_REPLY;
} else {
frame_type = FRAME_TYPE_BACNET_DATA_NOT_EXPECTING_REPLY;
}
/* convert the PDU into the MSTP Frame */
pdu_len = MSTP_Create_Frame(&mstp_port->OutputBuffer[0], /* <-- loading this */
mstp_port->OutputBufferSize, frame_type, pkt->destination_mac,
mstp_port->This_Station, (uint8_t *) & pkt->buffer[0], pkt->length);
(void) Ringbuf_Pop(&PDU_Queue, NULL);
return pdu_len;
}
void dlmstp_set_mac_address(
uint8_t mac_address)
{
/* Master Nodes can only have address 0-127 */
if (mac_address <= 127) {
MSTP_Port.This_Station = mac_address;
/* FIXME: implement your data storage */
/* I2C_Write_Byte(
EEPROM_DEVICE_ADDRESS,
mac_address,
EEPROM_MSTP_MAC_ADDR); */
if (mac_address > MSTP_Port.Nmax_master)
dlmstp_set_max_master(mac_address);
}
return;
}
uint8_t dlmstp_mac_address(
void)
{
return MSTP_Port.This_Station;
}
/* This parameter represents the value of the Max_Info_Frames property of */
/* the node's Device object. The value of Max_Info_Frames specifies the */
/* maximum number of information frames the node may send before it must */
/* pass the token. Max_Info_Frames may have different values on different */
/* nodes. This may be used to allocate more or less of the available link */
/* bandwidth to particular nodes. If Max_Info_Frames is not writable in a */
/* node, its value shall be 1. */
void dlmstp_set_max_info_frames(
uint8_t max_info_frames)
{
if (max_info_frames >= 1) {
MSTP_Port.Nmax_info_frames = max_info_frames;
/* FIXME: implement your data storage */
/* I2C_Write_Byte(
EEPROM_DEVICE_ADDRESS,
(uint8_t)max_info_frames,
EEPROM_MSTP_MAX_INFO_FRAMES_ADDR); */
}
return;
}
uint8_t dlmstp_max_info_frames(
void)
{
return MSTP_Port.Nmax_info_frames;
}
/* This parameter represents the value of the Max_Master property of the */
/* node's Device object. The value of Max_Master specifies the highest */
/* allowable address for master nodes. The value of Max_Master shall be */
/* less than or equal to 127. If Max_Master is not writable in a node, */
/* its value shall be 127. */
void dlmstp_set_max_master(
uint8_t max_master)
{
if (max_master <= 127) {
if (MSTP_Port.This_Station <= max_master) {
MSTP_Port.Nmax_master = max_master;
/* FIXME: implement your data storage */
/* I2C_Write_Byte(
EEPROM_DEVICE_ADDRESS,
max_master,
EEPROM_MSTP_MAX_MASTER_ADDR); */
}
}
return;
}
uint8_t dlmstp_max_master(
void)
{
return MSTP_Port.Nmax_master;
}
/* RS485 Baud Rate 9600, 19200, 38400, 57600, 115200 */
void dlmstp_set_baud_rate(
uint32_t baud)
{
RS485_Set_Baud_Rate(baud);
}
uint32_t dlmstp_baud_rate(
void)
{
return RS485_Get_Baud_Rate();
}
void dlmstp_get_my_address(
BACNET_ADDRESS * my_address)
{
int i = 0; /* counter */
my_address->mac_len = 1;
my_address->mac[0] = MSTP_Port.This_Station;
my_address->net = 0; /* local only, no routing */
my_address->len = 0;
for (i = 0; i < MAX_MAC_LEN; i++) {
my_address->adr[i] = 0;
}
return;
}
void dlmstp_get_broadcast_address(
BACNET_ADDRESS * dest)
{ /* destination address */
int i = 0; /* counter */
if (dest) {
dest->mac_len = 1;
dest->mac[0] = MSTP_BROADCAST_ADDRESS;
dest->net = BACNET_BROADCAST_NETWORK;
dest->len = 0; /* always zero when DNET is broadcast */
for (i = 0; i < MAX_MAC_LEN; i++) {
dest->adr[i] = 0;
}
}
return;
}
bool dlmstp_init(
char *ifname)
{
unsigned long hThread = 0;
pthread_condattr_t attr;
int rv = 0;
pthread_condattr_init(&attr);
if ((rv = pthread_condattr_setclock(&attr, CLOCK_MONOTONIC)) != 0) {
fprintf(stderr, "MS/TP Interface: %s\n failed to set MONOTONIC clock\n",
ifname);
exit(1);
}
/* initialize PDU queue */
Ringbuf_Init(&PDU_Queue, (uint8_t *) & PDU_Buffer,
sizeof(struct mstp_pdu_packet), MSTP_PDU_PACKET_COUNT);
/* initialize packet queue */
Receive_Packet.ready = false;
Receive_Packet.pdu_len = 0;
rv = pthread_cond_init(&Receive_Packet_Flag, &attr);
if (rv != 0) {
fprintf(stderr,
"MS/TP Interface: %s\n cannot allocate PThread Condition.\n",
ifname);
exit(1);
}
rv = pthread_mutex_init(&Receive_Packet_Mutex, NULL);
if (rv != 0) {
fprintf(stderr,
"MS/TP Interface: %s\n cannot allocate PThread Mutex.\n", ifname);
exit(1);
}
/* initialize hardware */
if (ifname) {
RS485_Set_Interface(ifname);
#if PRINT_ENABLED
fprintf(stderr, "MS/TP Interface: %s\n", ifname);
#endif
}
RS485_Initialize();
MSTP_Port.InputBuffer = &RxBuffer[0];
MSTP_Port.InputBufferSize = sizeof(RxBuffer);
MSTP_Port.OutputBuffer = &TxBuffer[0];
MSTP_Port.OutputBufferSize = sizeof(TxBuffer);
clock_gettime(CLOCK_MONOTONIC, &start);
MSTP_Port.SilenceTimer = Timer_Silence;
MSTP_Port.SilenceTimerReset = Timer_Silence_Reset;
MSTP_Init(&MSTP_Port);
#if PRINT_ENABLED
fprintf(stderr, "MS/TP MAC: %02X\n", MSTP_Port.This_Station);
fprintf(stderr, "MS/TP Max_Master: %02X\n", MSTP_Port.Nmax_master);
fprintf(stderr, "MS/TP Max_Info_Frames: %u\n", MSTP_Port.Nmax_info_frames);
#endif
/* start the threads */
/* rv = pthread_create(&hThread, NULL, dlmstp_receive_fsm_task, NULL); */
/* if (rv != 0) {
fprintf(stderr, "Failed to start recive FSM task\n");
} */
rv = pthread_create(&hThread, NULL, dlmstp_master_fsm_task, NULL);
if (rv != 0) {
fprintf(stderr, "Failed to start Master Node FSM task\n");
}
return true;
}
#ifdef TEST_DLMSTP
#include <stdio.h>
void apdu_handler(
BACNET_ADDRESS * src, /* source address */
uint8_t * apdu, /* APDU data */
uint16_t pdu_len)
{ /* for confirmed messages */
(void) src;
(void) apdu;
(void) pdu_len;
}
static char *Network_Interface = NULL;
int main(
int argc,
char *argv[])
{
uint16_t pdu_len = 0;
/* argv has the "COM4" or some other device */
if (argc > 1) {
Network_Interface = argv[1];
}
dlmstp_set_baud_rate(38400);
dlmstp_set_mac_address(0x05);
dlmstp_set_max_info_frames(DEFAULT_MAX_INFO_FRAMES);
dlmstp_set_max_master(DEFAULT_MAX_MASTER);
dlmstp_init(Network_Interface);
/* forever task */
for (;;) {
pdu_len = dlmstp_receive(NULL, NULL, 0, UINT_MAX);
MSTP_Create_And_Send_Frame(&MSTP_Port, FRAME_TYPE_TEST_REQUEST,
MSTP_Port.SourceAddress, MSTP_Port.This_Station, NULL, 0);
}
return 0;
}
#endif
+33
View File
@@ -0,0 +1,33 @@
#Makefile to build test case
CC = gcc
BASEDIR = .
# -g for debugging with gdb
DEFINES = -DBIG_ENDIAN=0 -DBACDL_MSTP=1 -DTEST_DLMSTP
INCLUDES = -I. -I../../
CFLAGS = -Wall $(INCLUDES) $(DEFINES) -g
SRCS = rs485.c \
dlmstp.c \
../../mstp.c \
../../crc.c
OBJS = ${SRCS:.c=.o}
TARGET = dlmstp
all: ${TARGET}
${TARGET}: ${OBJS}
${CC} -pthread -o $@ ${OBJS}
.c.o:
${CC} -c ${CFLAGS} $*.c -o $@
depend:
rm -f .depend
${CC} -MM ${CFLAGS} *.c >> .depend
clean:
rm -rf core ${TARGET} $(OBJS) *.bak *.1 *.ini
include: .depend
+979
View File
@@ -0,0 +1,979 @@
/**************************************************************************
*
* Copyright (C) 2008 Steve Karg <skarg@users.sourceforge.net>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*********************************************************************/
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include "bacdef.h"
#include "bacaddr.h"
#include "mstp.h"
/*#include "dlmstp.h" */
#include "dlmstp_linux.h"
#include "rs485.h"
#include "npdu.h"
#include "bits.h"
/* OS Specific include */
#include "net.h"
#include "ringbuf.h"
/** @file linux/dlmstp.c Provides Linux-specific DataLink functions for MS/TP. */
#define BACNET_PDU_CONTROL_BYTE_OFFSET 1
#define BACNET_DATA_EXPECTING_REPLY_BIT 2
#define BACNET_DATA_EXPECTING_REPLY(control) ( (control & (1 << BACNET_DATA_EXPECTING_REPLY_BIT) ) > 0 )
#define INCREMENT_AND_LIMIT_UINT16(x) {if (x < 0xFFFF) x++;}
uint32_t Timer_Silence(
void *poPort)
{
struct timeval now, tmp_diff;
SHARED_MSTP_DATA *poSharedData;
struct mstp_port_struct_t *mstp_port =
(struct mstp_port_struct_t *) poPort;
if (!mstp_port) {
return -1;
}
poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData;
if (!poSharedData) {
return -1;
}
int32_t res;
gettimeofday(&now, NULL);
timersub(&poSharedData->start, &now, &tmp_diff);
res = ((tmp_diff.tv_sec) * 1000 + (tmp_diff.tv_usec) / 1000);
return (res >= 0 ? res : -res);
}
void Timer_Silence_Reset(
void *poPort)
{
SHARED_MSTP_DATA *poSharedData;
struct mstp_port_struct_t *mstp_port =
(struct mstp_port_struct_t *) poPort;
if (!mstp_port) {
return;
}
poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData;
if (!poSharedData) {
return;
}
gettimeofday(&poSharedData->start, NULL);
}
void get_abstime(
struct timespec *abstime,
unsigned long milliseconds)
{
struct timeval now, offset, result;
gettimeofday(&now, NULL);
offset.tv_sec = 0;
offset.tv_usec = milliseconds * 1000;
timeradd(&now, &offset, &result);
abstime->tv_sec = result.tv_sec;
abstime->tv_nsec = result.tv_usec * 1000;
}
void dlmstp_cleanup(
void *poPort)
{
SHARED_MSTP_DATA *poSharedData;
struct mstp_port_struct_t *mstp_port =
(struct mstp_port_struct_t *) poPort;
if (!mstp_port) {
return;
}
poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData;
if (!poSharedData) {
return;
}
/* restore the old port settings */
tcsetattr(poSharedData->RS485_Handle, TCSANOW,
&poSharedData->RS485_oldtio);
close(poSharedData->RS485_Handle);
pthread_cond_destroy(&poSharedData->Received_Frame_Flag);
sem_destroy(&poSharedData->Receive_Packet_Flag);
pthread_cond_destroy(&poSharedData->Master_Done_Flag);
pthread_mutex_destroy(&poSharedData->Received_Frame_Mutex);
pthread_mutex_destroy(&poSharedData->Master_Done_Mutex);
}
/* returns number of bytes sent on success, zero on failure */
int dlmstp_send_pdu(
void *poPort,
BACNET_ADDRESS * dest, /* destination address */
uint8_t * pdu, /* any data to be sent - may be null */
unsigned pdu_len)
{ /* number of bytes of data */
int bytes_sent = 0;
struct mstp_pdu_packet *pkt;
unsigned i = 0;
SHARED_MSTP_DATA *poSharedData;
struct mstp_port_struct_t *mstp_port =
(struct mstp_port_struct_t *) poPort;
if (!mstp_port) {
return 0;
}
poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData;
if (!poSharedData) {
return 0;
}
pkt = (struct mstp_pdu_packet *) Ringbuf_Data_Peek(&poSharedData->PDU_Queue);
if (pkt) {
pkt->data_expecting_reply =
BACNET_DATA_EXPECTING_REPLY(pdu[BACNET_PDU_CONTROL_BYTE_OFFSET]);
for (i = 0; i < pdu_len; i++) {
pkt->buffer[i] = pdu[i];
}
pkt->length = pdu_len;
pkt->destination_mac = dest->mac[0];
if (Ringbuf_Data_Put(&poSharedData->PDU_Queue, (uint8_t *)pkt)) {
bytes_sent = pdu_len;
}
}
return bytes_sent;
}
uint16_t dlmstp_receive(
void *poPort,
BACNET_ADDRESS * src, /* source address */
uint8_t * pdu, /* PDU data */
uint16_t max_pdu, /* amount of space available in the PDU */
unsigned timeout)
{ /* milliseconds to wait for a packet */
uint16_t pdu_len = 0;
struct timespec abstime;
int rv = 0;
SHARED_MSTP_DATA *poSharedData;
struct mstp_port_struct_t *mstp_port =
(struct mstp_port_struct_t *) poPort;
if (!mstp_port) {
return 0;
}
poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData;
if (!poSharedData) {
return 0;
}
(void) max_pdu;
/* see if there is a packet available, and a place
to put the reply (if necessary) and process it */
get_abstime(&abstime, timeout);
rv = sem_timedwait(&poSharedData->Receive_Packet_Flag, &abstime);
if (rv == 0) {
if (poSharedData->Receive_Packet.ready) {
if (poSharedData->Receive_Packet.pdu_len) {
poSharedData->MSTP_Packets++;
if (src) {
memmove(src, &poSharedData->Receive_Packet.address,
sizeof(poSharedData->Receive_Packet.address));
}
if (pdu) {
memmove(pdu, &poSharedData->Receive_Packet.pdu,
sizeof(poSharedData->Receive_Packet.pdu));
}
pdu_len = poSharedData->Receive_Packet.pdu_len;
}
poSharedData->Receive_Packet.ready = false;
}
}
return pdu_len;
}
void *dlmstp_receive_fsm_task(
void *pArg)
{
bool received_frame;
SHARED_MSTP_DATA *poSharedData;
struct mstp_port_struct_t *mstp_port = (struct mstp_port_struct_t *) pArg;
if (!mstp_port) {
return NULL;
}
poSharedData =
(SHARED_MSTP_DATA *) ((struct mstp_port_struct_t *) pArg)->UserData;
if (!poSharedData) {
return NULL;
}
for (;;) {
/* only do receive state machine while we don't have a frame */
if ((mstp_port->ReceivedValidFrame == false) &&
(mstp_port->ReceivedInvalidFrame == false)) {
do {
RS485_Check_UART_Data(mstp_port);
MSTP_Receive_Frame_FSM((volatile struct mstp_port_struct_t *)
pArg);
received_frame = mstp_port->ReceivedValidFrame ||
mstp_port->ReceivedInvalidFrame;
if (received_frame) {
pthread_cond_signal(&poSharedData->Received_Frame_Flag);
break;
}
} while (mstp_port->DataAvailable);
}
}
return NULL;
}
void *dlmstp_master_fsm_task(
void *pArg)
{
uint32_t silence = 0;
bool run_master = false;
SHARED_MSTP_DATA *poSharedData;
struct mstp_port_struct_t *mstp_port = (struct mstp_port_struct_t *) pArg;
if (!mstp_port) {
return NULL;
}
poSharedData =
(SHARED_MSTP_DATA *) ((struct mstp_port_struct_t *) pArg)->UserData;
if (!poSharedData) {
return NULL;
}
for (;;) {
if (mstp_port->ReceivedValidFrame == false &&
mstp_port->ReceivedInvalidFrame == false) {
RS485_Check_UART_Data(mstp_port);
MSTP_Receive_Frame_FSM(mstp_port);
}
if (mstp_port->ReceivedValidFrame || mstp_port->ReceivedInvalidFrame) {
run_master = true;
} else {
silence = mstp_port->SilenceTimer(NULL);
switch (mstp_port->master_state) {
case MSTP_MASTER_STATE_IDLE:
if (silence >= Tno_token)
run_master = true;
break;
case MSTP_MASTER_STATE_WAIT_FOR_REPLY:
if (silence >= poSharedData->Treply_timeout)
run_master = true;
break;
case MSTP_MASTER_STATE_POLL_FOR_MASTER:
if (silence >= poSharedData->Tusage_timeout)
run_master = true;
break;
default:
run_master = true;
break;
}
}
if (run_master) {
if (mstp_port->This_Station <= DEFAULT_MAX_MASTER) {
while (MSTP_Master_Node_FSM(mstp_port)) {
/* do nothing while immediate transitioning */
}
} else if (mstp_port->This_Station < 255) {
MSTP_Slave_Node_FSM(mstp_port);
}
}
}
return NULL;
}
void dlmstp_fill_bacnet_address(
BACNET_ADDRESS * src,
uint8_t mstp_address)
{
int i = 0;
if (mstp_address == MSTP_BROADCAST_ADDRESS) {
/* mac_len = 0 if broadcast address */
src->mac_len = 0;
src->mac[0] = 0;
} else {
src->mac_len = 1;
src->mac[0] = mstp_address;
}
/* fill with 0's starting with index 1; index 0 filled above */
for (i = 1; i < MAX_MAC_LEN; i++) {
src->mac[i] = 0;
}
src->net = 0;
src->len = 0;
for (i = 0; i < MAX_MAC_LEN; i++) {
src->adr[i] = 0;
}
}
/* for the MS/TP state machine to use for putting received data */
uint16_t MSTP_Put_Receive(
volatile struct mstp_port_struct_t *mstp_port)
{
uint16_t pdu_len = 0;
SHARED_MSTP_DATA *poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData;
if (!poSharedData) {
return 0;
}
if (!poSharedData->Receive_Packet.ready) {
/* bounds check - maybe this should send an abort? */
pdu_len = mstp_port->DataLength;
if (pdu_len > sizeof(poSharedData->Receive_Packet.pdu))
pdu_len = sizeof(poSharedData->Receive_Packet.pdu);
memmove((void *) &poSharedData->Receive_Packet.pdu[0],
(void *) &mstp_port->InputBuffer[0], pdu_len);
dlmstp_fill_bacnet_address(&poSharedData->Receive_Packet.address,
mstp_port->SourceAddress);
poSharedData->Receive_Packet.pdu_len = mstp_port->DataLength;
poSharedData->Receive_Packet.ready = true;
sem_post(&poSharedData->Receive_Packet_Flag);
}
return pdu_len;
}
/* for the MS/TP state machine to use for getting data to send */
/* Return: amount of PDU data */
uint16_t MSTP_Get_Send(
volatile struct mstp_port_struct_t * mstp_port,
unsigned timeout)
{ /* milliseconds to wait for a packet */
uint16_t pdu_len = 0;
uint8_t frame_type = 0;
struct mstp_pdu_packet *pkt;
SHARED_MSTP_DATA *poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData;
if (!poSharedData) {
return 0;
}
(void) timeout;
if (Ringbuf_Empty(&poSharedData->PDU_Queue)) {
return 0;
}
pkt = (struct mstp_pdu_packet *) Ringbuf_Peek(&poSharedData->PDU_Queue);
if (pkt->data_expecting_reply) {
frame_type = FRAME_TYPE_BACNET_DATA_EXPECTING_REPLY;
} else {
frame_type = FRAME_TYPE_BACNET_DATA_NOT_EXPECTING_REPLY;
}
/* convert the PDU into the MSTP Frame */
pdu_len = MSTP_Create_Frame(&mstp_port->OutputBuffer[0], /* <-- loading this */
mstp_port->OutputBufferSize, frame_type, pkt->destination_mac,
mstp_port->This_Station, (uint8_t *) & pkt->buffer[0], pkt->length);
(void) Ringbuf_Pop(&poSharedData->PDU_Queue, NULL);
return pdu_len;
}
bool dlmstp_compare_data_expecting_reply(
uint8_t * request_pdu,
uint16_t request_pdu_len,
uint8_t src_address,
uint8_t * reply_pdu,
uint16_t reply_pdu_len,
uint8_t dest_address)
{
uint16_t offset;
/* One way to check the message is to compare NPDU
src, dest, along with the APDU type, invoke id.
Seems a bit overkill */
struct DER_compare_t {
BACNET_NPDU_DATA npdu_data;
BACNET_ADDRESS address;
uint8_t pdu_type;
uint8_t invoke_id;
uint8_t service_choice;
};
struct DER_compare_t request;
struct DER_compare_t reply;
/* unused parameters */
request_pdu_len = request_pdu_len;
reply_pdu_len = reply_pdu_len;
/* decode the request data */
request.address.mac[0] = src_address;
request.address.mac_len = 1;
offset =
npdu_decode(&request_pdu[0], NULL, &request.address,
&request.npdu_data);
if (request.npdu_data.network_layer_message) {
#if PRINT_ENABLED
fprintf(stderr,
"DLMSTP: DER Compare failed: " "Request is Network message.\n");
#endif
return false;
}
request.pdu_type = request_pdu[offset] & 0xF0;
if (request.pdu_type != PDU_TYPE_CONFIRMED_SERVICE_REQUEST) {
#if PRINT_ENABLED
fprintf(stderr,
"DLMSTP: DER Compare failed: " "Not Confirmed Request.\n");
#endif
return false;
}
request.invoke_id = request_pdu[offset + 2];
/* segmented message? */
if (request_pdu[offset] & BIT3) {
request.service_choice = request_pdu[offset + 5];
} else {
request.service_choice = request_pdu[offset + 3];
}
/* decode the reply data */
reply.address.mac[0] = dest_address;
reply.address.mac_len = 1;
offset =
npdu_decode(&reply_pdu[0], &reply.address, NULL, &reply.npdu_data);
if (reply.npdu_data.network_layer_message) {
#if PRINT_ENABLED
fprintf(stderr,
"DLMSTP: DER Compare failed: " "Reply is Network message.\n");
#endif
return false;
}
/* reply could be a lot of things:
confirmed, simple ack, abort, reject, error */
reply.pdu_type = reply_pdu[offset] & 0xF0;
switch (reply.pdu_type) {
case PDU_TYPE_SIMPLE_ACK:
reply.invoke_id = reply_pdu[offset + 1];
reply.service_choice = reply_pdu[offset + 2];
break;
case PDU_TYPE_COMPLEX_ACK:
reply.invoke_id = reply_pdu[offset + 1];
/* segmented message? */
if (reply_pdu[offset] & BIT3) {
reply.service_choice = reply_pdu[offset + 4];
} else {
reply.service_choice = reply_pdu[offset + 2];
}
break;
case PDU_TYPE_ERROR:
reply.invoke_id = reply_pdu[offset + 1];
reply.service_choice = reply_pdu[offset + 2];
break;
case PDU_TYPE_REJECT:
case PDU_TYPE_ABORT:
reply.invoke_id = reply_pdu[offset + 1];
break;
default:
return false;
}
/* these don't have service choice included */
if ((reply.pdu_type == PDU_TYPE_REJECT) ||
(reply.pdu_type == PDU_TYPE_ABORT)) {
if (request.invoke_id != reply.invoke_id) {
#if PRINT_ENABLED
fprintf(stderr,
"DLMSTP: DER Compare failed: " "Invoke ID mismatch.\n");
#endif
return false;
}
} else {
if (request.invoke_id != reply.invoke_id) {
#if PRINT_ENABLED
fprintf(stderr,
"DLMSTP: DER Compare failed: " "Invoke ID mismatch.\n");
#endif
return false;
}
if (request.service_choice != reply.service_choice) {
#if PRINT_ENABLED
fprintf(stderr,
"DLMSTP: DER Compare failed: " "Service choice mismatch.\n");
#endif
return false;
}
}
if (request.npdu_data.protocol_version != reply.npdu_data.protocol_version) {
#if PRINT_ENABLED
fprintf(stderr,
"DLMSTP: DER Compare failed: "
"NPDU Protocol Version mismatch.\n");
#endif
return false;
}
#if 0
/* the NDPU priority doesn't get passed through the stack, and
all outgoing messages have NORMAL priority */
if (request.npdu_data.priority != reply.npdu_data.priority) {
#if PRINT_ENABLED
fprintf(stderr,
"DLMSTP: DER Compare failed: " "NPDU Priority mismatch.\n");
#endif
return false;
}
#endif
if (!bacnet_address_same(&request.address, &reply.address)) {
#if PRINT_ENABLED
fprintf(stderr,
"DLMSTP: DER Compare failed: " "BACnet Address mismatch.\n");
#endif
return false;
}
return true;
}
/* Get the reply to a DATA_EXPECTING_REPLY frame, or nothing */
uint16_t MSTP_Get_Reply(
volatile struct mstp_port_struct_t * mstp_port,
unsigned timeout)
{ /* milliseconds to wait for a packet */
uint16_t pdu_len = 0; /* return value */
bool matched = false;
uint8_t frame_type = 0;
struct mstp_pdu_packet *pkt;
SHARED_MSTP_DATA *poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData;
if (!poSharedData) {
return 0;
}
if (Ringbuf_Empty(&poSharedData->PDU_Queue)) {
return 0;
}
pkt = (struct mstp_pdu_packet *) Ringbuf_Peek(&poSharedData->PDU_Queue);
/* is this the reply to the DER? */
matched =
dlmstp_compare_data_expecting_reply(&mstp_port->InputBuffer[0],
mstp_port->DataLength, mstp_port->SourceAddress,
(uint8_t *) & pkt->buffer[0], pkt->length, pkt->destination_mac);
if (!matched) {
/* Walk the rest of the ring buffer to see if we can find a match */
while (!matched &&
(pkt = (struct mstp_pdu_packet *)Ringbuf_Peek_Next(&poSharedData->PDU_Queue, (uint8_t *)pkt)) != NULL) {
matched =
dlmstp_compare_data_expecting_reply(&mstp_port->InputBuffer[0],
mstp_port->DataLength,
mstp_port->SourceAddress,
(uint8_t *) & pkt->buffer[0],
pkt->length,
pkt->destination_mac);
}
if (!matched) {
/* Still didn't find a match so just bail out */
return 0;
}
}
if (pkt->data_expecting_reply) {
frame_type = FRAME_TYPE_BACNET_DATA_EXPECTING_REPLY;
} else {
frame_type = FRAME_TYPE_BACNET_DATA_NOT_EXPECTING_REPLY;
}
/* convert the PDU into the MSTP Frame */
pdu_len = MSTP_Create_Frame(&mstp_port->OutputBuffer[0], /* <-- loading this */
mstp_port->OutputBufferSize, frame_type, pkt->destination_mac,
mstp_port->This_Station, (uint8_t *) & pkt->buffer[0], pkt->length);
/* This will pop the element no matter where we found it */
(void) Ringbuf_Pop_Element(&poSharedData->PDU_Queue, (uint8_t *)pkt, NULL);
return pdu_len;
}
void dlmstp_set_mac_address(
void *poPort,
uint8_t mac_address)
{
/* SHARED_MSTP_DATA * poSharedData; */
struct mstp_port_struct_t *mstp_port =
(struct mstp_port_struct_t *) poPort;
if (!mstp_port) {
return;
}
/*
poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData;
if(!poSharedData)
{
return;
}
*/
/* Master Nodes can only have address 0-127 */
if (mac_address <= 127) {
mstp_port->This_Station = mac_address;
/* FIXME: implement your data storage */
/* I2C_Write_Byte(
EEPROM_DEVICE_ADDRESS,
mac_address,
EEPROM_MSTP_MAC_ADDR); */
if (mac_address > mstp_port->Nmax_master)
dlmstp_set_max_master(mstp_port, mac_address);
}
return;
}
uint8_t dlmstp_mac_address(
void *poPort)
{
/* SHARED_MSTP_DATA * poSharedData; */
struct mstp_port_struct_t *mstp_port =
(struct mstp_port_struct_t *) poPort;
if (!mstp_port) {
return 0;
}
/* poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData;
if(!poSharedData)
{
return 0;
}
*/
return mstp_port->This_Station;
}
/* This parameter represents the value of the Max_Info_Frames property of */
/* the node's Device object. The value of Max_Info_Frames specifies the */
/* maximum number of information frames the node may send before it must */
/* pass the token. Max_Info_Frames may have different values on different */
/* nodes. This may be used to allocate more or less of the available link */
/* bandwidth to particular nodes. If Max_Info_Frames is not writable in a */
/* node, its value shall be 1. */
void dlmstp_set_max_info_frames(
void *poPort,
uint8_t max_info_frames)
{
/* SHARED_MSTP_DATA * poSharedData; */
struct mstp_port_struct_t *mstp_port =
(struct mstp_port_struct_t *) poPort;
if (!mstp_port) {
return;
}
/*
poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData;
if(!poSharedData)
{
return;
}
*/
if (max_info_frames >= 1) {
mstp_port->Nmax_info_frames = max_info_frames;
/* FIXME: implement your data storage */
/* I2C_Write_Byte(
EEPROM_DEVICE_ADDRESS,
(uint8_t)max_info_frames,
EEPROM_MSTP_MAX_INFO_FRAMES_ADDR); */
}
return;
}
uint8_t dlmstp_max_info_frames(
void *poPort)
{
/* SHARED_MSTP_DATA * poSharedData; */
struct mstp_port_struct_t *mstp_port =
(struct mstp_port_struct_t *) poPort;
if (!mstp_port) {
return 0;
}
/*
poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData;
if(!poSharedData)
{
return 0;
}
*/
return mstp_port->Nmax_info_frames;
}
/* This parameter represents the value of the Max_Master property of the */
/* node's Device object. The value of Max_Master specifies the highest */
/* allowable address for master nodes. The value of Max_Master shall be */
/* less than or equal to 127. If Max_Master is not writable in a node, */
/* its value shall be 127. */
void dlmstp_set_max_master(
void *poPort,
uint8_t max_master)
{
/* SHARED_MSTP_DATA * poSharedData; */
struct mstp_port_struct_t *mstp_port =
(struct mstp_port_struct_t *) poPort;
if (!mstp_port) {
return;
}
/*
poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData;
if(!poSharedData)
{
return;
}
*/
if (max_master <= 127) {
if (mstp_port->This_Station <= max_master) {
mstp_port->Nmax_master = max_master;
/* FIXME: implement your data storage */
/* I2C_Write_Byte(
EEPROM_DEVICE_ADDRESS,
max_master,
EEPROM_MSTP_MAX_MASTER_ADDR); */
}
}
return;
}
uint8_t dlmstp_max_master(
void *poPort)
{
/* SHARED_MSTP_DATA * poSharedData; */
struct mstp_port_struct_t *mstp_port =
(struct mstp_port_struct_t *) poPort;
if (!mstp_port) {
return 0;
}
/*
poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData;
if(!poSharedData)
{
return 0;
}
*/
return mstp_port->Nmax_master;
}
/* RS485 Baud Rate 9600, 19200, 38400, 57600, 115200 */
void dlmstp_set_baud_rate(
void *poPort,
uint32_t baud)
{
SHARED_MSTP_DATA *poSharedData;
struct mstp_port_struct_t *mstp_port =
(struct mstp_port_struct_t *) poPort;
if (!mstp_port) {
return;
}
poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData;
if (!poSharedData) {
return;
}
switch (baud) {
case 9600:
poSharedData->RS485_Baud = B9600;
break;
case 19200:
poSharedData->RS485_Baud = B19200;
break;
case 38400:
poSharedData->RS485_Baud = B38400;
break;
case 57600:
poSharedData->RS485_Baud = B57600;
break;
case 115200:
poSharedData->RS485_Baud = B115200;
break;
default:
break;
}
}
uint32_t dlmstp_baud_rate(
void *poPort)
{
SHARED_MSTP_DATA *poSharedData;
struct mstp_port_struct_t *mstp_port =
(struct mstp_port_struct_t *) poPort;
if (!mstp_port) {
return false;
}
poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData;
if (!poSharedData) {
return false;
}
switch (poSharedData->RS485_Baud) {
case B19200:
return 19200;
case B38400:
return 38400;
case B57600:
return 57600;
case B115200:
return 115200;
default:
case B9600:
return 9600;
}
}
void dlmstp_get_my_address(
void *poPort,
BACNET_ADDRESS * my_address)
{
int i = 0; /* counter */
SHARED_MSTP_DATA *poSharedData;
struct mstp_port_struct_t *mstp_port =
(struct mstp_port_struct_t *) poPort;
if (!mstp_port) {
return;
}
poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData;
if (!poSharedData) {
return;
}
my_address->mac_len = 1;
my_address->mac[0] = mstp_port->This_Station;
my_address->net = 0; /* local only, no routing */
my_address->len = 0;
for (i = 0; i < MAX_MAC_LEN; i++) {
my_address->adr[i] = 0;
}
return;
}
void dlmstp_get_broadcast_address(
BACNET_ADDRESS * dest)
{ /* destination address */
int i = 0; /* counter */
if (dest) {
dest->mac_len = 1;
dest->mac[0] = MSTP_BROADCAST_ADDRESS;
dest->net = BACNET_BROADCAST_NETWORK;
dest->len = 0; /* always zero when DNET is broadcast */
for (i = 0; i < MAX_MAC_LEN; i++) {
dest->adr[i] = 0;
}
}
return;
}
bool dlmstp_init(
void *poPort,
char *ifname)
{
unsigned long hThread = 0;
int rv = 0;
SHARED_MSTP_DATA *poSharedData;
struct mstp_port_struct_t *mstp_port =
(struct mstp_port_struct_t *) poPort;
if (!mstp_port) {
return false;
}
poSharedData = (SHARED_MSTP_DATA *) ((struct mstp_port_struct_t *)
mstp_port)->UserData;
if (!poSharedData) {
return false;
}
poSharedData->RS485_Port_Name = ifname;
/* initialize PDU queue */
Ringbuf_Init(&poSharedData->PDU_Queue,
(uint8_t *) & poSharedData->PDU_Buffer, sizeof(struct mstp_pdu_packet),
MSTP_PDU_PACKET_COUNT);
/* initialize packet queue */
poSharedData->Receive_Packet.ready = false;
poSharedData->Receive_Packet.pdu_len = 0;
rv = sem_init(&poSharedData->Receive_Packet_Flag, 0, 0);
if (rv != 0) {
fprintf(stderr,
"MS/TP Interface: %s\n cannot allocate PThread Condition.\n",
ifname);
exit(1);
}
struct termios newtio;
printf("RS485: Initializing %s", poSharedData->RS485_Port_Name);
/*
Open device for reading and writing.
Blocking mode - more CPU effecient
*/
poSharedData->RS485_Handle =
open(poSharedData->RS485_Port_Name,
O_RDWR | O_NOCTTY | O_NONBLOCK /*| O_NDELAY */ );
if (poSharedData->RS485_Handle < 0) {
perror(poSharedData->RS485_Port_Name);
exit(-1);
}
#if 0
/* non blocking for the read */
fcntl(poSharedData->RS485_Handle, F_SETFL, FNDELAY);
#else
/* efficient blocking for the read */
fcntl(poSharedData->RS485_Handle, F_SETFL, 0);
#endif
/* save current serial port settings */
tcgetattr(poSharedData->RS485_Handle, &poSharedData->RS485_oldtio);
/* clear struct for new port settings */
bzero(&newtio, sizeof(newtio));
/*
BAUDRATE: Set bps rate. You could also use cfsetispeed and cfsetospeed.
CRTSCTS : output hardware flow control (only used if the cable has
all necessary lines. See sect. 7 of Serial-HOWTO)
CLOCAL : local connection, no modem contol
CREAD : enable receiving characters
*/
newtio.c_cflag =
poSharedData->RS485_Baud | poSharedData->RS485MOD | CLOCAL | CREAD;
/* Raw input */
newtio.c_iflag = 0;
/* Raw output */
newtio.c_oflag = 0;
/* no processing */
newtio.c_lflag = 0;
/* activate the settings for the port after flushing I/O */
tcsetattr(poSharedData->RS485_Handle, TCSAFLUSH, &newtio);
/* flush any data waiting */
usleep(200000);
tcflush(poSharedData->RS485_Handle, TCIOFLUSH);
/* ringbuffer */
FIFO_Init(&poSharedData->Rx_FIFO, poSharedData->Rx_Buffer,
sizeof(poSharedData->Rx_Buffer));
printf("=success!\n");
mstp_port->InputBuffer = &poSharedData->RxBuffer[0];
mstp_port->InputBufferSize = sizeof(poSharedData->RxBuffer);
mstp_port->OutputBuffer = &poSharedData->TxBuffer[0];
mstp_port->OutputBufferSize = sizeof(poSharedData->TxBuffer);
gettimeofday(&poSharedData->start, NULL);
mstp_port->SilenceTimer = Timer_Silence;
mstp_port->SilenceTimerReset = Timer_Silence_Reset;
MSTP_Init(mstp_port);
#if PRINT_ENABLED
fprintf(stderr, "MS/TP MAC: %02X\n", mstp_port->This_Station);
fprintf(stderr, "MS/TP Max_Master: %02X\n", mstp_port->Nmax_master);
fprintf(stderr, "MS/TP Max_Info_Frames: %u\n",
mstp_port->Nmax_info_frames);
#endif
rv = pthread_create(&hThread, NULL, dlmstp_master_fsm_task, mstp_port);
if (rv != 0) {
fprintf(stderr, "Failed to start Master Node FSM task\n");
}
return true;
}
+207
View File
@@ -0,0 +1,207 @@
/**************************************************************************
*
* Copyright (C) 2012 Steve Karg <skarg@users.sourceforge.net>
*
* 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.
*********************************************************************/
#ifndef DLMSTP_LINUX_H
#define DLMSTP_LINUX_H
#include "mstp.h"
/*#include "dlmstp.h" */
#include "bits/pthreadtypes.h"
#include <semaphore.h>
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#include "bacdef.h"
#include "npdu.h"
#include <termios.h>
#include "fifo.h"
#include "ringbuf.h"
/* defines specific to MS/TP */
/* preamble+type+dest+src+len+crc8+crc16 */
#define MAX_HEADER (2+1+1+1+2+1+2)
#define MAX_MPDU (MAX_HEADER+MAX_PDU)
/* count must be a power of 2 for ringbuf library */
#ifndef MSTP_PDU_PACKET_COUNT
#define MSTP_PDU_PACKET_COUNT 8
#endif
typedef struct dlmstp_packet {
bool ready; /* true if ready to be sent or received */
BACNET_ADDRESS address; /* source address */
uint8_t frame_type; /* type of message */
uint16_t pdu_len; /* packet length */
uint8_t pdu[MAX_MPDU]; /* packet */
} DLMSTP_PACKET;
/* data structure for MS/TP PDU Queue */
struct mstp_pdu_packet {
bool data_expecting_reply;
uint8_t destination_mac;
uint16_t length;
uint8_t buffer[MAX_MPDU];
};
typedef struct shared_mstp_data {
/* Number of MS/TP Packets Rx/Tx */
uint16_t MSTP_Packets;
/* packet queues */
DLMSTP_PACKET Receive_Packet;
DLMSTP_PACKET Transmit_Packet;
/*
RT_SEM Receive_Packet_Flag;
*/
sem_t Receive_Packet_Flag;
/* mechanism to wait for a frame in state machine */
/*
RT_COND Received_Frame_Flag;
RT_MUTEX Received_Frame_Mutex;
*/
pthread_cond_t Received_Frame_Flag;
pthread_mutex_t Received_Frame_Mutex;
pthread_cond_t Master_Done_Flag;
pthread_mutex_t Master_Done_Mutex;
/* buffers needed by mstp port struct */
uint8_t TxBuffer[MAX_MPDU];
uint8_t RxBuffer[MAX_MPDU];
/* The minimum time without a DataAvailable or ReceiveError event */
/* that a node must wait for a station to begin replying to a */
/* confirmed request: 255 milliseconds. (Implementations may use */
/* larger values for this timeout, not to exceed 300 milliseconds.) */
uint16_t Treply_timeout;
/* The minimum time without a DataAvailable or ReceiveError event that a */
/* node must wait for a remote node to begin using a token or replying to */
/* a Poll For Master frame: 20 milliseconds. (Implementations may use */
/* larger values for this timeout, not to exceed 100 milliseconds.) */
uint8_t Tusage_timeout;
/* Timer that indicates line silence - and functions */
uint16_t SilenceTime;
/* handle returned from open() */
int RS485_Handle;
/* baudrate settings are defined in <asm/termbits.h>, which is
included by <termios.h> */
unsigned int RS485_Baud;
/* serial port name, /dev/ttyS0,
/dev/ttyUSB0 for USB->RS485 from B&B Electronics USOPTL4 */
char *RS485_Port_Name;
/* serial I/O settings */
struct termios RS485_oldtio;
/* some terminal I/O have RS-485 specific functionality */
tcflag_t RS485MOD;
/* Ring buffer for incoming bytes, in order to speed up the receiving. */
FIFO_BUFFER Rx_FIFO;
/* buffer size needs to be a power of 2 */
uint8_t Rx_Buffer[4096];
struct timeval start;
RING_BUFFER PDU_Queue;
struct mstp_pdu_packet PDU_Buffer[MSTP_PDU_PACKET_COUNT];
} SHARED_MSTP_DATA;
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
bool dlmstp_init(
void *poShared,
char *ifname);
void dlmstp_reset(
void *poShared);
void dlmstp_cleanup(
void *poShared);
/* returns number of bytes sent on success, negative on failure */
int dlmstp_send_pdu(
void *poShared,
BACNET_ADDRESS * dest, /* destination address */
uint8_t * pdu, /* any data to be sent - may be null */
unsigned pdu_len); /* number of bytes of data */
/* returns the number of octets in the PDU, or zero on failure */
uint16_t dlmstp_receive(
void *poShared,
BACNET_ADDRESS * src, /* source address */
uint8_t * pdu, /* PDU data */
uint16_t max_pdu, /* amount of space available in the PDU */
unsigned timeout); /* milliseconds to wait for a packet */
/* This parameter represents the value of the Max_Info_Frames property of */
/* the node's Device object. The value of Max_Info_Frames specifies the */
/* maximum number of information frames the node may send before it must */
/* pass the token. Max_Info_Frames may have different values on different */
/* nodes. This may be used to allocate more or less of the available link */
/* bandwidth to particular nodes. If Max_Info_Frames is not writable in a */
/* node, its value shall be 1. */
void dlmstp_set_max_info_frames(
void *poShared,
uint8_t max_info_frames);
uint8_t dlmstp_max_info_frames(
void *poShared);
/* This parameter represents the value of the Max_Master property of the */
/* node's Device object. The value of Max_Master specifies the highest */
/* allowable address for master nodes. The value of Max_Master shall be */
/* less than or equal to 127. If Max_Master is not writable in a node, */
/* its value shall be 127. */
void dlmstp_set_max_master(
void *poShared,
uint8_t max_master);
uint8_t dlmstp_max_master(
void *poShared);
/* MAC address 0-127 */
void dlmstp_set_mac_address(
void *poShared,
uint8_t my_address);
uint8_t dlmstp_mac_address(
void *poShared);
void dlmstp_get_my_address(
void *poShared,
BACNET_ADDRESS * my_address);
void dlmstp_get_broadcast_address(
BACNET_ADDRESS * dest); /* destination address */
/* RS485 Baud Rate 9600, 19200, 38400, 57600, 115200 */
void dlmstp_set_baud_rate(
void *poShared,
uint32_t baud);
uint32_t dlmstp_baud_rate(
void *poShared);
void dlmstp_fill_bacnet_address(
BACNET_ADDRESS * src,
uint8_t mstp_address);
bool dlmstp_sole_master(
void);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /*DLMSTP_LINUX_H */
+458
View File
@@ -0,0 +1,458 @@
/*####COPYRIGHTBEGIN####
-------------------------------------------
Copyright (C) 2005 Steve Karg
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to:
The Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA.
As a special exception, if other files instantiate templates or
use macros or inline functions from this file, or you compile
this file and link it with other works to produce a work based
on this file, this file does not by itself cause the resulting
work to be covered by the GNU General Public License. However
the source code for this file must still be made available in
accordance with section (3) of the GNU General Public License.
This exception does not invalidate any other reasons why a work
based on this file might be covered by the GNU General Public
License.
-------------------------------------------
####COPYRIGHTEND####*/
#include <stdint.h> /* for standard integer types uint8_t etc. */
#include <stdbool.h> /* for the standard bool type. */
#include "net.h"
#include "bacdef.h"
#include "ethernet.h"
#include "bacint.h"
/** @file linux/ethernet.c Provides Linux-specific functions for BACnet/Ethernet. */
/* commonly used comparison address for ethernet */
uint8_t Ethernet_Broadcast[MAX_MAC_LEN] =
{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
/* commonly used empty address for ethernet quick compare */
uint8_t Ethernet_Empty_MAC[MAX_MAC_LEN] = { 0, 0, 0, 0, 0, 0 };
/* my local device data - MAC address */
uint8_t Ethernet_MAC_Address[MAX_MAC_LEN] = { 0 };
static int eth802_sockfd = -1; /* 802.2 file handle */
static struct sockaddr eth_addr = { 0 }; /* used for binding 802.2 */
bool ethernet_valid(
void)
{
return (eth802_sockfd >= 0);
}
void ethernet_cleanup(
void)
{
if (ethernet_valid())
close(eth802_sockfd);
eth802_sockfd = -1;
return;
}
#if 0
/*----------------------------------------------------------------------
Portable function to set a socket into nonblocking mode.
Calling this on a socket causes all future read() and write() calls on
that socket to do only as much as they can immediately, and return
without waiting.
If no data can be read or written, they return -1 and set errno
to EAGAIN (or EWOULDBLOCK).
Thanks to Bjorn Reese for this code.
----------------------------------------------------------------------*/
int setNonblocking(
int fd)
{
int flags;
if (-1 == (flags = fcntl(fd, F_GETFL, 0)))
flags = 0;
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
#endif
/* opens an 802.2 socket to receive and send packets */
static int ethernet_bind(
struct sockaddr *eth_addr,
char *interface_name)
{
int sock_fd = -1; /* return value */
#if 0
int sockopt = 0;
#endif
int uid = 0;
fprintf(stderr, "ethernet: opening \"%s\"\n", interface_name);
/* check to see if we are being run as root */
uid = getuid();
if (uid != 0) {
fprintf(stderr,
"ethernet: Unable to open an 802.2 socket. "
"Try running with root priveleges.\n");
return sock_fd;
}
/* note: on some systems you may have to add or enable in */
/* modules.conf (or in modutils/alias on Debian with update-modules) */
/* alias net-pf-17 af_packet */
/* Then follow it by: # modprobe af_packet */
/* Note: PF_INET/SOCK_PACKET has been replaced with
PF_PACKET/(SOCK_PACKET, SOCK_DGRAM, SOCK_RAW). */
/* Attempt to open the socket for 802.2 ethernet frames */
if ((sock_fd = socket(PF_INET, SOCK_PACKET, htons(ETH_P_802_2))) < 0) {
/* Error occured */
fprintf(stderr, "ethernet: Error opening socket: %s\n",
strerror(errno));
fprintf(stderr,
"You might need to add the following to modules.conf\n"
"(or in /etc/modutils/alias on Debian with update-modules):\n"
"alias net-pf-17 af_packet\n"
"Also, add af_packet to /etc/modules.\n" "Then follow it by:\n"
"# modprobe af_packet\n");
exit(-1);
}
#if 0
/* It is very advisable to do a IP_HDRINCL call, to make sure
that the kernel knows the header is included in the data,
and doesn't insert its own header into the packet before our data */
if (setsockopt(sock_fd, IPPROTO_IP, IP_HDRINCL, &sockopt,
sizeof(sockopt)) < 0) {
printf("Warning: Cannot set HDRINCL!\n");
}
#endif
/* Bind the socket to an address */
eth_addr->sa_family = PF_INET;
/* Clear the memory before copying */
memset(eth_addr->sa_data, '\0', sizeof(eth_addr->sa_data));
/* Strcpy the interface name into the address */
strncpy(eth_addr->sa_data, interface_name, sizeof(eth_addr->sa_data) - 1);
fprintf(stderr, "ethernet: binding \"%s\"\n", eth_addr->sa_data);
/* Attempt to bind the socket to the interface */
if (bind(sock_fd, eth_addr, sizeof(struct sockaddr)) != 0) {
/* Bind problem, close socket and return */
fprintf(stderr, "ethernet: Unable to bind 802.2 socket : %s\n",
strerror(errno));
fprintf(stderr,
"You might need to add the following to modules.conf\n"
"(or in /etc/modutils/alias on Debian with update-modules):\n"
"alias net-pf-17 af_packet\n"
"Also, add af_packet to /etc/modules.\n" "Then follow it by:\n"
"# modprobe af_packet\n");
/* Close the socket */
close(sock_fd);
exit(-1);
}
atexit(ethernet_cleanup);
return sock_fd;
}
/* function to find the local ethernet MAC address */
static int get_local_hwaddr(
const char *ifname,
unsigned char *mac)
{
struct ifreq ifr;
int fd;
int rv; /* return value - error value from df or ioctl call */
/* determine the local MAC address */
strcpy(ifr.ifr_name, ifname);
fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
if (fd < 0)
rv = fd;
else {
rv = ioctl(fd, SIOCGIFHWADDR, &ifr);
if (rv >= 0) /* worked okay */
memcpy(mac, ifr.ifr_hwaddr.sa_data, IFHWADDRLEN);
}
return rv;
}
bool ethernet_init(
char *interface_name)
{
if (interface_name) {
get_local_hwaddr(interface_name, Ethernet_MAC_Address);
eth802_sockfd = ethernet_bind(&eth_addr, interface_name);
} else {
get_local_hwaddr("eth0", Ethernet_MAC_Address);
eth802_sockfd = ethernet_bind(&eth_addr, "eth0");
}
return ethernet_valid();
}
int ethernet_send(
uint8_t * mtu,
int mtu_len)
{
int bytes = 0;
/* Send the packet */
bytes =
sendto(eth802_sockfd, &mtu, mtu_len, 0, (struct sockaddr *) &eth_addr,
sizeof(struct sockaddr));
/* did it get sent? */
if (bytes < 0)
fprintf(stderr, "ethernet: Error sending packet: %s\n",
strerror(errno));
return bytes;
}
/* function to send a packet out the 802.2 socket */
/* returns number of bytes sent on success, negative on failure */
int ethernet_send_pdu(
BACNET_ADDRESS * dest, /* destination address */
BACNET_NPDU_DATA * npdu_data, /* network information */
uint8_t * pdu, /* any data to be sent - may be null */
unsigned pdu_len)
{ /* number of bytes of data */
int i = 0; /* counter */
int bytes = 0;
BACNET_ADDRESS src = { 0 }; /* source address for npdu */
uint8_t mtu[MAX_MPDU] = { 0 }; /* our buffer */
int mtu_len = 0;
(void) npdu_data;
/* load the BACnet address for NPDU data */
for (i = 0; i < 6; i++) {
src.mac[i] = Ethernet_MAC_Address[i];
src.mac_len++;
}
/* don't waste time if the socket is not valid */
if (eth802_sockfd < 0) {
fprintf(stderr, "ethernet: 802.2 socket is invalid!\n");
return -1;
}
/* load destination ethernet MAC address */
if (dest->mac_len == 6) {
for (i = 0; i < 6; i++) {
mtu[i] = dest->mac[i];
}
} else {
fprintf(stderr, "ethernet: invalid destination MAC address!\n");
return -2;
}
/* load source ethernet MAC address */
if (src.mac_len == 6) {
for (i = 0; i < 6; i++) {
mtu[6 + i] = src.mac[i];
}
} else {
fprintf(stderr, "ethernet: invalid source MAC address!\n");
return -3;
}
/* Logical PDU portion */
mtu[14] = 0x82; /* DSAP for BACnet */
mtu[15] = 0x82; /* SSAP for BACnet */
mtu[16] = 0x03; /* Control byte in header */
mtu_len = 17;
if ((mtu_len + pdu_len) > MAX_MPDU) {
fprintf(stderr, "ethernet: PDU is too big to send!\n");
return -4;
}
memcpy(&mtu[mtu_len], pdu, pdu_len);
mtu_len += pdu_len;
/* packet length - only the logical portion, not the address */
encode_unsigned16(&mtu[12], 3 + pdu_len);
/* Send the packet */
bytes =
sendto(eth802_sockfd, &mtu, mtu_len, 0, (struct sockaddr *) &eth_addr,
sizeof(struct sockaddr));
/* did it get sent? */
if (bytes < 0)
fprintf(stderr, "ethernet: Error sending packet: %s\n",
strerror(errno));
return bytes;
}
/* receives an 802.2 framed packet */
/* returns the number of octets in the PDU, or zero on failure */
uint16_t ethernet_receive(
BACNET_ADDRESS * src, /* source address */
uint8_t * pdu, /* PDU data */
uint16_t max_pdu, /* amount of space available in the PDU */
unsigned timeout)
{ /* number of milliseconds to wait for a packet */
int received_bytes;
uint8_t buf[MAX_MPDU] = { 0 }; /* data */
uint16_t pdu_len = 0; /* return value */
fd_set read_fds;
int max;
struct timeval select_timeout;
/* Make sure the socket is open */
if (eth802_sockfd <= 0)
return 0;
/* we could just use a non-blocking socket, but that consumes all
the CPU time. We can use a timeout; it is only supported as
a select. */
if (timeout >= 1000) {
select_timeout.tv_sec = timeout / 1000;
select_timeout.tv_usec =
1000 * (timeout - select_timeout.tv_sec * 1000);
} else {
select_timeout.tv_sec = 0;
select_timeout.tv_usec = 1000 * timeout;
}
FD_ZERO(&read_fds);
FD_SET(eth802_sockfd, &read_fds);
max = eth802_sockfd;
if (select(max + 1, &read_fds, NULL, NULL, &select_timeout) > 0)
received_bytes = read(eth802_sockfd, &buf[0], sizeof(buf));
else
return 0;
/* See if there is a problem */
if (received_bytes < 0) {
/* EAGAIN Non-blocking I/O has been selected */
/* using O_NONBLOCK and no data */
/* was immediately available for reading. */
if (errno != EAGAIN)
fprintf(stderr, "ethernet: Read error in receiving packet: %s\n",
strerror(errno));
return 0;
}
if (received_bytes == 0)
return 0;
/* the signature of an 802.2 BACnet packet */
if ((buf[14] != 0x82) && (buf[15] != 0x82)) {
/*fprintf(stderr,"ethernet: Non-BACnet packet\n"); */
return 0;
}
/* copy the source address */
src->mac_len = 6;
memmove(src->mac, &buf[6], 6);
/* check destination address for when */
/* the Ethernet card is in promiscious mode */
if ((memcmp(&buf[0], Ethernet_MAC_Address, 6) != 0)
&& (memcmp(&buf[0], Ethernet_Broadcast, 6) != 0)) {
/*fprintf(stderr, "ethernet: This packet isn't for us\n"); */
return 0;
}
(void) decode_unsigned16(&buf[12], &pdu_len);
pdu_len -= 3 /* DSAP, SSAP, LLC Control */ ;
/* copy the buffer into the PDU */
if (pdu_len < max_pdu)
memmove(&pdu[0], &buf[17], pdu_len);
/* ignore packets that are too large */
else
pdu_len = 0;
return pdu_len;
}
void ethernet_set_my_address(
BACNET_ADDRESS * my_address)
{
int i = 0;
for (i = 0; i < 6; i++) {
Ethernet_MAC_Address[i] = my_address->mac[i];
}
return;
}
void ethernet_get_my_address(
BACNET_ADDRESS * my_address)
{
int i = 0;
my_address->mac_len = 0;
for (i = 0; i < 6; i++) {
my_address->mac[i] = Ethernet_MAC_Address[i];
my_address->mac_len++;
}
my_address->net = 0; /* DNET=0 is local only, no routing */
my_address->len = 0;
for (i = 0; i < MAX_MAC_LEN; i++) {
my_address->adr[i] = 0;
}
return;
}
void ethernet_get_broadcast_address(
BACNET_ADDRESS * dest)
{ /* destination address */
int i = 0; /* counter */
if (dest) {
for (i = 0; i < 6; i++) {
dest->mac[i] = Ethernet_Broadcast[i];
}
dest->mac_len = 6;
dest->net = BACNET_BROADCAST_NETWORK;
dest->len = 0; /* always zero when DNET is broadcast */
for (i = 0; i < MAX_MAC_LEN; i++) {
dest->adr[i] = 0;
}
}
return;
}
void ethernet_debug_address(
const char *info,
BACNET_ADDRESS * dest)
{
int i = 0; /* counter */
if (info)
fprintf(stderr, "%s", info);
if (dest) {
fprintf(stderr, "Address:\n");
fprintf(stderr, " MAC Length=%d\n", dest->mac_len);
fprintf(stderr, " MAC Address=");
for (i = 0; i < MAX_MAC_LEN; i++) {
fprintf(stderr, "%02X ", (unsigned) dest->mac[i]);
}
fprintf(stderr, "\n");
fprintf(stderr, " Net=%hu\n", dest->net);
fprintf(stderr, " Len=%d\n", dest->len);
fprintf(stderr, " Adr=");
for (i = 0; i < MAX_MAC_LEN; i++) {
fprintf(stderr, "%02X ", (unsigned) dest->adr[i]);
}
fprintf(stderr, "\n");
}
return;
}
+315
View File
@@ -0,0 +1,315 @@
/*####COPYRIGHTBEGIN####
-------------------------------------------
Copyright (C) 2008 Steve Karg
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to:
The Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307
USA.
As a special exception, if other files instantiate templates or
use macros or inline functions from this file, or you compile
this file and link it with other works to produce a work based
on this file, this file does not by itself cause the resulting
work to be covered by the GNU General Public License. However
the source code for this file must still be made available in
accordance with section (3) of the GNU General Public License.
This exception does not invalidate any other reasons why a work
based on this file might be covered by the GNU General Public
License.
-------------------------------------------
####COPYRIGHTEND####*/
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
/* OS specific include*/
#include "net.h"
#include "timer.h"
/* local includes */
#include "bytes.h"
#include "rs485.h"
#include "crc.h"
#include "mstp.h"
#include "dlmstp.h"
#include "mstptext.h"
#include "bacint.h"
/** @file linux/mstpsnap.c Example application testing BACnet MS/TP on Linux. */
#ifndef max
#define max(a,b) (((a) (b)) ? (a) : (b))
#define min(a,b) (((a) < (b)) ? (a) : (b))
#endif
/* local port data - shared with RS-485 */
static volatile struct mstp_port_struct_t MSTP_Port;
/* buffers needed by mstp port struct */
static uint8_t RxBuffer[MAX_MPDU];
static uint8_t TxBuffer[MAX_MPDU];
static uint32_t Timer_Silence(
void *pArg)
{
uint32_t delta_time = 0;
delta_time = timer_milliseconds(TIMER_SILENCE);
if (delta_time > 0xFFFF) {
delta_time = 0xFFFF;
}
return delta_time;
}
static void Timer_Silence_Reset(
void *pArg)
{
timer_reset(TIMER_SILENCE);
}
/* functions used by the MS/TP state machine to put or get data */
uint16_t MSTP_Put_Receive(
volatile struct mstp_port_struct_t *mstp_port)
{
(void) mstp_port;
return 0;
}
/* for the MS/TP state machine to use for getting data to send */
/* Return: amount of PDU data */
uint16_t MSTP_Get_Send(
volatile struct mstp_port_struct_t * mstp_port,
unsigned timeout)
{ /* milliseconds to wait for a packet */
(void) mstp_port;
(void) timeout;
return 0;
}
uint16_t MSTP_Get_Reply(
volatile struct mstp_port_struct_t * mstp_port,
unsigned timeout)
{ /* milliseconds to wait for a packet */
(void) mstp_port;
(void) timeout;
return 0;
}
static int network_init(
const char *name,
int protocol)
{
/* check to see if we are being run as root */
if (getuid() != 0) {
fprintf(stderr, "Requires root priveleges.\n");
return -1;
}
int sockfd = socket(PF_PACKET, SOCK_RAW, htons(protocol));
if (sockfd == -1) {
perror("Unable to create socket");
return sockfd;
}
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, name, strlen(name));
if (ioctl(sockfd, SIOCGIFINDEX, &ifr) == -1) {
perror("Unable to get interface index");
return -1;
}
struct sockaddr_ll sll;
memset(&sll, 0, sizeof(sll));
sll.sll_family = AF_PACKET;
sll.sll_ifindex = ifr.ifr_ifindex;
sll.sll_protocol = htons(protocol);
if (bind(sockfd, (struct sockaddr *) &sll, sizeof(sll)) == -1) {
perror("Unable to bind socket");
return -1;
}
return sockfd;
}
static void snap_received_packet(
volatile struct mstp_port_struct_t *mstp_port,
int sockfd)
{
uint16_t mtu_len = 0; /* number of octets of packet saved in file */
unsigned i = 0; /* counter */
static uint8_t mtu[1500] = { 0 };
uint16_t max_data = 0;
mtu[0] = 0;
mtu[1] = 0;
mtu[2] = 0;
mtu[3] = 0;
mtu[4] = 0;
mtu[5] = mstp_port->DestinationAddress;
mtu[6] = 0;
mtu[7] = 0;
mtu[8] = 0;
mtu[9] = 0;
mtu[10] = 0;
mtu[11] = mstp_port->SourceAddress;
/* length - 12, 13 */
mtu[14] = 0xaa; /* DSAP for SNAP */
mtu[15] = 0xaa; /* SSAP for SNAP */
mtu[16] = 0x03; /* Control Field for SNAP */
mtu[17] = 0x00; /* Organization Code: Cimetrics */
mtu[18] = 0x10; /* Organization Code: Cimetrics */
mtu[19] = 0x90; /* Organization Code: Cimetrics */
mtu[20] = 0x00; /* Protocol ID */
mtu[21] = 0x01; /* Protocol ID */
mtu[22] = 0x00; /* delta time */
mtu[23] = 0x00; /* delta time */
mtu[24] = 0x80; /* unknown byte */
mtu[25] = mstp_port->FrameType;
mtu[26] = mstp_port->DestinationAddress;
mtu[27] = mstp_port->SourceAddress;
mtu[28] = HI_BYTE(mstp_port->DataLength);
mtu[29] = LO_BYTE(mstp_port->DataLength);
mtu[30] = mstp_port->HeaderCRCActual;
mtu_len = 31;
if (mstp_port->DataLength) {
max_data = min(mstp_port->InputBufferSize, mstp_port->DataLength);
for (i = 0; i < max_data; i++) {
mtu[31 + i] = mstp_port->InputBuffer[i];
}
mtu[31 + max_data] = mstp_port->DataCRCActualMSB;
mtu[31 + max_data + 1] = mstp_port->DataCRCActualLSB;
mtu_len += (max_data + 2);
}
/* Ethernet length is data only - not address or length bytes */
encode_unsigned16(&mtu[12], mtu_len - 14);
(void) write(sockfd, &mtu[0], mtu_len);
}
static void cleanup(
void)
{
}
#if (!defined(_WIN32))
static void sig_int(
int signo)
{
(void) signo;
cleanup();
exit(0);
}
void signal_init(
void)
{
signal(SIGINT, sig_int);
signal(SIGHUP, sig_int);
signal(SIGTERM, sig_int);
}
#endif
/* simple test to packetize the data and print it */
int main(
int argc,
char *argv[])
{
volatile struct mstp_port_struct_t *mstp_port;
long my_baud = 38400;
uint32_t packet_count = 0;
int sockfd = -1;
char *my_interface = "eth0";
/* mimic our pointer in the state machine */
mstp_port = &MSTP_Port;
if ((argc > 1) && (strcmp(argv[1], "--help") == 0)) {
printf("mstsnap [serial] [baud] [network]\r\n"
"Captures MS/TP packets from a serial interface\r\n"
"and sends them to a network interface using SNAP \r\n"
"protocol packets (mimics Cimetrics U+4 packet).\r\n" "\r\n"
"Command line options:\r\n" "[serial] - serial interface.\r\n"
" defaults to /dev/ttyUSB0.\r\n"
"[baud] - baud rate. 9600, 19200, 38400, 57600, 115200\r\n"
" defaults to 38400.\r\n" "[network] - network interface.\r\n"
" defaults to eth0.\r\n" "");
return 0;
}
/* initialize our interface */
if (argc > 1) {
RS485_Set_Interface(argv[1]);
}
if (argc > 2) {
my_baud = strtol(argv[2], NULL, 0);
}
if (argc > 3) {
my_interface = argv[3];
}
sockfd = network_init(my_interface, ETH_P_ALL);
if (sockfd == -1) {
return 1;
}
RS485_Set_Baud_Rate(my_baud);
RS485_Initialize();
MSTP_Port.InputBuffer = &RxBuffer[0];
MSTP_Port.InputBufferSize = sizeof(RxBuffer);
MSTP_Port.OutputBuffer = &TxBuffer[0];
MSTP_Port.OutputBufferSize = sizeof(TxBuffer);
MSTP_Port.This_Station = 127;
MSTP_Port.Nmax_info_frames = 1;
MSTP_Port.Nmax_master = 127;
MSTP_Port.SilenceTimer = Timer_Silence;
MSTP_Port.SilenceTimerReset = Timer_Silence_Reset;
MSTP_Init(mstp_port);
fprintf(stdout, "mstpcap: Using %s for capture at %ld bps.\n",
RS485_Interface(), (long) RS485_Get_Baud_Rate());
atexit(cleanup);
#if defined(_WIN32)
SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_PROCESSED_INPUT);
SetConsoleCtrlHandler((PHANDLER_ROUTINE) CtrlCHandler, TRUE);
#else
signal_init();
#endif
/* run forever */
for (;;) {
RS485_Check_UART_Data(mstp_port);
MSTP_Receive_Frame_FSM(mstp_port);
/* process the data portion of the frame */
if (mstp_port->ReceivedValidFrame) {
mstp_port->ReceivedValidFrame = false;
snap_received_packet(mstp_port, sockfd);
packet_count++;
} else if (mstp_port->ReceivedInvalidFrame) {
mstp_port->ReceivedInvalidFrame = false;
fprintf(stderr, "ReceivedInvalidFrame\n");
snap_received_packet(mstp_port, sockfd);
packet_count++;
}
if (!(packet_count % 100)) {
fprintf(stdout, "\r%hu packets", packet_count);
}
}
return 0;
}
+65
View File
@@ -0,0 +1,65 @@
#Makefile to build BACnet Application for the Linux Port
# Compiler to use
CC = gcc
# Executable file name
TARGET = mstpsnap
# Configure the BACnet Datalink Layer
BACDL_DEFINE = -DBACDL_MSTP
BACNET_DEFINES = -DPRINT_ENABLED=1 -DBACAPP_ALL -DBACFILE
DEFINES = $(BACNET_DEFINES) $(BACDL_DEFINE)
# Directories
BACNET_PORT = linux
BACNET_PORT_DIR = .
BACNET_SOURCE_DIR = ../../src
BACNET_INCLUDE = ../../include
# Compiler Setup
INCLUDES = -I$(BACNET_INCLUDE) -I$(BACNET_PORT_DIR)
ifeq (${BACNET_PORT},linux)
PFLAGS = -pthread
TARGET_BIN = ${TARGET}
LIBRARIES=-lc,-lgcc,-lrt,-lm
endif
ifeq (${BACNET_PORT},win32)
TARGET_BIN = ${TARGET}.exe
LIBRARIES=-lws2_32,-lgcc,-lm,-liphlpapi
endif
#DEBUGGING = -g
#OPTIMIZATION = -O0
OPTIMIZATION = -Os
CFLAGS = -Wall $(DEBUGGING) $(OPTIMIZATION) $(INCLUDES) $(DEFINES) -fdata-sections -ffunction-sections
LFLAGS = -Wl,-Map=$(TARGET).map,$(LIBRARIES),--gc-sections
SRCS = mstpsnap.c \
${BACNET_PORT_DIR}/rs485.c \
${BACNET_PORT_DIR}/timer.c \
${BACNET_SOURCE_DIR}/bacint.c \
${BACNET_SOURCE_DIR}/mstp.c \
${BACNET_SOURCE_DIR}/fifo.c \
${BACNET_SOURCE_DIR}/mstptext.c \
${BACNET_SOURCE_DIR}/debug.c \
${BACNET_SOURCE_DIR}/indtext.c \
${BACNET_SOURCE_DIR}/crc.c
OBJS = ${SRCS:.c=.o}
all: ${TARGET_BIN}
size ${TARGET_BIN}
${TARGET_BIN}: ${OBJS}
${CC} ${PFLAGS} ${OBJS} ${LFLAGS} -o $@
.c.o:
${CC} -c ${CFLAGS} $*.c -o $@
depend:
rm -f .depend
${CC} -MM ${CFLAGS} *.c >> .depend
clean:
rm -f core ${TARGET_BIN} ${OBJS} $(TARGET).map
include: .depend
+110
View File
@@ -0,0 +1,110 @@
/**************************************************************************
*
* Copyright (C) 2005 Steve Karg <skarg@users.sourceforge.net>
*
* 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.
*
*********************************************************************/
#ifndef NET_H
#define NET_H
/* common unix sockets headers needed */
#include <sys/types.h> /* basic system data types */
#include <sys/time.h> /* timeval{} for select() */
#include <time.h> /* timespec{} for pselect() */
#include <netinet/in.h> /* sockaddr_in{} and other Internet defns */
#include <arpa/inet.h> /* inet(3) functions */
#include <fcntl.h> /* for nonblocking */
#include <netdb.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h> /* for S_xxx file mode constants */
#include <sys/uio.h> /* for iovec{} and readv/writev */
#include <unistd.h>
#include <sys/wait.h>
#include <sys/un.h> /* for Unix domain sockets */
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h> /* for convenience */
#endif
#ifdef HAVE_POLL_H
#include <poll.h> /* for convenience */
#endif
#ifdef HAVE_STRINGS_H
#include <strings.h> /* for convenience */
#endif
/* Three headers are normally needed for socket/file ioctl's:
* <sys/ioctl.h>, <sys/filio.h>, and <sys/sockio.h>.
*/
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#ifdef HAVE_SYS_FILIO_H
#include <sys/filio.h>
#endif
#ifdef HAVE_SYS_SOCKIO_H
#include <sys/sockio.h>
#endif
#include <pthread.h>
#include <semaphore.h>
#define ENUMS
#include <sys/socket.h>
#ifndef __CYGWIN__
#include <net/route.h>
#endif
#include <net/if.h>
#ifndef __CYGWIN__
#include <net/if_arp.h>
#endif
#include <features.h> /* for the glibc version number */
#if __GLIBC__ >= 2 && __GLIBC_MINOR >= 1
#include <netpacket/packet.h>
#include <net/ethernet.h> /* the L2 protocols */
#else
#include <asm/types.h>
#ifndef __CYGWIN__
#include <linux/if_packet.h>
#include <linux/if_arcnet.h>
#include <linux/if_ether.h>
#endif
#endif
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/un.h>
#include <sys/ioctl.h>
#include <netdb.h>
/** @file linux/net.h Includes Linux network headers. */
/* Local helper functions for this port */
extern int bip_get_local_netmask(
struct in_addr *netmask);
#endif
+2
View File
@@ -0,0 +1,2 @@
This is a port to Linux for testing.
The unit tests can be run via the test.sh script.
+748
View File
@@ -0,0 +1,748 @@
/*####COPYRIGHTBEGIN####
-------------------------------------------
Copyright (C) 2007 Steve Karg <skarg@users.sourceforge.net>
Updated by Nikola Jelic 2011 <nikola.jelic@euroicc.com>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to:
The Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307
USA.
As a special exception, if other files instantiate templates or
use macros or inline functions from this file, or you compile
this file and link it with other works to produce a work based
on this file, this file does not by itself cause the resulting
work to be covered by the GNU General Public License. However
the source code for this file must still be made available in
accordance with section (3) of the GNU General Public License.
This exception does not invalidate any other reasons why a work
based on this file might be covered by the GNU General Public
License.
-------------------------------------------
####COPYRIGHTEND####*/
/** @file linux/rs485.c Provides Linux-specific functions for RS-485 serial. */
/* The module handles sending data out the RS-485 port */
/* and handles receiving data from the RS-485 port. */
/* Customize this file for your specific hardware */
#include <errno.h>
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Linux includes */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <sched.h>
#include <linux/serial.h> /* for struct serial_struct */
#include <math.h> /* for calculation of custom divisor */
#include <sys/ioctl.h>
/* for scandir */
#include <dirent.h>
/* for basename */
#include <libgen.h>
/* Local includes */
#include "mstp.h"
#include "rs485.h"
#include "fifo.h"
#include <sys/select.h>
#include <sys/time.h>
#include "dlmstp_linux.h"
/* Posix serial programming reference:
http://www.easysw.com/~mike/serial/serial.html */
/* Use ionice wrapper to improve serial performance:
$ sudo ionice -c 1 -n 0 ./bin/bacserv 12345
*/
/* handle returned from open() */
static int RS485_Handle = -1;
/* baudrate settings are defined in <asm/termbits.h>, which is
included by <termios.h> */
static unsigned int RS485_Baud = B38400;
/* serial port name, /dev/ttyS0,
/dev/ttyUSB0 for USB->RS485 from B&B Electronics USOPTL4 */
static char *RS485_Port_Name = "/dev/ttyUSB0";
/* some terminal I/O have RS-485 specific functionality */
#ifndef RS485MOD
#define RS485MOD 0
#endif
/* serial I/O settings */
static struct termios RS485_oldtio;
/* for setting custom divisor */
static struct serial_struct RS485_oldserial;
/* indicator of special baud rate */
static bool RS485_SpecBaud = false;
/* Ring buffer for incoming bytes, in order to speed up the receiving. */
static FIFO_BUFFER Rx_FIFO;
/* buffer size needs to be a power of 2 */
static uint8_t Rx_Buffer[4096];
#define _POSIX_SOURCE 1 /* POSIX compliant source */
/*********************************************************************
* DESCRIPTION: Configures the interface name
* RETURN: none
* ALGORITHM: none
* NOTES: none
*********************************************************************/
void RS485_Set_Interface(
char *ifname)
{
/* note: expects a constant char, or char from the heap */
if (ifname) {
RS485_Port_Name = ifname;
}
}
/*********************************************************************
* DESCRIPTION: Returns the interface name
* RETURN: none
* ALGORITHM: none
* NOTES: none
*********************************************************************/
const char *RS485_Interface(
void)
{
return RS485_Port_Name;
}
/****************************************************************************
* DESCRIPTION: Returns the baud rate that we are currently running at
* RETURN: none
* ALGORITHM: none
* NOTES: none
*****************************************************************************/
uint32_t RS485_Get_Baud_Rate(
void)
{
uint32_t baud = 0;
switch (RS485_Baud) {
case B0:
baud = 0;
break;
case B50:
baud = 50;
break;
case B75:
baud = 75;
break;
case B110:
baud = 110;
break;
case B134:
baud = 134;
break;
case B150:
baud = 150;
break;
case B200:
baud = 200;
break;
case B300:
baud = 300;
break;
case B600:
baud = 600;
break;
case B1200:
baud = 1200;
break;
case B1800:
baud = 1800;
break;
case B2400:
baud = 2400;
break;
case B4800:
baud = 4800;
break;
case B9600:
baud = 9600;
break;
case B19200:
baud = 19200;
break;
case B38400:
if (!RS485_SpecBaud) {
/* Linux asks for custom divisor
only when baud is set on 38400 */
baud = 38400;
} else {
baud = 76800;
}
break;
case B57600:
baud = 57600;
break;
case B115200:
baud = 115200;
break;
case B230400:
baud = 230400;
break;
default:
baud = 9600;
}
return baud;
}
/****************************************************************************
* DESCRIPTION: Returns the baud rate that we are currently running at
* RETURN: none
* ALGORITHM: none
* NOTES: none
*****************************************************************************/
uint32_t RS485_Get_Port_Baud_Rate(
volatile struct mstp_port_struct_t * mstp_port)
{
uint32_t baud = 0;
SHARED_MSTP_DATA *poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData;
if (!poSharedData) {
return 0;
}
switch (poSharedData->RS485_Baud) {
case B0:
baud = 0;
break;
case B50:
baud = 50;
break;
case B75:
baud = 75;
break;
case B110:
baud = 110;
break;
case B134:
baud = 134;
break;
case B150:
baud = 150;
break;
case B200:
baud = 200;
break;
case B300:
baud = 300;
break;
case B600:
baud = 600;
break;
case B1200:
baud = 1200;
break;
case B1800:
baud = 1800;
break;
case B2400:
baud = 2400;
break;
case B4800:
baud = 4800;
break;
case B9600:
baud = 9600;
break;
case B19200:
baud = 19200;
break;
case B38400:
baud = 38400;
break;
case B57600:
baud = 57600;
break;
case B115200:
baud = 115200;
break;
case B230400:
baud = 230400;
break;
default:
baud = 9600;
break;
}
return baud;
}
/****************************************************************************
* DESCRIPTION: Sets the baud rate for the chip USART
* RETURN: none
* ALGORITHM: none
* NOTES: none
*****************************************************************************/
bool RS485_Set_Baud_Rate(
uint32_t baud)
{
bool valid = true;
RS485_SpecBaud = false;
switch (baud) {
case 0:
RS485_Baud = B0;
break;
case 50:
RS485_Baud = B50;
break;
case 75:
RS485_Baud = B75;
break;
case 110:
RS485_Baud = B110;
break;
case 134:
RS485_Baud = B134;
break;
case 150:
RS485_Baud = B150;
break;
case 200:
RS485_Baud = B200;
break;
case 300:
RS485_Baud = B300;
break;
case 600:
RS485_Baud = B600;
break;
case 1200:
RS485_Baud = B1200;
break;
case 1800:
RS485_Baud = B1800;
break;
case 2400:
RS485_Baud = B2400;
break;
case 4800:
RS485_Baud = B4800;
break;
case 9600:
RS485_Baud = B9600;
break;
case 19200:
RS485_Baud = B19200;
break;
case 38400:
RS485_Baud = B38400;
break;
case 57600:
RS485_Baud = B57600;
break;
case 76800:
RS485_Baud = B38400;
RS485_SpecBaud = true;
break;
case 115200:
RS485_Baud = B115200;
break;
case 230400:
RS485_Baud = B230400;
break;
default:
valid = false;
break;
}
if (valid) {
/* FIXME: store the baud rate */
}
return valid;
}
/****************************************************************************
* DESCRIPTION: Transmit a frame on the wire
* RETURN: none
* ALGORITHM: none
* NOTES: none
*****************************************************************************/
void RS485_Send_Frame(
volatile struct mstp_port_struct_t *mstp_port, /* port specific data */
uint8_t * buffer, /* frame to send (up to 501 bytes of data) */
uint16_t nbytes)
{ /* number of bytes of data (up to 501) */
uint32_t turnaround_time = Tturnaround * 1000;
uint32_t baud;
ssize_t written = 0;
int greska;
SHARED_MSTP_DATA *poSharedData = NULL;
if (mstp_port) {
poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData;
}
if (!poSharedData) {
baud = RS485_Get_Baud_Rate();
/* sleeping for turnaround time is necessary to give other devices
time to change from sending to receiving state. */
usleep(turnaround_time / baud);
/*
On success, the number of bytes written are returned (zero indicates
nothing was written). On error, -1 is returned, and errno is set
appropriately. If count is zero and the file descriptor refers to a
regular file, 0 will be returned without causing any other effect. For
a special file, the results are not portable.
*/
written = write(RS485_Handle, buffer, nbytes);
greska = errno;
if (written <= 0) {
printf("write error: %s\n", strerror(greska));
} else {
/* wait until all output has been transmitted. */
tcdrain(RS485_Handle);
}
/* tcdrain(RS485_Handle); */
/* per MSTP spec, sort of */
if (mstp_port) {
mstp_port->SilenceTimerReset((void *) mstp_port);
}
} else {
baud = RS485_Get_Port_Baud_Rate(mstp_port);
/* sleeping for turnaround time is necessary to give other devices
time to change from sending to receiving state. */
usleep(turnaround_time / baud);
/*
On success, the number of bytes written are returned (zero indicates
nothing was written). On error, -1 is returned, and errno is set
appropriately. If count is zero and the file descriptor refers to a
regular file, 0 will be returned without causing any other effect. For
a special file, the results are not portable.
*/
written = write(poSharedData->RS485_Handle, buffer, nbytes);
greska = errno;
if (written <= 0) {
printf("write error: %s\n", strerror(greska));
} else {
/* wait until all output has been transmitted. */
tcdrain(poSharedData->RS485_Handle);
}
/* tcdrain(RS485_Handle); */
/* per MSTP spec, sort of */
if (mstp_port) {
mstp_port->SilenceTimerReset((void *) mstp_port);
}
}
return;
}
/****************************************************************************
* DESCRIPTION: Get a byte of receive data
* RETURN: none
* ALGORITHM: none
* NOTES: none
*****************************************************************************/
void RS485_Check_UART_Data(
volatile struct mstp_port_struct_t *mstp_port)
{
fd_set input;
struct timeval waiter;
uint8_t buf[2048];
int n;
SHARED_MSTP_DATA *poSharedData = (SHARED_MSTP_DATA *) mstp_port->UserData;
if (!poSharedData) {
if (mstp_port->ReceiveError == true) {
/* do nothing but wait for state machine to clear the error */
/* burning time, so wait a longer time */
waiter.tv_sec = 0;
waiter.tv_usec = 5000;
} else if (mstp_port->DataAvailable == false) {
/* wait for state machine to read from the DataRegister */
if (FIFO_Count(&Rx_FIFO) > 0) {
/* data is available */
mstp_port->DataRegister = FIFO_Get(&Rx_FIFO);
mstp_port->DataAvailable = true;
/* FIFO is giving data - just poll */
waiter.tv_sec = 0;
waiter.tv_usec = 0;
} else {
/* FIFO is empty - wait a longer time */
waiter.tv_sec = 0;
waiter.tv_usec = 5000;
}
}
/* grab bytes and stuff them into the FIFO every time */
FD_ZERO(&input);
FD_SET(RS485_Handle, &input);
n = select(RS485_Handle + 1, &input, NULL, NULL, &waiter);
if (n < 0) {
return;
}
if (FD_ISSET(RS485_Handle, &input)) {
n = read(RS485_Handle, buf, sizeof(buf));
FIFO_Add(&Rx_FIFO, &buf[0], n);
}
} else {
if (mstp_port->ReceiveError == true) {
/* do nothing but wait for state machine to clear the error */
/* burning time, so wait a longer time */
waiter.tv_sec = 0;
waiter.tv_usec = 5000;
} else if (mstp_port->DataAvailable == false) {
/* wait for state machine to read from the DataRegister */
if (FIFO_Count(&poSharedData->Rx_FIFO) > 0) {
/* data is available */
mstp_port->DataRegister = FIFO_Get(&poSharedData->Rx_FIFO);
mstp_port->DataAvailable = true;
/* FIFO is giving data - just poll */
waiter.tv_sec = 0;
waiter.tv_usec = 0;
} else {
/* FIFO is empty - wait a longer time */
waiter.tv_sec = 0;
waiter.tv_usec = 5000;
}
}
/* grab bytes and stuff them into the FIFO every time */
FD_ZERO(&input);
FD_SET(poSharedData->RS485_Handle, &input);
n = select(poSharedData->RS485_Handle + 1, &input, NULL, NULL,
&waiter);
if (n < 0) {
return;
}
if (FD_ISSET(poSharedData->RS485_Handle, &input)) {
n = read(poSharedData->RS485_Handle, buf, sizeof(buf));
FIFO_Add(&poSharedData->Rx_FIFO, &buf[0], n);
}
}
}
void RS485_Cleanup(
void)
{
/* restore the old port settings */
tcsetattr(RS485_Handle, TCSANOW, &RS485_oldtio);
ioctl(RS485_Handle, TIOCSSERIAL, &RS485_oldserial);
close(RS485_Handle);
}
void RS485_Initialize(
void)
{
struct termios newtio;
struct serial_struct newserial;
float baud_error = 0.0;
printf("RS485: Initializing %s", RS485_Port_Name);
/*
Open device for reading and writing.
Blocking mode - more CPU effecient
*/
RS485_Handle = open(RS485_Port_Name, O_RDWR | O_NOCTTY /*| O_NDELAY */ );
if (RS485_Handle < 0) {
perror(RS485_Port_Name);
exit(-1);
}
#if 0
/* non blocking for the read */
fcntl(RS485_Handle, F_SETFL, FNDELAY);
#else
/* efficient blocking for the read */
fcntl(RS485_Handle, F_SETFL, 0);
#endif
/* save current serial port settings */
tcgetattr(RS485_Handle, &RS485_oldtio);
/* we read the old serial setup */
ioctl(RS485_Handle, TIOCGSERIAL, &RS485_oldserial);
/* we need a copy of existing settings */
memcpy(&newserial, &RS485_oldserial, sizeof(struct serial_struct));
/* clear struct for new port settings */
bzero(&newtio, sizeof(newtio));
/*
BAUDRATE: Set bps rate. You could also use cfsetispeed and cfsetospeed.
CRTSCTS : output hardware flow control (only used if the cable has
all necessary lines. See sect. 7 of Serial-HOWTO)
CS8 : 8n1 (8bit,no parity,1 stopbit)
CLOCAL : local connection, no modem contol
CREAD : enable receiving characters
*/
newtio.c_cflag = RS485_Baud | CS8 | CLOCAL | CREAD | RS485MOD;
/* Raw input */
newtio.c_iflag = 0;
/* Raw output */
newtio.c_oflag = 0;
/* no processing */
newtio.c_lflag = 0;
/* activate the settings for the port after flushing I/O */
tcsetattr(RS485_Handle, TCSAFLUSH, &newtio);
if (RS485_SpecBaud) {
/* 76800, custom divisor must be set */
newserial.flags |= ASYNC_SPD_CUST;
newserial.custom_divisor =
round(((float) newserial.baud_base) / 76800);
/* we must check that we calculated some sane value;
small baud bases yield bad custom divisor values */
baud_error =
fabs(1 -
((float) newserial.baud_base) /
((float) newserial.custom_divisor) / 76800);
if ((newserial.custom_divisor == 0) || (baud_error > 0.02)) {
/* bad divisor */
fprintf(stderr, "bad custom divisor %d, base baud %d\n",
newserial.custom_divisor, newserial.baud_base);
exit(EXIT_FAILURE);
}
/* if all goes well, set new divisor */
ioctl(RS485_Handle, TIOCSSERIAL, &newserial);
}
printf(" at Baud Rate %u", RS485_Get_Baud_Rate());
/* destructor */
atexit(RS485_Cleanup);
/* flush any data waiting */
usleep(200000);
tcflush(RS485_Handle, TCIOFLUSH);
/* ringbuffer */
FIFO_Init(&Rx_FIFO, Rx_Buffer, sizeof(Rx_Buffer));
printf("=success!\n");
}
/* Print in a format for Wireshark ExtCap */
void RS485_Print_Ports(void)
{
int n;
struct dirent **namelist;
const char* sysdir = "/sys/class/tty/";
struct stat st;
char buffer[1024];
char device_dir[1024];
char *driver_name = NULL;
int fd = 0;
bool valid_port = false;
struct serial_struct serinfo;
// Scan through /sys/class/tty - it contains all tty-devices in the system
n = scandir(sysdir, &namelist, NULL, NULL);
if (n < 0) {
perror("RS485: scandir");
} else {
while (n--) {
if (strcmp(namelist[n]->d_name,"..") &&
strcmp(namelist[n]->d_name,".")) {
snprintf(device_dir, sizeof(device_dir),
"%s%s/device",
sysdir, namelist[n]->d_name);
// Stat the devicedir and handle it if it is a symlink
if (lstat(device_dir, &st)==0 && S_ISLNK(st.st_mode)) {
memset(buffer, 0, sizeof(buffer));
snprintf(device_dir, sizeof(device_dir),
"%s%s/device/driver",
sysdir, namelist[n]->d_name);
if (readlink(device_dir, buffer, sizeof(buffer)) > 0) {
valid_port = false;
driver_name=basename(buffer);
if (strcmp(driver_name,"serial8250") == 0) {
// serial8250-devices must be probed
snprintf(device_dir, sizeof(device_dir),
"/dev/%s", namelist[n]->d_name);
fd = open(device_dir,
O_RDWR | O_NONBLOCK | O_NOCTTY);
if (fd >= 0) {
// Get serial_info
if (ioctl(fd, TIOCGSERIAL, &serinfo)==0) {
// If device type is not PORT_UNKNOWN
// we accept the port
if (serinfo.type != PORT_UNKNOWN) {
valid_port = true;
}
}
close(fd);
}
} else {
valid_port = true;
}
if (valid_port) {
// print full absolute file path
printf("interface {value=/dev/%s}"
"{display=MS/TP Capture on /dev/%s}\n",
namelist[n]->d_name, namelist[n]->d_name);
}
}
}
}
free(namelist[n]);
}
free(namelist);
}
}
#ifdef TEST_RS485
#include <string.h>
int main(
int argc,
char *argv[])
{
volatile struct mstp_port_struct_t mstp_port = {0};
uint8_t token_buf[8] = {0x55, 0xFF, 0x00, 0x7E, 0x07, 0x00, 0x00, 0xFD};
uint8_t pfm_buf[8] = {0x55, 0xFF, 0x01, 0x67, 0x07, 0x00, 0x00, 0x3E};
long baud = 38400;
bool write_token = false;
bool write_pfm = false;
/* argv has the "/dev/ttyS0" or some other device */
if (argc > 1) {
RS485_Set_Interface(argv[1]);
}
if (argc > 2) {
baud = strtol(argv[2], NULL, 0);
}
if (argc > 3) {
if (strcmp("token", argv[3]) == 0) {
write_token = true;
}
if (strcmp("pfm", argv[3]) == 0) {
write_pfm = true;
}
}
RS485_Set_Baud_Rate(baud);
RS485_Initialize();
for (;;) {
if (write_token) {
RS485_Send_Frame(NULL,token_buf, sizeof(token_buf));
usleep(25000);
} else if (write_pfm) {
RS485_Send_Frame(NULL,pfm_buf, sizeof(pfm_buf));
usleep(100000);
} else {
RS485_Check_UART_Data(&mstp_port);
if (mstp_port.DataAvailable) {
fprintf(stderr, "%02X ", mstp_port.DataRegister);
mstp_port.DataAvailable = false;
}
}
}
return 0;
}
#endif
+76
View File
@@ -0,0 +1,76 @@
/*####COPYRIGHTBEGIN####
-------------------------------------------
Copyright (C) 2004 Steve Karg
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to:
The Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307
USA.
As a special exception, if other files instantiate templates or
use macros or inline functions from this file, or you compile
this file and link it with other works to produce a work based
on this file, this file does not by itself cause the resulting
work to be covered by the GNU General Public License. However
the source code for this file must still be made available in
accordance with section (3) of the GNU General Public License.
This exception does not invalidate any other reasons why a work
based on this file might be covered by the GNU General Public
License.
-------------------------------------------
####COPYRIGHTEND####*/
#ifndef RS485_H
#define RS485_H
#include <stdint.h>
#include "mstp.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
void RS485_Set_Interface(
char *ifname);
const char *RS485_Interface(
void);
void RS485_Initialize(
void);
void RS485_Send_Frame(
volatile struct mstp_port_struct_t *mstp_port, /* port specific data */
uint8_t * buffer, /* frame to send (up to 501 bytes of data) */
uint16_t nbytes); /* number of bytes of data (up to 501) */
void RS485_Check_UART_Data(
volatile struct mstp_port_struct_t *mstp_port); /* port specific data */
uint32_t RS485_Get_Port_Baud_Rate(
volatile struct mstp_port_struct_t *mstp_port);
uint32_t RS485_Get_Baud_Rate(
void);
bool RS485_Set_Baud_Rate(
uint32_t baud);
void RS485_Cleanup(
void);
void RS485_Print_Ports(
void);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif
+36
View File
@@ -0,0 +1,36 @@
#Makefile to build test case
#CC = gcc
TARGET = rs485
# Directories
BACNET_SOURCE_DIR = ../../src
BACNET_INCLUDE = ../../include
# -g for debugging with gdb
DEFINES = -DBIG_ENDIAN=0 -DTEST_RS485 -DBACDL_TEST
INCLUDES = -I. -I../../ -I$(BACNET_INCLUDE)
CFLAGS = -Wall $(INCLUDES) $(DEFINES) -g
LIBRARIES=-lc,-lgcc,-lrt,-lm
LFLAGS = -Wl,-Map=$(TARGET).map,$(LIBRARIES),--gc-sections
SRCS = rs485.c \
${BACNET_SOURCE_DIR}/fifo.c
OBJS = ${SRCS:.c=.o}
all: ${TARGET}
${TARGET}: ${OBJS}
${CC} ${OBJS} ${LFLAGS} -o $@
.c.o:
${CC} -c ${CFLAGS} $*.c -o $@
depend:
rm -f .depend
${CC} -MM ${CFLAGS} *.c >> .depend
clean:
rm -rf core ${TARGET} $(OBJS) *.bak *.1 *.ini
include: .depend
+367
View File
@@ -0,0 +1,367 @@
/*####COPYRIGHTBEGIN####
-------------------------------------------
Copyright (C) 2007 Steve Karg
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to:
The Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307
USA.
As a special exception, if other files instantiate templates or
use macros or inline functions from this file, or you compile
this file and link it with other works to produce a work based
on this file, this file does not by itself cause the resulting
work to be covered by the GNU General Public License. However
the source code for this file must still be made available in
accordance with section (3) of the GNU General Public License.
This exception does not invalidate any other reasons why a work
based on this file might be covered by the GNU General Public
License.
-------------------------------------------
####COPYRIGHTEND####*/
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
/* Linux includes */
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h> /* signal handling functions */
/* local includes */
#include "bytes.h"
#include "rs485.h"
#include "crc.h"
#include "mstp.h"
#include "mstptext.h"
/** @file linux/rx_fsm.c Example app testing MS/TP Rx State Machine on Linux. */
#ifndef max
#define max(a,b) (((a) (b)) ? (a) : (b))
#define min(a,b) (((a) < (b)) ? (a) : (b))
#endif
#ifndef LOCAL_PRINT
#define LOCAL_PRINT 1
#endif
/* local port data - shared with RS-485 */
static volatile struct mstp_port_struct_t MSTP_Port;
/* buffers needed by mstp port struct */
static uint8_t RxBuffer[MAX_MPDU];
static uint8_t TxBuffer[MAX_MPDU];
static uint16_t SilenceTime;
#define INCREMENT_AND_LIMIT_UINT16(x) {if (x < 0xFFFF) x++;}
static uint16_t Timer_Silence(
void)
{
return SilenceTime;
}
static void Timer_Silence_Reset(
void)
{
SilenceTime = 0;
}
static void dlmstp_millisecond_timer(
void)
{
INCREMENT_AND_LIMIT_UINT16(SilenceTime);
}
void *milliseconds_task(
void *pArg)
{
struct timespec timeOut, remains;
timeOut.tv_sec = 0;
timeOut.tv_nsec = 10000000; /* 1 milliseconds */
for (;;) {
nanosleep(&timeOut, &remains);
dlmstp_millisecond_timer();
}
return NULL;
}
/* functions used by the MS/TP state machine to put or get data */
uint16_t MSTP_Put_Receive(
volatile struct mstp_port_struct_t * mstp_port)
{
(void) mstp_port;
return 0;
}
/* for the MS/TP state machine to use for getting data to send */
/* Return: amount of PDU data */
uint16_t MSTP_Get_Send(
volatile struct mstp_port_struct_t * mstp_port,
unsigned timeout)
{ /* milliseconds to wait for a packet */
(void) mstp_port;
(void) timeout;
return 0;
}
uint16_t MSTP_Get_Reply(
volatile struct mstp_port_struct_t * mstp_port,
unsigned timeout)
{ /* milliseconds to wait for a packet */
(void) mstp_port;
(void) timeout;
return 0;
}
/* returns a delta timestamp */
int timestamp_ms(
void)
{
struct timeval tv;
int delta_ticks = 0;
long ticks = 0;
static long last_ticks = 0;
int rv = 0;
rv = gettimeofday(&tv, NULL);
if (rv == -1)
ticks = 0;
else
ticks = (tv.tv_sec * 1000) + (tv.tv_usec / 1000);
delta_ticks = ticks - last_ticks;
last_ticks = ticks;
return delta_ticks;
}
static const char *Capture_Filename = "mstp.cap";
static FILE *pFile = NULL; /* stream pointer */
/* write packet to file in libpcap format */
static void write_global_header(
void)
{
uint32_t magic_number = 0xa1b2c3d4; /* magic number */
uint16_t version_major = 2; /* major version number */
uint16_t version_minor = 4; /* minor version number */
int32_t thiszone = 0; /* GMT to local correction */
uint32_t sigfigs = 0; /* accuracy of timestamps */
uint32_t snaplen = 65535; /* max length of captured packets, in octets */
uint32_t network = 165; /* data link type - BACNET_MS_TP */
/* create a new file. */
pFile = fopen(Capture_Filename, "wb");
if (pFile) {
fwrite(&magic_number, sizeof(magic_number), 1, pFile);
fwrite(&version_major, sizeof(version_major), 1, pFile);
fwrite(&version_minor, sizeof(version_minor), 1, pFile);
fwrite(&thiszone, sizeof(thiszone), 1, pFile);
fwrite(&sigfigs, sizeof(sigfigs), 1, pFile);
fwrite(&snaplen, sizeof(snaplen), 1, pFile);
fwrite(&network, sizeof(network), 1, pFile);
} else {
fprintf(stderr, "rx_fsm: failed to open %s: %s\n", Capture_Filename,
strerror(errno));
}
}
static void write_received_packet(
volatile struct mstp_port_struct_t *mstp_port)
{
uint32_t ts_sec; /* timestamp seconds */
uint32_t ts_usec; /* timestamp microseconds */
uint32_t incl_len; /* number of octets of packet saved in file */
uint32_t orig_len; /* actual length of packet */
uint8_t header[8]; /* MS/TP header */
struct timeval tv;
size_t max_data = 0;
if (pFile) {
gettimeofday(&tv, NULL);
ts_sec = tv.tv_sec;
ts_usec = tv.tv_usec;
fwrite(&ts_sec, sizeof(ts_sec), 1, pFile);
fwrite(&ts_usec, sizeof(ts_usec), 1, pFile);
if (mstp_port->DataLength) {
max_data = min(mstp_port->InputBufferSize, mstp_port->DataLength);
incl_len = orig_len = 8 + max_data + 2;
} else {
incl_len = orig_len = 8;
}
fwrite(&incl_len, sizeof(incl_len), 1, pFile);
fwrite(&orig_len, sizeof(orig_len), 1, pFile);
header[0] = 0x55;
header[1] = 0xFF;
header[2] = mstp_port->FrameType;
header[3] = mstp_port->DestinationAddress;
header[4] = mstp_port->SourceAddress;
header[5] = HI_BYTE(mstp_port->DataLength);
header[6] = LO_BYTE(mstp_port->DataLength);
header[7] = mstp_port->HeaderCRCActual;
fwrite(header, sizeof(header), 1, pFile);
if (mstp_port->DataLength) {
fwrite(mstp_port->InputBuffer, max_data, 1, pFile);
fwrite((char *) &(mstp_port->DataCRCActualMSB), 1, 1, pFile);
fwrite((char *) &(mstp_port->DataCRCActualLSB), 1, 1, pFile);
}
} else {
fprintf(stderr, "rx_fsm: failed to open %s: %s\n", Capture_Filename,
strerror(errno));
}
}
static void cleanup(
void)
{
if (pFile) {
fflush(pFile); /* stream pointer */
fclose(pFile); /* stream pointer */
}
pFile = NULL;
}
#if LOCAL_PRINT
static void print_received_packet(
volatile struct mstp_port_struct_t *mstp_port)
{
unsigned i = 0;
int timestamp = 0;
size_t max_data = 0;
timestamp = timestamp_ms();
fprintf(stderr, "%03d ", timestamp);
/* Preamble: two octet preamble: X`55', X`FF' */
/* Frame Type: one octet */
/* Destination Address: one octet address */
/* Source Address: one octet address */
/* Length: two octets, most significant octet first, of the Data field */
/* Header CRC: one octet */
/* Data: (present only if Length is non-zero) */
/* Data CRC: (present only if Length is non-zero) two octets, */
/* least significant octet first */
/* (pad): (optional) at most one octet of padding: X'FF' */
fprintf(stderr, "55 FF %02X %02X %02X %02X %02X %02X ",
mstp_port->FrameType, mstp_port->DestinationAddress,
mstp_port->SourceAddress, HI_BYTE(mstp_port->DataLength),
LO_BYTE(mstp_port->DataLength), mstp_port->HeaderCRCActual);
if (mstp_port->DataLength) {
max_data = min(mstp_port->InputBufferSize, mstp_port->DataLength);
for (i = 0; i < max_data; i++) {
fprintf(stderr, "%02X ", mstp_port->InputBuffer[i]);
}
fprintf(stderr, "%02X %02X ", mstp_port->DataCRCActualMSB,
mstp_port->DataCRCActualLSB);
}
fprintf(stderr, "%s", mstptext_frame_type(mstp_port->FrameType));
fprintf(stderr, "\n");
}
#endif
static void sig_int(
int signo)
{
(void) signo;
cleanup();
exit(0);
}
void signal_init(
void)
{
signal(SIGINT, sig_int);
signal(SIGHUP, sig_int);
signal(SIGTERM, sig_int);
}
/* simple test to packetize the data and print it */
int main(
int argc,
char *argv[])
{
volatile struct mstp_port_struct_t *mstp_port;
int rc = 0;
pthread_t hThread;
int my_mac = 127;
long my_baud = 38400;
/* mimic our pointer in the state machine */
mstp_port = &MSTP_Port;
/* initialize our interface */
if (argc > 1) {
RS485_Set_Interface(argv[1]);
}
if (argc > 2) {
my_baud = strtol(argv[2], NULL, 0);
}
if (argc > 3) {
my_mac = strtol(argv[3], NULL, 0);
if (my_mac > 127)
my_mac = 127;
}
RS485_Set_Baud_Rate(my_baud);
RS485_Initialize();
MSTP_Port.InputBuffer = &RxBuffer[0];
MSTP_Port.InputBufferSize = sizeof(RxBuffer);
MSTP_Port.OutputBuffer = &TxBuffer[0];
MSTP_Port.OutputBufferSize = sizeof(TxBuffer);
MSTP_Port.This_Station = my_mac;
MSTP_Port.Nmax_info_frames = 1;
MSTP_Port.Nmax_master = 127;
MSTP_Port.SilenceTimer = Timer_Silence;
MSTP_Port.SilenceTimerReset = Timer_Silence_Reset;
MSTP_Init(mstp_port);
mstp_port->Lurking = true;
/* start our MilliSec task */
rc = pthread_create(&hThread, NULL, milliseconds_task, NULL);
atexit(cleanup);
write_global_header();
fflush(pFile); /* stream pointer */
/* run forever */
for (;;) {
RS485_Check_UART_Data(mstp_port);
MSTP_Receive_Frame_FSM(mstp_port);
/* process the data portion of the frame */
if (mstp_port->ReceivedValidFrame) {
mstp_port->ReceivedValidFrame = false;
#if LOCAL_PRINT
print_received_packet(mstp_port);
#endif
write_received_packet(mstp_port);
fflush(pFile); /* stream pointer */
} else if (mstp_port->ReceivedInvalidFrame) {
mstp_port->ReceivedInvalidFrame = false;
fprintf(stderr, "ReceivedInvalidFrame\n");
#if LOCAL_PRINT
print_received_packet(mstp_port);
#endif
write_received_packet(mstp_port);
fflush(pFile); /* stream pointer */
}
}
return 0;
}
+36
View File
@@ -0,0 +1,36 @@
#Makefile to build test case
CC = gcc
SRCDIR = ../../src
INCDIR = ../../include
# -g for debugging with gdb
DEFINES = -DBIG_ENDIAN=0 -DBACDL_MSTP=1
INCLUDES = -I. -I$(INCDIR)
CFLAGS = -Wall $(INCLUDES) $(DEFINES) -g
SRCS = rs485.c \
rx_fsm.c \
$(SRCDIR)/mstp.c \
$(SRCDIR)/mstptext.c \
$(SRCDIR)/indtext.c \
$(SRCDIR)/crc.c
OBJS = ${SRCS:.c=.o}
TARGET = rx_fsm
all: ${TARGET}
${TARGET}: ${OBJS}
${CC} -pthread -o $@ ${OBJS}
.c.o:
${CC} -c ${CFLAGS} $*.c -o $@
depend:
rm -f .depend
${CC} -MM ${CFLAGS} *.c >> .depend
clean:
rm -rf core ${TARGET} $(OBJS) *.bak *.1 *.ini
include: .depend
+140
View File
@@ -0,0 +1,140 @@
/**************************************************************************
*
* Copyright (C) 2009 Steve Karg <skarg@users.sourceforge.net>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*********************************************************************/
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <time.h>
#include "timer.h"
/** @file linux/timer.c Provides Linux-specific time and timer functions. */
/* counter for the various timers */
static volatile uint32_t Millisecond_Counter[MAX_MILLISECOND_TIMERS];
/* start time for the clock */
static struct timespec start;
/* The timeGetTime function retrieves the system time, in milliseconds.
The system time is the time elapsed since Windows was started. */
uint32_t timeGetTime(
void)
{
struct timespec now;
uint32_t ticks;
clock_gettime(CLOCK_MONOTONIC, &now);
ticks =
(now.tv_sec - start.tv_sec) * 1000 + (now.tv_nsec -
start.tv_nsec) / 1000000;
return ticks;
}
/*************************************************************************
* Description: returns the current millisecond count
* Returns: none
* Notes: none
*************************************************************************/
uint32_t timer_milliseconds(
unsigned index)
{
uint32_t now = timeGetTime();
uint32_t delta_time = 0;
if (index < MAX_MILLISECOND_TIMERS) {
if (Millisecond_Counter[index] <= now) {
delta_time = now - Millisecond_Counter[index];
} else {
delta_time = (UINT32_MAX - Millisecond_Counter[index]) + now + 1;
}
}
return delta_time;
}
/*************************************************************************
* Description: compares the current time count with a value
* Returns: true if the time has elapsed
* Notes: none
*************************************************************************/
bool timer_elapsed_milliseconds(
unsigned index,
uint32_t value)
{
return (timer_milliseconds(index) >= value);
}
/*************************************************************************
* Description: compares the current time count with a value
* Returns: true if the time has elapsed
* Notes: none
*************************************************************************/
bool timer_elapsed_seconds(
unsigned index,
uint32_t seconds)
{
return ((timer_milliseconds(index) / 1000) >= seconds);
}
/*************************************************************************
* Description: compares the current time count with a value
* Returns: true if the time has elapsed
* Notes: none
*************************************************************************/
bool timer_elapsed_minutes(
unsigned index,
uint32_t minutes)
{
return ((timer_milliseconds(index) / (1000 * 60)) >= minutes);
}
/*************************************************************************
* Description: Sets the timer counter to zero.
* Returns: none
* Notes: none
*************************************************************************/
uint32_t timer_reset(
unsigned index)
{
uint32_t timer_value = 0;
if (index < MAX_MILLISECOND_TIMERS) {
timer_value = timer_milliseconds(index);
Millisecond_Counter[index] = timeGetTime();
}
return timer_value;
}
/*************************************************************************
* Description: Initialization for timer
* Returns: none
* Notes: none
*************************************************************************/
void timer_init(
void)
{
clock_gettime(CLOCK_MONOTONIC, &start);
}
+65
View File
@@ -0,0 +1,65 @@
/**************************************************************************
*
* Copyright (C) 2009 Steve Karg <skarg@users.sourceforge.net>
*
* 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.
*********************************************************************/
#ifndef TIMER_H
#define TIMER_H
#include <stdbool.h>
#include <stdint.h>
#include <sys/time.h> /* for timeval */
/* Timer Module */
#ifndef MAX_MILLISECOND_TIMERS
#define TIMER_SILENCE 0
#define MAX_MILLISECOND_TIMERS 1
#endif
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
uint32_t timeGetTime(
void);
void timer_init(
void);
uint32_t timer_milliseconds(
unsigned index);
bool timer_elapsed_milliseconds(
unsigned index,
uint32_t value);
bool timer_elapsed_seconds(
unsigned index,
uint32_t value);
bool timer_elapsed_minutes(
unsigned index,
uint32_t seconds);
uint32_t timer_milliseconds_set(
unsigned index,
uint32_t value);
uint32_t timer_reset(
unsigned index);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif