/* * Copyright (c) 2007-2012 Pigeon Point Systems. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistribution of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistribution in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Pigeon Point Systems nor the names of * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * This software is provided "AS IS," without a warranty of any kind. * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. * PIGEON POINT SYSTEMS ("PPS") AND ITS LICENSORS SHALL NOT BE LIABLE * FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING * OR DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL * PPS OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, * OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR * PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF * LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, * EVEN IF PPS HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. */ #define _GNU_SOURCE 1 /* Serial Interface, Terminal Mode plugin. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(HAVE_CONFIG_H) # include #endif #define IPMI_SERIAL_TIMEOUT 5 #define IPMI_SERIAL_RETRY 5 #define IPMI_SERIAL_MAX_RESPONSE 256 /* * Terminal Mode interface is required to support 40 byte transactions. */ #define IPMI_SERIAL_MAX_RQ_SIZE 37 /* 40 - 3 */ #define IPMI_SERIAL_MAX_RS_SIZE 36 /* 40 - 4 */ /* * IPMB message header */ struct ipmb_msg_hdr { unsigned char rsSA; unsigned char netFn; /* NET FN | RS LUN */ unsigned char csum1; unsigned char rqSA; unsigned char rqSeq; /* RQ SEQ | RQ LUN */ unsigned char cmd; unsigned char data[0]; }; /* * Send Message command request for IPMB-format */ struct ipmi_send_message_rq { unsigned char channel; struct ipmb_msg_hdr msg; }; /* * Get Message command response for IPMB-format */ struct ipmi_get_message_rp { unsigned char completion; unsigned char channel; unsigned char netFn; unsigned char csum1; unsigned char rsSA; unsigned char rqSeq; unsigned char cmd; unsigned char data[0]; }; /* * Terminal mode message header */ struct serial_term_hdr { unsigned char netFn; unsigned char seq; unsigned char cmd; }; /* * Sending context */ struct serial_term_request_ctx { uint8_t netFn; uint8_t sa; uint8_t seq; uint8_t cmd; }; /* * Table of supported baud rates */ static const struct { int baudinit; int baudrate; } rates[] = { { B2400, 2400 }, { B9600, 9600 }, { B19200, 19200 }, { B38400, 38400 }, { B57600, 57600 }, { B115200, 115200 }, { B230400, 230400 }, #ifdef B460800 { B460800, 460800 }, #endif }; static int is_system; static int ipmi_serial_term_open(struct ipmi_intf * intf) { struct termios ti; unsigned int rate = 9600; char *p; int i; if (!intf->devfile) { lprintf(LOG_ERR, "Serial device is not specified"); return -1; } is_system = 0; /* check if baud rate is specified */ if ((p = strchr(intf->devfile, ':'))) { char * pp; /* separate device name from baud rate */ *p++ = '\0'; /* check for second colon */ if ((pp = strchr(p, ':'))) { /* this is needed to normally acquire baud rate */ *pp++ = '\0'; /* check if it is a system interface */ if (pp[0] == 'S' || pp[0] == 's') { is_system = 1; } } if (str2uint(p, &rate)) { lprintf(LOG_ERR, "Invalid baud rate specified\n"); return -1; } } intf->fd = open(intf->devfile, O_RDWR | O_NONBLOCK, 0); if (intf->fd < 0) { lperror(LOG_ERR, "Could not open device at %s", intf->devfile); return -1; } for (i = 0; i < sizeof(rates) / sizeof(rates[0]); i++) { if (rates[i].baudrate == rate) { break; } } if (i >= sizeof(rates) / sizeof(rates[0])) { lprintf(LOG_ERR, "Unsupported baud rate %i specified", rate); return -1; } tcgetattr(intf->fd, &ti); cfsetispeed(&ti, rates[i].baudinit); cfsetospeed(&ti, rates[i].baudinit); /* 8N1 */ ti.c_cflag &= ~PARENB; ti.c_cflag &= ~CSTOPB; ti.c_cflag &= ~CSIZE; ti.c_cflag |= CS8; /* enable the receiver and set local mode */ ti.c_cflag |= (CLOCAL | CREAD); /* no flow control */ ti.c_cflag &= ~CRTSCTS; ti.c_iflag &= ~(IGNBRK | IGNCR | INLCR | ICRNL | INPCK | ISTRIP | IXON | IXOFF | IXANY); #ifdef IUCLC /* Only disable uppercase-to-lowercase mapping on input for platforms supporting the flag. */ ti.c_iflag &= ~(IUCLC); #endif ti.c_oflag &= ~(OPOST); ti.c_lflag &= ~(ICANON | ISIG | ECHO | ECHONL | NOFLSH); /* set the new options for the port with flushing */ tcsetattr(intf->fd, TCSAFLUSH, &ti); if (intf->ssn_params.timeout == 0) intf->ssn_params.timeout = IPMI_SERIAL_TIMEOUT; if (intf->ssn_params.retry == 0) intf->ssn_params.retry = IPMI_SERIAL_RETRY; intf->opened = 1; return 0; } static void ipmi_serial_term_close(struct ipmi_intf * intf) { if (intf->opened) { close(intf->fd); intf->fd = -1; } ipmi_intf_session_cleanup(intf); intf->opened = 0; } /* * This function waits for incoming byte during timeout (ms). */ static int serial_wait_for_data(struct ipmi_intf * intf) { int n; struct pollfd pfd; pfd.fd = intf->fd; pfd.events = POLLIN; pfd.revents = 0; n = poll(&pfd, 1, intf->ssn_params.timeout*1000); if (n < 0) { lperror(LOG_ERR, "Poll for serial data failed"); return -1; } else if (!n) { return -1; } return 0; } /* * Read a line from serial port * Returns > 0 if there is a line, < 0 on error or timeout */ static int serial_read_line(struct ipmi_intf * intf, char *str, int len) { int rv, i; *str = 0; i = 0; while (i < len) { if (serial_wait_for_data(intf)) { return -1; } rv = read(intf->fd, str + i, 1); if (rv < 0) { return -1; } else if (!rv) { lperror(LOG_ERR, "Serial read failed: %s", strerror(errno)); return -1; } if (str[i] == '\n' || str[i] == '\r') { if (verbose > 4) { char c = str[i]; str[i] = '\0'; fprintf(stderr, "Received data: %s\n", str); str[i] = c; } return i + 1; } else { i++; } } lprintf(LOG_ERR, "Serial data is too long"); return -1; } /* * Send zero-terminated string to serial port * Returns the string length or negative error code */ static int serial_write_line(struct ipmi_intf * intf, const char *str) { int rv, cnt = 0; int cb = strlen(str); while (cnt < cb) { rv = write(intf->fd, str + cnt, cb - cnt); if (rv < 0) { return -1; } else if (rv == 0) { return -1; } cnt += rv; } return cnt; } /* * Flush the buffers */ static int serial_flush(struct ipmi_intf * intf) { #if defined(TCFLSH) return ioctl(intf->fd, TCFLSH, TCIOFLUSH); #elif defined(TIOCFLUSH) return ioctl(intf->fd, TIOCFLUSH); #else # error "unsupported platform, missing flush support (TCFLSH/TIOCFLUSH)" #endif } /* * Receive IPMI response from the device * Len: buffer size * Returns: -1 or response lenth on success */ static int recv_response(struct ipmi_intf * intf, unsigned char *data, int len) { char hex_rs[IPMI_SERIAL_MAX_RESPONSE * 3]; int i, j, resp_len = 0; long rv; char *p, *pp; char ch, str_hex[3]; p = hex_rs; while (1) { if ((rv = serial_read_line(intf, p, sizeof(hex_rs) - resp_len)) < 0) { /* error */ return -1; } p += rv; resp_len += rv; if (*(p - 2) == ']' && (*(p - 1) == '\n' || *(p - 1) == '\r')) { *p = 0; break; } } p = strrchr(hex_rs, '['); if (!p) { lprintf(LOG_ERR, "Serial response is invalid"); return -1; } p++; pp = strchr(p, ']'); if (!pp) { lprintf(LOG_ERR, "Serial response is invalid"); return -1; } *pp = 0; /* was it an error? */ if (strncmp(p, "ERR ", 4) == 0) { serial_write_line(intf, "\r\r\r\r"); sleep(1); serial_flush(intf); errno = 0; rv = strtoul(p + 4, &p, 16); if ((rv && rv < 0x100 && *p == '\0') || (rv == 0 && !errno)) { /* The message didn't get it through. The upper level will have to re-send */ return 0; } else { lprintf(LOG_ERR, "Serial response is invalid"); return -1; } } /* this is needed for correct string to long conversion */ str_hex[2] = 0; /* parse the response */ i = 0; j = 0; while (*p) { if (i >= len) { lprintf(LOG_ERR, "Serial response is too long(%d, %d)", i, len); return -1; } ch = *(p++); if (isxdigit(ch)) { str_hex[j++] = ch; } else { if (j == 1 || !isspace(ch)) { lprintf(LOG_ERR, "Serial response is invalid"); return -1; } } if (j == 2) { unsigned long tmp; errno = 0; /* parse the hex number */ tmp = strtoul(str_hex, NULL, 16); if ( tmp > 0xFF || ( !tmp && errno ) ) { lprintf(LOG_ERR, "Serial response is invalid"); return -1; } data[i++] = tmp; j = 0; } } return i; } /* * Allocate sequence number for tracking */ static uint8_t serial_term_alloc_seq(void) { static uint8_t seq = 0; if (++seq == 64) { seq = 0; } return seq; } /* * Build IPMB message to be transmitted */ static int serial_term_build_msg(const struct ipmi_intf * intf, const struct ipmi_rq * req, uint8_t * msg, size_t max_len, struct serial_term_request_ctx * ctx, int * msg_len) { uint8_t * data = msg, seq; struct serial_term_hdr * term_hdr = (struct serial_term_hdr *) msg; struct ipmi_send_message_rq * outer_rq = NULL; struct ipmi_send_message_rq * inner_rq = NULL; int bridging_level; /* acquire bridging level */ if (intf->target_addr && intf->target_addr != intf->my_addr) { if (intf->transit_addr != 0) { bridging_level = 2; } else { bridging_level = 1; } } else { bridging_level = 0; } /* check overall packet length */ if(req->msg.data_len + 3 + bridging_level * 8 > max_len) { lprintf(LOG_ERR, "ipmitool: Message data is too long"); return -1; } /* allocate new sequence number */ seq = serial_term_alloc_seq() << 2; /* check for bridging */ if (bridging_level) { /* compose terminal message header */ term_hdr->netFn = 0x18; term_hdr->seq = seq; term_hdr->cmd = 0x34; /* set pointer to send message request data */ outer_rq = (struct ipmi_send_message_rq *) (term_hdr + 1); if (bridging_level == 2) { /* compose the outer send message request */ outer_rq->channel = intf->transit_channel | 0x40; outer_rq->msg.rsSA = intf->transit_addr; outer_rq->msg.netFn = 0x18; outer_rq->msg.csum1 = -(outer_rq->msg.rsSA + outer_rq->msg.netFn); outer_rq->msg.rqSA = intf->my_addr; outer_rq->msg.rqSeq = seq; outer_rq->msg.cmd = 0x34; /* inner request is further */ inner_rq = (outer_rq + 1); } else { /* there is only one header */ inner_rq = outer_rq; } /* compose the inner send message request */ inner_rq->channel = intf->target_channel | 0x40; inner_rq->msg.rsSA = intf->target_addr; inner_rq->msg.netFn = (req->msg.netfn << 2) | req->msg.lun; inner_rq->msg.csum1 = -(inner_rq->msg.rsSA + inner_rq->msg.netFn); inner_rq->msg.rqSA = intf->my_addr; inner_rq->msg.rqSeq = seq; inner_rq->msg.cmd = req->msg.cmd; /* check if interface is the system one */ if (is_system) { /* need response to LUN 2 */ outer_rq->msg.rqSeq |= 2; /* do not track response */ outer_rq->channel &= ~0x40; /* restore BMC SA if bridging not to primary IPMB channel */ if (outer_rq->channel) { outer_rq->msg.rqSA = IPMI_BMC_SLAVE_ADDR; } } /* fill the second context */ ctx[1].netFn = outer_rq->msg.netFn; ctx[1].sa = outer_rq->msg.rsSA; ctx[1].seq = outer_rq->msg.rqSeq; ctx[1].cmd = outer_rq->msg.cmd; /* move write pointer */ msg = (uint8_t *)(inner_rq + 1); } else { /* compose terminal message header */ term_hdr->netFn = (req->msg.netfn << 2) | req->msg.lun; term_hdr->seq = seq; term_hdr->cmd = req->msg.cmd; /* move write pointer */ msg = (uint8_t *)(term_hdr + 1); } /* fill the first context */ ctx[0].netFn = term_hdr->netFn; ctx[0].seq = term_hdr->seq; ctx[0].cmd = term_hdr->cmd; /* write request data */ memcpy(msg, req->msg.data, req->msg.data_len); /* move write pointer */ msg += req->msg.data_len; if (bridging_level) { /* write inner message checksum */ *msg++ = ipmi_csum(&inner_rq->msg.rqSA, req->msg.data_len + 3); /* check for double bridging */ if (bridging_level == 2) { /* write outer message checksum */ *msg++ = ipmi_csum(&outer_rq->msg.rqSA, 4); } } /* save message length */ *msg_len = msg - data; /* return bridging level */ return bridging_level; } /* * Send message to serial port */ static int serial_term_send_msg(struct ipmi_intf * intf, uint8_t * msg, int msg_len) { int i, size, tmp = 0; uint8_t * buf, * data; if (verbose > 3) { fprintf(stderr, "Sending request:\n"); fprintf(stderr, " NetFN/rsLUN = 0x%x\n", msg[0]); fprintf(stderr, " rqSeq = 0x%x\n", msg[1]); fprintf(stderr, " cmd = 0x%x\n", msg[2]); if (msg_len > 7) { fprintf(stderr, " data_len = %d\n", msg_len - 3); fprintf(stderr, " data = %s\n", buf2str(msg + 3, msg_len - 3)); } } if (verbose > 4) { fprintf(stderr, "Message data:\n"); fprintf(stderr, " %s\n", buf2str(msg, msg_len)); } /* calculate required buffer size */ size = msg_len * 2 + 4; /* allocate buffer for output data */ buf = data = (uint8_t *) alloca(size); if (!buf) { lperror(LOG_ERR, "ipmitool: alloca error"); return -1; } /* start character */ *buf++ = '['; /* body */ for (i = 0; i < msg_len; i++) { buf += sprintf( buf, "%02x", msg[i]); } /* stop character */ *buf++ = ']'; /* carriage return */ *buf++ = '\r'; /* line feed */ *buf++ = '\n'; /* write data to serial port */ tmp = write(intf->fd, data, size); if (tmp <= 0) { lperror(LOG_ERR, "ipmitool: write error"); return -1; } return 0; } /* * Wait for request response */ static int serial_term_wait_response(struct ipmi_intf * intf, struct serial_term_request_ctx * req_ctx, uint8_t * msg, size_t max_len) { struct serial_term_hdr * hdr = (struct serial_term_hdr *) msg; int msg_len; /* wait for response(s) */ do { /* receive message */ msg_len = recv_response(intf, msg, max_len); /* check if valid message received */ if (msg_len > 0) { /* validate message size */ if (msg_len < 4) { /* either bad response or non-related message */ continue; } /* check for the waited response */ if (hdr->netFn == (req_ctx->netFn|4) && (hdr->seq & ~3) == req_ctx->seq && hdr->cmd == req_ctx->cmd) { /* check if something new has been parsed */ if (verbose > 3) { fprintf(stderr, "Got response:\n"); fprintf(stderr, " NetFN/rsLUN = 0x%x\n", msg[0]); fprintf(stderr, " rqSeq/Bridge = 0x%x\n", msg[1]); fprintf(stderr, " cmd = 0x%x\n", msg[2]); fprintf(stderr, " completion code = 0x%x\n", msg[3]); if (msg_len > 8) { fprintf(stderr, " data_len = %d\n", msg_len - 4); fprintf(stderr, " data = %s\n", buf2str(msg + 4, msg_len - 4)); } } /* move to start from completion code */ memmove(msg, hdr + 1, msg_len - sizeof (*hdr)); /* the waited one */ return msg_len - sizeof (*hdr); } } } while (msg_len > 0); return 0; } /* * Get message from receive message queue */ static int serial_term_get_message(struct ipmi_intf * intf, struct serial_term_request_ctx * req_ctx, uint8_t * msg, size_t max_len) { uint8_t data[IPMI_SERIAL_MAX_RESPONSE]; struct serial_term_request_ctx tmp_ctx; struct ipmi_get_message_rp * rp = (struct ipmi_get_message_rp *) data; struct serial_term_hdr hdr; clock_t start, tm; int rv, netFn, rqSeq; start = clock(); do { /* fill-in request context */ tmp_ctx.netFn = 0x18; tmp_ctx.seq = serial_term_alloc_seq() << 2; tmp_ctx.cmd = 0x33; /* fill-in request data */ hdr.netFn = tmp_ctx.netFn; hdr.seq = tmp_ctx.seq; hdr.cmd = tmp_ctx.cmd; /* send request */ serial_flush(intf); serial_term_send_msg(intf, (uint8_t *) &hdr, 3); /* wait for response */ rv = serial_term_wait_response(intf, &tmp_ctx, data, sizeof (data)); /* check for IO error or timeout */ if (rv <= 0) { return rv; } netFn = (req_ctx->netFn & ~3)|(req_ctx->seq & 3)|4; rqSeq = req_ctx->seq & ~3; /* check completion code */ if (rp->completion == 0) { /* check for the waited response */ if (rp->netFn == netFn && rp->rsSA == req_ctx->sa && rp->rqSeq == rqSeq && rp->cmd == req_ctx->cmd) { /* copy the rest of message */ memcpy(msg, rp + 1, rv - sizeof (*rp) - 1); return rv - sizeof (*rp) - 1; } } else if (rp->completion != 0x80) { return 0; } tm = clock() - start; tm /= CLOCKS_PER_SEC; } while (tm < intf->ssn_params.timeout); return 0; } static struct ipmi_rs * ipmi_serial_term_send_cmd(struct ipmi_intf * intf, struct ipmi_rq * req) { static struct ipmi_rs rsp; uint8_t msg[IPMI_SERIAL_MAX_RESPONSE], * resp = msg; struct serial_term_request_ctx req_ctx[2]; int retry, rv, msg_len, bridging_level; if (!intf->opened && intf->open && intf->open(intf) < 0) { return NULL; } /* Send the message and receive the answer */ for (retry = 0; retry < intf->ssn_params.retry; retry++) { /* build output message */ bridging_level = serial_term_build_msg(intf, req, msg, sizeof (msg), req_ctx, &msg_len); if (msg_len < 0) { return NULL; } /* send request */ serial_flush(intf); serial_term_send_msg(intf, msg, msg_len); /* wait for response */ rv = serial_term_wait_response(intf, &req_ctx[0], msg, sizeof (msg)); /* check for IO error */ if (rv < 0) { return NULL; } /* check for timeout */ if (rv == 0) { continue; } /* check for bridging */ if (bridging_level && msg[0] == 0) { /* in the case of payload interface we check receive message queue */ if (is_system) { /* check message flags */ rv = serial_term_get_message(intf, &req_ctx[1], msg, sizeof (msg)); /* check for IO error */ if (rv < 0) { return NULL; } /* check for timeout */ if (rv == 0) { continue; } /* check if response for inner request is not encapsulated */ } else if (rv == 1) { /* wait for response for inner request */ rv = serial_term_wait_response(intf, &req_ctx[1], msg, sizeof (msg)); /* check for IO error */ if (rv < 0) { return NULL; } /* check for timeout */ if (rv == 0) { continue; } } else { /* skip outer level header */ resp = msg + sizeof (struct ipmb_msg_hdr) + 1; /* decrement response size */ rv -= + sizeof (struct ipmb_msg_hdr) + 2; } /* check response size */ if (resp[0] == 0 && bridging_level == 2 && rv < 8) { lprintf(LOG_ERR, "ipmitool: Message response is too short"); /* invalid message length */ return NULL; } } /* check for double bridging */ if (bridging_level == 2 && resp[0] == 0) { /* get completion code */ rsp.ccode = resp[7]; rsp.data_len = rv - 9; memcpy(rsp.data, resp + 8, rsp.data_len); } else { rsp.ccode = resp[0]; rsp.data_len = rv - 1; memcpy(rsp.data, resp + 1, rsp.data_len); } /* return response */ return &rsp; } /* no valid response */ return NULL; } static int ipmi_serial_term_setup(struct ipmi_intf * intf) { /* setup default LAN maximum request and response sizes */ intf->max_request_data_size = IPMI_SERIAL_MAX_RQ_SIZE; intf->max_response_data_size = IPMI_SERIAL_MAX_RS_SIZE; return 0; } struct ipmi_intf ipmi_serial_term_intf = { .name = "serial-terminal", .desc = "Serial Interface, Terminal Mode", .setup = ipmi_serial_term_setup, .open = ipmi_serial_term_open, .close = ipmi_serial_term_close, .sendrecv = ipmi_serial_term_send_cmd, };