Files
bacnet_stack/ports/linux/arcnet.c
T
Steve Karg 7e725ce028 Bugfix/win32 build warnings (#936)
* Changed win32 port of localtime to use secure OS API functions when compiled with MSVC

* Changed all the sprintf to use snprintf instead in BSC tests to ensure null string endings.
2025-03-03 10:32:28 -06:00

379 lines
12 KiB
C

/**************************************************************************
*
* Copyright (C) 2005 Steve Karg
*
* SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
*
*********************************************************************/
#include <stdint.h>
#include "bacnet/bacdef.h"
#include "bacnet/npdu.h"
#include "bacnet/datalink/arcnet.h"
#include "bacport.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(const 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 */
snprintf(
ARCNET_Socket_Address.sa_data,
sizeof(ARCNET_Socket_Address.sa_data), "%s", interface_name);
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);
}
}
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", interface_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 */
snprintf(
ARCNET_Socket_Address.sa_data, sizeof(ARCNET_Socket_Address.sa_data),
"%s", interface_name);
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;
}