/* * Copyright (C) 2000 Kees Cook * cook@cpoint.net, http://outflux.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. * http://www.gnu.org/copyleft/gpl.html * */ #include #include #include #include #include #include #include /* ioctl */ #include #define __USE_XOPEN #define __USE_GNU #include #include "sts.h" #include "sts_fas.h" /* fas funcs & defines */ /* helper macros for 16bit Least significant bit first */ #define GRAB_16BIT(ptr, start) ((ptr)[(start)] + ((ptr)[(start)+1]<<8)) #define STORE_16BIT(ptr,start,val) \ (ptr)[(start)]=((val) & 0xff); \ (ptr)[(start)+1]=(((val) & 0xff00) >> 8); /* helper macros for bit-title manipulations: bit# 0, 1, ... 7 */ #define IS_SET(byte, bit) (((byte) & (1 << (bit))) == (1 << (bit))) #define SET(byte, bit) ((byte) | (1 << (bit))) #define UNSET(byte, bit) ((byte) & (~(1 << (bit)))) /****** cmd list manipulation functions FIXME: I think we need to be atomically locked way prior to calling */ /* adds a FAS command to the doubly linked list of queued cmds */ void add_cmd(sts_state * sts, char * data, int len) { cmd_list * cmd; if (!sts) { fprintf(stderr,"add_cmd: Got null sts\n"); return; } if (!data && len!=0) { fprintf(stderr,"add_cmd: Got null data pointer\n"); return; } if (!(cmd=malloc(sizeof(cmd_list)))) { perror("add_cmd pointer malloc"); return; } if (!(cmd->data=malloc(len))) { perror("add_cmd data malloc"); return; } memcpy(cmd->data, data, len); cmd->size=len; cmd->next=NULL; cmd->prev=sts->tail; if (sts->tail) sts->tail->next=cmd; sts->tail=cmd; if (!sts->head) sts->head=cmd; } /* find a properly size cmd in the list, starting with "cmd" */ cmd_list * find_cmd(sts_state * sts, cmd_list *cmd, int slotsize) { if (!sts) { fprintf(stderr,"find_cmd: Got null sts\n"); return NULL; } if (!cmd) cmd=sts->head; for (;cmd;cmd=cmd->next) if (cmd->size<=slotsize) break; return cmd; } /* throws away a cmd in the list */ void delete_cmd(sts_state * sts, cmd_list * cmd) { if (!sts) { fprintf(stderr,"delete_cmd: Got null sts\n"); return; } if (!cmd) { fprintf(stderr,"delete_cmd: Got null cmd\n"); return; } if (cmd->prev) cmd->prev->next=cmd->next; if (cmd->next) cmd->next->prev=cmd->prev; if (sts->head == cmd) sts->head=cmd->next; if (sts->tail == cmd) sts->tail=cmd->prev; free(cmd->data); free(cmd); } /* start our scsi command (need to know output size) */ int start_scsi_cmd ( int fd, /* sg fd */ unsigned cmd_len, /* command length */ unsigned in_size, /* input data size */ unsigned char *i_buff, /* input buffer */ unsigned out_size /* output data size */ ) { int status = 0; struct sg_header *sg_hd; /* safety checks */ if (!cmd_len) return -1; /* need a cmd_len != 0 */ if (!i_buff) return -1; /* need an input buffer != NULL */ /* keep the packet size under 4k */ if (SG_LEN + cmd_len + in_size > MAX_PACKET) return -1; /* generic scsi device header construction */ sg_hd = (struct sg_header *) i_buff; /* useful stuff */ sg_hd->reply_len = SG_LEN + out_size; sg_hd->twelve_byte = (cmd_len == 12); /* should be set */ /* not really used stuff */ sg_hd->other_flags = 0; /* not used */ sg_hd->result = 0; sg_hd->pack_id = 0; /* ignored stuff */ #if 0 sg_hd->pack_len = SG_LEN + cmd_len + in_size; /* not necessary */ #endif /* send command */ #if 0 printf("SG_LEN: %d\n",SG_LEN); printf("cmd_len: %d\n",cmd_len); printf("in_size: %d\n",in_size); printf("out_size: %d\n",out_size); #endif /* FIXME: handle EAGAIN errno */ status = write (fd, i_buff, SG_LEN + cmd_len + in_size); if (status < 0 || status != SG_LEN + cmd_len + in_size) { /* some error happened */ fprintf (stderr, "write(sg fd %d) result = 0x%x cmd = 0x%x: ", fd, sg_hd->result, i_buff[SG_LEN]); perror (""); return status; } return 0; } /* get results from last scsi cmd (need output buffer and size) */ int finish_scsi_cmd ( int fd, /* sg fd */ unsigned out_size, /* output data size */ unsigned char *o_buff /* output buffer */ ) { int status = 0; struct sg_header *sg_hd; /* safety checks */ if (SG_LEN + out_size > MAX_PACKET) return -1; if (!o_buff) out_size = 0; /* retrieve result */ /* FIXME: handle EINTR errno */ status = read (fd, o_buff, SG_LEN + out_size); if (status < 0 || status != SG_LEN + out_size) { /* some error happened */ fprintf (stderr, "read(sg fd %d) result = 0x%x cmd = 0x%x\n", fd, sg_hd->result, o_buff[SG_LEN]); fprintf (stderr, "read(sg fd %d) sense " "%x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x\n", fd, sg_hd->sense_buffer[0], sg_hd->sense_buffer[1], sg_hd->sense_buffer[2], sg_hd->sense_buffer[3], sg_hd->sense_buffer[4], sg_hd->sense_buffer[5], sg_hd->sense_buffer[6], sg_hd->sense_buffer[7], sg_hd->sense_buffer[8], sg_hd->sense_buffer[9], sg_hd->sense_buffer[10], sg_hd->sense_buffer[11], sg_hd->sense_buffer[12], sg_hd->sense_buffer[13], sg_hd->sense_buffer[14], sg_hd->sense_buffer[15]); if (status < 0) perror (""); } /* Look if we got what we expected to get */ if (status == SG_LEN + out_size) status = 0; /* got them all */ return status; /* 0 means no error */ } /* process a complete scsi cmd. Use the generic scsi interface. */ int waitfor_scsi_cmd ( int fd, /* sg fd */ unsigned cmd_len, /* command length */ unsigned in_size, /* input data size */ unsigned char *i_buff, /* input buffer */ unsigned out_size, /* output data size */ unsigned char *o_buff /* output buffer */ ) { int result; if ((result=start_scsi_cmd(fd, cmd_len, in_size, i_buff, out_size))!=0) { fprintf(stderr,"start_scsi_cmd failed\n"); return result; } /* FIXME: this seems dangerous ... */ if (!o_buff) o_buff=i_buff; if ((result=finish_scsi_cmd(fd, out_size, o_buff))!=0) { fprintf(stderr,"finish_scsi_cmd failed\n"); return result; } return 0; } void printchars(unsigned char * str, int len) { int i; for (i=0;isense_buffer + ADD_SENSECODE) != NO_MEDIA_SC || *(((struct sg_header *) cmd)->sense_buffer + ADD_SC_QUALIFIER) != NO_MEDIA_SCQ; } char * fas_opcodes[] = { "FAS_GLOBAL", "FAS_ENABLE", "FAS_DISABLE", "FAS_SEND", "FAS_RECV", "FAS_IN_TIMERS", "FAS_OUTPUT_CTL", "FAS_INPUT_CTL", "FAS_SET_MODEM", "FAS_STAT_CHG", "FAS_SEND_BRK", "FAS_SET_PARAMS", "FAS_FLOW_CTL", "FAS_RESERVE", "FAS_RELEASE" }; /* send characters to an STS port */ int fas_send(sts_state * sts, unsigned int port, unsigned char * data, unsigned int len) { unsigned char fascmd[] = { FAS_SEND, /* Command Opcode */ 0, /* Port number */ 0,0, /* byte count */ 0,0,0,0 /* Unused */ }; int sent; unsigned char buffer[FAS_SEND_MAX + FAS_CMD_LEN]; if (!sts) { fprintf(stderr,"fas_send: got null sts!\n"); return 0; } if (sts->portsensed) { if (port>=sts->total_ports) { fprintf(stderr,"fas_send: port %d out of range (> %d)!\n", port, sts->total_ports-1); return 0; } } else { fprintf(stderr,"fas_send: port auto sensing not finished\n"); return 0; } fascmd[1]=port; /* send the string in FAS_SEND_MAX chunks */ while (len) { sent=len; if (len>FAS_SEND_MAX) { sts_debug("big string: %d\n",len); sent=FAS_SEND_MAX; } STORE_16BIT(fascmd,2,sent); memcpy(buffer,fascmd,FAS_CMD_LEN); memcpy(buffer+FAS_CMD_LEN,data,sent); add_cmd(sts, buffer, FAS_CMD_LEN + sent); len-=sent; data+=sent; } /* set up port's cmd status */ sts->ports[port]->status[FAS_SEND]=FAS_PENDING; return 1; } /* return from fas_send */ void fas_send_ret(sts_state * sts, unsigned int port, unsigned int status) { if (!sts) { fprintf(stderr,"fas_send_ret: got null sts!\n"); return; } if (sts->portsensed) { if (port>=sts->total_ports) { fprintf(stderr,"fas_send_ret: port %d out of range (> %d)!\n", port, sts->total_ports-1); return; } } else { fprintf(stderr,"fas_send_ret: port auto sensing not finished\n"); return; } if ((sts->ports[port]->status[FAS_SEND]=status)!=FAS_OK) { fprintf(stderr,"FAS_SEND: error: "); fas_error(stderr,status); } } /* ask to recv characters from a port */ int fas_recv(sts_state * sts, unsigned int port, unsigned int len) { unsigned char fascmd[] = { FAS_RECV, /* Command Opcode */ 0, /* Port number */ 0,0, /* Max byte count */ 0,0,0,0 /* Unused */ }; if (!sts) { fprintf(stderr,"fas_recv: got null sts!\n"); return 0; } if (sts->portsensed) { if (port>=sts->total_ports) { fprintf(stderr,"fas_recv: port %d out of range (> %d)!\n", port, sts->total_ports-1); return 0; } } else { fprintf(stderr,"fas_recv: port auto sensing not finished\n"); return 0; } fascmd[1]=port; STORE_16BIT(fascmd,2,len); add_cmd(sts, fascmd, FAS_CMD_LEN); return 1; } void fas_recv_ret(sts_state * sts, unsigned int port, unsigned int status, unsigned char * data, unsigned int len) { if (!sts) { fprintf(stderr,"fas_recv_ret: got null sts!\n"); return; } if (sts->portsensed) { if (port>=sts->total_ports) { fprintf(stderr,"fas_recv_ret: port %d out of range (> %d)!\n", port, sts->total_ports-1); return; } } else { /* silently ignore any stale port reports during port sensing */ if (sts->portsensed) { fprintf(stderr,"fas_recv_ret: port auto sensing not finished\n"); } return; } if (status == FAS_OK) { /* sts_debug("FAS_RECV got %d bytes:\n",len); printchars(data,len); */ /* FIXME: handle BUSY, INTR, and under-writes */ write(sts->ttys[port]->ptm,data,len); /* re-post the recv for the next bunch of chars */ fas_recv(sts,port,300); /* FIXME: 300 baud, har har har */ } else { /* FIXME: aren't chars still good from this call, even when in error? */ /* ignore errors during shutdown */ if (!sts->shutdown) { fprintf(stderr,"FAS_RECV: error: "); fas_error(stderr,status); } } } /* FIXME: reportonly should be called "sending_to_sts" or something */ void process_fas_block(sts_state * sts,unsigned char * block, int size, int reportonly) { int remaining; /* how much left to read */ unsigned char * ptr; /* string pointer into block */ int cmdat; /* debug: start of command */ unsigned char cmd[FAS_CMD_LEN]; /* FAS command */ int len; /* how much data follows FAS_RECV */ if (!sts) { fprintf(stderr,"process_fas_block: got null sts!\n"); return; } if (!block && size!=0) { fprintf(stderr,"process_fas_block: got null block!\n"); return; } sts_debug("\t(FAS block is %d byte%s)\n",size,size==1?"":"s"); cmdat=0; ptr=block; remaining=size; for (;remaining;cmdat+=8) { *cmd=*ptr++; if (*cmd == PKT_END) { sts_debug("\tat %d: PKT_END\n",cmdat); /* empty packet? */ if (cmdat == 0) { sts->flushed=1; } break; /* stop processing */ } if (remaining < FAS_CMD_LEN) { fprintf(stderr,"process_fas_block: buffer read " "underrun\n"); return; } memcpy(cmd+1,ptr,FAS_CMD_LEN - 1); ptr+=FAS_CMD_LEN-1; remaining-=FAS_CMD_LEN; if (*cmd > FAS_CMD_MAX) { fprintf(stderr,"process_fas_block: invalid command: " "0x%02X -- skipping 8 bytes\n",*cmd); continue; } if (*cmd == FAS_RECV || *cmd == FAS_SEND) { unsigned int port,status; /* largest buffer size for FAS_RECV */ unsigned char data[FAS_PKT_MAX]; sts_debug("\tat %d: FAS_%s\n",cmdat, *cmd == FAS_RECV ? "RECV" : "SEND"); port=cmd[1]; status=cmd[2]; if ((reportonly && *cmd==FAS_SEND) || (!reportonly && *cmd==FAS_RECV)) { len=GRAB_16BIT(cmd,*cmd==FAS_RECV ? 4 : 2); if (remaining < len) { fprintf(stderr,"process_fas_block: FAS_%s " "data underrun -- expected %d got %d " "or less\n", *cmd == FAS_RECV ? "RECV" : "SEND", len,remaining); return; } if (len>0) { memcpy(data,ptr,len); ptr+=len; remaining-=len; sts_debug("\tat %d: FAS_%s data (%d " "byte%s\n", cmdat+FAS_CMD_LEN, *cmd == FAS_RECV ? "RECV" : "SEND", len, len == 1 ? "" : "s"); cmdat+=len; printchars(data,len); } } if (!reportonly) { if (*cmd==FAS_RECV) fas_recv_ret(sts,port,status,data,len); else fas_send_ret(sts,port,status); } } else { sts_debug("\tat %d: ",cmdat); /* FIXME: need proper debug here */ if (reportonly) { sts_debug("%s\n",fas_opcodes[*cmd]); } else { fas_packet_ret(sts,cmd); } } } } /* gather fas cmds for a single transmission block. return final length */ int gather_fas_block(sts_state * sts, unsigned char * block) { cmd_list * cmd; cmd_list * next; unsigned char * ptr; int remaining; int len; ptr=block; next=sts->head; remaining=FAS_PKT_MAX; len=0; while ((cmd=find_cmd(sts,next,remaining))) { memcpy(ptr, cmd->data, cmd->size); remaining-=cmd->size; len+=cmd->size; ptr+=cmd->size; if (cmd != next) /* if we didn't get the first cmd */ next=cmd->next; /* we need to look at the next packet */ else next=NULL; /* othersize, start from the top */ delete_cmd(sts,cmd); if (!next) next=sts->head; /* restart from the top on NULL */ if (remaining==0) break; /* stop cramming cmds in */ } if (remaining!=FAS_PKT_MAX && remaining!=0) { /* mark the end of the packet, if room */ *ptr=PKT_END; len++; } return len; /* how many bytes went in? */ } void fas_error(FILE * fd, unsigned int status) { static char * fas_status_strings[FAS_ERR_MAX+1] = { /* 0 */ "OK", /* 1 */ "Extra command received for command type that only allows one in progress at a time", /* 2 */ "Command parameter out of range", /* 3 */ "Specific line number out of range not present", /* 4 */ "Input character(s) received with parity errors", /* 5 */ "Overrun(s) during input", /* 6 */ "Input character(s) received with framing errors", /* 7 */ "Possibly multiple errors on received characters", /* 8 */ "Command issued on uninitialized line or line reinitialized", /* 9 */ "Command code not valid", /* A */ "Command aborted by a flush", /* B */ "Break condition at a line", /* C */ "Input buffers overflowed at STS, some input has been lost" }; unsigned int low; if (status == FAS_OK) { fprintf(fd,"OK\n"); } else { fprintf(fd,"(0x%02X)",status); low=(status & (~0x80)); if ((status & FAS_ERR_FAIL) == FAS_ERR_FAIL) { fprintf(fd,"Serious condition: "); } if (low>FAS_ERR_MAX) { fprintf(fd,"unknown status code 0x%02X!\n",low); } else { fprintf(fd,"%s\n",fas_status_strings[low]); } } } /* FIXME: need to make port ints into unsigned values */ void fas_enable_ret(sts_state * sts, unsigned int port, unsigned int status, unsigned int disable) { char * opname; opname=disable ? "DISABLE" : "ENABLE"; sts_debug("FAS_%s: port: %d status: 0x%02X\n", opname, port, status); if (!sts) { fprintf(stderr,"fas_enable_ret: got null sts!\n"); return; } if (sts->portsensed) { if (port>sts->total_ports) { fprintf(stderr,"fas_enable_ret: got response for " "unmanaged port %d (> %d)!\n",port, sts->total_ports-1); return; } } if (status == FAS_OK) { if (!sts->portsensed && !disable) { #if 0 fprintf(stderr,"fas_enable_ret: got enable response " "for port %d during auto-sense!\n",port); #endif return; } /* do memory allocation for newly discovered ports */ if (!sts->ports) { /* allocate array table */ sts->ports=(port_state **)calloc((port+1),sizeof(port_state *)); if (!sts->ports) { fprintf(stderr,"fas_enable_ret: alloc 'ports' failed\n"); perror("calloc"); exit(11); } sts->total_ports=port+1; } if (sts->total_portsports=(port_state **)realloc((void*)sts->ports, (port+1)*sizeof(port_state *)); if (!sts->ports) { fprintf(stderr,"fas_enable_ret: realloc 'ports' failed\n"); perror("realloc"); exit(12); } /* clear the memory just made available */ for (i=sts->total_ports;i<=port;i++) { sts->ports[i]=NULL; } sts->total_ports=port+1; } if (!sts->ports[port]) { /* allocate array table */ sts->ports[port]=(port_state *)calloc(1,sizeof(port_state)); if (!sts->ports[port]) { fprintf(stderr,"fas_enable_ret: alloc port %d failed\n",port); perror("calloc"); exit(13); } } sts->ports[port]->enabled=!disable; } else { if (sts->portsensed) { /* when not autosensing */ if (sts->ports[port]->enabled && status == (FAS_ERR_FAIL | FAS_ERR_INITD)) { /* ignore reenablement complaints */ } else { /* but report anything else */ fprintf(stderr,"FAS_%s: error: ", opname); fas_error(stderr,status); } } else { /* when autosensing */ if (status != (FAS_ERR_FAIL | FAS_ERR_NOT_PRESENT) && status != (FAS_ERR_FAIL | FAS_ERR_INITD)) { /* report anything not expected */ fprintf(stderr,"FAS_%s: error: ", opname); fas_error(stderr,status); } } } } void fas_reserve_ret(sts_state * sts, unsigned int port, unsigned int status, unsigned int release) { char * opname; opname=release ? "RELEASE" : "RESERVE"; sts_debug("FAS_%s: port: %d status: 0x%02X\n", opname, port, status); if (!sts) { fprintf(stderr,"fas_reserve_ret: got null sts!\n"); return; } if (sts->portsensed) { if (port>sts->total_ports) { fprintf(stderr,"fas_reserve_ret: got response for " "unmanaged port %d (> %d)!\n",port, sts->total_ports-1); return; } } else { fprintf(stderr,"fas_reserve_ret: got response for " "port %d during auto-sense!\n", port); return; } if (status == FAS_OK) { sts->ports[port]->reserved=!release; } else { fprintf(stderr,"FAS_%s: error: ", opname); fas_error(stderr,status); } } void fas_set_params_ret(sts_state * sts, unsigned int port, unsigned int status) { sts_debug("FAS_SET_PARAMS: port: %d status: 0x%02X\n", port, status); if (!sts) { fprintf(stderr,"fas_set_params_ret: got null sts!\n"); return; } if (sts->portsensed) { if (port>=sts->total_ports) { fprintf(stderr,"fas_set_params_ret: port %d out of range " "(> %d)!\n", port, sts->total_ports-1); return; } } else { fprintf(stderr,"fas_set_params_ret: port auto sensing not finished\n"); return; } if ((sts->ports[port]->status[FAS_SET_PARAMS]=status)!=FAS_OK) { fprintf(stderr,"FAS_SET_PARAMS: error: "); fas_error(stderr,status); } } void fas_set_modem_ret(sts_state * sts, unsigned int port, unsigned int status) { sts_debug("FAS_SET_MODEM: port: %d status: 0x%02X\n", port, status); if (!sts) { fprintf(stderr,"fas_set_modem_ret: got null sts!\n"); return; } if (sts->portsensed) { if (port>=sts->total_ports) { fprintf(stderr,"fas_set_modem_ret: port %d out of range " "(> %d)!\n", port, sts->total_ports-1); return; } } else { fprintf(stderr,"fas_set_modem_ret: port auto sensing not finished\n"); return; } if ((sts->ports[port]->status[FAS_SET_MODEM]=status)!=FAS_OK) { fprintf(stderr,"FAS_SET_MODEM: error: "); fas_error(stderr,status); } } void fas_global_ret(sts_state * sts, unsigned int version, unsigned int status) { sts_debug("FAS_GLOBAL: version: %d status: 0x%02X\n",version,status); if (!sts) { fprintf(stderr,"fas_global_ret: got null sts!\n"); return; } if (status == FAS_OK) { sts->globaled=1; } else { fprintf(stderr,"FAS_GLOBAL: error: "); fas_error(stderr,status); } } /* this assumes a valid cmd has 8 bytes in it */ void fas_packet_ret(sts_state * sts, unsigned char * cmd) { unsigned int opcode,port,status; if (!sts) { fprintf(stderr,"fas_packet_ret: got null sts\n"); return; } if (!cmd) { fprintf(stderr,"fas_packet_ret: got null cmd\n"); return; } opcode=cmd[0]; port= cmd[1]; status=cmd[2]; switch (opcode) { case FAS_GLOBAL: fas_global_ret(sts,port,status); break; case FAS_RESERVE: case FAS_RELEASE: fas_reserve_ret(sts,port,status, (opcode == FAS_RELEASE)); break; case FAS_ENABLE: case FAS_DISABLE: fas_enable_ret(sts,port,status, (opcode == FAS_DISABLE)); break; case FAS_SET_MODEM: fas_set_modem_ret(sts,port,status); break; case FAS_SET_PARAMS: fas_set_params_ret(sts,port,status); break; /* case FAS_SEND: fas_send_ret(sts,port,status); break; */ default: fprintf(stderr,"got unsupported FAS opcode: 0x%02X " "port: %d status: 0x%02X\n",opcode, port, status); } } int send_fas_block(sts_state * sts, unsigned char * fasblock, int len) { #define SENDMSG_CMD 0x0A #define SENDMSG_CMDLEN 6 static unsigned char sendblk[SENDMSG_CMDLEN] = { SENDMSG_CMD, /* Op Code */ 0, /* Lun/reserved FIXME? */ 0,0,0, /* transfer length (needs to be set) */ 0 /* Control ?! */ }; static unsigned char cmd[MAX_PACKET]; int result; unsigned char buffer[FAS_PKT_MAX + SG_LEN]; if (!sts) { fprintf(stderr,"send_fas_block: got null sts!\n"); return 1; } if (len > FAS_PKT_MAX) { fprintf(stderr,"send_fas_block: block too large: %d > %d\n", len, FAS_PKT_MAX); return 1; } /* set the length bytes */ sendblk[2]=((len & 0xff0000) >> 16); sendblk[3]=((len & 0xff00) >> 8); sendblk[4]=(len & 0xff); memcpy (cmd + SG_LEN, sendblk, sizeof (sendblk)); memcpy (cmd + SG_LEN + sizeof(sendblk), fasblock, len); /*cmd[SG_LEN + sizeof(sendblk) + len - 1]=PKT_END; *//* don't tack on PKT_END */ sts_debug("sending:\n"); /* FIXME: make the report-only var a define here */ process_fas_block(sts,cmd+SG_LEN+sizeof(sendblk),len,1); result=waitfor_scsi_cmd( sts->sendfd, SENDMSG_CMDLEN, /*sizeof(sendblk)+*/len, cmd, 0, /* FAS_PKT_MAX, */ buffer); if (result!=0) { fprintf(stderr,"send_fas_block: SCSI transport failure\n"); return result; } return 0; } /* send SCSI command to get info from STS */ int recv_fas_block(sts_state * sts) { #define GETMSG_CMD 0x08 #define GETMSG_CMDLEN 6 static unsigned char recvblk[GETMSG_CMDLEN] = { GETMSG_CMD, /* Op Code */ 0, /* Lun/reserved FIXME? */ 0,8,0, /* transfer len: 2048 */ 0 /* Control ?! */ }; static unsigned char cmd[MAX_PACKET]; int result; if (!sts) { fprintf(stderr,"recv_fas_block: got null sts!\n"); return 1; } memcpy(cmd + SG_LEN, recvblk, sizeof (recvblk)); result=start_scsi_cmd( sts->recvfd, GETMSG_CMDLEN, 0, /*sizeof(recvblk),*/ /* input size=0, only a cmd */ cmd, FAS_PKT_MAX); if (result!=0) { fprintf(stderr,"recv_fas_block: SCSI transport failure\n"); } return result; } int recv_fas_block_ret(sts_state * sts) { int result; unsigned char buffer[FAS_PKT_MAX + SG_LEN]; /* FIXME: is this really needed? */ /* fill overflow data with PKT_ENDs for safety */ memset(buffer+SG_LEN,PKT_END,FAS_PKT_MAX); result=finish_scsi_cmd( sts->recvfd, FAS_PKT_MAX, buffer); if (result!=0) { fprintf(stderr,"recv_fas_block_ret: SCSI transport failure\n"); return result; } sts_debug("received:\n"); process_fas_block(sts,buffer+SG_LEN,FAS_PKT_MAX,0); return 0; } int waitfor_recv_fas_block(sts_state * sts) { int result; if ((result=recv_fas_block(sts))==FAS_OK) result=recv_fas_block_ret(sts); return result; } int do_one_fas_cmd(sts_state * sts, unsigned char * fascmd, int len) { int result; if ((result=send_fas_block(sts, fascmd, len)) == FAS_OK) result=waitfor_recv_fas_block(sts); return result; } int fas_reserve(sts_state * sts, unsigned int port, unsigned int release) { unsigned char fascmd[] = { FAS_RESERVE, /* Command Opcode */ 0, /* Port number */ 0,0,0,0,0,0 /* Unused */ }; if (!sts) { fprintf(stderr,"fas_reserve: got null sts!\n"); return 0; } if (sts->portsensed) { if (port>=sts->total_ports) { fprintf(stderr,"fas_reserve: port %d out of range (> %d)!\n", port, sts->total_ports-1); return 0; } } if (release) fascmd[0]=FAS_RELEASE; else fascmd[0]=FAS_RESERVE; fascmd[1]=port; add_cmd(sts, fascmd, FAS_CMD_LEN); return 1; } int fas_enable(sts_state * sts, unsigned int port, unsigned int disable) { unsigned char fascmd[] = { FAS_ENABLE, /* Command Opcode */ 0, /* Port number */ 0,0,0,0,0,0 /* Unused */ }; if (!sts) { fprintf(stderr,"fas_enable: got null sts!\n"); return 0; } if (sts->portsensed) { if (port>=sts->total_ports) { fprintf(stderr,"fas_enable: port %d out of range (> %d)!\n", port, sts->total_ports-1); return 0; } #ifdef DO_RESERVATIONS if (!sts->ports[port]->reserved) { fprintf(stderr,"fas_enable: port %d is not reserved!\n", port); return 0; } #endif } if (disable) fascmd[0]=FAS_DISABLE; else fascmd[0]=FAS_ENABLE; fascmd[1]=port; add_cmd(sts, fascmd, FAS_CMD_LEN); return 1; } /* stuff a FAS_SET_PARAMS into the cmd list */ int fas_set_params(sts_state * sts, unsigned port, unsigned int bits, unsigned int framing, unsigned int parity, unsigned int flags, unsigned int baud) { unsigned char fascmd[] = { FAS_SET_PARAMS, /* Command Opcode */ 0, /* Port number */ 0, /* Bits */ 0, /* Framing */ 0, /* Parity */ 0, /* Flags */ 0, /* Baud */ 0 /* Unused */ }; if (!sts) { fprintf(stderr,"fas_set_params: got null sts!\n"); return 0; } if (sts->portsensed) { if (port>=sts->total_ports) { fprintf(stderr,"fas_set_params: port %d out of range (> %d)!\n", port, sts->total_ports-1); return 0; } } else { fprintf(stderr,"fas_set_params: port auto sensing not finished\n"); return 0; } fascmd[1]=port; fascmd[2]=bits; fascmd[3]=framing; fascmd[4]=parity; fascmd[5]=flags; fascmd[6]=((baud << 4) | (baud)); add_cmd(sts, fascmd, FAS_CMD_LEN); /* set up port's cmd status */ sts->ports[port]->status[FAS_SET_PARAMS]=FAS_PENDING; return 1; } /* stuff a FAS_SET_MODEM into the cmd list */ int fas_set_modem(sts_state * sts, unsigned int port, unsigned int rts, unsigned int dtr) { unsigned char fascmd[] = { FAS_SET_MODEM, /* Command Opcode */ 0, /* Port number */ 0, /* RTS Control */ 0, /* DTR Control */ 0,0,0 /* Unused */ }; if (!sts) { fprintf(stderr,"fas_set_modem: got null sts!\n"); return 0; } if (sts->portsensed) { if (port>=sts->total_ports) { fprintf(stderr,"fas_set_modem: port %d out of range (> %d)!\n", port, sts->total_ports-1); return 0; } } else { fprintf(stderr,"fas_set_modem: port auto sensing not finished\n"); return 0; } fascmd[1]=port; fascmd[2]=rts; fascmd[3]=dtr; add_cmd(sts, fascmd, FAS_CMD_LEN); /* set up port's cmd status */ sts->ports[port]->status[FAS_SET_MODEM]=FAS_PENDING; return 1; } /* stuffs a FAS_GLOBAL into the cmd list */ int fas_global(sts_state * sts, unsigned int blinky) { unsigned char fascmd[] = { FAS_GLOBAL, /* Command Opcode */ 0, /* Version (FIXME: we should say 4.1 or 5.7) */ 30,0, /* interrupt rate (least sig first) */ 0, 0, /* Buffer Size */ 0, /* Option Flags */ #ifdef STS_DEBUG 1 /* heavy error-checking */ #else 0 /* No heavy error-checking */ #endif }; if (!sts) { fprintf(stderr,"fas_global: got null sts!\n"); return 0; } if (blinky) fascmd[6]|=GLOBAL_LED_BLINK; else fascmd[6]=(fascmd[6] & (~GLOBAL_LED_BLINK)); #ifdef STS_DEBUG fascmd[6]|=GLOBAL_DEBUG_MODE; #endif add_cmd(sts, fascmd, FAS_CMD_LEN); return 1; } /* waits for all message to flush from the SCSI queue */ int fas_msg_flush(sts_state * sts) { int i; if (!sts) { fprintf(stderr,"fas_msg_flush: got null sts!\n"); return 0; } sts->flushed=0; for (i=0;i<10;i++) { waitfor_recv_fas_block(sts); if (sts->flushed) return 1; } fprintf(stderr,"STS SCSI Recv queue would not flush -- aborting\n"); return 0; } /* gathers and sends all the cmds until the cmd list is empty */ void flush_and_wait(sts_state * sts) { int len; unsigned char buffer[FAS_PKT_MAX]; for (len=gather_fas_block(sts,buffer); len; len=gather_fas_block(sts,buffer)) { if (send_fas_block(sts, buffer, len) == FAS_OK) waitfor_recv_fas_block(sts); } } /* gathers each block and sends it */ void flush_cmd_list(sts_state * sts) { int len; unsigned char buffer[FAS_PKT_MAX]; for (len=gather_fas_block(sts,buffer); len; len=gather_fas_block(sts,buffer)) { send_fas_block(sts, buffer, len); } } sts_state * initialize_sts(char * argv[]) { sts_state *sts; Sg_scsi_id id_info; int seen_id=0, lun, fd, flags; unsigned char extra_len; unsigned char * response; unsigned char * ptr; char * extra; int i,port; /* alloc with cleared memory */ if (!(sts=(sts_state *)calloc(1,sizeof(sts_state)))) { perror("calloc"); return NULL; } sts->sendfd=sts->recvfd=-1; /* to read and verify the device args */ for (i=1;i<3;i++) { if ((fd = open (argv[i], O_RDWR | O_EXCL | O_NONBLOCK))<0) { fprintf(stderr, "Need exclusive read/write permissions on '%s'\nopen: ", argv[i]); perror(""); return NULL; } if ((flags = fcntl(fd, F_GETFL))<0) { perror("fcntl F_GETFL"); return NULL; } if (fcntl(fd, F_SETFL, flags & (~ O_NONBLOCK))<0) { perror("fcntl F_SETFL"); return NULL; } if (sts->sendfd<0) sts->sendfd=fd; else sts->recvfd=fd; if (ioctl(fd, SG_GET_SCSI_ID, &id_info)<0) { perror("ioctl SG_GET_SCSI_ID"); return NULL; } if (!seen_id) { seen_id=1; sts->host=id_info.host_no; sts->chan=id_info.channel; sts->id=id_info.scsi_id; lun=id_info.lun; } else { if (sts->host != id_info.host_no || sts->chan != id_info.channel || sts->id != id_info.scsi_id || lun != !id_info.lun) { /* FIXME: dump both sets of IDs */ fprintf(stderr,"Mismatched devices! host/chan/id are " "different, or LUNs are the same.\n"); return NULL; } } response = Inquiry(fd); extra_len = response[EXTRA_OFF]; ptr=response + VENDOR_START; memcpy(sts->vendor,ptr, VENDOR_LEN); sts->vendor[VENDOR_LEN]='\0'; ptr+=VENDOR_LEN; memcpy(sts->product,ptr, PRODUCT_LEN); sts->product[PRODUCT_LEN]='\0'; ptr+=PRODUCT_LEN; memcpy(sts->rev,ptr, REV_LEN); sts->rev[REV_LEN]='\0'; /* get extra if it is there */ ptr+=REV_LEN; extra_len -= ((ptr-response) - 1); if (fd == sts->sendfd) extra = sts->sendextra; else extra = sts->recvextra; /* clear out extra */ memset(extra,0,EXTRA_LEN+1); if (extra_len > 0) memcpy(extra,ptr, extra_len); printf("vendor: [%s] product: [%s] rev: [%s%s%s%s]\n",sts->vendor, sts->product,sts->rev,*extra ? " (" : "", extra, *extra ? ")" : ""); /* look if medium is loaded */ if (!TestForMedium (sts->sendfd)) { fprintf (stderr,"device is not ready -- aborting...\n"); return NULL; } printf(" host: %d chan: %d id: %d lun: %d\n",sts->host, sts->chan, sts->id, id_info.lun); } #if 0 sts_debug("flushing SCSI commands ...\n"); /* flush the msg queue in case anything is pending */ if (!fas_msg_flush(sts)) return NULL; #endif /* set up global parameters */ fas_global(sts,atoi(argv[3])); flush_and_wait(sts); if (!sts->globaled) { fprintf(stderr,"Cannot set global STS parameters -- aborting\n"); return NULL; } sts_debug("sensing ports ...\n"); /* autosense ports */ for (port=0;;port++) { fas_enable(sts,port,0); fas_enable(sts,port,1); flush_and_wait(sts); /* did it fail to be reserved? */ if (port>=sts->total_ports) break; /* || !sts->ports[port]->reserved) break;*/ } fas_msg_flush(sts); /* just in case */ sts->portsensed=1; /* done sensing ports */ if (!sts->total_ports) { fprintf(stderr,"Cannot enable any ports -- aborting\n"); return NULL; } else { printf("%d port%s detected.\n",(sts->total_ports), sts->total_ports==0 ? "" : "s"); } /* FIXME: auto-sense maximum baud rate here */ #ifdef DO_RESERVATIONS sts_debug("reserving ports ...\n"); /* reserve ports */ for (port=0;porttotal_ports;port++) { fas_reserve(sts,port,0); } flush_and_wait(sts); fas_msg_flush(sts); /* just in case */ #endif sts_debug("enabling ports ...\n"); /* enable ports */ for (port=0;porttotal_ports;port++) { fas_enable(sts,port,0); } flush_and_wait(sts); fas_msg_flush(sts); /* just in case */ for (port=0;porttotal_ports;port++) { /* did it get enabled? */ if (!sts->ports[port]->enabled) { fprintf(stderr,"warning: port %d did not enable!\n",port); } } for (port=0;porttotal_ports;port++) { fas_set_modem(sts,port,1,1); fas_set_params(sts,port,PARAMS_BITS8,PARAMS_STOP1,PARAMS_PARITYNO, PARAMS_STRIPNO|PARAMS_RECVENABLE,PARAMS_BAUD38400); } flush_and_wait(sts); fas_msg_flush(sts); /* just in case */ for (port=0;porttotal_ports;port++) { /* did it get set? */ if (sts->ports[port]->status[FAS_SET_MODEM]) { fprintf(stderr,"warning: port %d did not take modem " "settings!\n",port); } } return sts; } int open_pty(struct tty_state_t * tty, int i) { char * ptr; if ((tty->ptm=getpt())<0) { perror("getpt"); return 0; } if (grantpt(tty->ptm)<0) { close(tty->ptm); perror("grantpt"); return 0; } if (unlockpt(tty->ptm)<0) { close(tty->ptm); perror("unlockpt"); return 0; } if ( (ptr = ptsname(tty->ptm)) ) { if (!(tty->pts_name=strdup(ptr))) { perror("strdup"); return 0; } printf("STS port %d is '%s'\n",i,ptr); } else { perror("ptsname"); return 0; } return 1; } int initialize_ptys(sts_state * sts) { int i; if (!(sts->ttys=calloc(sts->total_ports,sizeof(tty_state *)))) { fprintf(stderr,"Cannot allocate tty array\n"); perror("calloc"); return 0; } for (i=0;itotal_ports;i++) { if (!(sts->ttys[i]=calloc(1,sizeof(tty_state)))) { fprintf(stderr,"Cannot allocate tty data\n"); perror("calloc"); return 0; } if (!open_pty(sts->ttys[i],i)) { return 0; } } return 1; } void sts_loop(sts_state * sts) { int i, maxfd, len; fd_set read_set; fd_set write_set; fd_set excl_set; unsigned char buffer[FAS_PKT_MAX]; /* find largest fd */ maxfd=0; if (sts->recvfd > maxfd) maxfd=sts->recvfd; if (sts->sendfd > maxfd) maxfd=sts->sendfd; /* post RECV to every port */ /* post STAT_CHG to every port */ for (i=0;itotal_ports;i++) { if (sts->ttys[i]->ptm > maxfd) maxfd=sts->ttys[i]->ptm; fas_recv(sts,i,FAS_SEND_MAX); /* FIXME: 300 baud, har har har */ /* FIXME: write this fas_stat_chg(sts,i); */ } /* flush commands */ flush_cmd_list(sts); /* issue Recv processing */ recv_fas_block(sts); maxfd++; printf("Unit Online.\n"); /* loop until shutdown */ while (!sts->shutdown) { FD_ZERO( &read_set ); FD_ZERO( &write_set ); FD_ZERO( &excl_set ); /* add ttys w/o pending STS write to readset */ /* add all ttys to exclset */ for (i=0;itotal_ports;i++) { FD_SET( sts->ttys[i]->ptm, &excl_set ); if (sts->ports[i]->status[FAS_SEND]!=FAS_PENDING) { FD_SET( sts->ttys[i]->ptm, &read_set ); } else { sts_debug("sts_loop: skipping tty %d (FAS_SEND " "pending)\n",i); } } /* add sts fds to readset */ FD_SET( sts->recvfd, &read_set ); #if 0 FD_SET( sts->sendfd, &read_set ); /* add sts send fs to writeset */ FD_SET( sts->sendfd, &write_set ); #endif /* add sts fds to exclset */ FD_SET( sts->sendfd, &excl_set ); FD_SET( sts->recvfd, &excl_set ); /* select (or poll?) */ if ((select( maxfd, &read_set, &write_set, &excl_set, NULL)) < 0) { if (errno != EINTR) { /* caught our signal */ perror("select"); } sts->shutdown=1; continue; } sts_debug("select finished\n"); /* FIXME: must test excl sets! */ /* if sts recv fd is readable */ if (FD_ISSET( sts->recvfd, &read_set )) { /* process FAS block */ recv_fas_block_ret(sts); /* post new Recv */ recv_fas_block(sts); } /* foreach tty */ for (i=0;itotal_ports;i++) { /* if tty readable */ if (FD_ISSET(sts->ttys[i]->ptm, &read_set)) { sts_debug("got ptm input\n"); /* read tty, post FAS_SEND */ len=read(sts->ttys[i]->ptm, buffer, FAS_SEND_MAX); if (len>0) { fas_send(sts,i,buffer,len); } else if (len<0 && errno!=EIO) { fprintf(stderr,"sts_loop: tty %d error\n", i); perror("read"); } if (len==0 || (len<0 && errno==EIO)) { /* end of file, close & reopen pty */ close(sts->ttys[i]->ptm); if (!open_pty(sts->ttys[i],i)) { fprintf(stderr,"Could not reopen TTY " "%d\n",i); return; } } } } #if 0 /* if sts Send fd is readable */ if (FD_ISSET( sts->sendfd, &read_set )) { /* report any errors */ } /* if FAS cmds are queued, & Send fd available */ if (FD_ISSET( sts->sendfd, &write_set ) && sts->head) { /* post Send to STS */ flush_cmd_list(sts); } #endif /* FIXME: something is wrong with this. Basically, if I'm doing a flush (which waits on the SCSI answer), the sendfd will ALWAYS be writable. Therefore, this code loop will just spin very very rapidly. However, unless an event occurs elsewhere, I shouldn't ever need to send more commands, as long as I flush the entire command list each time through to loop. That, however, might cause delays? */ flush_cmd_list(sts); } printf("Unit Shutting Down.\n"); } void shutdown_sts(sts_state * sts) { int i; if (!sts) { fprintf(stderr,"shutdown_sts: sts is NULL\n"); return; } for (i=0;itotal_ports;i++) { if (sts->ports[i]->enabled) fas_enable(sts,i,1); } fas_global(sts,0); /* send all the fas commands */ flush_cmd_list(sts); /* handle last half of recv */ recv_fas_block_ret(sts); /* reflush, just in case */ fas_msg_flush(sts); /* close all the pty's */ for (i=0;itotal_ports;i++) close(sts->ttys[i]->ptm); close(sts->sendfd); close(sts->recvfd); sts->sendfd=sts->recvfd=-1; return; } void die_off(int sig) { /* do nothing */ return; } int main (int argc, char * argv[]) { sts_state *sts; struct sigaction sig; if (argc<4) { fprintf (stderr, "Which 2 devices should be used for the sts? (and blink on or off?)\n"); return 1; } /* initialize STS & ports */ if (!(sts = initialize_sts(argv))) { fprintf(stderr,"STS not initialized\n"); return 2; } /* open, clear, and initialize ptys */ if (!initialize_ptys(sts)) { fprintf(stderr,"Could not open ptys\n"); return 3; } /* set up signal handlers */ memset(&sig,0,sizeof(struct sigaction)); sig.sa_handler=die_off; sigaction(2,&sig,NULL); /* INT */ sigaction(3,&sig,NULL); /* QUIT */ sigaction(15,&sig,NULL);/* TERM */ /* processing loop */ sts_loop(sts); /* shutdown and close STS ports & PTYs*/ /* shutdown STS */ shutdown_sts(sts); return 0; } #ifdef JUST_MY_DESIGN 08/07/2000 Goals: design a user space (and eventually a kernel-space) SCSI Terminal Server driver. Implementation: - use SCSI generic driver to access both LUNs of the STS - use ptys to attach the STS ports to Blocks: - I have no idea how to pass user-changes of termios back to the STS Basic operational outline: - verify that we have the proper device pair (by matching host/channel/id/lun) - run INQUIRYs - run TEST_UNIT_READYs - query device for # of ports - initialize global STS parameters - open, clear, and initialize STS ports - open, clear, and initialize ptys - mark STS write fd as writable - post initial STS read cmd - enter processing loop (bulk of run-time) - exit processing loop - close PTYs safely - shutdown and close STS ports - shutdown STS The processing loop outline: - add PTYs without pending STS writes to read set - add all PTYS to exclusion set - add STS fds to readset - add STS fds to exclusion set - select on fd set - if STS read fd is readable: - process STS packet(s) - repost STS read cmd - for each PTY: - if PTY is readable: - read PTY - buffer reads for future STS writes - if STS write fd is readable - process STS packet(s) - mark STS write fd as writable - if STS write fd is writable - build STS packet(s) - post STS write cmd - mark STS write fd as not writable - repeat from the top Some structures: typeset struct sts_state_t { int bus, ctrlr, id; /* SCSI identifiers */ char * manufacturer; /* SCSI manufacturer string */ char * model; /* SCSI model string */ char * rev; /* SCSI revision */ int writefd, readfd; /* write and read fds (LUN 0 and 1) */ int total_ports; /* how many ports in the STS */ struct port_state_t * ports; /* array of STS port info structures */ int writable:1; /* can I post an STS write cmd? */ int readable:1; /* can I post an STS read cmd? */ struct tty_state_t * ttys; /* array of PTY info structures */ } sts_state; typeset struct port_state_t { int available; /* is this port usable at all? */ int writable; /* is it OK to post an STS writes for this port? */ /* serial stuff should go here ..... */ } port_state; typeset struct tty_state_t { int ptm; /* pseudo terminal master fd */ char * pts_name; /* strdup'd copy of the pt slave name */ /* anything else here? ..... */ } tty_state; #endif