/* A2037E Relay Software, for RCM2200 ---------------------------------- NOTE: After you install Dynamic C, and before you compile this code, edit BIOS\RABBITBIOS.C and change XMEM_RESERVE_SIZE to 0x4000L or greater. The A2037E Relay (Version 7+) accepts connections on a single IP port, only one connection at a time. Clients can connect, exercise the driver, and read and write to the configuration file inn EEPROM. Clients cannot change the currently-active configuration. To activate the configuration stored in EEPROM, you must reset the driver with the hardware reset switch. If you want to return the EEPROM configuration file to the factory default, then you hold the configuration switch down and reset the driver. Hold the configuration switch down for at least three seconds after you release the hardware reset switch. The factory default configuration is defined by the constants beginning with the prefix "MY_". The Relay (Version 7+) allows clients to set the global variable logged_in with a LOGIN message accompanied by the a string matching the global password string. The global variable security_level, which is also a configuration parameter, determines when the client is required to log in. With security level zero, the client is never required to log in. With security level one or higher, the client must log in to read from or write to the configuration files. With security level two or higher, the client must log in to execute any message other than a LOGIN message. Version 8 adds the MAC_READ instruction, to which the Relay responds by transmitting its MAC address. Version 8 corrects a bug in the buffered TCP/IP read routine, and guarantees that closing the TCP/IP socket will always cause the Relay to return to its listening state. Verion 8 corrects a bug in the security level, so that trying to execute any instruction other than a LOGIN when the security level is 2 and you have not yet logged in results in an immediate closing of the socket, and a return to the listening state. Version 9 slows down the controller interface by switching to seven wait states instead of 3. Adds the STREAM_DELETE job, which writes a constant byte value repeatedly to the same address. Version 10 adds comments and cleans up TCPIP error handling and socket closure. In particular, when the relay receives an invalid message start code, it closes the existing socket immediately, instead of flushing the socket and waiting for more input. */ // Version and default IP configuration #define VERSION_NUM 10 // current version number #define REPORT 1 // set to 1 to debug #define REPORT_TCP 1 // set to 1 to debug TCP/IP buffer // Default configuration #define MY_IP_ADDRESS "10.0.0.37" // IP of driver #define MY_GATEWAY "10.0.0.1" // IP of gateway #define MY_NETMASK "255.255.255.0" // IP mask #define MY_PORT 90 // IP connection port #define MY_PASSWORD "LWDAQ" // password for access #define MY_SECURITY 1 // security level #define MY_TIMEOUT 0 // tcpip timeout seconds (0=infinite) #define MY_ID "not_assigned" // driver serial number #define MY_TIMESTAMP "00000000000000" // null time stamp // Ethernet configuration #define ETH_MAXBUFS 3 #define ETH_MTU 1500 #define BUFF_SIZE (ETH_MTU-40) #define TCP_BUF_SIZE (BUFF_SIZE*6) #define CHECK_TCP_COUNT 1000 // LWDAQ messages #define START_CODE 0xA5 // correct value for start code #define END_CODE 0x5A // correct value for end code #define START_OFFSET 0 #define ID_OFFSET 1 #define CLEN_OFFSET 5 #define CONTENT_OFFSET 9 #define FRAME_SIZE 10 #define VERSION_READ 0x00000000 #define BYTE_READ 0x00000001 #define BYTE_WRITE 0x00000002 #define STREAM_READ 0x00000003 #define DATA_RETURN 0x00000004 #define BYTE_POLL 0x00000005 #define LOGIN 0x00000006 #define CONFIG_READ 0x00000007 #define CONFIG_WRITE 0x00000008 #define MAC_READ 0x00000009 #define STREAM_DELETE 0x0000000A // Controller registers #define CS_ADDR 40 // configuration switch address // EEPROM file system. #define FS2_USE_PROGRAM_FLASH 16 // kbytes for file system #define CONFIG_LENGTH 1024 // bytes for config file buffer #define CONFIG_FILE_NAME 10 // a numerical name for the config file #define SEPCHARS " :\n,;=" // separator characters in config file // General #define BITS_PER_BYTE 8 // number of buts per byte #define MAC_LENGTH 6 // bytes per MAC address // Libraries #memmap xmem #use "dcrtcp.lib" #use "fs2.lib" // function prototypes long flip_bytes(long); void write_controller_byte(char,char); char read_controller_byte(char); int read_relay_configuration(); int buffered_socket_read(tcp_Socket* s, byte* dp, int len); // global variables char logged_in; int ip_port,tcp_timeout,security_level; char password[32]; char configuration[CONFIG_LENGTH]; char tcpip_buffer[BUFF_SIZE]; int tcp_first,tcp_available; char* tcp_destination; /* main listens for a TCPIP connection, and then attends to that connection while rejecting all others. When the socket closes, main goes back to listening. */ main() { tcp_Socket socket; char in_buffer[BUFF_SIZE],out_buffer[BUFF_SIZE]; char message[255]; int status,socket_open,byte_num; char code,value,register_addr,count_lb,count_hb; long in_length,content_length; long in_message_id; long *long_ptr; long loop_counter; // Configure controller interface. WrPortI(SPCR,&SPCRShadow,0x84); // Disable slave port. WrPortI(PADR,&PADRShadow,0xff); // Set PA to all ones. WrPortI(PEFR,&PEFRShadow,(PEFRShadow|0x80)); // PE7 as chip control signal WrPortI(PEDDR,&PEDDRShadow,(PEDDRShadow|0x80)); // PE7 as an output WrPortI(IB7CR,&IB7CRShadow,0x78); // PE7 as data strobe, 7 wait states // Configure TCP/IP interface. sock_init(); // Loads MY_GATEWAY, MY_IP_ADDRESS, and MY_NETMASK strcpy(password,MY_PASSWORD); ip_port=MY_PORT; security_level=MY_SECURITY; tcp_timeout=MY_TIMEOUT; // Read relay configuration file. read_relay_configuration(); while (1) { logged_in=0; tcp_first=0; tcp_available=0; if (REPORT) printf("Listening for connection on port %d...\n",ip_port); tcp_listen(&socket,ip_port,0,0,NULL,0); sock_wait_established(&socket,0,NULL,&status); if (REPORT) printf("Connection established...\n"); while (1) { if (tcp_available==0) { if (REPORT) printf("Waiting for input...\n"); sock_wait_input(&socket,tcp_timeout,NULL,&status); } buffered_socket_read(&socket,&code,sizeof(code)); if (code!=START_CODE) { if (REPORT) printf("Invalid start code, will close socket.\n"); sock_close(&socket); break; } buffered_socket_read(&socket,(char*)&in_message_id,sizeof(in_message_id)); in_message_id=flip_bytes(in_message_id); buffered_socket_read(&socket,(char*)&in_length,sizeof(in_length)); in_length=flip_bytes(in_length); if (in_length>BUFF_SIZE) { if( REPORT ) printf("Invalid message length, will flush input buffer.\n"); sock_fastread(&socket,in_buffer,BUFF_SIZE); tcp_available=0; continue; } if (in_length>0) buffered_socket_read(&socket,in_buffer,(int) in_length); buffered_socket_read(&socket,&code,sizeof(code)); if (code!=END_CODE) { if (REPORT) printf("Invalid end code, will flush input buffer.\n"); sock_fastread(&socket,in_buffer,BUFF_SIZE); tcp_available=0; continue; } if ((security_level>=2) && (logged_in==0) && ((int) in_message_id != LOGIN)) { if (REPORT) printf("LOGIN required for security level %d,closing connection.\n", security_level); sock_close(&socket); break; } switch ((int) in_message_id) { case VERSION_READ:{ if (REPORT) printf("VERSION_READ\n"); out_buffer[START_OFFSET]=START_CODE; long_ptr=(long*)&out_buffer[ID_OFFSET]; *long_ptr=flip_bytes(DATA_RETURN); long_ptr=(long*)&out_buffer[CLEN_OFFSET]; *long_ptr=flip_bytes(sizeof(long)); long_ptr=(long*)&out_buffer[CONTENT_OFFSET]; *long_ptr=flip_bytes(VERSION_NUM); out_buffer[CONTENT_OFFSET+sizeof(long)]=END_CODE; sock_write(&socket,out_buffer,FRAME_SIZE+sizeof(long)); break; } case BYTE_READ:{ register_addr=in_buffer[3]; value=read_controller_byte(register_addr); if (REPORT) printf("BYTE_READ from %d of %d.\n",register_addr,value); out_buffer[START_OFFSET]=START_CODE; long_ptr=(long*)&out_buffer[ID_OFFSET]; *long_ptr=flip_bytes(DATA_RETURN); long_ptr=(long*)&out_buffer[CLEN_OFFSET]; *long_ptr=flip_bytes(sizeof(char)); out_buffer[CONTENT_OFFSET]=value; out_buffer[CONTENT_OFFSET+sizeof(char)]=END_CODE; sock_write(&socket,out_buffer,FRAME_SIZE+sizeof(char)); break; } case BYTE_WRITE:{ register_addr=in_buffer[3]; value=in_buffer[4]; write_controller_byte(register_addr,value); if (REPORT) printf("BYTE_WRITE to %d of %d.\n",register_addr,value); break; } case STREAM_READ:{ register_addr=in_buffer[3] & 0x3F; WrPortI(PADR,NULL,(0x40 | register_addr)); out_buffer[START_OFFSET]=START_CODE; long_ptr=(long*)&out_buffer[ID_OFFSET]; *long_ptr=flip_bytes(DATA_RETURN); long_ptr=(long*)&in_buffer[4]; content_length=flip_bytes(*long_ptr); long_ptr=(long*)&out_buffer[CLEN_OFFSET]; *long_ptr=flip_bytes(content_length); if (REPORT) printf("STREAM_READ from %d of length %lu...", register_addr,content_length); sock_write(&socket,out_buffer,9); while (content_length>0) { if (content_length>BUFF_SIZE) { count_hb=(char) ((BUFF_SIZE & 0x0000FF00) >> BITS_PER_BYTE); count_lb=(char) (BUFF_SIZE & 0x000000FF); if (count_lb>0) {count_hb=count_hb+1;} } else { count_hb=(char) ((content_length & 0x0000FF00) >> BITS_PER_BYTE); count_lb=(char) (content_length & 0x000000FF); if (count_lb>0) {count_hb=count_hb+1;} } #asm ld hl,count_hb ld c,(hl) ld hl,count_lb ld b,(hl) ld de,out_buffer outer_loop_a: inner_loop_a: ioe ld a,(0xE000) ld (de),a inc de dec b jr nz,inner_loop_a dec c jr nz,outer_loop_a #endasm if (content_length>BUFF_SIZE) { sock_write(&socket,out_buffer,BUFF_SIZE); content_length=content_length-BUFF_SIZE; } else { sock_write(&socket,out_buffer,(int) content_length); content_length=0; } } out_buffer[0]=END_CODE; sock_write(&socket,out_buffer,1); if (REPORT) printf("\n"); break; } case STREAM_DELETE:{ register_addr=in_buffer[3] & 0x3F; WrPortI(PADR,NULL,register_addr); long_ptr=(long*)&in_buffer[4]; content_length=flip_bytes(*long_ptr); value=in_buffer[8]; if (REPORT) printf("STREAM_DELETE to %d of length %lu, over-writing with %d...", register_addr,content_length,value); while (content_length>0) { if (content_length>BUFF_SIZE) { count_hb=(char) ((BUFF_SIZE & 0x0000FF00) >> BITS_PER_BYTE); count_lb=(char) (BUFF_SIZE & 0x000000FF); if (count_lb>0) {count_hb=count_hb+1;} } else { count_hb=(char) ((content_length & 0x0000FF00) >> BITS_PER_BYTE); count_lb=(char) (content_length & 0x000000FF); if (count_lb>0) {count_hb=count_hb+1;} } #asm ld hl,count_hb ld c,(hl) ld hl,count_lb ld b,(hl) ld hl,value ld a,(hl) outer_loop_aa: inner_loop_aa: ioe ld (0xE000),a dec b jr nz,inner_loop_aa dec c jr nz,outer_loop_aa #endasm if (content_length>BUFF_SIZE) { content_length=content_length-BUFF_SIZE; } else { content_length=0; } } if (REPORT) printf("\n"); break; } case BYTE_POLL:{ register_addr=in_buffer[3]; value=in_buffer[4]; if (REPORT) printf("POLL_BYTE for %d in %d...",value,register_addr); while (read_controller_byte(register_addr) != value) if (!tcp_tick(&socket)) {status =1; goto sock_err;} if (REPORT) printf("\n"); break; } case LOGIN:{ if (in_length<1) { if (REPORT) printf("LOGIN failed with empty password.\n"); break; } if (!strcmp(password,in_buffer)) { logged_in=1; if (REPORT) printf("LOGIN successful.\n"); } else { logged_in=0; if (REPORT) printf("LOGIN failed with password: %s.\n",in_buffer); } out_buffer[START_OFFSET]=START_CODE; long_ptr=(long*)&out_buffer[ID_OFFSET]; *long_ptr=flip_bytes(DATA_RETURN); long_ptr=(long*)&out_buffer[CLEN_OFFSET]; *long_ptr=flip_bytes(sizeof(char)); out_buffer[CONTENT_OFFSET]=logged_in; out_buffer[CONTENT_OFFSET+sizeof(char)]=END_CODE; sock_write(&socket,out_buffer,FRAME_SIZE+sizeof(char)); break; } case CONFIG_READ:{ if (REPORT) printf("CONFIG_READ"); out_buffer[START_OFFSET]=START_CODE; long_ptr=(long*)&out_buffer[ID_OFFSET]; *long_ptr=flip_bytes(DATA_RETURN); if ((logged_in==1) || (security_level==0)) { if (REPORT) printf(" accepted..."); content_length=strlen(configuration)+1; long_ptr=(long*)&out_buffer[CLEN_OFFSET]; *long_ptr=flip_bytes(content_length); strcpy((char*)&out_buffer[CONTENT_OFFSET],configuration); } else { if (REPORT) printf(" rejected..."); strcpy(message,"You must log in to read the configuration file."); content_length=strlen(message)+1; long_ptr=(long*)&out_buffer[CLEN_OFFSET]; *long_ptr=flip_bytes(content_length); strcpy((char*)&out_buffer[CONTENT_OFFSET],message); } out_buffer[CONTENT_OFFSET + (int) content_length]=END_CODE; sock_write(&socket,out_buffer,FRAME_SIZE + (int) content_length); if (REPORT) printf("transmitted %d characters.\n",content_length); break; } case CONFIG_WRITE:{ if (REPORT) printf("CONFIG_WRITE"); if ((logged_in==1) || (security_level==0)) { if (in_length=len) { memcpy(dp,&tcpip_buffer[tcp_first],len); tcp_first=tcp_first+len; tcp_available=tcp_available-len; } else { tcp_remaining=len; tcp_destination=dp; if (tcp_available>0) { memcpy(tcp_destination,&tcpip_buffer[tcp_first],tcp_available); tcp_remaining=tcp_remaining-tcp_available; tcp_destination=tcp_destination+tcp_available; if (REPORT_TCP) printf("Read last %d available, need %d more.\n", tcp_available,tcp_remaining); } tcp_available=0; tcp_first=0; while (tcp_remaining>0) { if (REPORT_TCP) printf("Waiting for %d bytes...",tcp_remaining); while (tcp_available==0) { tcp_available=sock_fastread(s,&tcpip_buffer[tcp_first],BUFF_SIZE); if (!tcp_tick(s)) { if (REPORT_TCP) printf("connection broken."); return 0; } } if (REPORT_TCP) printf("received %d bytes.\n",tcp_available); if (tcp_available>=tcp_remaining) { memcpy(tcp_destination,&tcpip_buffer[tcp_first],tcp_remaining); tcp_first=tcp_remaining; tcp_available=tcp_available-tcp_remaining; tcp_remaining=0; } else { memcpy(tcp_destination,&tcpip_buffer[tcp_first],tcp_available); tcp_destination=tcp_destination+tcp_available; tcp_remaining=tcp_remaining-tcp_available; tcp_available=0; tcp_first=0; } } } return len; } /* read_relay_configuration reads the configuration parameters out of EEPROM, assuming the EEPROM has been initialized and written already. If the EEPROM file system has not been created, this routine creates it. If there is no configuration file existing, it uses default values for all parameters, which you will find declared above as global constants. If the user is pressing the driver's configuration switch, the routine detects this and creates the default configuration file. */ int read_relay_configuration() { int rc,fn,lx; File config_file; static char scratch[CONFIG_LENGTH]; char* parameter; char* value; lx=fs_get_flash_lx(); if (REPORT) printf("Initializing file system...",lx); rc=fs_init(0,0); if (rc) { if (REPORT) { printf("failed with error %d.\n",errno); if (errno=EINVAL) printf("The reserveblocks parameter was non-zero.\n"); if (errno=EIO) printf("I/O error. This indicates a hardware problem.\n"); if (errno=ENOMEM) printf("Insufficient memory for required buffers.\n"); if (errno=ENOSPC) printf("No recognized flash or RAM memory device available.\n"); } return 1; } if (REPORT) printf("\n"); if (!read_controller_byte(CS_ADDR)) { if (REPORT) printf("Formatting EEPROM disk..."); rc=lx_format(lx,0); if (rc) { if (REPORT) printf("failed with error %d.\n",errno); return 2; } if (REPORT) printf("\n"); } if (REPORT) printf("Disk capacity is %ld \n",fs_get_lx_size(lx,1,0)); if (REPORT) printf("Available space is %ld \n",fs_get_lx_size(lx,0,0)); fs_set_lx(lx,lx); if (!read_controller_byte(CS_ADDR)) { if (REPORT) printf("Creating configuration file..."); rc=fcreate(&config_file,CONFIG_FILE_NAME); if (rc) { if (REPORT) printf("failed with error %d.\n",errno); return 3; } if (REPORT) printf("\n"); if (REPORT) printf("Writing configuration file..."); rc=fopen_wr(&config_file,CONFIG_FILE_NAME); if (rc) { if (REPORT) printf("failed with error %d.\n",errno); return 4; } sprintf(configuration,"lwdaq_relay_configuration:\n"); sprintf(scratch,"operator: relay_version_%d\n",VERSION_NUM); strcat(configuration,scratch); sprintf(scratch,"configuration_time: %s\n",MY_TIMESTAMP); strcat(configuration,scratch); sprintf(scratch,"password: %s\n",MY_PASSWORD); strcat(configuration,scratch); sprintf(scratch,"driver_id: %s\n",MY_ID); strcat(configuration,scratch); sprintf(scratch,"ip_addr: %s\n",MY_IP_ADDRESS); strcat(configuration,scratch); sprintf(scratch,"ip_port: %d\n",MY_PORT); strcat(configuration,scratch); sprintf(scratch,"tcp_timeout: %d\n",MY_TIMEOUT); strcat(configuration,scratch); sprintf(scratch,"security_level: %d\n",MY_SECURITY); strcat(configuration,scratch); sprintf(scratch,"gateway_addr: %s\n",MY_GATEWAY); strcat(configuration,scratch); sprintf(scratch,"subnet_mask: %s\n",MY_NETMASK); strcat(configuration,scratch); fwrite(&config_file,configuration,strlen(configuration)+1); fclose(&config_file); if (REPORT) printf("\n"); } if (REPORT) printf("Reading configuration file..."); rc=fopen_rd(&config_file,CONFIG_FILE_NAME); if (rc) { if (REPORT) printf("failed with error %d.\n",errno); return 5; } fseek(&config_file,0,SEEK_SET); rc=fread(&config_file,configuration,CONFIG_LENGTH); fclose(&config_file); if (REPORT) printf("\n"); strcpy(scratch,configuration); parameter=strtok(scratch,SEPCHARS); if (strcmp(parameter,"lwdaq_relay_configuration")) { if (REPORT) printf("Error: contents start with <%s>.\n",parameter); return 6; } parameter=strtok(NULL,SEPCHARS); value=strtok(NULL,SEPCHARS); while ((parameter != NULL) && (value != NULL)) { if (!strcmp(parameter,"operator")) { if (REPORT) printf("Configuration supplied by %s\n",value); } if (!strcmp(parameter,"configuration_time")) { if (REPORT) printf("Configuration time stamp is %s\n",value); } if (!strcmp(parameter,"driver_id")) { if (REPORT) printf("Driver identification is %s\n",value); } if (!strcmp(parameter,"password")) { strcpy(password,value); if (REPORT) printf("Setting %s to %s\n",parameter,value); } if (!strcmp(parameter,"ip_port")) { ip_port=atoi(value); if (REPORT) printf("Setting %s to %d\n",parameter,ip_port); } if (!strcmp(parameter,"ip_addr")) { tcp_config("MY_IP_ADDRESS",value); if (REPORT) printf("Setting %s to %s\n",parameter,value); } if (!strcmp(parameter,"gateway_addr")) { tcp_config("MY_GATEWAY",value); if (REPORT) printf("Setting %s to %s\n",parameter,value); } if (!strcmp(parameter,"subnet_mask")) { tcp_config("MY_NETMASK",value); if (REPORT) printf("Setting %s to %s\n",parameter,value); } if (!strcmp(parameter,"security_level")) { security_level=atoi(value); if (REPORT) printf("Setting %s to %d\n",parameter,security_level); } if (!strcmp(parameter,"tcp_timeout")) { tcp_timeout=atoi(value); if (REPORT) printf("Setting %s to %d\n",parameter,tcp_timeout); } parameter=strtok(NULL,SEPCHARS); value=strtok(NULL,SEPCHARS); } return 0; } /* write_relay_configuration writes a string to the relay configuration file. It does not check the string to see if it is in the correct format. If the format is not correct, a subsequent read_relay_configuration will fail. */ int write_relay_configuration(char* contents) { int rc,lx; File config_file; static char scratch[CONFIG_LENGTH]; char* parameter; char* value; if (REPORT) printf("Writing new contents to configuration file..."); rc=fopen_wr(&config_file,CONFIG_FILE_NAME); if (rc) { if (REPORT) printf("failed with error %d.\n",errno); return 3; } fseek(&config_file,0,SEEK_SET); fwrite(&config_file,contents,strlen(contents)+1); fclose(&config_file); if (REPORT) printf("done.\n"); strcpy(scratch,contents); parameter=strtok(scratch,SEPCHARS); if (REPORT) printf("%s:\n",parameter); parameter=strtok(NULL,SEPCHARS); value=strtok(NULL,SEPCHARS); while ((parameter != NULL) && (value != NULL)) { if (REPORT) printf("%s: %s\n",parameter,value); parameter=strtok(NULL,SEPCHARS); value=strtok(NULL,SEPCHARS); } return 0; }