Threaded version 0.7
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
#include "atom_server.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
typedef struct { am_client c; ar_transport rtu; int serial; uint32_t generation; int used, retired; } slot;
|
||||
typedef struct { am_client c; ar_transport rtu; int serial; uint32_t generation; int used, retired, suspended; uint64_t heartbeat; } slot;
|
||||
static slot slots[AM_POOL_SIZE];
|
||||
static uint32_t token(unsigned i) { return (slots[i].generation<<8)|(i+1u); }
|
||||
static slot *lookup(uint32_t h) {
|
||||
@@ -24,7 +24,7 @@ static void snapshot(const am_client *c,uint32_t h,uint32_t *s) {
|
||||
s[18]=c->function; s[19]=c->unit; s[20]=(uint32_t)c->enabled;
|
||||
s[21]=c->connect_timeout_ms; s[22]=c->response_timeout_ms; s[23]=c->retry_delay_ms;
|
||||
}
|
||||
uint32_t ab_call(uint32_t cmd,uint32_t h,uint32_t ip,uint16_t port,
|
||||
uint32_t ab_dispatch(uint32_t cmd,uint32_t h,uint32_t ip,uint16_t port,
|
||||
uint8_t unit,uint8_t f,uint16_t addr,uint16_t q,uint16_t *data,uint32_t cap,
|
||||
uint32_t *status,uint32_t sc,uint64_t now,uint32_t ct,uint32_t rt,uint32_t retry) {
|
||||
slot *s; unsigned i; uint32_t result=0;
|
||||
@@ -47,7 +47,7 @@ uint32_t ab_call(uint32_t cmd,uint32_t h,uint32_t ip,uint16_t port,
|
||||
text[q]=0;
|
||||
if(!ar_init(&s->c,&s->rtu,text,ip,unit,f,(uint8_t)addr,ct))return AM_ARGUMENT;
|
||||
} else if(!am_init(&s->c,text,port)) return AM_ARGUMENT;
|
||||
s->generation++; s->used=1; h=token(i);
|
||||
s->generation++; s->used=1; s->suspended=0; s->heartbeat=now; h=token(i);
|
||||
snapshot(&s->c,h,status); return 0;
|
||||
}
|
||||
s=lookup(h);
|
||||
@@ -58,9 +58,12 @@ uint32_t ab_call(uint32_t cmd,uint32_t h,uint32_t ip,uint16_t port,
|
||||
/* Never recycle a generation after wrap: stale handles stay invalid. */
|
||||
if(s->generation==0xffffffu) s->retired=1;
|
||||
snapshot(&s->c,0,status); return 0;
|
||||
case AB_ENABLE: am_enable(&s->c,1,now); break;
|
||||
case AB_DISABLE: if(s->serial)ar_close(&s->c);else am_enable(&s->c,0,now); break;
|
||||
case AB_TICK: if(s->serial)ar_tick(&s->c,&s->rtu,now);else am_tick(&s->c,now); break;
|
||||
case AB_ENABLE: s->heartbeat=now; s->suspended=0; am_enable(&s->c,1,now); break;
|
||||
case AB_DISABLE: s->suspended=0; if(s->serial)ar_close(&s->c);else am_enable(&s->c,0,now); break;
|
||||
case AB_TICK:
|
||||
s->heartbeat=now;
|
||||
if(s->suspended) { s->suspended=0; am_enable(&s->c,1,now); }
|
||||
break;
|
||||
case AB_RECONNECT: if(s->serial)ar_reconnect(&s->c,&s->rtu,now);else am_reconnect(&s->c,now); break;
|
||||
case AB_CONFIGURE:
|
||||
if(s->c.enabled) result=AM_BUSY;
|
||||
@@ -68,6 +71,7 @@ uint32_t ab_call(uint32_t cmd,uint32_t h,uint32_t ip,uint16_t port,
|
||||
else { s->c.connect_timeout_ms=ct; s->c.response_timeout_ms=rt; s->c.retry_delay_ms=retry; }
|
||||
break;
|
||||
case AB_SUBMIT:
|
||||
if(s->c.done || s->c.error) { result=AM_BUSY; break; }
|
||||
if((f==5 || f==6 || f==15 || f==16) && (!data || cap<q)) result=AB_BAD_CAPACITY;
|
||||
else result=(uint32_t)(s->serial?ar_request(&s->c,&s->rtu,unit,f,addr,q,data,now):am_request(&s->c,unit,f,addr,q,data,now));
|
||||
break;
|
||||
@@ -76,6 +80,7 @@ uint32_t ab_call(uint32_t cmd,uint32_t h,uint32_t ip,uint16_t port,
|
||||
else if(!data || cap<s->c.quantity) result=AB_BAD_CAPACITY;
|
||||
else memcpy(data,s->c.data,s->c.quantity*sizeof(*data));
|
||||
break;
|
||||
case 12: break;
|
||||
case AB_ACK: s->c.done=0; s->c.error=0; break;
|
||||
case AB_REQUEST_TIMEOUT:
|
||||
if(s->c.busy) result=AM_BUSY;
|
||||
@@ -84,9 +89,15 @@ uint32_t ab_call(uint32_t cmd,uint32_t h,uint32_t ip,uint16_t port,
|
||||
break;
|
||||
default: result=AB_BAD_COMMAND; break;
|
||||
}
|
||||
snapshot(&s->c,h,status); return result;
|
||||
snapshot(&s->c,h,status);
|
||||
if(cmd==12){
|
||||
status[21]=(uint32_t)(s->c.completed_ms>=s->c.started_ms?s->c.completed_ms-s->c.started_ms:0);
|
||||
uint64_t age=s->c.has_success?now-s->c.last_success_ms:0;
|
||||
status[22]=age>UINT32_MAX?UINT32_MAX:(uint32_t)age;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
void ab_shutdown(void) {
|
||||
void ab_close_all(void) {
|
||||
as_shutdown();
|
||||
unsigned i;
|
||||
for(i=0;i<AM_POOL_SIZE;i++) if(slots[i].used) {
|
||||
@@ -94,3 +105,16 @@ void ab_shutdown(void) {
|
||||
if(slots[i].generation==0xffffffu) slots[i].retired=1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Called only by the worker while holding the shared engine mutex. */
|
||||
void ab_pump(uint64_t now,uint32_t watchdog_ms) {
|
||||
for(unsigned i=0;i<AM_POOL_SIZE;i++) if(slots[i].used) {
|
||||
slot *s=&slots[i];
|
||||
if(s->c.enabled && now-s->heartbeat >= watchdog_ms) {
|
||||
if(s->serial)ar_close(&s->c);else am_close(&s->c);
|
||||
s->suspended=1;
|
||||
}
|
||||
if(s->serial)ar_tick(&s->c,&s->rtu,now);else am_tick(&s->c,now);
|
||||
}
|
||||
as_pump(now,watchdog_ms);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
#ifndef ATOM_BRIDGE_H
|
||||
#define ATOM_BRIDGE_H
|
||||
#include <stdint.h>
|
||||
/* Fixed pool, one IEC task owns ALL calls. Do not share across tasks.
|
||||
/* Fixed pool; serialized calls and worker I/O. IEC instances still belong to one task.
|
||||
* Only synchronous pointer access; generation-checked handles, no heap. */
|
||||
#define AM_STATUS_WORDS 24
|
||||
#define AM_POOL_SIZE 16
|
||||
enum { AB_OPEN=1, AB_CLOSE, AB_ENABLE, AB_DISABLE, AB_TICK, AB_SUBMIT,
|
||||
AB_COPY_READ, AB_RECONNECT, AB_CONFIGURE, AB_ACK, AB_REQUEST_TIMEOUT };
|
||||
enum { AB_OPEN_RTU=20 };
|
||||
enum { AB_BAD_HANDLE=256, AB_POOL_FULL, AB_BAD_CAPACITY, AB_BAD_COMMAND };
|
||||
enum { AB_BAD_HANDLE=256, AB_POOL_FULL, AB_BAD_CAPACITY, AB_BAD_COMMAND, AB_WORKER_ERROR };
|
||||
uint32_t ab_call(uint32_t command,uint32_t handle,uint32_t ip,uint16_t port,
|
||||
uint8_t unit,uint8_t function,uint16_t address,uint16_t quantity,
|
||||
uint16_t *data,uint32_t capacity,uint32_t *status,uint32_t status_capacity,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <poll.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
#ifndef MSG_NOSIGNAL
|
||||
@@ -14,12 +15,12 @@ static uint16_t get16(const uint8_t *p) { return (uint16_t)((p[0]<<8)|p[1]); }
|
||||
static void put16(uint8_t *p,uint16_t v) { p[0]=(uint8_t)(v>>8); p[1]=(uint8_t)v; }
|
||||
static int writing(uint8_t f) { return f==5 || f==6 || f==15 || f==16; }
|
||||
static void drop(am_client *c) { if(c->fd>=0) { if(c->serial_port)ioctl(c->fd,TIOCNXCL);close(c->fd); } c->fd=-1; }
|
||||
static void finish(am_client *c,am_error e) {
|
||||
c->busy=0; c->done=e==AM_OK; c->error=e!=AM_OK; c->result=e;
|
||||
static void finish(am_client *c,am_error e,uint64_t now) {
|
||||
c->completed_ms=now; c->busy=0; c->done=e==AM_OK; c->error=e!=AM_OK; c->result=e;
|
||||
if(e==AM_OK) c->completed++; else { c->failed++; c->last_error=e; }
|
||||
}
|
||||
static void fault(am_client *c,am_error e,uint64_t now) {
|
||||
if(c->busy) { c->write_uncertain=writing(c->function) && c->tx_pos>0; finish(c,e); }
|
||||
if(c->busy) { c->write_uncertain=writing(c->function) && c->tx_pos>0; finish(c,e,now); }
|
||||
c->last_error=e; drop(c); c->state=c->enabled?AM_RETRY:AM_DISABLED;
|
||||
c->deadline_ms=now+c->retry_delay_ms;
|
||||
}
|
||||
@@ -63,7 +64,7 @@ am_error am_request(am_client *c,uint8_t unit,uint8_t f,uint16_t addr,uint16_t q
|
||||
else if(v[i]) c->tx[13+i/8]|=(uint8_t)(1u<<(i%8));
|
||||
}
|
||||
}
|
||||
c->unit=unit; c->function=f; c->address=addr; c->quantity=q;
|
||||
c->started_ms=now; c->unit=unit; c->function=f; c->address=addr; c->quantity=q;
|
||||
c->tx_len=n; c->tx_pos=0; c->rx_len=0; c->rx_need=7;
|
||||
c->busy=1; c->done=0; c->error=0; c->exception=0; c->write_uncertain=0;
|
||||
c->result=AM_OK; c->state=AM_SENDING; c->deadline_ms=now+c->response_timeout_ms;
|
||||
@@ -73,7 +74,7 @@ void am_accept_response(am_client *c,uint64_t now) {
|
||||
unsigned i; size_t bytes;
|
||||
if(c->rx[7]==(uint8_t)(c->function|0x80)) {
|
||||
if(c->rx_need!=9 || !c->rx[8]) { fault(c,AM_PROTOCOL,now); return; }
|
||||
c->exception=c->rx[8]; finish(c,AM_EXCEPTION); c->state=AM_READY; return;
|
||||
c->exception=c->rx[8]; finish(c,AM_EXCEPTION,now); c->state=AM_READY; return;
|
||||
}
|
||||
if(c->rx[7]!=c->function) { fault(c,AM_PROTOCOL,now); return; }
|
||||
if(writing(c->function)) {
|
||||
@@ -84,7 +85,7 @@ void am_accept_response(am_client *c,uint64_t now) {
|
||||
for(i=0;i<c->quantity;i++) c->data[i]=c->function<=2?
|
||||
(uint16_t)((c->rx[9+i/8]>>(i%8))&1):get16(c->rx+9+2*i);
|
||||
}
|
||||
c->last_success_ms=now; c->has_success=1; finish(c,AM_OK); c->state=AM_READY;
|
||||
c->last_success_ms=now; c->has_success=1; finish(c,AM_OK,now); c->state=AM_READY;
|
||||
}
|
||||
void am_tick(am_client *c,uint64_t now) {
|
||||
struct sockaddr_in a; struct pollfd p; int e,r; socklen_t len; ssize_t n;
|
||||
@@ -92,8 +93,10 @@ void am_tick(am_client *c,uint64_t now) {
|
||||
if(c->state==AM_RETRY) {
|
||||
if(now<c->deadline_ms) return;
|
||||
c->connect_attempts++; if(c->connect_attempts>1) c->reconnects++;
|
||||
c->socket_error=0; c->fd=socket(AF_INET,SOCK_STREAM,0);
|
||||
c->socket_error=0; c->fd=socket(AF_INET,SOCK_STREAM|SOCK_CLOEXEC,0);
|
||||
if(c->fd<0) { c->socket_error=errno; fault(c,AM_SOCKET,now); return; }
|
||||
int one=1;
|
||||
setsockopt(c->fd,IPPROTO_TCP,TCP_NODELAY,&one,sizeof(one));
|
||||
r=fcntl(c->fd,F_GETFL,0);
|
||||
if(r<0 || fcntl(c->fd,F_SETFL,r|O_NONBLOCK)<0) { c->socket_error=errno; fault(c,AM_SOCKET,now); return; }
|
||||
memset(&a,0,sizeof(a)); a.sin_family=AF_INET; a.sin_port=htons(c->port);
|
||||
|
||||
@@ -15,7 +15,7 @@ typedef struct {
|
||||
uint8_t exception, unit, function;
|
||||
uint16_t transaction, address, quantity;
|
||||
uint32_t connect_timeout_ms, response_timeout_ms, retry_delay_ms;
|
||||
uint64_t deadline_ms, last_success_ms;
|
||||
uint64_t deadline_ms, last_success_ms, started_ms, completed_ms;
|
||||
uint32_t connect_attempts, reconnects, completed, failed;
|
||||
int busy, done, error, write_uncertain, has_success;
|
||||
char ip[16]; uint16_t port;
|
||||
|
||||
@@ -29,7 +29,7 @@ int ar_init(am_client *c,ar_transport *r,const char *path,uint32_t baud,uint8_t
|
||||
return 1;
|
||||
}
|
||||
static void ar_fault(am_client *c,ar_transport *r,am_error error,uint64_t now) {
|
||||
if(c->busy) { c->write_uncertain=(c->function>=5 && r->tx_pos>0);c->busy=0;c->done=0;c->error=1;c->result=error;c->failed++; }
|
||||
if(c->busy) { c->write_uncertain=(c->function>=5 && r->tx_pos>0);c->completed_ms=now;c->busy=0;c->done=0;c->error=1;c->result=error;c->failed++; }
|
||||
c->last_error=error;if(c->fd>=0){ioctl(c->fd,TIOCNXCL);close(c->fd);}c->fd=-1;c->state=c->enabled?AM_RETRY:AM_DISABLED;
|
||||
c->deadline_ms=now+c->retry_delay_ms;r->quiet_until=c->deadline_ms;r->rx_len=0;
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
#endif
|
||||
typedef struct { int fd;uint8_t rx[260],tx[260];size_t received,needed,sent,length;uint64_t deadline; } as_peer;
|
||||
typedef struct {
|
||||
int used,enabled,listener,running,error,os_error;uint32_t generation,received,replied,exceptions,retry;
|
||||
uint32_t ip;uint16_t port,base[4],count[4],values[4][AS_ITEMS];uint64_t retry_at;
|
||||
int used,enabled,suspended,listener,running,error,os_error;uint32_t generation,received,replied,exceptions,retry;
|
||||
uint32_t ip;uint16_t port,base[4],count[4],values[4][AS_ITEMS];uint64_t retry_at,heartbeat; uint8_t dirty[4][AS_ITEMS];
|
||||
as_peer peers[AS_PEERS];
|
||||
} as_server;
|
||||
static as_server as_slots[AS_POOL];
|
||||
@@ -46,13 +46,13 @@ static void as_reply(as_server *s,as_peer *p){
|
||||
table=f==5?0:2;
|
||||
if(p->needed!=12 || (f==5 && q!=0 && q!=0xff00)){exception=3;break;}
|
||||
if(!as_range(s,table,address,1)){exception=2;break;}
|
||||
s->values[table][address-s->base[table]]=f==5?(q!=0):q;memcpy(p->tx+8,p->rx+8,4);n=12;break;
|
||||
s->values[table][address-s->base[table]]=f==5?(q!=0):q;s->dirty[table][address-s->base[table]]=1;memcpy(p->tx+8,p->rx+8,4);n=12;break;
|
||||
case 15:case 16:
|
||||
table=f==15?0:2;
|
||||
if(!q || q>(f==15?1968:123) || p->needed<13 || p->rx[12]!=(f==15?(q+7u)/8u:2u*q) || p->needed!=(size_t)(13+p->rx[12])){exception=3;break;}
|
||||
if(!as_range(s,table,address,q)){exception=2;break;}
|
||||
index=address-s->base[table];
|
||||
for(unsigned i=0;i<q;i++)s->values[table][index+i]=f==15?(uint16_t)((p->rx[13+i/8]>>(i%8))&1):as_get16(p->rx+13+2*i);
|
||||
for(unsigned i=0;i<q;i++){s->values[table][index+i]=f==15?(uint16_t)((p->rx[13+i/8]>>(i%8))&1):as_get16(p->rx+13+2*i);s->dirty[table][index+i]=1;}
|
||||
memcpy(p->tx+8,p->rx+8,4);n=12;break;
|
||||
default:exception=1;break;
|
||||
}
|
||||
@@ -103,7 +103,7 @@ uint32_t as_call(uint32_t cmd,uint32_t h,uint32_t ip,uint16_t port,uint8_t table
|
||||
memset(out,0,24*sizeof(*out));if(!port || !retry)return AM_ARGUMENT;
|
||||
for(i=0;i<AS_POOL;i++)if(!as_slots[i].used && as_slots[i].generation<0xffffffu)break;
|
||||
if(i==AS_POOL)return 257;
|
||||
s=&as_slots[i];uint32_t generation=s->generation+1;memset(s,0,sizeof(*s));s->generation=generation;s->used=1;s->listener=-1;s->ip=ip;s->port=port;s->retry=retry;
|
||||
s=&as_slots[i];uint32_t generation=s->generation+1;memset(s,0,sizeof(*s));s->generation=generation;s->used=1;s->listener=-1;s->ip=ip;s->port=port;s->retry=retry;s->heartbeat=now;
|
||||
for(unsigned j=0;j<AS_PEERS;j++){s->peers[j].fd=-1;as_peer_close(&s->peers[j]);}
|
||||
h=(generation<<8)|(i+65u);as_snapshot(s,h,out);return 0;
|
||||
}
|
||||
@@ -112,20 +112,32 @@ uint32_t as_call(uint32_t cmd,uint32_t h,uint32_t ip,uint16_t port,uint8_t table
|
||||
s=&as_slots[i-1];
|
||||
switch(cmd){
|
||||
case AS_CLOSE:as_stop(s);s->used=0;as_snapshot(s,0,out);return 0;
|
||||
case AS_ENABLE:s->enabled=1;break;
|
||||
case AS_DISABLE:s->enabled=0;as_stop(s);break;
|
||||
case AS_TICK:as_tick(s,now);break;
|
||||
case AS_ENABLE:s->enabled=1;s->suspended=0;s->heartbeat=now;break;
|
||||
case AS_DISABLE:s->suspended=0;s->enabled=0;as_stop(s);break;
|
||||
case AS_TICK:s->heartbeat=now;if(s->suspended){s->suspended=0;s->enabled=1;s->retry_at=now;}break;
|
||||
case AS_PUSH:case AS_PULL:
|
||||
if(table>3 || !q || q>AS_ITEMS || (uint32_t)addr+q>65536u)result=AM_ARGUMENT;
|
||||
else if(!data || cap<q)result=258;
|
||||
else if(cmd==AS_PUSH){
|
||||
if(table<2){for(unsigned j=0;j<q;j++)if(data[j]>1){result=AM_ARGUMENT;break;}}
|
||||
if(!result){s->base[table]=addr;s->count[table]=q;memcpy(s->values[table],data,q*sizeof(*data));}
|
||||
if(!result){
|
||||
if(s->count[table] && (s->base[table]!=addr || s->count[table]!=q)) { result=AM_ARGUMENT;break; }
|
||||
s->base[table]=addr;s->count[table]=q;
|
||||
for(unsigned j=0;j<q;j++)if(!s->dirty[table][j])s->values[table][j]=data[j];
|
||||
}
|
||||
}else if(s->base[table]!=addr || s->count[table]!=q)result=AM_ARGUMENT;
|
||||
else memcpy(data,s->values[table],q*sizeof(*data));
|
||||
else {memcpy(data,s->values[table],q*sizeof(*data));memset(s->dirty[table],0,q);}
|
||||
break;
|
||||
default:result=259;break;
|
||||
}
|
||||
as_snapshot(s,h,out);return result;
|
||||
}
|
||||
void as_shutdown(void){for(unsigned i=0;i<AS_POOL;i++)if(as_slots[i].used){as_stop(&as_slots[i]);as_slots[i].used=0;}}
|
||||
|
||||
void as_pump(uint64_t now,uint32_t watchdog_ms){
|
||||
for(unsigned i=0;i<AS_POOL;i++)if(as_slots[i].used){
|
||||
as_server *s=&as_slots[i];
|
||||
if(s->enabled && now-s->heartbeat>=watchdog_ms){as_stop(s);s->enabled=0;s->suspended=1;}
|
||||
as_tick(s,now);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,3 +5,5 @@ enum { AS_OPEN=30,AS_CLOSE,AS_ENABLE,AS_DISABLE,AS_TICK,AS_PUSH,AS_PULL };
|
||||
uint32_t as_call(uint32_t,uint32_t,uint32_t,uint16_t,uint8_t,uint16_t,uint16_t,uint16_t *,uint32_t,uint32_t *,uint64_t,uint32_t);
|
||||
void as_shutdown(void);
|
||||
#endif
|
||||
|
||||
void as_pump(uint64_t now,uint32_t watchdog_ms);
|
||||
|
||||
Reference in New Issue
Block a user